Array pointer to pointer

Consider the following C code:

int arr[2] = {0, 0}; int *ptr = (int*)&arr; ptr[0] = 5; printf("%d\n", arr[0]); 

Now it’s clear that the code prints 5 in common compilers. However, can anyone find the relevant sections in the C standard, which indicates that the code really works? Or is this undefined code behavior?

What, in fact, I ask, why &arr when clicking on void * matches arr when entering in void * ? Since I believe the code is equivalent:

 int arr[2] = {0, 0}; int *ptr = (int*)(void*)&arr; ptr[0] = 5; printf("%d\n", arr[0]); 

I came up with an example, reflecting on the question here: The intersecting end of an array with a pointer to an array ... but this is certainly a special question.

+3
c language-lawyer
Mar 24 '15 at 23:01
source share
3 answers

For unions and structures, cf. ISO 9899: 2011 §6.7.2.1 / 16f:

16 The size of the association is sufficient to contain the largest of its members. The value of not more than one of the members can be stored in the combined object at any time. A pointer to a union object, appropriately converted, points to each of its members (or if the element is a bit field, and then to the block in which it is located) and vice versa.

17 Inside the structure object, the members of the non-bit field and the units in which the bit fields are located have addresses that increase in the order in which they are declared. A pointer to a structure object, appropriately transformed, points to its initial member (or if this element is a bit field, and then to the block in which it is located) and vice versa. There may be an unnamed addition to the structure object, but not at the beginning.

For array types, the situation is a bit more complicated. First, look at what an array is, from ISO 9899: 2011 §6.2.5 / 20:

An array type describes an adjacent non-empty set of objects with a specific type of member object called an element type. The element type must be complete whenever an array type is specified. Array types are characterized by their element type and the number of elements in the array. An array type is called a derivative of its element type, and if its element type is T, the array type is sometimes called an "array of T". The construction of an array type from an element type is called "array type inference."

The adjacently distributed wording implies that there is no gasket between the elements of the array. This statement is supported by footnote 109:

Two objects can be contiguous in memory because they are contiguous elements of a larger array or neighboring elements of the structure without laying between them or because the implementation selected them that way even if they are not connected. If previously invalid pointer operations (such as accessing the boundaries of an external array) caused undefined behavior, subsequent comparisons also result in undefined behavior.

Using the sizeof operator in clause 6.5.3.5, example 2, expresses the intention that there are no shims either before or after the arrays:

EXAMPLE 2

Another use of the sizeof operator is to calculate the number of elements in an array:

 sizeof array / sizeof array[0] 

Therefore, I conclude that a pointer to an array converted to a pointer to the outlines of an element of this array points to the first element in the array. Also, pay attention to what the definition of equality with respect to pointers says (§6.5.9 / 6f.):

6 Two pointers compare the same 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 the beginning) or a function, both are pointers to one of the last elements of the same object array, or one pointer to one end of the end of one array object, and the other to a pointer to the beginning of another array object, which occurs immediately after the first array object in the address space. 109)

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

Since the first element of the array is a “subobject at the beginning”, a pointer to the first element of the array and a pointer to the array compare equal.

+4
Mar 24 '15 at 23:25
source share
— -

Below is a small refactoring version of your code to simplify the link:

 int arr[2] = { 0, 0 }; int *p1 = &arr[0]; int *p2 = (int *)&arr; 

with the question: Is p1 == p2 true or unspecified or UB?




Firstly: I think that the authors of the abstract memory model C assume that p1 == p2 true; and if the Standard does not actually talk about it, it will be a defect of the Standard.

Moving; the only relevant part of the text is represented by C11 6.3.2.3/7 (irrelevant text is cut out):

A pointer to an object type can be converted to a pointer to another object type. [...] When converting back, the result will be compared with the original pointer.

When a pointer to an object is converted to a pointer to a character type, the result points to the low address byte of the object. Successive increments of the result, up to the size of the object, give pointers to the remaining bytes of the object.

It does not specifically state what the result of the first conversion is. Ideally, this should say ... and the pointer points to the same address, but it does not.

However, I argue that this implies that the pointer must point to the same address after conversion. Here is an example:

 void *v1 = malloc( sizeof(int) ); int *i1 = (int *)v1; 

If we do not accept "and the pointer points to the same address", then i1 may not appear in the malloc space, which would be ridiculous.

My conclusion is that we should read 6.3.2.3/7, saying that the pointer order does not change the address pointed to. The part about using pointers to character types seems to support this.

Therefore, since p1 and p2 are of the same type and point to the same address, they compare equal ones.

+2
Mar 25 '15 at 0:10
source share

To answer directly:

Could someone find the relevant sections in the C standard, which indicates that the code really works?

  • 6.3.2.1 Lvalues, arrays and function pointers, paragraph 1
  • 6.3.2.3 Indices, paragraphs 1.5 and 6
  • 6.5.3.2 Address and Indirection Operators, clause 3

