Reputation: 1438
C++ standard (and C for that matter) allows to create (not dereference though) a pointer to one element past the end of the array. Does this mean that an array will never be allocated at such a location that its last element ends at the memory boundary? I understand that in practice some/all implementation might follow this convention, but which one the following is true:
Is anything different for the case of C?
Update:
It seems like 1 is the correct answer. See answer from James Kanze below, and also see efence
(http://linux.die.net/man/3/efence - thanks to Michael Chastain for the pointer to it)
Upvotes: 34
Views: 2497
Reputation: 911
There's an interesting passage at §3.9.2/3 [Compound types]:
The type of a pointer to void or a pointer to an object type is called an object pointer type. [...] A valid value of an object pointer type represents either the address of a byte in memory (1.7) or a null pointer (4.10).
Together with the text at §5.7/5 [Additive operators]:
[...] Moreover, if the expression P points to the last element of an array object, the expression (P)+1 points one past the last element of the array object, and if the expression Q points one past the last element of an array object, the expression (Q)-1 points to the last element of the array object.
it seems that an array ending at the last byte in memory can not be allocated, if there is a requirement that the one-past-the-end pointer must be valid. If the one-past-the-end pointer is allowed to be invalid, I don't know the answer.
The section §3.7.4.2/4 [Deallocation functions] states that:
The effect of using an invalid pointer value (including passing it to a deallocation function) is undefined.
Thus if comparing a one-past-the-end pointer for an allocated array must be supported, the one-past-the-end pointer must be valid.
Based on the comments I got, I assume that an implementation can allocate an array without having to care about if the array's one-past-the-end pointer is usable or not. However I would like to find out the relevant passages in the standard for this.
Upvotes: 6
Reputation: 76775
The standard states explicitly what happens when you increment the pointer to the last element. It gives you a value that can only be used as comparison to check if you're at or before the end of the array or not. The pointer may well point to validly allocated memory for some other object, but that is complete undefined (implementation defined?) behaviour and using that pointer as such is definitely undefined behaviour.
What I'm getting at is that the one-past-the-end pointer is just that: it is the pointer you get when you increment the pointer to the last element, to mark the end of the array in a very cheap way. But do note that comparing pointers of unrelated objects is completely nonsensical (and even undefined behaviour if I'm not mistaken). So the fact that there might be overlap in pointer "values" across different objects is a non-issue, as in exploiting this you enter the Land of Undefined Behaviour..
Upvotes: 3
Reputation: 154017
An implementation must allow a pointer to one past the end to exist. How it does this is its business. On many machines, you can safely put any value into a pointer, without risk (unless you dereference it); on such systems, the one past the end pointer may point to unmapped memory—I've actually encountered a case under Windows where it did.
On other machines, just loading a pointer to unmapped memory into a register will trap, causing the program to crash. On such machines, the implementation must ensure that this doesn't happen, either by refusing to use the last byte or word of allocated memory, or by ensuring that all use of the pointer other than dereferencing it avoids any instructions which might cause the hardware to treat it as an invalid pointer. (Most such systems have separate address and data registers, and will only trap if the pointer is loaded into an address register. If the data registers are large enough, the compiler can safely load the pointer into a data register for e.g. comparison. This is often necessary anyway, as the address registers don't always support comparison.)
Re your last question: C and C++ are exactly identical in this respect; C++ simply took over the rules from C.
Upvotes: 37
Reputation:
You're half right. Suppose a hypothetical implementation uses linearly addressed memory and pointers that are represented as 16-bit unsigned integers. Suppose also that the null pointer is represented as zero. And finally, suppose you ask to for 16 bytes of memory, with char *p = malloc(16);
. Then it's guaranteed that you will get a pointer of which the numeric value is less than 65520. The value 65520 itself wouldn't be valid, because as you rightly point out, assuming the allocation succeeded, p + 16
is a valid pointer that must not be a null pointer.
However, suppose now that a hypothetical implementation uses linearly addressed memory and pointers that are represented as 32-bit unsigned integers, but only has an address space of 16 bits. Suppose also again that the null pointer is represented as zero. And finally, suppose again that you ask for 16 bytes of memory, with char *p = malloc(16);
. Then it's only guaranteed that you will get a pointer of which the numeric value is less than or equal to 65520. The value 65520 itself would be valid, so long as the implementation makes sure that adding 16 to that gives you the value 65536, and that subtracting 16 gets you back to 65520. This is valid even if no memory (physical or virtual) exists at all at address 65536.
Upvotes: 4
Reputation: 328
This depends on implementation. At least in visual C++ without using any from of array bound checking, you could create a pointer any number of elements past the end of the array. If you dereference it it will still work as long as the memory address that you are accessing is within the allocated heap/stack of your program. You will read/modify whatever the value in that memory location. If the address is outside the allocated memory space it will give an error.
Debuggers has checks to detect these, as this kind of coding create bugs very difficult to track.
Upvotes: -2