Псевдоним указателя между struct и первым членом struct - PullRequest
1 голос
/ 05 марта 2020

Псевдоним указателя в C обычно не определен (из-за строгого псевдонима), но стандарт C11, кажется, позволяет совмещать указатель на структуру и указатель на первый член структуры

C11 6.7.2.1 (15) ... Указатель на объект структуры ... указывает на его начальный член ... и наоборот ...

Так что следующий код содержит неопределенное поведение?

struct Foo {
    int x;
    int y;
};

// does foe return always 100?
int foe() {
    struct Foo foo = { .x = 10, .y = 20 }, *pfoo = &foo;
    int *px = (int*)pfoo; *px = 100;
    return pfoo->x;
}

Ответы [ 2 ]

4 голосов
/ 05 марта 2020

Этот код правильный. Все версии Standard C и C ++ допускают это, хотя формулировки различаются.

Нет строгой проблемы с наложением псевдонимов, поскольку вы получаете доступ к объекту типа int через lvalue типа int. Правило строгого псевдонима может применяться, когда lvalue, выполняющий доступ, имеет другой тип для объекта, хранящегося в ячейке памяти.

В цитируемом вами тексте указано, что приведенный указатель фактически указывает на объект int.

0 голосов
/ 16 марта 2020

При написании Стандарта lvalue структуры или типа объединения может использоваться для доступа к объекту типа члена, но нет положения, позволяющего произвольное lvalue структуры или объединения тип члена для доступа к объекту типа struct или union. Поскольку, конечно, было бы абсурдно утверждать, что код не может использовать член структуры или объединения lvalue (который, конечно, будет иметь тип этого члена) для доступа к структуре или объединению, все компиляторы поддерживают некоторые общие шаблоны доступа. Поскольку компиляторы допускают такой доступ при различных обстоятельствах, однако, стандарт рассматривает всю поддержку таких доступов как проблему качества реализации, а не пытается точно указать, когда такая поддержка требуется.

Подход, наиболее соответствующий стандарту формулировка, которая позволила бы наиболее полезные оптимизации, и в то же время поддерживала бы большую часть кода, который должен был бы выполнять перетаскивание типов или другие методы, означала бы, что для целей N1570 6.5p7 указатель, который визуально получен из указателя или lvalue данного типа может использоваться в контексте такого деривации для доступа к вещам, которые (для целей 6.5p7) будут доступны с использованием lvalue этого типа. При таком подходе данный фрагмент кода, такой как:

struct foo { int index,len; int *dat; };
void test1(struct foo *p)
{
  int *pp = &foo->len;
  *pp = 4;
}
void test2(struct foo *p, int dat)
{
  if (p->index < p->len)
  {
    p->dat[p->index] = dat;
    p->index++;
  }
}

должен признать, что в test1 доступ к *pp может получить доступ к struct foo объекту *p, поскольку pp формируется из p С другой стороны, компилятору не требуется учитывать в test2 возможность того, что объект типа struct foo или его члены, такие как p->index, могут быть изменены с помощью указателя p->dat, потому что ничего внутри test2 приведет к тому, что адрес struct foo или любой его части будет сохранен в p->dat.

Clang и g cc, однако вместо этого выберите другой подход, ведущий себя как будто 6.5 p7 позволяет получить доступ к членам структуры через произвольные указатели их типов, но к элементам объединения вообще нельзя получить доступ через указатели, исключая арифметику указателей c, подразумеваемую выражениями в скобках. Учитывая, что union { uint16_t h[4]; uint32_t w[2];} u; clang и g cc распознают, что доступ к u.h[i] может взаимодействовать с u.w[j], но не распознают, что *(u.h+i) может взаимодействовать с *(u.w+j), даже если стандарт определяет значение первого выражения со скобками как эквивалентные последним формам.

Учитывая, что компиляторы последовательно обрабатывают все эти конструкции с пользой, когда псевдонимы на основе типов отключены. Стандарт, однако, не предъявляет никаких требований даже во многих распространенных случаях, и clang и g cc не дают никаких обещаний относительно поведения конструкций, не предписанных Стандартом, даже если все версии на сегодняшний день обрабатывали такие конструкции с пользой. Таким образом, я бы не рекомендовал полагаться на clang или g cc, чтобы с пользой обрабатывать все, что связано с доступом к хранилищу как к разным типам в разное время, кроме случаев использования -fno-strict-aliasing, и их использование не является проблемой при использовании этой опции, поэтому я рекомендую просто использовать эту опцию, если только или пока clang и g cc не примут более определенную абстракцию.

...