18

Hi I'm sure this must be a common question but I can't find the answer when I search for it. My question basically concerns two pointers. I want to compare their addresses and determine if one is bigger than the other. I would expect all addresses to be unsigned during comparison. Is this true, and does it vary between C89, C99 and C++? When I compile with gcc the comparison is unsigned.

If I have two pointers that I'm comparing like this:

char *a = (char *) 0x80000000; //-2147483648 or 2147483648 ?  
char *b = (char *) 0x1; 

Then a is greater. Is this guaranteed by a standard?


Edit to update on what I am trying to do. I have a situation where I would like to determine that if there's an arithmetic error it will not cause a pointer to go out of bounds. Right now I have the start address of the array and the end address. And if there's an error and the pointer calculation is wrong, and outside of the valid addresses of memory for the array, I would like to make sure no access violation occurs. I believe I can prevent this by comparing the suspect pointer, which has been returned by another function, and determining if it is within the acceptable range of the array. The question of negative and positive addresses has to do with whether I can make the comparisons, as discussed above in my original question.

I appreciate the answers so far. Based on my edit would you say that what I'm doing is undefined behavior in gcc and msvc? This is a program that will run on Microsoft Windows only.

Here's an over simplified example:

char letters[26];  
char *do_not_read = &letters[26];  
char *suspect = somefunction_i_dont_control(letters,26);  
if( (suspect >= letters) && (suspect < do_not_read) )  
    printf("%c", suspect);  



Another edit, after reading AndreyT's answer it appears to be correct. Therefore I will do something like this:

char letters[26];  
uintptr_t begin = letters;  
uintptr_t toofar = begin + sizeof(letters);  
char *suspect = somefunction_i_dont_control(letters,26);  
if( ((uintptr_t)suspect >= begin) && ((uintptr_t)suspect < toofar ) )
    printf("%c", suspect);  


Thanks everyone!

test
  • 411
  • 1
  • 4
  • 10

5 Answers5

27

Pointer comparisons cannot be signed or unsigned. Pointers are not integers.

C language (as well as C++) defines relative pointer comparisons only for pointers that point into the same aggregate (struct or array). The ordering is natural: the pointer that points to an element with smaller index in an array is smaller. The pointer that points to a struct member declared earlier is smaller. That's it.

