Вложенные структуры и строгое совмещение имен в - PullRequest
17 голосов
/ 07 декабря 2011

Пожалуйста, рассмотрите следующий код:

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?Пожалуйста, объясните, почему это законно / незаконно.

Ответы [ 4 ]

16 голосов
/ 07 декабря 2011

Строгое правило псевдонимов касается двух указателей разных типов, ссылающихся на одно и то же место в памяти (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.Указатель на объект структуры, соответствующим образом преобразованный, указывает на его начальный член

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

3 голосов
/ 07 декабря 2011

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

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

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

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

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

int age(void *object) {

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

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

2 голосов
/ 07 декабря 2011

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

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

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

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

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

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

0 голосов
/ 07 декабря 2011

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

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, а затем получите доступ к нужному члену профсоюза.

...