Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is this code involving arrays and pointers behaving as it does?

I was asked what the output of the following code is:

int a[5] = { 1, 3, 5, 7, 9 }; int *p = (int *)(&a + 1); printf("%d, %d", *(a + 1), *(p - 1)); 
  1. 3, 9
  2. Error
  3. 3, 1
  4. 2, 1

The answer is NO.1


It is easy to get *(a+1) is 3.

But how about int *p = (int *)(&a + 1); and *(p - 1) ?

like image 277
Coderon Avatar asked Oct 07 '21 12:10

Coderon


People also ask

Why are pointers and arrays the same?

An array is a pointer, and you can store that pointer into any pointer variable of the correct type. For example, int A[10]; int* p = A; p[0] = 0; makes variable p point to the first member of array A.

Why do we use pointers in arrays?

To access elements of the array, we have used pointers. In most contexts, array names decay to pointers. In simple words, array names are converted to pointers. That's the reason why you can use pointers to access elements of arrays.

Why are arrays treated as pointers in C?

C treats array parameter as pointers because it is less time consuming and more efficient. Though if we can pass the address of each element of the array to a function as argument but it will be more time consuming.

How do arrays and pointers work?

Arrays and pointers are synonymous in terms of how they use to access memory. But, the important difference between them is that, a pointer variable can take different addresses as value whereas, in case of array it is fixed. In C , name of the array always points to the first element of an array.


1 Answers

The answer to this could be either "1) 3,9" or "2) Error" (or more specifically undefined behavior) depending on how you read the C standard.

First, let's take this:

&a + 1 

The & operator takes the address of the array a giving us an expression of type int(*)[5] i.e. a pointer to an array of int of size 5. Adding 1 to this treats the pointer as pointing to the first element of an array of int [5], with the resulting pointer pointing to just after a.

Also, even though &a points to a singular object (in this case an array of type int [5]) we can still add 1 to this address. This is valid because 1) a pointer to a singular object can be treated as a pointer to the first element of an array of size 1, and 2) a pointer may point to one element past the end of an array.

Section 6.5.6p7 of the C standard states the following regarding treating a pointer to an object as a pointer to the first element of an array of size 1:

For the purposes of these operators, a pointer to an object that is not an element of an array behaves the same as a pointer to the first element of an array of length one with the type of the object as its element type.

And section 6.5.6p8 says the following regarding allowing a pointer to point to just past the end of an array:

When an expression that has integer type is added to or subtracted from a pointer, the result has the type of the pointer operand. If the pointer operand points to an element of an array object, and the array is large enough, the result points to an element offset from the original element such that the difference of the subscripts of the resulting and original array elements equals the integer expression. In other words, if the expression P points to the i-th element of an array object, the expressions (P)+N (equivalently, N+(P)) and (P)-N (where N has the value n) point to, respectively, the i+n-th and i−n-th elements of the array object, provided they exist. 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. If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined. If the result points one past the last element of the array object, it shall not be used as the operand of a unary * operator that is evaluated.

Now comes the questionable part, which is the cast:

(int *)(&a + 1) 

This converts the pointer of type int(*)[5] to type int *. The intent here is to change the pointer which points to the end of the 1-element array of int [5] to the end of the 5-element array of int.

However the C standard isn't clear on whether this conversion and the subsequent operation on the result is allowed. It does allow conversion from one object type to another and back, assuming the pointer is properly aligned. While the alignment shouldn't be an issue, using this pointer is iffy.

So this pointer is assigned to p:

int *p = (int *)(&a + 1) 

Which is then used as follows:

*(p - 1) 

If we assume that p validly points to one element past the end of the array a, subtracting 1 from it results in a pointer to the last element of the array. The * operator then dereferences this pointer to the last element, yielding the value 9.

So if we assume that (int *)(&a + 1) results in a valid pointer, then the answer is 1) 3,9 otherwise the answer is 2) Error.

like image 114
dbush Avatar answered Oct 07 '22 17:10

dbush