Является "int * ptr = * ((& a) + 1);" где «а» int [5] четко определено стандартом? - PullRequest
0 голосов
/ 26 июня 2018

На основании этого вопроса ( странная проблема вывода в c ) был ответ (, предоставленный @ Lundin ) об этой строке:

int *ptr = (int*)(&a+1);

, где он сказал:

the cast (int*) was hiding this bug.

Итак, я пришел со следующим:

#include <stdio.h>

int main( void ){
    int a[5] = {1,2,3,4,5};

    int *ptr = *( ( &a ) + 1 );
    printf("%d", *(ptr-1) );
}

Я хотел бы знать, если это:

int *ptr = *( ( &a ) + 1 );

Четко ли определено Стандартом?

EDIT

В какой-то момент @chux указал на §6.3.2.3.7, что:

A pointer to an object type may be converted to a pointer to a different object type. If the
resulting pointer is not correctly aligned68) for the referenced type, the behavior is
undefined. Otherwise, when converted back again, the result shall compare equal to the
original pointer. When a pointer to an object is converted to a pointer to a character type,
the result points to the lowest addressed byte of the object. Successive increments of the
result, up to the size of the object, yield pointers to the remaining bytes of the object.

Но я не уверен, правильно ли я понимаю.

Ответы [ 3 ]

0 голосов
/ 26 июня 2018

Это выражение вызывает неопределенное поведение в результате оператора разыменования *:

int *ptr = *( ( &a ) + 1 );

Сначала давайте начнем с ( &a ) + 1. Эта часть действительна. &a имеет тип int (*)[5], то есть указатель на массив размером 5. Выполнение арифметики с указателем путем добавления 1 допустимо, даже если a не является элементом массива.

В разделе 6.5.6 стандарта C , подробно описывающем Аддитивные операторы, параграф 7 гласит:

Для целей этих операторов указатель на объект, который не элемент массива ведет себя так же, как указатель на первый элемент массива длиной один с типом объекта в качестве его тип элемента.

Также разрешено создавать указатель, который указывает на один элемент после конца массива. Так что &a + 1 разрешено.

Проблема в том, что мы разыменовываем это выражение. Пункт 8 гласит:

Когда выражение с целочисленным типом добавляется или вычитается из указателя результат имеет тип операнда указателя. Если операнд указателя указывает на элемент объекта массива и массив достаточно велик, результат указывает на смещение элемента от исходный элемент такой, что разница индексов результирующие и исходные элементы массива равны целочисленному выражению. Другими словами, если выражение P указывает на i-й элемент объект массива, выражения (P) + N (эквивалентно, N + (P) ) и (P) -N (где N имеет значение n) указывают соответственно на i + n-й и i -ные элементы массива, если они существуют. Более того, если выражение P указывает на последний элемент объекта массива, выражение (P) + 1 указывает один за последним элементом массива объекта, и если выражение q указывает на один последний элемент массива объект, выражение (Q) -1 указывает на последний элемент массива объект. Если и операнд указателя, и результат указывают на элементы одного и того же объекта массива, или один за последним элементом массива объект, оценка не должна производить переполнение; в противном случае поведение не определено. Если результат указывает на один последний элемент объекта массива, он не должен использоваться в качестве операнда унарного * оцениваемый оператор.

Поскольку разыменование указателя на один конец конца массива не допускается, поведение не определено.

Возвращаясь к выражению в ссылочном посте:

int *ptr = (int*)(&a+1);
printf("%d %d", *(a+1), *(ptr-1));

Это также неопределенное поведение, но по другой причине. В этом случае int (*)[5] преобразуется в int *, и впоследствии используется преобразованное значение. Единственный случай, когда использование такого преобразованного значения является допустимым, - это преобразование указателя объекта в указатель на тип символа, например char * или unsigned char * и впоследствии разыменовывается для чтения байтов представления объекта.

EDIT:

Кажется, две строки выше на самом деле хорошо определены. В то время, когда происходит разыменование указателя *(ptr-1), объект, к которому осуществляется доступ, имеет эффективный тип int, который соответствует разыменованному типу ptr-1. Преобразование значения указателя &a+1 из int (*)[5] в int * допустимо, и выполнение арифметики указателя для приведенного значения указателя также допустимо, поскольку оно указывает либо внутри a, либо на один элемент после него.

0 голосов
/ 27 июня 2018

int *ptr = *( ( &a ) + 1 ); вызывается неопределенное поведение.

C11 - §6.5.6 «Аддитивные операторы» (P8):

Когда выражение, имеющее целочисленный тип, добавляется или вычитается из указателя, результат имеет тип операнда указателя. Если указатель операнда указывает на элемент объект массива, и массив достаточно велик, результат указывает на смещение элемента от исходный элемент такой, что разница индексов полученного и исходного элементы массива равны целочисленному выражению. Другими словами, если выражение P указывает на i -й элемент массива, выражения (P)+N (эквивалентно N+(P)) и (P)-N (где N имеет значение n) указывают соответственно на i+n -й и i−n -й элементы объект массива, если они существуют. Более того, если выражение P указывает на последний элемент массива, выражение (P)+1 указывает один за последним элементом объект массива, и если выражение Q указывает на один элемент после последнего элемента объекта массива, выражение (Q)-1 указывает на последний элемент объекта массива. [...]

0 голосов
/ 26 июня 2018

*( ( &a ) + 1 ) - UB из-за

... Если результат указывает на один последний элемент массива, он не должен использоваться как операнд оцениваемого унарного оператора *. C11 §6.5.6 8

( &a ) + 1 указывает на «одно прошлое». Использование * для этого идет против "не будет".

int a[5] = {1,2,3,4,5};
int *ptr = *( ( &a ) + 1 );

Даже если a было int a, это относится к

Для целей этих операторов указатель на объект, который не является элементом массива, ведет себя так же, как указатель на первый элемент массива длиной один с типом объекта в качестве его типа элемента. §6.5.6 7

...