views:

471

answers:

6

Is there a way to portably determine the upper and lower bound on void-pointer values in ANSI C89/ISO C90? (I currently do not have a copy of the standard with me (I have one at home). Of course if void-pointer values are guaranteed to be unsigned this task is trivial (via sizeof(void *)); however, I cannot recall if this is guaranteed or not. I can think of a few very inefficient algorithms (increment until overflow, etc.), but I would like to know if anyone has a relatively cheap (in terms of time-complexity) and portable way to calculate these bounds.)

--EDIT--

Also: Is there a portable way to determine the validity of the pointer values?

Why: This came up in a discussion with a co-worker and it stumped me. I don't know what he's working on, but I just want to know because I am interested! :-)

+1  A: 

Pointers are guaranteed by spec to be unsigned. But why on earth do you want to find the bounds? "Everything between 0x00000001 and 0xffffffff" is not really a useful test, since the number of valid pointers will be some tiny subset of that.

JSBangs
That range is only valid on a 32bit machine.
Darryl Braaten
Also, all `0` might be a valid bit pattern for pointers on some machines: The standard allows for conversions to happen when casting `((void *)0)` - the actual bit pattern of null pointers is unspecified!
Christoph
+1  A: 

I know that on Win32, 64-bit pointers are sign-extended. It's fun to inspect a 32-bit minidump from a 64-bit machine if you don't sign extend pointers.

See here for how a 64-bit pointer (POINTER_64) works on Win32.

MSN
Sign extending pointers can be bad. If a 32-bit "LARGEADDRESSAWARE" process passes a user address to the kernel, and the kernel sign extends it when converting it to a 64-bit pointer, it now points into the kernel address space and ProbeForRead/Write will reject it.
bk1e
Pointers in win32 are unsigned. User mode addresses can have the first bit set if the /3GB flag is set and the process is large address aware. You should NOT assume pointers are signed in Win32.
alabamasucks
Nope, I'm right. At least when dealing with 32-bit pointers from a 64-bit machine:http://msdn.microsoft.com/en-us/library/aa384264(VS.85).aspx
MSN
+5  A: 

There is no portable way to determine if a given pointer is valid or not. You have to know what kind of memory system you're dealing with. Depending on the operating system and processor, there may or may not be a way to query the virtual memory manager's page tables to determine the valid ranges of pointers.

For example, on Linux, you can examine the special mmap file under /proc to get the virtual memory map of a process. Here's an example of cat reading out its own memory map:

$ cat /proc/self/mmap
08048000-0804c000 r-xp 00000000 09:00 5128276                            /bin/cat
0804c000-0804d000 rw-p 00003000 09:00 5128276                            /bin/cat
0804d000-0806e000 rw-p 0804d000 00:00 0                                  [heap]
f7ca7000-f7e40000 r--p 00000000 09:00 3409654                            /usr/lib/locale/locale-archive
f7e40000-f7e41000 rw-p f7e40000 00:00 0 
f7e41000-f7f68000 r-xp 00000000 09:00 2654292                            /lib/tls/i686/cmov/libc-2.3.6.so
f7f68000-f7f6d000 r--p 00127000 09:00 2654292                            /lib/tls/i686/cmov/libc-2.3.6.so
f7f6d000-f7f6f000 rw-p 0012c000 09:00 2654292                            /lib/tls/i686/cmov/libc-2.3.6.so
f7f6f000-f7f72000 rw-p f7f6f000 00:00 0 
f7f83000-f7f85000 rw-p f7f83000 00:00 0 
f7f85000-f7f9a000 r-xp 00000000 09:00 2637871                            /lib/ld-2.3.6.so
f7f9a000-f7f9c000 rw-p 00014000 09:00 2637871                            /lib/ld-2.3.6.so
ff821000-ff836000 rw-p 7ffffffea000 00:00 0                              [stack]
ffffe000-fffff000 r-xp ffffe000 00:00 0                                  [vdso]

You can see the ranges of valid pointers, along with the bits indicating if the memory is (r)eadable, (w)ritable, e(x)ecutable, or (p)resent (i.e. not paged out to disk).

Adam Rosenfield
+1  A: 

void * is always big enough to hold a pointer to addressable memory. Any other use is strictly prohibited by the major league baseball association.

Example: The dec-10 was a 36 bit architecture with 36 bit words. Yet the addresses were 18 bits and you could hold 2 pointers in any register/word.

Yeah - that's an extreme example. If you must do math with pointers, sizeof is valid; but doing pointer math on anything other than a contiguous array is dodgier than dodgy.

Finally - never use a 'void *' to store a pointer to an object or pointer to member in C++. Many compiler implementation actually use multiple 'physical' pointers to implement multiple inheritence of concrete (or partially concrete) classes. In reality this almost never comes up because very few people use multiple inheritance in this way, and when they do, very rarely slice and unslice pointers. When it does come up, it's really hard to figure out what happened.

Joe
Could you mention some compilers which have such "fat" object pointers?
Anton Tykhyy
Sure. Visual C for certain. I've not disassembled gcc to see if it does the same thing, but I'm not sure how it couldn't. If you have multiple concrete inheritance and create pointers-to-members on virtuals of multiple base classes - I'm not sure how else they could be referenced correctly when use via the base class through the inheriting final class.
Joe
+1  A: 

You have to discern the integer value to which a void * can be cast from the actual bit pattern in memory - casting a void * to an integer type may involve conversions!

Assuming sizeof(void *) == sizeof(long), for a void * p the following may well be false:

((long)p) == *((long *)&p)

Also, the standard doesn't specify whether there even is an integer type large enough to hold the values of all valid pointers!

Therefore, there just is no portable way to do what you want to do...

Christoph
True, although in C99, you can use the types intptr_t and uintptr_t which are signed and unsigned integral types respectively which are guaranteed to be large enough to hold a pointer.
Adam Rosenfield
@Adam: There's only one small catch (C99-TC3, 7.18.1.4): "These types are optional."
Christoph
+1  A: 

Aside from a region that corresponds to NULL, there is no (portable) restriction on the memory addresses at all. A sufficiently hardened OS could utilize various CPU/OS mechanisms to supply each process with random and well-distributed addresses with each call to malloc(), and position independent executable plus ASLR can allow code to run from any address as well.

HUAGHAGUAH