Подтвердить что ты не робот

Вложенные структуры и строгий псевдоним в c

Обратите внимание на следующий код:

typedef struct {
  int type;
} object_t;

typedef struct {
  object_t object;
  int age;
} person_t;

int age(object_t *object) {
  if (object->type == PERSON) {
    return ((person_t *)object)->age;
  } else {
    return 0;
  }
}

Является ли этот юридический код или он нарушает правило сглаживания C99? Пожалуйста, объясните, почему это законно/незаконно.

4b9b3361

Ответ 1

Строгое правило сглаживания - это два указателя разных типов, ссылающихся на одно и то же место в памяти (ISO/IEC9899/TC2). Хотя ваш пример ретранслирует адрес object_t object как адрес person_t, он не ссылается на ячейку памяти внутри object_t через реинтерпретированный указатель, потому что age находится за границей object_t. Поскольку ячейки памяти, на которые ссылаются указатели, не совпадают, я бы сказал, что это не нарушает строгое правило псевдонимов. FWIW, gcc -fstrict-aliasing -Wstrict-aliasing=2 -O3 -std=c99, похоже, согласен с этой оценкой и не выдает предупреждения.

Этого недостаточно, чтобы решить, что это юридический код: ваш пример делает предположение, что адрес вложенной структуры такой же, как адрес его внешней структуры. Кстати, это безопасное предположение сделать согласно стандарту C99:

6.7.2.1-13. Указатель на объект структуры, соответствующим образом преобразованный, указывает на его начальный член

Два вышеупомянутых соображения заставляют меня думать, что ваш код является законным.

Ответ 2

Строгое правило псевдонимов ограничивает то, к каким типам вы обращаетесь к объекту (область памяти). В коде, где может возникнуть правило, есть несколько мест: внутри age() и при вызове age().

Внутри age вы должны рассмотреть object. ((person_t *)object) - выражение lvalue, поскольку оно имеет тип объекта и обозначает объект (область памяти). Однако ветвь достигается только, если object->type == PERSON, поэтому (предположительно) эффективный тип объекта является person_t*, поэтому приведение не нарушает строгого сглаживания. В частности, строгий псевдоним позволяет:

  • тип, совместимый с эффективным типом объекта,

При вызове age() вы предположительно будете передавать object_t* или тип, который спускается из object_t: структура, которая имеет object_t в качестве первого члена. Это разрешено как:

  • тип агрегата или объединения, который включает один из вышеупомянутых типов среди его членов.

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

Ответ 3

http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html

В качестве дополнения к принятому ответу, вот полная цитата из стандарта, с важной частью выделено, что другой ответ опущен, и еще один:

6.7.2.1-13: Внутри объекта структуры члены небитового поля и единицы в в которых расположены битовые поля, адреса, которые увеличиваются в порядке которые они объявлены. Указатель на объект структуры, подходящим образом преобразуется, указывает на его начальный член (или если этот член является бит-поле, затем в блок, в котором он находится), и наоборот. В объекте структуры может быть неназванное заполнение, но не на его начало.

6.3.2.3-7: Указатель на объект или неполный тип может быть преобразован в указатель на другой объект или неполный тип. Если полученный указатель неправильно выровнен для указанного типа, поведение undefined. В противном случае при обратном результат сравнивается с исходным указателем. [...]

Я считаю ваш пример идеальным местом для указателя пустоты:

int age(void *object) {

Почему? Потому что ваше очевидное намерение состоит в том, чтобы предоставить разные "объекты" типа для такой функции и получать информацию в соответствии с закодированным типом. В вашей версии вам требуется приведение при каждом вызове функции: age((object_t*)person);. Компилятор не будет жаловаться, когда вы указываете на него неправильный указатель, так что во всяком случае нет никакой безопасности типа. Затем вы можете также использовать указатель void и избегать приведения при вызове функции.

В качестве альтернативы вы могли бы вызвать функцию с помощью age(&person->object), конечно. Каждый раз, когда вы его вызываете.

Ответ 4

Одним из приемлемых способов, который явно допускается стандартом, является создание объединения структур с одинаковым начальным сегментом, например:

struct tag  { int value;                };
struct obj1 { int tag;    Foo x; Bar y; };
struct obj2 { int tag;    Zoo z; Car w; };

typedef union object_
{
  struct tag;
  struct obj1;
  struct obj2;
} object_t;

Теперь вы можете пройти object_t * p и протестировать p->tag.value безнаказанно, а затем получить доступ к желаемому члену объединения.