Is the pointer pointing to one-past-malloc used correctly?

In C, itโ€™s great to make a pointer that points to the last one element of the array and use it in pointer arithmetic if you don't find it:

int a[5], *p = a+5, diff = pa; // Well-defined 

However, this is UB:

 p = a+6; int b = *(a+5), diff = pa; // Dereferencing and pointer arithmetic 

Now I have a question: does this apply to dynamically allocated memory? Suppose I use a pointer pointing to one of the last in pointer arithmetic, without dereferencing it, and malloc() succeeds.

 int *a = malloc(5 * sizeof(*a)); assert(a != NULL, "Memory allocation failed"); // Question: int *p = a+5; int diff = pa; // Use in pointer arithmetic? 
+43
c language-lawyer malloc dynamic-allocation bounds
Dec 20 '17 at 7:03
source share
5 answers

Is a pointer pointing to one-past-malloc well used?

It is well defined if p points to one past of allocated memory, and it is not dereferenced.

n1570 - ยง6.5.6 (p8):

[...] If the result indicates one after the last element of an array object, it should not be used as the operand of the unary operator * , which is evaluated.

Subtraction of two pointers is permissible only when they point to elements of the same array object or one after the last element of the array object, otherwise this will lead to undefined behavior.

(p9) :

When two pointers are subtracted, both must point to the elements of the same array object or one after the last element of the array object [...]

The above quotes are well applicable for both dynamically and statically distributed memory.

 int a[5]; ptrdiff_t diff = &a[5] - &a[0]; // Well-defined int *d = malloc(5 * sizeof(*d)); assert(d != NULL, "Memory allocation failed"); diff = &d[5] - &d[0]; // Well-defined 

Another reason this is true for dynamically allocated memory, as pointed out by Jonathan Leffler in comment is:

ยง7.22.3 (p1) :

The storage order and adjacency allocated by successive calls to the aligned_alloc , calloc , malloc and realloc functions are undefined. The pointer is returned if the selection is successfully performed appropriately so that it can be assigned to a pointer to any type of object with a fundamental requirement for alignment, and then use to access such an object or an array of such objects in the allocated space (until the space is explicitly freed).

The pointer returned by malloc in the above fragment is assigned d , and the allocated memory is an array of 5 int objects.

+20
Dec 20 '17 at 8:05
source share

Project n4296 for C11 is explicit that the definition of one of the past arrays is clearly defined: 6.5.6 Language / expressions / Additive operators:

ยง 8 When an expression that has an integer type is added or subtracted from the pointer, result is the operand type of the pointer .... Also, if the expression P points to the last element of an array object, the expression (P) +1 indicates one after the last element of the array , and if the expression Q indicates one after the last element of the array object, the expression (Q) -1 indicates the last element of the array object ... If the result indicates one after the last element of the array object, it should not be used as the operand of the unary * operator ory estimated.

Since the type of memory is never specified in a subclause, it applies to any type of memory, including allocated memory.

This clearly means that after:

 int *a = malloc(5 * sizeof(*a)); assert(a != NULL, "Memory allocation failed"); 

both

 int *p = a+5; int diff = pa; 

perfectly defined and, as usual rules for pointer arithmetic apply, diff gets a value of 5 .

+24
Dec 20 '17 at 7:18
source share

Yes, the same rules apply to variables with dynamic and automatic storage duration. This even applies to a malloc query for a single element (scalar is equivalent to a singleton array in this regard).

Pointer arithmetic is valid only in arrays, including one end of the end of the array.

When dereferencing, it is important to note one consideration: regarding initialization int a[5] = {0}; the compiler should not try to dereference a[5] in the expression int* p = &a[5] ; he should compile it as int* p = a + 5; . The same goes for dynamic storage.

+7
Dec 20 '17 at 8:10
source share

Is a pointer pointing to one-past-malloc well used?

Yes, but there is an angular case when this is not clearly defined:

 void foo(size_t n) { int *a = malloc(n * sizeof *a); assert(a != NULL || n == 0, "Memory allocation failed"); int *p = a+n; intptr_t diff = pa; ... } 

Memory management features . If the size of the requested space is zero, the behavior is determined by the implementation: a null pointer is returned or the behavior is as if the size were some nonzero value, except that the returned pointer should not be used to access the object. C11dr ยง7.22.3 1

foo(0) malloc(0) can return NULL or non-NULL . In the first implementation, returning NULL not a "Memory Allocation Error". This means that the code is trying int *p = NULL + 0; with int *p = a+n; , which does not give guarantees regarding the mathematics of the pointer - or at least leads to the emergence of such code.

Benefits of portable code by avoiding 0 sizing.

 void bar(size_t n) { intptr_t diff; int *a; int *p; if (n > 0) { a = malloc(n * sizeof *a); assert(a != NULL, "Memory allocation failed"); p = a+n; diff = pa; } else { a = p = NULL; diff = 0; } ... } 
+7
Dec 20 '17 at 13:20
source share

I found that in C on Windows on an Intel X86 chip, I can have an invalid value in the pointer and not call the GPF if I don't search for the pointer. In C on Unix, on an HP 68000 processor, the program will have a core dump if the pointer had an invalid value, even if it was not dereferenced. (Although I admit that it was in the 1990s). Since then, I have been in the habit of not letting the pointer go beyond the end of the array. In addition, I often make arrays a little large on several elements to avoid my program from exploding or something else leading to a hole to protect against buffer overflows. It is easy to disconnect with one element. Do not allow the error to be an error that crashes your program (or gets your company on the news) in order to save several bytes. Since the answer is somewhat processor dependent, I recommend that you do not.

Update:. After reading a few comments, I did some research and two mechanisms that cause a program to crash with an invalid pointer that falls out of the edge of the array, even if you did not dereference the Invalid pointer speculative execution and branch prediction . In principle, a processor (even a modern processor) sometimes tries to process two instructions at once, sometimes discarding the results of the second instruction due to the results of the first instruction. In the case of a loop, the first loop instruction is executed again, even if the last loop instruction was a branch that decided not to loop again, apart from the results of the first loop instruction. I donโ€™t know if this will happen anymore, because I stopped programming the same way many years ago. If you intend to program this path, you can also read the Eager Evaluation and some optimizations for reordering compiler commands . If you are looking for something in the standards for this, I hope that the compiler does not add a bunch of extra bytes at the end of each array I allocated. If you are trying to find for yourself some standards of standard C, then this is the CPU, which will give your program an accelerating ticket.

-2
Dec 20 '17 at 21:13
source share



All Articles