You can't legally compare arbitrary pointers in C/C++. The result of such comparison is not defined. If you are interested in comparing the numerical values of the addresses stored in the pointers, it is your responsibility to manually convert the pointers to integer values first. In that case, you will have to decide whether to use a signed or unsigned integer type (intptr_t or uintptr_t). Depending on which type you choose, the comparison will be "signed" or "unsigned".

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • Same object, as @James said, which is somewhat more general than same array. – Ben Voigt Jul 15 '11 at 02:50
  • What about casting the pointers to void: (void *) ptr. Then performing a comparison, I thought that was perfectly acceptable? – Peter Jul 15 '11 at 02:53
  • As far as I know, comparing `void *` pointers is completely invalid for the same reason arithmetic on them is invalid. – R.. GitHub STOP HELPING ICE Jul 15 '11 at 03:02
  • @Peter: `void *`? `void *` pointers are not comparable. Some compilers allow it by treating `void *` as `char *` in comparison (and pointer arithmetic) contexts, but it is entirely non-standard. – AnT stands with Russia Jul 15 '11 at 03:10
  • So... this doesn't consider when you take the difference of 2 pointers, which is clearly an integer. – Matt Joiner Jul 15 '11 at 04:32
  • 2
    @R.: The reason that addition and subtraction on void pointers is invalid is because of a constraint in those operators that the pointer must be to an object type. That constraint does *not* apply for the relational operators; given `type x[2];`, the expression `(void *)x < (void *)(x+1)` is true. – caf Jul 15 '11 at 04:34
  • @caf: You seem to be correct. Incidentally it would be just as true for `type x[1];` and likewise for `type y; (void *)&y < (void *)(&y+1)`... – R.. GitHub STOP HELPING ICE Jul 15 '11 at 05:06
  • @caf thanks for checking, I was certain it was valid to perform relational comparisons on void pointers -- obviously not arithmetic. – Peter Jul 15 '11 at 05:17
  • @caf: I stand corrected, thanks. Indeed, comparisons on `void *` pointers are allowed. – AnT stands with Russia Jul 15 '11 at 07:13
  • @AndreyT, do you want to update your response to reflect this information? – Peter Jul 15 '11 at 17:06
  • Hi @caf, I was referring to his response to the question. Perhaps he could augment it to include some of this information. – Peter Jul 16 '11 at 11:30
  • @Peter: I don't see it as immediately relevant to the actual question. For that reason, that specific issue is not mentioned in my answer. I don't see why I should include it. – AnT stands with Russia Jul 16 '11 at 17:58
  • Comparisons are defined for pointers within arrays and trivially copyable objects. **Subtractions** are also allowed within (and 1-past-the-end of) arrays, where they are defined to return the number of elements between the operands. Sadly it seems that, at least formally, subtracting pointers within the same object - even if the members and/or pointers are `unsigned char *` to make them meaningful - is not defined. IMO this is a deficiency in what the Standard defines: many other passages _imply_ this would be allowed, but it is never formally codified as defined behaviour... that I can find. – underscore_d Aug 18 '16 at 11:33
  • Of course, there's `std::less<>` which extends that partial order to a full order in C++. – Deduplicator Jun 28 '17 at 20:01
  • @underscore_d: The Standard appears to be intended to allow code to manipulate objects as though they are a `char[]` of appropriate size (I can't think of any other reason for the special treatment of character pointers), which would in turn define the behavior of subtracting character pointers within an object as being equivalent to subtracting pointers within the equivalent `char[]` array. While I wouldn't rely on compiler writers to honor the Standard writers' intentions with regard to such things, I think such intention is pretty clear. – supercat Dec 18 '17 at 21:40
  • @supercat The rule for trivial copyability/`memcpy()` talks about objects as if they are an "an array of `unsigned char`, but I think it's just an analogy for such copying. I can't find the issue number, but there is a core issue seeking clarification on whether that's meant to be a general metaphor. But right now, I can see no provision for subtracting pointers, so the only thing I'd call pretty clear is that it's not defined to... Much as, at the time, I wished we could. I was very concerned about that back then, but I had to just change my design to use an actual array instead of an object. – underscore_d Dec 19 '17 at 09:32
  • @underscore_d: The metaphor was a fundamental part of Ritchie's language, and the ability to use character pointers to access objects of any type if one were limited to accessing the first byte of an object in such fashion. While C++ adds a requirement to "launder" pointers in certain cases, I see no reason a compiler should care about whether pointers are laundered prior to subtraction--much less comparison. Of course, one of the great tragedies of C (and to some extent C++) is that the authors of Standards thought that the lack of any reason a compiler should care about things meant... – supercat Dec 19 '17 at 15:23
  • ...there was no need to explicitly specify that compilers must ignore them. – supercat Dec 19 '17 at 15:23
8

The integer-to-pointer conversion is wholly implementation defined, so it depends on the implementation you are using.

That said, you are only allowed to relationally compare pointers that point to parts of the same object (basically, to subobjects of the same struct or elements of the same array). You aren't allowed to compare two pointers to arbitrary, wholly unrelated objects.

James McNellis
  • 348,265
  • 75
  • 913
  • 977
  • 2
    @Matt: C99 6.3.2.3/5 states that the integer-to-pointer conversion is implementation-defined. C99 6.5.8/5 states the restrictions on which pointers can be relationally compared. – James McNellis Jul 15 '11 at 02:49
  • The reality is, on gcc, `char *a = "ABC"; int i = 10; if (a < (char *) &i) printf("Greater\n"); else printf("Smaller\n");` doesn't even give out a warning. – shinkou Jul 15 '11 at 02:55
  • @James. btw I think the comparison part is not what concerns the OP. – shinkou Jul 15 '11 at 03:00
  • 1
    A draft of the standard is here: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf, and agrees with James' answer. – andrewdski Jul 15 '11 at 03:00
  • 1
    @shinkou: That fits into the term "undefined behaviour". The compiler implementor is free to do what he wants. – ChrisWue Jul 15 '11 at 03:04
  • @ChrisWue. True, but it is not *disallowed* by any means. – shinkou Jul 15 '11 at 03:09
4

From a draft C++ Standard 5.9:

If two pointers p and q of the same type point to different objects that are not members of the same object or elements of the same array or to different functions, or if only one of them is null, the results of p<q, p>q, p<=q, and p>=q are unspecified.

So, if you cast numbers to pointers and compare them, C++ gives you unspecified results. If you take the address of elements you can validly compare, the results of comparison operations are specified independently of the signed-ness of the pointer types.

Note unspecified is not undefined: it's quite possible to compare pointers to different objects of the same type that aren't in the same structure or array, and you can expect some self-consistent result (otherwise it'd be impossible to use such pointers as keys in trees, or to sort a vector of such pointers, binary search the vector etc., where a consistent intuitive overall < ordering is needed).

Note that in very old C++ Standards the behaviour was undefined - like the 2005 WG14/N1124 draft andrewdski links to under James McNellis's answer -

Community
  • 1
  • 1
Tony Delroy
  • 102,968
  • 15
  • 177
  • 252
  • `the results of pq` What was this supposed to mean? There must be a missing operator there. What was it? I'm guessing `==`, but there appear to be missing `<` and `>` here too. – underscore_d Aug 18 '16 at 11:39
  • @underscore_d: site ate the `<` and `>` - sorry about that. Note that `==` is not and should not be in the list. Cheers. – Tony Delroy Aug 20 '16 at 01:49
1

To complement the other answers, comparison between pointers that point to different objects depends on the standard.

In C99 (ISO/IEC 9899:1999 (E)), §6.5.8:

5 [...] In all other cases, the behavior is undefined.

In C++03 (ISO/IEC 14882:2003(E)), §5.9:

-Other pointer comparisons are unspecified.

jinawee
  • 492
  • 5
  • 16
0

I know several of the answers here say you cannot compare pointers unless they point to within the same structure, but that's a red herring and I'll try to explain why. One of your pointers points to the start of your array, the other to the end, so they are pointing to the same structure. A language lawyer could say that if your third pointer points outside of the object, the comparison is undefined, so x >= array.start might be true for all x. But this is no issue, since at the point of comparison C++ cannot know if the array isn't embedded in an even bigger structure. Furthermore, if your address space is linear, like it's bound to be these days, your pointer comparison will be implemented as an (un)signed integer comparison, since any other implementation would be slower. Even in the times of segments and offsets, (far) pointer comparison was implemented by first normalising the pointer and then comparing them as integers.

What this all boils down to then, is that if your compiler is okay, comparing the pointers without worrying about the signs should work, if all you care about is that the pointer points within the array, since the compiler should make the pointers signed or unsigned depending on which of the two boundaries a C++ object may straddle.

Different platforms behave differently in this matter, which is why C++ has to leave it up to the platform. There are even platforms in which both addresses near 0 and 80..00h are not mappable or already taken at process start-up. In that case, it doesn't matter, as long as you're consistent about it.

Sometimes this can cause compatibility issues. As an example, in Win32 pointers are unsigned. Now, it used to be the case that of the 4GB address space only the lower half (more precisely 10000h ... 7FFFFFFFh, because of the NULL-Pointer Assignment Partition) was available to applications; high addresses were only available to the kernel. This caused some people to put addresses in signed variables, and their programs would keep working since the high bit was always 0. But then came /3GB switch, which made almost 3 GB available to applications (more precisely 10000h ... BFFFFFFFh) and the application would crash or behave erratically.

You explicitly state your program will be Windows-only, which uses unsigned pointers. However, maybe you'll change your mind in the future, and using intptr_t or uintptr_t is bad for portability. I also wonder if you should be doing this at all... if you're indexing into an array it might be safer to compare indices instead. Suppose for example that you have a 1 GB array at 1500000h ... 41500000h, consisting of 16,384 elements of 64 kB each. Suppose you accidentally look up index 80,000 – clearly out of range. The pointer calculation will yield 39D00000h, so your pointer check will allow it, even though it shouldn't.

Anonymous
  • 11
  • 1
  • `I know several of the answers here say you cannot compare pointers unless they point to within the same structure, but that's a red herring and I'll try to explain why. One of your pointers points to the start of your array, the other to the end, so they are pointing to the same structure.` This entire answer is a red herring. **Comparison and subtraction are specifically allowed for arrays**, so jumping through hoops trying to say 'Listen, an array is a structure' is both (A) false and (B) completely unnecessary! And the rest of this answer is just tangential implementation-defined waffling. – underscore_d Aug 18 '16 at 11:40
  • Whether or not there would seem to be any way a compiler could know at the point of comparison that two possibly-unrelated pointers couldn't identify parts of some larger object created by outside code, some compiler writers--who apparently think "clever" and "dumb" are antonyms--try to make their compilers figure out and exploit such things. – supercat Dec 18 '17 at 21:50
  • suppose youre writing a generic function which takes a pointer to an array as void*, size of element as size_t and index, array size itself or element size may be huge so youll want to do smt like element = *(array + size * index), so which one will you convert the index? uintptr_t or ptr_t? overflow behaviour may be platform dependent as yanno – TakeMeAsAGuest Feb 16 '19 at 11:35
  • answering to my own question, its probably be ptrdiff_t which is signed. – TakeMeAsAGuest Feb 16 '19 at 11:46