C/CPP: Comparing pointers not in the same array

Started by
8 comments, last by Namethatnobodyelsetook 19 years, 7 months ago
Greetings, While reading through Bruce Eckel's TICPP Vol2, I came across this:
Quote:Technically, comparing two pointers that are not inside the same array is undefined behavior, but today's compilers don't complain about this.
Which was kinda surprising to me, given that we've always done it for a variety of things like guarding against self assignment (comparing this to another address). After a bit of searching and googling, it seems like this is to accomodate for segmented memory models where only offsets are compared during pointer comparison, but not flat memory models. So: - Is this accurate? (only segmented models) - Any chance it'll break on flat-memory-model platforms using some compiler? (all the ones I've used so far work fine, but who knows...) - Any common platforms at this day that use segmented memory models? (other than embedded systems, I assume) - If I'm to use a replacement (say in cases like guarding against self-assignment), what should it be? Thanks in advance. PS: For those who didn't know this, like me, these are several useful links that I found: The C Book - Expressions involving pointers Pointers - Part 3 C++ pitfalls

Advertisement
Wow. That'd certainly break my code.
I think that only applies to the relative comparisons ( < <= > >= ), not == and !=. Note that even for those, there might be problems if you're using multiple inheritance (I think casts can change pointer values).
I don't think that's entirely accurate. You can compare pointers as long as they are part of the same address space. When you cross address spaces, there may or may not be a way to compare the addresses. Being part of the same array might be one way to guarantee an address is from the same address space, but saying that it's illegal to compare pointers from different arrays is the most absurd thing I've heard in a long time.

In most modern systems, you have a flat 32 bit address range. You can freely compare pointers in your app. However, in the OS, or a DLL, it's possible you'll see addresses from different processes. Each process has it's own address space, and typically, a pointer in one address space has no corresponding address in another address space. ie: Every win32 app loads at 0x400000 in it's own address space, and one app cannot see the address space of another. Two process can open a memory mapped file, and will share access to each other's address space, but at different addresses. In one process the file could be at 0x800000, and at 0x900000 in the other process, yet still refer to the same thing, despite having different pointers.

In old realmode segmented memory, you had near pointers and far pointers. You could freely compare near pointers to other near pointers, because they're all refering to the same 64K address space of the program. If you casted a far pointer down to a near pointer it was invalid. With half a pointer you can't do anything. You can't freely compare far pointers to other far pointers. Because of the Intel oddity of "segment*16+offset = address", two identical pointers, yet different pointers could exist. For example: the keyboard led address 0:0x417 == 40:0x17. In this case, you could think of the segment as indicating an address space, and you could also easily convert between address spaces to compare two pointers.

So, it's address space, not array. Conversion between address spaces may or may not be possible depending on the system (and your privledges... I'm sure the Win32 OS has access to extra information to enable it to compare address that your app cannot compare).
Quote:Original post by Painless
I think that only applies to the relative comparisons ( < <= > >= ), not == and !=. Note that even for those, there might be problems if you're using multiple inheritance (I think casts can change pointer values).

That'd make sense. On revising what I've seen so far, it seems to be about relational comparisons, not equality. Which is good.

And yes, with multiple inheritance casts can change pointer values, but it's a non-issue.

Thanks a lot

Quote:Original post by Namethatnobodyelsetook
I don't think that's entirely accurate. You can compare pointers as long as they are part of the same address space. When you cross address spaces, there may or may not be a way to compare the addresses. Being part of the same array might be one way to guarantee an address is from the same address space, but saying that it's illegal to compare pointers from different arrays is the most absurd thing I've heard in a long time.

It actually comes from the language standard (seen a quote from the C standard, I believe). The MSDN docs state so too. Both say the results are "undefined", i.e. not illegal, but undefined - anything can happen. It seems though that the address space is really the reason behind this, so it makes sense that it should work correctly in our flat model systems.

Thanks for the very nice explanation ntnet. I owe you [smile]

I think the reason is that, potentially on some imaginary system, the pointer you get is just a handle to a LUT that points to the real location of the object in memory. Comparing pointers like that will not give you the true relationship of the objects in memory.
Quote:Original post by Coder
And yes, with multiple inheritance casts can change pointer values, but it's a non-issue.


I've heard that but I wondered why that's true, can you give me a quick explanation? Thanks.
Intel chips have been using a LUT rather than a real address since the 80386, so it's not such an imaginary system. But you don't care about the real physical address, just how two addresses in the same address space interact.
Quote:Original post by fallenang3l
I've heard that but I wondered why that's true, can you give me a quick explanation? Thanks.


Lets say we have an A object. At this+0 we have a variable 'a'.
Lets say we have a B object. At this+0 we have a variable 'b'.
Lets say we have a C object that is derived from A and B. In memory C looks like A followed by B. (this+0 = a, this+sizeof(a)=b)
If we cast C to A, we can use the pointer as is.
If we cast C to B, we need to offer the pointer by the size of A to point to the B object. b=this+sizeof(a)

Also, imagine we have Base that's got no virtuals. Imagine we have Derived that got a virtual.

In MSVC it looks like this in memory:
[vtable]
Base variables
Derived variables

In GCC it looks like this in memory:
Base variables
[vtable]
Derived variables.

If we make a Derived object, then cast to Base, the pointer in MSVC is adjusted to point to after the vtable. If you cast back to Derived, the pointer is adjusted again to point to the vtable. If you compare a Base to a Derived pointer, the compiler takes care of worrying about the mismatched pointers, and still works.

If you take a Derived object, cast to void *, then cast to Base, the pointer won't point accurately, because going through void * the compiler can't tell if the original pointer was a Base or a Derived object, and doesn't know that the pointer needs offseting. MSVC hides this if you do it in one function. If you pass the pointer as void * to a function and cast there, it'll break though.

Have I missed a permutation?

This topic is closed to new replies.

Advertisement