Or is this undefined code behavior?

The code you highlighted >, but it can be a "compiler / implementation" (in section 6.3.2.3 p5 / 6)

I wonder why &arr when entering into void * matches arr when entering into void * ?

This would mean why int *ptr = (int*)(void*)&arr gives the same results as int *ptr = (int*)(void*)arr; but for your submitted code you really ask why int *ptr = (int*)(void*)&arr gives the same thing as int *ptr = (int*)&arr .

In any case, I’ll talk about what your code actually does to help clarify:

Per 6.3.2.1p3:

Unless it is the operand of the sizeof operator, the _Alignof operator or the unary operator, or the string literal used to initialize the array, an expression that has an array type of type '' is converted to an expression of type '' with a pointer to a type that points to to the starting element of an array object and is not an lvalue. If the array object has a register storage class, the behavior is undefined .

and for 6.5.3.2p3:

Unary and operator gives the address of its operand. If the operand is of type type '', the result is of type '' pointer to type.

So in the first ad

int arr[2] = {0, 0};

arr initialized with an array type containing 2 int elements, equal to 0. Then on 6.3.2.1p3 it "decomposes" into a pointer type that points to the first element where it is called in scope ( except when it is used as sizeof(arr) , &arr , ++arr or --arr ).

So, in the next line, you can simply do the following:

int *ptr = arr; or int *ptr = &*arr; or int *ptr = &arr[0];

and ptr now a pointer to an int type that points to the first element of the arr array (ie &arr[0] ).

Instead, you declare it as such:

int *ptr = (int*)&arr;

Lets break it into parts:

  • &arr6.3.2.1p3 exception in 6.3.2.1p3 , so instead of getting &arr[0] you get the address arr , which is of type int(*)[2] (and not int* ), so you don't get pointer to an int , you get pointer to an int array

  • (int*)&arr (i.e. casting to int* ) → for 6.5.3.2p3, &arr takes the address of the variable arr to return a pointer to its type, so just saying int* ptr = &arr will give a warning about "incompatible pointer types" (since ptr is of type int* and &arr is of type int(*)[2] ), so you need to cast to int* .

Further in 6.3.2.3p1: "a pointer to void can be converted to a pointer to or from a pointer to any type of object. A pointer to any type of object can be converted to a pointer to void and vice versa; the result is compared with the original pointer . "

So you declare int* ptr = (int*)(void*)&arr; to get the same results as int* ptr = (int*)&arr; due to the types you use and convert to / from. Also as a note: ptr[0] = 5; matches *ptr = 5 , where ptr[1] = 5; will also be the same as *++ptr = 5;

Some links:

6.3.2.1 Lvalues, arrays, and function pointers

1. lvalue is an expression (with an object type other than void) that potentially denotes an object (* see Note); if lvalue does not indicate an object when evaluating it, the behavior is undefined. When an object is of a specific type, the type is determined by the value l used to denote the object. The lvalue modifiable value is an lvalue value that does not have an array type, does not have an incomplete type, does not have a constqualified type, and, if it is a structure or union, does not have any member (including, recursively, any element or element of all contained aggregates or unions) with an inconsistent type.

* The name '' lvalue comes originally from the assignment expression E1 = E2, in which the left operand E1 must be (mutable) lvalue. This is perhaps best seen as representing the value of the object's locator. What is sometimes called the "rvalue" is found in this International Standard, described as the "value of an expression". An obvious example of lvalue is the identifier of an object. As another example, if E is a unary expression that is a pointer to an object, * E is the value l, which denotes the object that E. points to.

2. Unless it is an operand of the sizeof operator, the _Alignof operator, the unary operator &, the ++ operator, the operator operator, or the left operand. operator or assignment operator, the value of l, which does not have the type of an array, is converted to the value stored in the specified object (and is no longer an lvalue); this is called an lvalue conversion. If an lvalue is of a qualified type, the value is an unqualified version of the lvalue type; in addition, if the lvalue is of atomic type, the value has a non-atomic version of the lvalue type; otherwise, the value is of type lvalue. If the lvalue is of an incomplete type and does not have an array type, the behavior is undefined. If lvalue denotes an object with an automatic storage duration that could be declared with a register storage class (its address was never accepted), and this object is not initialized (not declared with an initializer, and its assignment was not executed before use), the behavior is undefined.

3. Unless it is an operand of the sizeof operator, the _Alignof operator, or a unary operator or is a string literal used to initialize an array, an expression that is of type '' array of type is converted to an expression with type '' pointer to a type that points to the starting element of an array object and is not an lvalue value. If the array object has a register storage class, the behavior is undefined.

6.3.2.3 Pointers

