I'm working on a C project where I use a void*
variable to hold pointer values. I also need to embed some state information within the same variable. For example, I already use NULL
to indicate that the pointer does not refer to any valid memory (i.e., a "null state").
My question is:
Are there any other values—like a specific number (e.g., (void*)-1
)—that are guaranteed to represent an "invalid" or "sentinel" state in a portable way? In other words, besides NULL, are there any standard or recommended values I can store in a void* to indicate that it doesn't point to a valid object?
If not, what is the best practice for embedding extra state information in pointer variables while ensuring portability across different systems?
Any insights or examples would be greatly appreciated.
What did I try?
I attempted to call __builtin_return_address(0)
to retrieve the current function's return address. My idea was to use that address as a unique state marker in a void*
variable—essentially, to represent a specific "state" that is distinct from NULL.
What was I expecting? I expected that since __builtin_return_address(0) returns a concrete address (obtained from the stack during the function call), it could be used as a non-NULL sentinel value. In other words, I thought that because the return address is stored in memory (albeit on the stack), it might serve as a reliable indicator for a special state within the function, separate from a valid pointer or a NULL pointer.
I'm working on a C project where I use a void*
variable to hold pointer values. I also need to embed some state information within the same variable. For example, I already use NULL
to indicate that the pointer does not refer to any valid memory (i.e., a "null state").
My question is:
Are there any other values—like a specific number (e.g., (void*)-1
)—that are guaranteed to represent an "invalid" or "sentinel" state in a portable way? In other words, besides NULL, are there any standard or recommended values I can store in a void* to indicate that it doesn't point to a valid object?
If not, what is the best practice for embedding extra state information in pointer variables while ensuring portability across different systems?
Any insights or examples would be greatly appreciated.
What did I try?
I attempted to call __builtin_return_address(0)
to retrieve the current function's return address. My idea was to use that address as a unique state marker in a void*
variable—essentially, to represent a specific "state" that is distinct from NULL.
What was I expecting? I expected that since __builtin_return_address(0) returns a concrete address (obtained from the stack during the function call), it could be used as a non-NULL sentinel value. In other words, I thought that because the return address is stored in memory (albeit on the stack), it might serve as a reliable indicator for a special state within the function, separate from a valid pointer or a NULL pointer.
Share Improve this question asked Feb 17 at 21:32 Adem PelitAdem Pelit 311 bronze badge New contributor Adem Pelit is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct. 6- 5 This sounds like an XY problem to me. Whatever problem you are trying to solve, I doubt that this is the ideal solution to your problem. Why don't you store the additional information outside the pointer? – Andreas Wenzel Commented Feb 17 at 21:50
- Why include state inside a pointer? A pointer is supposed to be the address of something in memory. What if a valid address happens to set the value of the state? Also setting arbitrary bits in terms of pointer could lead to other corruption. – Kevin Hannan Commented Feb 17 at 21:54
- Be aware that null pointers do not necessarily have unique values (see stackoverflow/questions/69211439/…). – nielsen Commented 2 days ago
- I recall duplicates on this but cannot find them. Can anybody else? – Eric Postpischil Commented 2 days ago
- The question could be clarified a bit as I think a couple of people above have got the wrong impression of what you are trying to do, and think you are trying to combine an address with some other state information in the same pointer variable at the same time (i.e., using spare bits in the pointer value to store additional state), whereas what most people assume you are trying to do is store either an address, or a null pointer value, or some other special "sentinel" value in the pointer variable. – Ian Abbott Commented 2 days ago
3 Answers
Reset to default 16Just use the address of any global.
char ERROR_FOO_OBJ;
#define ERROR_FOO ((void*)&ERROR_FOO_OBJ)
return ERROR_FOO;
if ( p == ERROR_FOO ) ...;
Just-beyond-an-object is a valid pointer (although not one that can be dereferenced). So while unlikely, it's possible to have a valid pointer equal to ERROR_FOO
. If it makes sense to return a pointer that points just beyond an object, you can protect yourself by using the following instead:
char ERROR_CODE_OBJS[2];
#define ERROR_FOO ((void*)&ERROR_CODE_OBJS[1])
return ERROR_FOO;
if ( p == ERROR_FOO ) ...;
Thanks to Nate Eldredge who raised this issue in the comments.
Trying to collecting possible answers. Several reserved values exist in various standards, and many in practice have constant values (if a new OS violates this rule, it would break a lot of programs, so this is unlikely to happen).
Data pointers:
- 0 NULL (C89; special casting rules - bit pattern might not match)
- -1 MAP_FAILED for mmap (POSIX.1-2001, SVr4, 4.4BSD)
- -1 (specified as a number) for sbrk (4.3BSD, SUSv1; no longer standard)
- -1 RTLD_NEXT for dlsym (POSIX.1-2001)
- 0 RTLD_DEFAULT for dlsym (POSIX.1-2001)
- -1 PTHREAD_CANCELED for pthread_join (POSIX.1-2001)
- -1 FE_DFL_ENV for fesetenv (C99)
- -2 FE_NOMASK_ENV for fesetenv (GNU)
- -1 LC_GLOBAL_LOCALE for uselocale and duplocale (POSIX.1-2008; specified as distinct from NULL)
Code pointers (may be different than data pointers on some platforms):
- 0 NULL (C89; special casting rules - bit pattern might not match)
- -1 SIG_ERR for signal (C99, POSIX.1-2001)
- 0 SIG_DFL for signal (C99, POSIX.1-2001)
- 1 SIG_IGN for signal (C99, POSIX.1-2001)
- 2 SIG_HOLD for sigset (SVr4, POSIX.1-2001; obsolete)
Again: unless otherwise noted, assume the actual numerical values are not standard. I've checked these on Linux but am pretty sure most have the same values on most other platforms.
Various non-system software often specifies a greater range of fake pointers, usually well within "page" size (but how big that is is highly system-dependent; 128 is probably safe in practice due to errno values).
(I am vaguely aware that Windows has special values with the high bit set, but Windows is as far from standard as possible)
My question is: Are there any other values—like a specific number (e.g., (void*)-1)—that are guaranteed to represent an "invalid" or "sentinel" state in a portable way?
No.
A null pointer is the only portable sentinel value. For a truly portable program, we can't really assume anything else about the addresses at all.
For example the big majority of microcontroller systems uses the value 0xFFFF... for uninitialized, non-programmed flash. Meaning that the value -1
is super likely to appear in such a system - any non-initialized pointer stored in flash will have that value.
Also as pointed out in comments, any pointer in C is allowed to point one item past the end of an array. Where an "item" could be anything, of any size and any alignment. To dodge that we would have to pick an address at the top of a memory segment so that nothing can get allocated before it. Which means we'd have to drag hard-coded addresses into the source - which is far from portable.
Another possibility would be to grab a function address and store it inside an object. But that is far from portable too; some systems may even trap if they detect that.