2

If it exists, it should satisfy the following properties:

  • Has the type void *
  • Does not require the instantiation of a "dummy object" to act as the address
  • It is guaranteed to not compare equal to NULL
  • Can be constructed without invoking undefined behavior
  • Works with standards-conforming compiler without needing non-standard extensions

At first I thought I could do something like (NULL + 1) or (void *)1, but these appear to be problematic. The former uses pointer arithmetic on NULL which I believe is undefined behavior. The second relies on the fact that NULL does not have physical address 1. (i.e. it is entirely possible that (void *)0 == (void *)1)

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Ryan
  • 112
  • 1
  • 6
  • 3
    No there's nothing like that in standard C. May I ask why you need it? What is the problem you try to solve with it? – Some programmer dude Feb 19 '20 at 07:42
  • 1
    @Stargateur it is not. It is IDB – 0___________ Feb 19 '20 at 08:19
  • @P__J__I don't remember have seen IDB anywhere, I don't know what it's mean. I think it's UB but some compiler could define it. I think standard say it's UB by default. I'm really not sure anymore :p – Stargateur Feb 19 '20 at 10:26
  • 3
    For what is worth, Rust's `dangling()` does the equivalent of `(void*)alignof(T)` to ensure proper alignment of the pointer, while being as near to the 0 address as possible. – rodrigo Feb 19 '20 at 11:11
  • 1
    @Stargateur Converting an integer to a pointer is implementation-defined behavior. Naturally, since the address map is implementation-specific. However, doing so on hosted systems with virtual address space is certainly questionable practice. On lower level systems that address physical memory directly, it's perfectly fine. – Lundin Feb 19 '20 at 11:41
  • 1
    @Someprogrammerdude I want to reduce a struct's memory footprint because I have a lot of them stored in an array. Rust is able to compress structs with the knowledge that `NonNull` can never be null, and therefore `Option` or `Option` don't need an extra "flag byte." Basically, think of tagged unions in C, but without needing a tag. – Ryan Feb 19 '20 at 16:05
  • @rodrigo If you fleshed out your comment a little bit and posted it as an answer I may be willing to accept it. Not that the existing answer is incorrect in any way, but it mostly addresses the C side of things without providing a Rust-like analogue. – Ryan Feb 20 '20 at 23:17
  • @RyanAvella: I've done so, with a few examples and everything. – rodrigo Feb 21 '20 at 10:42

2 Answers2

2

Any void pointer fulfils all your requirements.

As long as you know for sure which addresses that are valid and taken on the specific system, you could create such a pointer manually:

void* dangling = (void*)0x12345678; // an address which you know for sure isn't taken

This is fully standard compliant. The outcome is implementation-defined, since things like allocated valid addresses and alignment are system-specific.

As for what good this will do you, I have no idea. Null pointers are what should be used when a pointer isn't set to point at an allocated address.


At first I thought I could do something like (NULL + 1) or (void *)1, but these appear to be problematic. The former uses pointer arithmetic on NULL which I believe is undefined behavior.

You are mixing up null pointers with the null pointer constant NULL. NULL can expand to 0 or (void*)0.

  • If you do arithmetic 0 + 1 you simply get an integer constant expression 1. Which can be converted to a pointer if you want, same impl.defined behavior as above and indeed equivalent to (void*)1.
  • If you do arithmetic (void*)0 + 1 then the code won't compile since you can't do arithmetic on void pointers. And it is UB if you do pointer arithmetic on a pointer not pointing on an allocated array.