1. A pointer to void can be converted to or from a pointer to any type of object. A pointer to any type of object can be converted to a pointer to void and vice versa; The result is compared with the original pointer.

5. An integer can be converted to any type of pointer. Except, as indicated earlier, the result is determined by the implementation, may not be aligned correctly, may not point to an object of reference type, and may be a trap representation (the mapping functions for converting a pointer to an integer or integer into a pointer must correspond to the addressing structure of the runtime environment).

6. Any type of pointer can be converted to an integer type. Except as noted above, the result is determined by implementation. If the result cannot be represented in an integer type, the behavior is undefined. The result does not have to be in the range of values ​​of any integer type.

6.5.3.2 Address and Indirection Operators

1. The operand of the unary operator & must be either the function name, the result of the [] operator, or the unary *, or lvalue, which indicates an object that is not a bit field and is not declared using register storage, the class specifier.

3. The unary operator & gives the address of its operand. If the operand is of type type '', the result is of type '' pointer to type. If the operand is the result of a unary * operator, neither this operator nor the & operator is evaluated, and the result is as if both were omitted, except that the restrictions on the operators are still applied, and the result is not a value of l. Similarly, if the operand is the result of the [] operator, neither the & operator nor the unary *, which is implied by [], are evaluated, and the result looks as if the & operator was deleted and the [] operator was changed to a +. Otherwise, the result will be a pointer to the object or function assigned by its operand.

4. The unary operator * denotes an indirect direction. If the operand points to a function, the result will denote a function; if it points to an object, the result is an lvalue representing the object. If the operand is of type `` pointer to type, the result is of type ''. If an invalid value is assigned to the pointer, the behavior of the unary * operator is undefined (* see Note).

* Thus, & * E is equivalent to E (even if E is a null pointer) and & (E1 [E2]) - ((E1) + (E2)). It is always true that if E is a function designation or lvalue, which is a valid operand of the unary operator &, * & E is a function designation or value l equal to E. If * P is the value l, and T is the name of the pointer type * (T ) P is an l-value that is of a type compatible with that indicated by T. Among the invalid values ​​for dereferencing with a pointer, the unary * operator is a null pointer, an address inadequately aligned with the type of object that it points to, and the address of the object after ending his life.

6.5.4 Role Operators

5. The preceding expression, using the type name in brackets, converts the value of the expression into a named type. This construction is called cast (casting does not give an lvalue, so casting to a qualified type has the same effect as casting to an unqualified version of a type). A listing that does not indicate a conversion does not affect the type or value of the expression.

6. If the value of the expression is represented with a larger range or precision than is required by the type called cast (6.3.1.8), then cast indicates the conversion, even if the type of the expression is the same as the named type and removes any additional range and precision.

6.5.16.1 Simple assignment

2. In a simple assignment (=), the value of the correct operand is converted to the type of the assignment expression and replaces the value stored in the object indicated by the left operand.

6.7.6.2 Manifest declaration

1. In addition to the optional type classifiers and the static keyword, [and] can limit the expression or *. If they limit the expression (which sets the size of the array), the expression must be an integer type. If the expression is a constant expression, it must have a value greater than zero. The item type must not be incomplete or functional. Optional classifier types and the static keyword should appear only in the declaration of the function parameter with the array type, and then only in the outermost division of the array type.

3. If in the declaration '' T D1, D1 has one of the forms:

D [type-qualifier-listopt assign-expressionopt]
D [static classifier-type-listopt-expression-expression]
D [static assignment-expression of list-classifier-type]
D [type-qualifier-listopt *]


and the type specified for the identifier in the declaration '' TD is a '' derived-declarator-type-type T, then the type specified for the identifier is `` array-type-list-type-list-array from T.142 ) (See 6.7. 6.3 for the meaning of optional type classifiers and the static keyword.)

4. If there is no size, the type of the array is incomplete. If the size is * instead of an expression, the array type is an array type of variable length of undetermined size, which can only be used in declarations or type names using the function prototype area; 143) such arrays, however, are complete types. If the size is an integer constant expression, and the element type has a known constant size, the array type is not an array of variable length; otherwise, the array type is an array type of variable length. (Arrays of variable length are a conditional function, the implementation of which is not required, see 6.10.8.3.)

5. , : , , *; , , . . sizeof , , .

6. , , , , . , , undefined, .

PS , :

 #include <stdio.h> int main(int argc, char** argv) { int arr[2] = {10, 20}; X Y printf("%d,%d\n", arr[0],arr[1]); return 0; } 

X :

 int *ptr = (int*)(void*)&arr; int *ptr = (int*)&arr; int *ptr = &arr[0]; 

Y :

 ptr[0] = 15; *ptr = 15; 

OpenBSD gcc 4.2.1 20070719 -S .

0
25 . '15 6:20
source share



All Articles