Чтобы ответить прямо:
Может ли кто-нибудь найти соответствующие разделы в стандарте C, в которых указано, что код действительно работает?
- 6.3.2.1 Значения L, массивы и указатели функций, параграф 1
- 6.3.2.3 Указатели, пункты 1,5 и 6
- 6.5.3.2 Операторы адреса и косвенности, параграф 3
Или поведение кода не определено?
Код, который вы опубликовали, не является неопределенным, но может зависеть от компилятора/реализации (согласно разделу 6.3.2.3 p5/6)
По сути, я спрашиваю, почему &arr
при преобразовании в void *
совпадает с arr
при преобразовании в void *
?
Это означало бы, что вы спрашиваете, почему int *ptr = (int*)(void*)&arr
дает те же результаты, что и int *ptr = (int*)(void*)arr;
, но согласно вашему опубликованному коду вы на самом деле спрашиваете, почему int *ptr = (int*)(void*)&arr
дает то же самое, что и int *ptr = (int*)&arr
.
В любом случае я расскажу о том, что на самом деле делает ваш код, чтобы уточнить:
Согласно 6.3.2.1p3:
За исключением случаев, когда это операнд оператора sizeof, оператора _Alignof или унарного оператора &, или строкового литерала, используемого для инициализации массива, выражение, имеющее тип "массив типов", преобразуется в выражение с типом «указатель на тип», которое указывает на начальный элемент объекта массива и не является lvalue. Если объект массива имеет класс хранения регистра, поведение не определено.
и согласно 6.5.3.2p3:
Унарный оператор & возвращает адрес своего операнда. Если операнд имеет тип «тип», результат имеет тип «указатель на тип».
Итак, в вашем первом объявлении
int arr[2] = {0, 0};
arr
инициализируется типом массива, содержащим 2 элемента типа int
, оба равны 0. Затем для 6.3.2.1p3
он распадается на тип указателя, указывающий на первый элемент в любом месте, где он вызывается в области видимости (кроме случаев, когда он используется как sizeof(arr)
, &arr
, ++arr
или --arr
).
Итак, в следующей строке вы можете просто сделать следующее:
int *ptr = arr;
or int *ptr = &*arr;
or int *ptr = &arr[0];
а ptr
теперь является указателем на тип int, который указывает на первый элемент массива arr
(т.е. &arr[0]
).
Вместо этого вы объявляете это как таковое:
int *ptr = (int*)&arr;
Разобьем это на части:
&arr
-> вызывает исключение для 6.3.2.1p3
, поэтому вместо получения &arr[0]
вы получаете адрес arr
, который является типом int(*)[2]
(а не типом int*
), поэтому вы получаете не pointer to an int
, вы получаете pointer to an int array
(int*)&arr
, (т. е. преобразование в int*
) -> согласно 6.5.3.2p3, &arr
берет адрес переменной arr
, возвращая указатель на ее тип, поэтому просто произнесение int* ptr = &arr
выдаст предупреждение о несовместимых типах указателей< /b> (поскольку ptr
имеет тип int*
, а &arr
имеет тип int(*)[2]
), поэтому вам нужно привести к int*
.
Далее согласно 6.3.2.3p1: указатель на void может быть преобразован в указатель на любой тип объекта или из него. Указатель на объект любого типа может быть преобразован в указатель на void и обратно; результат будет равен исходному указателю.
Таким образом, ваше объявление int* ptr = (int*)(void*)&arr;
даст те же результаты, что и int* ptr = (int*)&arr;
, из-за типов, которые вы используете и конвертируете в/из. Также в качестве примечания: ptr[0] = 5;
совпадает с *ptr = 5
, где ptr[1] = 5;
также совпадает с *++ptr = 5;
.
Некоторые ссылки:
6.3.2.1 Lvalues, массивы и указатели функций
1. An lvalue is an expression (with an object type other than void) that potentially designates an object (*see note); if an lvalue does not designate an object when it is evaluated, the behavior is undefined. When an object is said to have a particular type, the type is specified by the lvalue used to designate the object. A modifiable lvalue is an lvalue that does not have 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 member or element of all contained aggregates or unions) with a constqualified type.
*The name ‘‘lvalue’’ comes originally from the assignment expression E1 = E2, in which the left operand E1 is required to be a (modifiable) lvalue. It is perhaps better considered as representing an object ‘‘locator value’’. What is sometimes called ‘‘rvalue’’ is in this International Standard described as the ‘‘value of an expression’’. An obvious example of an lvalue is an identifier of an object. As a further example, if E is a unary expression that is a pointer to an object, *E is an lvalue that designates the object to which E points.
2. Except when it is the operand of the sizeof operator, the _Alignof operator, the unary & operator, the ++ operator, the -- operator, or the left operand of the . operator or an assignment operator, an lvalue that does not have array type is converted to the value stored in the designated object (and is no longer an lvalue); this is called lvalue conversion. If the lvalue has qualified type, the value has the unqualified version of the type of the lvalue; additionally, if the lvalue has atomic type, the value has the non-atomic version of the type of the lvalue; otherwise, the value has the type of the lvalue. If the lvalue has an incomplete type and does not have array type, the behavior is undefined. If the lvalue designates an object of automatic storage duration that could have been declared with the register storage class (never had its address taken), and that object is uninitialized (not declared with an initializer and no assignment to it has been performed prior to use), the behavior is undefined.
3. Except when it is the operand of the sizeof operator, the _Alignof operator, or the unary & operator, or is a string literal used to initialize an array, an expression that has type ‘‘array of type’’ is converted to an expression with type ‘‘pointer to type’’ that points to the initial element of the array object and is not an lvalue. If the array object has register storage class, the behavior is undefined.
6.3.2.3 Указатели
1. A pointer to void may be converted to or from a pointer to any object type. A pointer to any object type may be converted to a pointer to void and back again; the result shall compare equal to the original pointer.
5. An integer may be converted to any pointer type. Except as previously specified, the result is implementation-defined, might not be correctly aligned, might not point to an entity of the referenced type, and might be a trap representation (the mapping functions for converting a pointer to an integer or an integer to a pointer are intended to be consistent with the addressing structure of the execution environment).
6. Any pointer type may be converted to an integer type. Except as previously specified, the result is implementation-defined. If the result cannot be represented in the integer type, the behavior is undefined. The result need not be in the range of values of any integer type.
6.5.3.2 Операторы адреса и косвенного обращения
1. The operand of the unary & operator shall be either a function designator, the result of a [] or unary * operator, or an lvalue that designates an object that is not a bit-field and is not declared with the register storage-class specifier.
3. The unary & operator yields the address of its operand. If the operand has type ‘‘type’’, the result has type ‘‘pointer to type’’. If the operand is the result of a unary * operator, neither that operator nor the & operator is evaluated and the result is as if both were omitted, except that the constraints on the operators still apply and the result is not an lvalue. Similarly, if the operand is the result of a [] operator, neither the & operator nor the unary * that is implied by the [] is evaluated and the result is as if the & operator were removed and the [] operator were changed to a + operator. Otherwise, the result is a pointer to the object or function designated by its operand.
4. The unary * operator denotes indirection. If the operand points to a function, the result is a function designator; if it points to an object, the result is an lvalue designating the object. If the operand has type ‘‘pointer to type’’, the result has type ‘‘type’’. If an invalid value has been 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]) to ((E1)+(E2)). It is always true that if E is a function designator or an lvalue that is a valid operand of the unary & operator, *&E is a function designator or an lvalue equal to E. If *P is an lvalue and T is the name of an object pointer type, *(T)P is an lvalue that has a type compatible with that to which T points. Among the invalid values for dereferencing a pointer by the unary * operator are a null pointer, an address inappropriately aligned for the type of object pointed to, and the address of an object after the end of its lifetime.
6.5.4 Операторы приведения
5. Preceding an expression by a parenthesized type name converts the value of the expression to the named type. This construction is called a cast (a cast does not yield an lvalue; thus, a cast to a qualified type has the same effect as a cast to the unqualified version of the type). A cast that specifies no conversion has no effect on the type or value of an expression.
6. If the value of the expression is represented with greater range or precision than required by the type named by the cast (6.3.1.8), then the cast specifies a conversion even if the type of the expression is the same as the named type and removes any extra range and precision.
6.5.16.1 Простое назначение
2. In simple assignment (=), the value of the right operand is converted to the type of the assignment expression and replaces the value stored in the object designated by the left operand.
6.7.6.2 Деклараторы массивов
1. In addition to optional type qualifiers and the keyword static, the [ and ] may delimit an expression or *. If they delimit an expression (which specifies the size of an array), the expression shall have an integer type. If the expression is a constant expression, it shall have a value greater than zero. The element type shall not be an incomplete or function type. The optional type qualifiers and the keyword static shall appear only in a declaration of a function parameter with an array type, and then only in the outermost array type derivation.
3. If, in the declaration ‘‘T D1’’, D1 has one of the forms:
D[ type-qualifier-listopt assignment-expressionopt ]
D[ static type-qualifier-listopt assignment-expression ]
D[ type-qualifier-list static assignment-expression ]
D[ type-qualifier-listopt * ]
and the type specified for ident in the declaration ‘‘T D’’ is ‘‘derived-declarator-type-list T’’, then the type specified for ident is ‘‘derived-declarator-type-list array of T’’.142) (See 6.7.6.3 for the meaning of the optional type qualifiers and the keyword static.)
4. If the size is not present, the array type is an incomplete type. If the size is * instead of being an expression, the array type is a variable length array type of unspecified size, which can only be used in declarations or type names with function prototype scope;143) such arrays are nonetheless complete types. If the size is an integer constant expression and the element type has a known constant size, the array type is not a variable length array type; otherwise, the array type is a variable length array type. (Variable length arrays are a conditional feature that implementations need not support; see 6.10.8.3.)
5. If the size is an expression that is not an integer constant expression: if it occurs in a declaration at function prototype scope, it is treated as if it were replaced by *; otherwise, each time it is evaluated it shall have a value greater than zero. The size of each instance of a variable length array type does not change during its lifetime. Where a size expression is part of the operand of a sizeof operator and changing the value of the size expression would not affect the result of the operator, it is unspecified whether or not the size expression is evaluated.
6. For two array types to be compatible, both shall have compatible element types, and if both size specifiers are present, and are integer constant expressions, then both size specifiers shall have the same constant value. If the two array types are used in a context which requires them to be compatible, it is undefined behavior if the two size specifiers evaluate to unequal values.
P.S. В качестве примечания, учитывая следующий код:
#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
вывод ассемблера для всех файлов был абсолютно одинаковым.
person
txtechhelp
schedule
25.03.2015
int(*)[2]
, и вам действительно нужно приведение, иначе компилятор предупредит. - person juhist   schedule 25.03.2015&arr
не имеет смысла даватьint**
, поскольку нет объектаint*
, на который он мог бы указывать. - person Keith Thompson   schedule 25.03.2015int *ptr = &arr;
является нарушением ограничения. Соответствующий компилятор должен выдать диагностику. Эта диагностика может быть либо предупреждением, либо фатальной ошибкой. (ИМХО, это должно быть фатальной ошибкой по умолчанию, но, например, авторы gcc, похоже, не согласны.) - person Keith Thompson   schedule 25.03.2015(int*)&arr == &arr[0]
. В частности, ссылаясь на проект N1570, семантика преобразования указателя в 6.3.2.3p7 не распространяется на этот случай. - person Keith Thompson   schedule 25.03.2015&arr
абсолютно не является адресом адреса. Это адрес объекта массива. (Операнд унарного&
— это один из трех контекстов, в которых выражения массива не преобразуются неявным образом в указатель.) - person Keith Thompson   schedule 25.03.2015&
. - person Carl Norum   schedule 25.03.2015arr
это не адрес. Массивы не являются указателями. Указатели не являются массивами.arr
— это имя объекта массива. Когда имяarr
появляется как выражение, оно (в большинстве, но не во всех контекстах) неявно преобразуется в указатель на первый элемент объекта массива. Рекомендуемая литература: раздел 6 часто задаваемых вопросов о comp.lang.c. - person Keith Thompson   schedule 25.03.2015