Lundin
  • 195,001
  • 40
  • 254
  • 396
  • 6
    *As for what good this will do you, I have no idea.* => In Rust, `NonNull` guarantees that it is never null, which allows using the null value to represent another state. For example, the tagged union `Option>` has exactly the size of a pointer, and the null byte pattern represents the `None` alternative while any non-null byte pattern represents `Some(NonNull)`. Such a trick can be desirable in C when one wishes for a compact memory representation. – Matthieu M. Feb 19 '20 at 08:57
  • 1
    @MatthieuM. Sounds pretty useless. Using a pointer as a means to keep track of "states" is most likely bad program design; use an enum instead. Besides, in C you wouldn't fiddle around with the contents of an object pointer for such purposes, but rather use function pointers. Name a function "dangling", never call it, and there you go. – Lundin Feb 19 '20 at 09:03
  • 5
    So, at a guess, you are not working on JavaScript engines (using NaN tagging to put pointers into double) nor are you working on clang/LLVM (using the lowest pointer bits to store meta-information). What you have deemed useless, others have found worthwhile indeed, since memory is too often a bottleneck. – Matthieu M. Feb 19 '20 at 09:39
  • 1
    @MatthieuM. I also agree with lundin, this would not make sense in C. If you want use NULL as None value you can, I think a lot of code do it. In Rust this require to tell to the language some hint but in C just do it. – Stargateur Feb 19 '20 at 10:22
  • 2
    @Stargateur: The point of `dangling` is to create a non-NULL value, so pointing out that NULL can be used as None does not help. – Matthieu M. Feb 19 '20 at 11:24
  • 1
    @MatthieuM. No I'm working in C, which is what this question is about. Where memory bottlenecks are most often caused by the actual data, not by abstraction layers, engines and meta programming. Although from a language independent point of view, it is simply bad design to use the same variable for multiple unrelated purposes. – Lundin Feb 19 '20 at 11:38
  • 3
    *I'm working in C* — and which JavaScript *engines* are commonly built in. *it is simply bad design* — while true in the majority of cases, such a blanket statement cannot be applied in every case. C even has bitfields for a similar reason. – Shepmaster Feb 19 '20 at 14:42
  • 2
    @Shepmaster C has bitfields for what reason? For creating arbitrary behaving binary blobs that can't be reliably used for any other purpose than as a chunk of poorly memory-optimized boolean values allocated in strange or unknown ways? Because that's what they are. They were certainly not created for fiddling around with the internal representation of null pointers, that's for sure. – Lundin Feb 19 '20 at 16:26
1

NonNull::dangling() exists in Rust to be able to temporarily initialize a NonNull value before giving it the real value. You cannot use null as the temporary because it is a NonNull and it would render Undefined behavior.

For example this perfectly safe (I guess) self-referentiable example requires NonNull::dangling():

struct SelfRef {
    myself: NonNull<SelfRef>,
    data: String,
}

impl SelfRef {
    fn new(data: String) -> Pin<Box<SelfRef>> {
        let mut x = Box::pin(SelfRef {
            myself: NonNull::dangling(),
            data,
        });
        x.myself = unsafe { NonNull::new_unchecked(x.as_mut().get_unchecked_mut()) };
        x
    }
}

About your question of the equivalent to NonNull::dangling() in C is that, in C there is no NonNull, so for these kinds of temporary initialization you can NULL or just leave it unitilizalized until you have the proper value.

struct SelfRef {
    SelfRef *myself;
    //...
};

struct SelfRef *new_selfref() {
    struct SelfRef *x = malloc(sizeof(struct SelfRef));
    //Here x->myself is uninitialized, that is as good as dangling()
    x->myself = x;
    return x;
}

That said, I'm sure that there are other uses of NonNull::dangling other than temporary initialization of self-referentiable structs. For those you may actually need an equivalent C code. The equivalent C code would be (in macro form as it takes a type as argument):

#define DANGLING(T) ((T*)alignof(T))

That is, a pointer as near to zero as possible while complying with the alignment of the given type. The idea is that in most architectures the NULL pointer is actually at address 0, and the first few kilobytes are never mapped, so that the runtime can catch NULL dereferences. And since the maximum alignment requirements are usually just a few bytes, this will never point to valid memory.

rodrigo
  • 94,151
  • 12
  • 143
  • 190