I was looking at the GNU implementation of obstacks, and I noticed the obstack_free subroutine is using pointer comparison to the beginnings and ends of the previous links of the linked list to find what block the pointer-to-be freed belongs to.
https://code.woboq.org/userspace/glibc/malloc/obstack.c.html
while (lp != 0 && ((void *) lp >= obj || (void *) (lp)->limit < obj))
{
plp = lp->prev;
CALL_FREEFUN (h, lp);
lp = plp;
h->maybe_empty_object = 1;
} //...
Such comparison appears to be undefined as per http://port70.net/~nsz/c/c11/n1570.html#6.5.8p5:
When two pointers are compared, the result depends on the relative
locations in the address space of the objects pointed to. If two
pointers to object types both point to the same object, or both point
one past the last element of the same array object, they compare
equal. If the objects pointed to are members of the same aggregate
object, pointers to structure members declared later compare greater
than pointers to members declared earlier in the structure, and
pointers to array elements with larger subscript values compare
greater than pointers to elements of the same array with lower
subscript values. All pointers to members of the same union object
compare equal. If the expression P points to an element of an array
object and the expression Q points to the last element of the same
array object, the pointer expression Q+1 compares greater than P. In
all other cases, the behavior is undefined.
Is there a fully standard compliant way to implement obstacks. If not, what platforms could such comparison practically break on?
I am not a language-lawyer, so I don't know how to answer OP's question, except that a plain reading of the standard does not describe the entire picture.
While the standard says that comparing unrelated pointers yields undefined results, the behaviour of a standards-compliant C compiler is much more restricted.
The first sentence in the section concerning pointer comparison is
When two pointers are compared, the result depends on the relative locations in the address space of the objects pointed to.
and for a very good reason.
If we examine the possibilities how the pointer comparison code may be used, we find that unless the compiler can determine which objects the compared pointers belong to at compile time, all pointers in the same address space must compare arithmetically, according to the addresses they refer to.
(If we prove that a standards-compliant C compiler is required by the standard to provide specific results when a plain reading of the C standard itself says the results are undefined, is such code standards-compliant or not? I don't know. I only know such code works in practice.)
A literal interpretation of the standard may lead to one believing that there is absolutely no way of determining whether a pointer refers to an array element or not. In particular, observing
int is_within(const char *arr, const size_t len, const char *ptr)
{
return (ptr >= arr) && (ptr < (arr + len));
}
a standards compliant C compiler could decide that because comparison between unrelated pointers is undefined, it is justified in optimizing the above function into
int is_within(const char *arr, const size_t len, const char *ptr)
{
if (size)
return ptr != (arr + len);
else
return 0;
}
which returns 1 for pointers within array const char arr[len], and zero at the element just past the end of the array, just like the standard requires; and 1 for all undefined cases.
The problem in that line of thinking arises when a caller, in a separate compilation unit, does e.g.
char buffer[1024];
char *p = buffer + 768;
if (is_within(buffer, (sizeof buffer) / 2, p)) {
/* bug */
} else {
/* correct */
}
Obviously, if the is_within() function was declared static (or static inline), the compiler could examine all call chains that end up in is_within(), and produce correct code.
However, when is_within() is in a separate compilation unit compared to its callers, the compiler can no longer make such assumptions: it simply does not, and cannot know, the object boundaries beforehand. Instead, the only way it can be implemented by a standards-compliant C compiler, is to rely on the addresses the pointers refer to, blindly; something like
int is_within(const char *arr, const size_t len, const char *ptr)
{
const uintptr_t start = POINTER_TO_UINTPTR(arr);
const uintptr_t limit = POINTER_TO_UINTPTR(arr + len);
const uintptr_t thing = POINTER_TO_UINTPTR(ptr);
return (thing >= start) && (thing < limit);
}
where the POINTER_TO_UINTPTR() would be a compiler-internal macro or function, that converts the pointer losslessly to an unsigned integer value (with the intent that there would be a corresponding UINTPTR_TO_POINTER() that could recover the exact same pointer from the unsigned integer value), without consideration for any optimizations or rules allowed by the C standard.
So, if we assume that the code is compiled in a separate compilation unit to its users, the compiler is forced to generate code that provides more quarantees than a simple reading of the C standard would indicate.
In particular, if arr and ptr are in the same address space, the C compiler must generate code that compares the addresses the pointers point to, even if the C standard says that comparison of unrelated pointers yields undefined results; simply because it is at least theoretically possible for an array of objects to occupy any subregion of the address space. The compiler just cannot make assumptions that break conforming C code later on.
In the GNU obstack implementation, the obstacks all exist in the same address space (because of how they are obtained from the OS/kernel). The code assumes that the pointers supplied to it refer to these objects. Although the code does return an error if it detects that a pointer is invalid, it does not guarantee it always detects invalid pointers; thus, we can ignore the invalid pointer cases, and simply assume that because all obstacks are from the same address space, so are all the user-supplied pointers.
There are many architectures with multiple address spaces. x86 with a segmented memory model is one of these. Many microcontrollers have Harvard architecture, with separate address spaces for code and data. Some microcontrollers have a separate address space (different machine instructions) for accessing RAM and flash memory (but capable of executing from both), and so on.
It is even possible for there to be an architecture where each pointer has not only its memory address, but some kind of unique object ID associated with it. This is nothing special; it just means that on such an architecture, each object has their own address space.
Related
When two pointers are compared, the result depends on the relative
locations in the address space of the objects pointed to. If two
pointers to object or incomplete types both point to the same object,
or both point one past the last element of the same array object, they
compare equal. If the objects pointed to are members of the same
aggregate object, pointers to structure members declared later compare
greater than pointers to members declared earlier in the structure,
and pointers to array elements with larger subscript values compare
greater than pointers to elements of the same array with lower
subscript values. All pointers to members of the same union object
compare equal. If the expression P points to an element of an array
object and the expression Q points to the last element of the same
array object, the pointer expression Q+1 compares greater than P. In
all other cases, the behavior is undefined.
If we have two pointers referencing the same type arrays and we have lengths of those arrays can we find if those arrays do not overlap without invoking a UB?
Remark: I am not interested in examples showing me that in the real life (implementation etc) it can be done. So please do not show the code (unless you can prove [standardwise] that is UB free).
It is possible in standard C, though not as efficient as a non-standard approach.
The above quoted passage from section 6.5.8p5 of the C11 standard applies to relational operators, i.e. <, >, <=, and >=. The equality operators == and != do not have this restriction. They can be used to compare any two object pointers for equality.
Specifically, section 6.5.9p6 regarding the equality operators state:
Two pointers compare equal if and only if both are null pointers, both
are pointers to the same object (including a pointer to an object and
a subobject at its beginning) or function, both are pointers to one
past the last element of the same array object, or one is a pointer to
one past the end of one array object and the other is a pointer to the
start of a different array object that happens to immediately follow
the first array object in the address space.
So you can check for overlap in a standard-compliant way by using == along with a pair of unsigned char * to iterate through the bytes of each object and compare their addresses for equality.
For example:
int overlap = 0;
unsigned char *o1 = (unsigned char *)&obj1;
unsigned char *o2 = (unsigned char *)&obj2;
for (int i=0; !overlap && i < sizeof obj1; i++) {
for (int j=0; !overlap && j < sizeof obj2; j++) {
if (o1 + i == o2 + j) {
overlap = 1;
}
}
}
A more efficient approach would be to check the addresses of only the first byte of one object against the addresses of each byte in the other object, since if there is an overlap then the start of one object must be within the other:
int overlap(const void *p1, size_t size1, const void *p2, size_t size2)
{
const unsigned char *o1 = p1;
const unsigned char *o2 = p2;
for (int i=0; i < size1; i++) {
if (o1 + i == o2) {
return 1;
}
}
for (int i=0; i < size2; i++) {
if (o2 + i == o1) {
return 1;
}
}
return 0;
}
The accepted answer is addressing OP's question by referring the appropriate section of language standard. But the second snippet of code posted in accepted answer will fail, in case, when the first object (array) is subset of second object (array) in such a way that first object is completely overlapped by second object but excluding the start and end element of second object i.e. overlapping like this -
object 2
|
+-----------------------------------------------------------+
| |
| |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| | | | | | | | | | | | | | | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| |
| |
+---------------------------------------------------+
|
object 1 (any subset of this region)
This post is just a couple of modifications to address the problem in #dbush post second code snippet as well as making it a little more efficient by considering the size of element type of array in question.
/*
* Parameters:
* obj1 : Pointer to array1
* obj1_sz : Size of array1
* obj2 : Pointer to array2
* obj2_sz : Size of array2
* type_sz : Size of type of elements of array
*
* Return:
* 0 - No overlap
* 1 - Overlap
*
* [Assumption: Both array1 and array2 are of same type]
*/
int check_overlap (const void *obj1, size_t obj1_sz, const void *obj2, size_t obj2_sz, size_t type_sz) {
const unsigned char *pobj1 = obj1;
const unsigned char *pobj2 = obj2;
size_t sz1 = obj1_sz;
size_t sz2 = obj2_sz;
if (obj1_sz < obj2_sz) {
pobj1 = obj2;
pobj2 = obj1;
sz1 = obj2_sz;
sz2 = obj1_sz;
}
for (size_t i = 0; i < sz1; ++i) {
if ((pobj1 + (i * type_sz) == pobj2) ||
(pobj1 + (i * type_sz) == pobj2 + ((sz2 - 1) * type_sz))) {
return 1;
}
}
return 0;
}
Not in a portable way. There are several false negatives.
Counterexample #1: Memory aliasing
It is unusual for a device (e.g. RAM, ROM, or memory-mapped I/O) to use all of the address pins coming out of the processor. Typically, whatever number of address lines are needed by the device are connected to the lowest-order address lines of the processor, the highest address lines are used to select the device, and the address lines in between are not connected:
MSB -------- Address bus -------- LSB
| | ... | | x x ... x x | | ... | |
chip select unconnected to device
Such a device can be addressed as a block in the address space. However, the device also appears as several other blocks in the address space; each of these blocks physically point to the same locations on the device! The effect is called memory aliasing, and is much more common than you may realize.
For example, imagine a system with 16-bit addresses. Perhaps the top 4 address lines are used to select which chip is being addressed. Suppose we have a device assigned to A15:A12 == 0xE. Furthermore, this device only has 8 address lines coming out of it, so we connect those to A7:A0.
This device appears as addresses 0xE000 through 0xE0FF. However, it also appears at 0xE100 through 0xE1FF. Indeed, it appears 16 times in the address space, at any block 0xEz00 through 0xEzFF. Worse, each of these blocks physically point to the same thing. An access to 0xE123 is the same as an access to 0xE223, 0xE323, 0xE423, and so on.
So you can have two objects in memory that seem to point to different areas of memory, but in fact actually point to the same thing:
char *x = (char *)0xE000;
char *y = (char *)0xE300;
if (overlap(x, y, 16)) { ... }
A naive implementation of overlap() would report these as two different objects. But they are the same object; writing to x[] changes y[]. Therefore, in this case you will get a false negative. A correct implementation of overlap() would require and depend upon full knowledge of the system's memory map, making such a function completely non-portable.
Counterexample #2: Shared memory
Suppose x and y are overlapping objects in process A. We then use the operating system to create shared memory between process A and process B. Specifically, xx is a shared memory pointer in process B that points to x, and yy is a shared memory pointer in process B that points to y.
Back in process A, it's not hard to write a function that determines that x and y indeed overlap.
But depending on the operating system, pointers xx and yy in process B may look nothing like overlapping objects. But in reality, they do indeed point to overlapping objects. So you will get a false negative.
Is it theoretically possible to write a function that checks across processes for overlap? Probably, but keep in mind that I can make the problem even more difficult. I can create subsets of xx and yy that still overlap; I can share memory from process B to a third process; and so on. In any case, any such solution is not portable.
Counterexample #3: 8086 far pointers
The 8086 architecture on the original IBM PC used a type of memory mapping called "segmentation". A 16-bit register called the "segment" was multiplied by 16 and then added to another 16-bit register with the "base address" to obtain the 20-bit physical address.
Programs needing less than 64k of memory could get away with just the 16-bit base addresses, called "near pointers". But programs that needed more than 64k of memory had to maintain 32-bit "far pointers" which contained both the segment and the base address.
Because of the pointer arithmetic of segmentation, it is quite easy to make two far pointers that seem to be quite different, yet point to the same object:
far char *x = (far char *)0x12340005L;
far char *y = (far char *)0x10002345L;
In this case, x and y both point to the same physical address 0x12345, even though they are very different bit patterns.
Some compilers would treat x == y as false because they have different bit patterns. Other compilers would do the math (with a performance penalty) and return true. Yet other compilers let you choose either behavior with a command-line switch or #pragma.
The OP complains that these examples represent compilers that are not "standard-conforming". The argument is that if two pointers actually do point to the same object, then the standard says they must compare ==.
If you're going to be such a language-lawyer, then no compiler has even conformed to the standard. Not gcc, not Microsoft C (two compilers proud of their conformance). Basically every system that has had a C compiler has had some degree of memory aliasing (counterexample #1). So every C compiler is guilty of allowing two != pointers point to the same thing.
On the other hand, if you interpret the standard by its intended meaning instead of its literal meaning, then those compilers do conform to the standard.
Sure, these are edge cases. Most programs are in user space, where #1 is hidden away. Few programs use shared memory (#2). And no one likes programming in a segmented memory model (#3). But exceptions like these are why the standard has so many instances of undefined behavior; many things which work in one case cannot be made to work that way on other cases.
Well, since you didn't say anything about preserving data:
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
bool overlaps(void* p1, void* p2, size_t sz1, size_t sz2) {
if (!p1 || !p2 || !sz1 || !sz2) return false; /* empty ranges ignored */
memset(p1, 0, sz1);
memset(p2, 1, sz2);
return !!memchr(p1, 1, sz1);
}
This is completely well defined.
You can check in linear time whether &obj1[i] == &obj2[0] for some i, or &obj1[0] == &obj2[i] for some i and determine this way if there is overlap or not.
Before you do that, you cast obj1 and obj2 to uintptr_t, assume (without evidence) that pointers cast to uintptr_t behave similar to char*, and calculate i, j so that &obj1[i] should equal &obj2[j] according to your assumptions, and both indices are valid. Since comparing unrelated pointers for equality or inequality doesn't invoke UB you might be able to prove that the arrays are overlapping this way. If your implementation is weird then this doesn't help, but also won't give you wrong results. And if the arrays don't overlap, it doesn't work either. In those case you go back to the first method.
In the language the Standard was written to describe, it would be possible to use the equality comparison operator to check the starting address of each object with every possible address within the other. If the objects overlap, one such comparison should report a match.
In the language processed by clang and gcc, however, the equality comparison operator may only be used with two pointers that each identify a byte in some object, or with two pointers that each point just past the last byte of some object, or with a null pointer and a pointer of either of the above categories. Using it with one pointer from each of the first two categories is not allowed.
The inability of clang and gcc to reliably handle corner cases involving comparisons between pointers of the first two categories has been entered on both compilers' bug reporting systems years ago; the fact that both compilers continue to make "optimizations" that break in such cases implies that their maintainers believe the language forbids such comparisons and imposes no requirements whatsoever on the behavior of any program that performs them.
Well, if we're going to be language-lawyering, I raise you this:
// SPDX-License-Identifier: CC0-1.0
#include <stddef.h>
#include <stdbool.h>
#include <stdint.h>
bool overlap(const void *p1, size_t s1, const void *p2, size_t s2)
{
const uintptr_t p1b = (uintptr_t) p1;
const uintptr_t p2b = (uintptr_t) p2;
const uintptr_t p1e = (uintptr_t) ((char*) p1 + (s1 - 1));
const uintptr_t p2e = (uintptr_t) ((char*) p2 + (s2 - 1));
return (p1b <= p2b && p2b <= p1e)
|| (p2b <= p1b && p1b <= p2e);
}
This code invokes implementation-defined behavior, not undefined behavior.[1] Obviously, this is by no means portable, but in most cases this should work.
[1]: ISO/IEC 9899:2018, § 6.3.2.3, par. 6 ("Any pointer type may be converted to an integer type. Except as previously specified, the result
is implementation-defined.").
The problem maybe more complex, when these objects have other (and different) objects as members (subobjects) which also may overlap. Like an array of strings.
Your overlap problem is more a program logic problem, because every object should have its own memory or some shared data from a data store, which than no one own.
Depending on the problem, you can also use an additional memory struct array which is maintaining all start and end addresses of the components and than you only are comparing addresses.
I'm trying to further my knowledge and experience in C, so I'm writing some small utilities.
I'm copying memory, and according to the man page for memcpy(3):
NOTES
Failure to observe the requirement that the memory areas do not overlap has been
the source of real bugs. (POSIX and the C standards are explicit that employing
memcpy() with overlapping areas produces undefined behavior.) Most notably, in
glibc 2.13 a performance optimization of memcpy() on some platforms (including
x86-64) included changing the order in which bytes were copied from src to dest.
Clearly, overlapping memory regions passed to memcpy(3) can cause a lot of problems.
I'm trying to write a safe wrapper as part of learning C to make sure that these memory regions don't overlap:
int safe_memcpy(void *dest, void *src, size_t length);
The logic I'm trying to implement is:
Check both the source and destination pointers for NULL.
Establish the pointer "range" for both source and dest with the length parameter.
Determine if the source range intersects with the destination range, and vice versa.
My implementation so far:
#define SAFE_MEMCPY_ERR_NULL 1
#define SAFE_MEMCPY_ERR_SRC_OVERLAP 2
#define SAFE_MEMCPY_ERR_DEST_OVERLAP 3
int safe_memcpy(void *dest, void *src, size_t length) {
if (src == NULL || dest == NULL) {
return SAFE_MEMCPY_ERR_NULL;
}
void *dest_end = &dest[length - 1];
void *src_end = &src[length - 1];
if ((&src >= &dest && &src <= &dest_end) ||
(&src_end >= &dest && &src_end <= &dest_end)) {
// the start of src falls within dest..dest_end OR
// the end of src falls within dest..dest_end
return SAFE_MEMCPY_ERR_SRC_OVERLAP;
}
if ((&dest >= &src && &dest <= &src_end) ||
(&dest_end >= &src && &dest_end <= &src_end)) {
// the start of dest falls within src..src_end
// the end of dest falls within src..src_end
return SAFE_MEMCPY_ERR_DEST_OVERLAP;
}
// do the thing
memcpy(dest, src, length);
return 0;
}
There's probably a better way to do errors, but this is what I've got for now.
I'm pretty sure I'm triggering some undefined behavior in this code, as I'm hitting SAFE_MEMCPY_ERR_DEST_OVERLAP on memory regions that do not overlap. When I examine the state using a debugger, I see (for instance) the following values:
src: 0x7ffc0b75c5fb
src_end: 0x7ffc0b75c617
dest: 0x1d05420
dest_end: 0x1d0543c
Clearly, these addresses do not even remotely overlap, hence why I'm thinking I'm triggering UB, and compiler warnings indicate as such:
piper.c:68:27: warning: dereferencing ‘void *’ pointer
void *dest_end = &dest[length - 1];
It seems that I need to cast the pointers as a different type, but I'm not sure which type to use: the memory is untyped so should I use a char * to "look at" the memory as bytes? If so, should I cast everything as a char *? Should I instead use intptr_t or uintptr_t?
Given two pointers and a length for each of them, how can I safely check if these regions overlap one another?
In the first place, a conforming program cannot perform pointer arithmetic on a pointer of type void *, nor (relatedly) apply the indexing operator to it, not even with index 0. void is an incomplete type, and unique among those in that it cannot be completed. The most relevant implication of that is that that type does not convey any information about the size of the thing to which it points, and pointer arithmetic is defined in terms of the pointed-to object.
So yes, expressions such as your &dest[length - 1] have undefined behavior with respect to the C standard. Some implementations provide extensions affecting that, and others reject such code at compile time. In principle, an implementation could accept the code and do something bizarre with it, but that's relatively unlikely.
In the second place, you propose to
write a safe wrapper as part of learning C to make sure that these memory regions don't overlap
, but there is no conforming way to do that for general pointers. Pointer comparisons and pointer differences are defined only for pointers into the same array (or to one element past the end of the array), where a pointer to a scalar is considered in that regard as a pointer to the first element of dimension-1 array.
Converting to a different pointer type, perhaps char *, would resolve the pointer arithmetic issue, but not, in the general case, the pointer comparability issue. It might get exactly the behavior you want out of some implementations, reliably even, but it is not a conforming approach to the problem, and the ensuing undefined behavior might produce genuine bugs in other implementations.
Relatively often, you can know statically that pointers do not point to overlapping regions. In particular, if one pointer in question is a pointer to an in-scope local variable or to a block of memory allocated by the current function, then you can usually be sure whether there is an overlap. For cases where you do not know, or where you know that there definitely is overlap, the correct approach is to use memmove() instead of memcpy().
This "safe" memcpy is not safe as well as it does not copy anything when programmes expects it. Use memmove to be safe
You should not use &src and &dest as it is not beginning of the data or buffer but the address of the parameter src and dest itself.
Same is with srcend and destend
Given two pointers and a length for each of them, how can I safely check if these regions overlap one another?
<, <=, >=, > are not defined when 2 pointers are not related to the same object.
A tedious approach checks the endpoints of one against all the other's elements and takes advantage that the length of the source and destination are the same.
int safe_memcpy(void *dest, const void *src, size_t length) {
if (length > 0) {
unsigned char *d = dest;
const unsigned char *s = src;
const unsigned char *s_last = s + length - 1;
for (size_t i = 0; i < length; i++) {
if (s == &d[i]) return 1; // not safe
if (s_last == &d[i]) return 1; // not safe
}
memcpy(dest, src, length);
}
return 0;
}
If the buffer lengths differ, check the shorter one's endpoints against the addresses of the longer one's elements.
should I cast everything as a char *
Use unsigned char *.
mem...(), str...() behave as if each array element was unsigned char.
For all functions in this subclause, each character shall be interpreted as if it had the type unsigned char (and therefore every possible object representation is valid and has a different value). C17dr § 7.24.1 3
With rare non-2's complement, unsigned char is important to avoid signed char traps and maintain -0, +0 distinctiveness. Strings only stop on +0.
With functions like int strcmp/memcmp(), unsigned char that use integer math, it is important when comparing elements outside the range of [0...CHAR_MAX] to return the correctly signed result.
Even if void * indexing was allowed, void *dest_end = &dest[length - 1]; is very bad when length == 0 as that is like &dest[SIZE_MAX];
&src >= &dest s/b src >= dest for even a chance at working.
The addresses of src, dest are irrelevant to the copy, only their values are important.
I suspect this errant code leads to UB in OP's other code.
Should I instead use intptr_t or uintptr_t?
Note that (u)intptr_t are optional types - they might not exist in a conforming compiler.
Even when the types exist, math on the pointers is not defined to be related to math on the integer values.
Clearly, these addresses do not even remotely overlap, hence why I'm thinking I'm triggering UB,
"Clearly" if ones assumes a liner mapping addresses to integers, something not specified in C.
The memory is untyped so should I use a char * to "look at" the memory as bytes? If so, should I cast everything as a char *?
Use unsigned char* if you need to dereference the data, or just char* when you want to increment/decrement the pointer value by count of bytes.
It's common to do:
void a_function_that_takes_void(void *x, void *y) {
char *a = x;
char *b = y;
/* uses a and b throughout here */
}
If so, should I cast everything as a char *?
Yes. It's also common to do:
void_pointer = (char*)void_pointer + 1;
Should I instead use intptr_t or uintptr_t?
You could, but that would be the same as using char*, except for a char* to intptr_t conversion.
how can I safely check if these regions overlap one another?
It's good to do some research. how to implement overlap-checking memcpy in C
I have a piece of memory I am "guarding", defined by
typedef unsigned char byte;
byte * guardArea;
size_t guardSize;
byte * guardArea = getGuardArea();
size_t guardSize = getGuardSize();
An acceptable implementation for the sake of this would be:
size_t glGuardSize = 1024; /* protect an area of 1kb */
byte * getGuardArea()
{
return malloc( glGuardSize );
}
size_t getGuardSize()
{
return glGuardSize;
}
Can the following snippet return true for any pointer (from a different malloc, from the stack etc)?
if ( ptr >= guardArea && ptr < (guardArea + guardSize)) {
return true;
}
The standard states that:
values within the area will return true. (When ptr was a member, all acts correctly.)
pointers will be distinct (a == b only if they are the same).
all addresses within the byte array can be accessed by incrementing the base.
any pointer can be converted to and from a char *, without damage.
So I can't understand how the result could be true for any pointer from a different object (as it would break the distinct rule for one of the pointers within the area).
Edit:
What is the use case?
The ability to detect whether a pointer is within a region is really important, at some point code is written
if ( isInMyAreaOfInterest( unknownPointer ) ) {
doMySpecialThing( unknownPointer );
} else {
doSomethingElse( unknownPointer );
}
I think the language needs to support the developer by making such constructs simple and obvious, and our interpretation of the standard, is that the developer needs to cast to int. Due to the "undefined behavior" of pointer comparisons of distinct objects.
I was hoping for some clarity of why I can't do what I would like (my snippet), as all the posts on SO I found say that the standard claims undefined behavior, without any explanation, or examples of why the standard is better than how I would like it to work.
At the moment, we have a rule, we are neither understanding why the rule exists, or questioning if the rule is helping us
Example posts:
SO: checking if a pointer is in a malloced area
SO: C compare pointers
It is still possible for an allocation to generate a pointer that satisfies the condition despite the pointer not pointing into the region. This will happen, for example, on an 80286 in protected mode, which is used by Windows 3.x in Standard mode and OS/2 1.x.
In this system, pointers are 32-bit values, split into two 16-bit parts, traditionally written as XXXX:YYYY. The first 16-bit part (XXXX) is the "selector", which chooses a bank of 64KB. The second 16-bit part (YYYY) is the "offset", which chooses a byte within that 64KB bank. (It's more complicated than this, but let's just leave it at that for the purpose of this discussion.)
Memory blocks larger than 64KB are broken up into 64KB chunks. To move from one chunk to the next, you add 8 to the selector. For example, the byte after 0101:FFFF is 0109:0000.
But why do you add 8 to move to the next selector? Why not just increment the selector? Because the bottom three bits of the selector are used for other things.
In particular, the bottom bit of the selector is used to choose the selector table. (Let's ignore bits 1 and 2 since they are not relevant to the discussion. Assume for convenience that they are always zero.)
There are two selector tables, the Global Selector Table (for memory shared across all processes) and the Local Selector Table (for memory private to a single process). Therefore, the selectors available for process private memory are 0001, 0009, 0011, 0019, etc. Meanwhile, the selectors available for global memory are 0008, 0010, 0018, 0020, etc. (Selector 0000 is reserved.)
Okay, now we can set up our counter-example. Suppose guardArea = 0101:0000 and guardSize = 0x00020000. This means that the guarded addresses are 0101:0000 through 0101:FFFF and 0109:0000 through 0109:FFFF. Furthermore, guardArea + guardSize = 0111:0000.
Meanwhile, suppose there is some global memory that happens to be allocated at 0108:0000. This is a global memory allocation because the selector is an even number.
Observe that the global memory allocation is not part of the guarded region, but its pointer value does satisfy the numeric inequality 0101:0000 <= 0108:0000 < 0111:0000.
Bonus chatter: Even on CPU architectures with a flat memory model, the test can fail. Modern compilers take advantage of undefined behavior and optimize accordingly. If they see a relational comparison between pointers, they are permitted to assume that the pointers point into the same array (or one past the last element of that array). Specifically, the only pointers that can legally be compared with guardArea are the ones of the form guardArea, guardArea+1, guardArea+2, ..., guardArea + guardSize. For all of these pointers, the condition ptr >= guardArea is true and can therefore be optimized out, reducing your test to
if (ptr < (guardArea + guardSize))
which will now be satisfied for pointers that are numerically less than guardArea.
Moral of the story: This code is not safe, not even on flat architectures.
But all is not lost: The pointer-to-integer conversion is implementation-defined, which means that your implementation must document how it works. If your implementation defines the pointer-to-integer conversion as producing the numeric value of the pointer, and you know that you are on a flat architecture, then what you can do is compare integers rather than pointers. Integer comparisons are not constrained in the same way that pointer comparisons are.
if ((uintptr_t)ptr >= (uintptr_t)guardArea &&
(uintptr_t)ptr < (uintptr_t)guardArea + (uintptr_t)guardSize)
Yes.
void foo(void) {}
void(*a) = foo;
void *b = malloc(69);
uintptr_t ua = a, ub = b;
ua and ub are in fact permitted to have the same value. This occurred frequently on segmented systems (like MS-DOS) which might put code and data in separate segments.
int *p;
{
int x = 0;
p = &x;
}
// p is no longer valid
{
int x = 0;
if (&x == p) {
*p = 2; // Is this valid?
}
}
Accessing a pointer after the thing it points to has been freed is undefined behavior, but what happens if some later allocation happens in the same area, and you explicitly compare the old pointer to a pointer to the new thing? Would it have mattered if I cast &x and p to uintptr_t before comparing them?
(I know it's not guaranteed that the two x variables occupy the same spot. I have no reason to do this, but I can imagine, say, an algorithm where you intersect a set of pointers that might have been freed with a set of definitely valid pointers, removing the invalid pointers in the process. If a previously-invalidated pointer is equal to a known good pointer, I'm curious what would happen.)
By my understanding of the standard (6.2.4. (2))
The value of a pointer becomes indeterminate when the object it points to (or just past) reaches the end of its lifetime.
you have undefined behaviour when you compare
if (&x == p) {
as that meets these points listed in Annex J.2:
— The value of a pointer to an object whose lifetime has ended is used (6.2.4).
— The value of an object with automatic storage duration is used while it is indeterminate (6.2.4, 6.7.9, 6.8).
Okay, this seems to be interpreted as a two- make that three part question by some people.
First, there were concerns if using the pointer for a comparison is defined at all.
As is pointed out in the comments, the mere use of the pointer is UB, since $J.2: says use of pointer to object whose lifetime has ended is UB.
However, if that obstacle is passed (which is well in the range of UB, it can work after all and will on many platforms), here is what I found about the other concerns:
Given the pointers do compare equal, the code is valid:
C Standard, §6.5.3.2,4:
[...] If an invalid value has been assigned to the pointer, the behavior of the unary * operator is undefined.
Although a footnote at that location explicitly says. that the address of an object after the end of its lifetime is an invalid pointer value, this does not apply here, since the if makes sure the pointer's value is the address of x and thus is valid.
C++ Standard, §3.9.2,3:
If an object of type T is located at an address A, a pointer of type cv T* whose value is the address A is said to point to that object, regardless of how the value was obtained. [ Note: For instance, the address one past the end of an array (5.7) would be considered to point to an unrelated object of the array’s element type that might be located at that address.
Emphasis is mine.
It will probably work with most of the compilers but it still is undefined behavior. For the C language these x are two different objects, one has ended its lifetime, so you have UB.
More seriously, some compilers may decide to fool you in a different way than you expect.
The C standard says
Two pointers compare equal if and only if both are null pointers, both
are pointers to the same object (including a pointer to an object and
a subobject at its beginning) or function, both are pointers to one
past the last element of the same array object, or one is a pointer to
one past the end of one array object and the other is a pointer to the
start of a different array object that happens to immediately follow
the first array object in the address space.
Note in particular the phrase "both are pointers to the same object". In the sense of the standard the two "x"s are not the same object. They may happen to be realized in the same memory location, but this is to the discretion of the compiler. Since they are clearly two distinct objects, declared in different scopes the comparison should in fact never be true. So an optimizer might well cut away that branch completely.
Another aspect that has not yet been discussed of all that is that the validity of this depends on the "lifetime" of the objects and not the scope. If you'd add a possible jump into that scope
{
int x = 0;
p = &x;
BLURB: ;
}
...
if (...)
...
if (something) goto BLURB;
the lifetime would extend as long as the scope of the first x is reachable. Then everything is valid behavior, but still your test would always be false, and optimized out by a decent compiler.
From all that you see that you better leave it at argument for UB, and don't play such games in real code.
It would work, if by work you use a very liberal definition, roughly equivalent to that it would not crash.
However, it is a bad idea. I cannot imagine a single reason why it is easier to cross your fingers and hope that the two local variables are stored in the same memory address than it is to write p=&x again. If this is just an academic question, then yes it's valid C - but whether the if statement is true or not is not guaranteed to be consistent across platforms or even different programs.
Edit: To be clear, the undefined behavior is whether &x == p in the second block. The value of p will not change, it's still a pointer to that address, that address just doesn't belong to you anymore. Now the compiler might (probably will) put the second x at that same address (assuming there isn't any other intervening code). If that happens to be true, it's perfectly legal to dereference p just as you would &x, as long as it's type is a pointer to an int or something smaller. Just like it's legal to say p = 0x00000042; if (p == &x) {*p = whatever;}.
The behaviour is undefined. However, your question reminds me of another case where a somewhat similar concept was being employed. In the case alluded, there were these threads which would get different amounts of cpu times because of their priorities. So, thread 1 would get a little more time because thread 2 was waiting for I/O or something. Once its job was done, thread 1 would write values to the memory for the thread two to consume. This is not "sharing" the memory in a controlled way. It would write to the calling stack itself. Where variables in thread 2 would be allocated memory. Now, when thread 2 eventually got round to execution,all its declared variables would never have to be assigned values because the locations they were occupying had valid values. I don't know what they did if something went wrong in the process but this is one of the most hellish optimizations in C code I have ever witnessed.
Winner #2 in this undefined behavior contest is rather similar to your code:
#include <stdio.h>
#include <stdlib.h>
int main() {
int *p = (int*)malloc(sizeof(int));
int *q = (int*)realloc(p, sizeof(int));
*p = 1;
*q = 2;
if (p == q)
printf("%d %d\n", *p, *q);
}
According to the post:
Using a recent version of Clang (r160635 for x86-64 on Linux):
$ clang -O realloc.c ; ./a.out
1 2
This can only be explained if the Clang developers consider that this example, and yours, exhibit undefined behavior.
Put aside the fact if it is valid (and I'm convinced now that it's not, see Arne Mertz's answer) I still think that it's academic.
The algorithm you are thinking of would not produce very useful results, as you could only compare two pointers, but you have no chance to determine if these pointers point to the same kind of object or to something completely different. A pointer to a struct could now be the address of a single char for example.
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!
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".
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.
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 -
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.
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.