Приводит ли структура T* к неопределенному поведению структуры C*, если первым полем T является C?

Пытаясь отладить проблему, с которой я столкнулся, используя Speex, я заметил, что он (ну, не только Speex, но и некоторый пример кода) делает следующее:

  • Возвращает указатель на EncState из функции инициализации.
  • Приведите этот указатель к пустому указателю
  • Сохраните пустой указатель
  • (в другом месте)
  • Приведите указатель void к указателю на указатель на SpeexMode
  • Разыменовать указатель

Так получилось, что определение EncState начинается с поля типа SpeexMode *, поэтому целочисленные значения указателя на первое поле и указателя на структуру совпадают. Разыменование работает во время выполнения.

Но... действительно ли язык позволяет это? Может ли компилятор делать все, что захочет, если он скомпилирует это? Приводит ли структура T* к неопределенному поведению структуры C*, если T''s first field is a C`?


person Craig Gidney    schedule 24.01.2013    source источник
comment
Также дубликат совместимости указателя структуры   -  person netcoder    schedule 25.01.2013
comment
Технически это UB, да, из-за строгого правила алиасинга. Предпочтительным способом обычно является использование объединения с членами обоих типов.   -  person netcoder    schedule 25.01.2013
comment
@netcoder: поведение с объединением не более и не менее определено, чем без него, поскольку на самом деле не существует какого-либо правила, позволяющего получить доступ к объекту типа структуры или объединения через lvalue типа члена. Компилятор, который может видеть, что указатель или lvalue имеет новую ассоциацию со структурой или объединением, должен учитывать возможность его использования для доступа к структуре или объединению, но вопрос о том, когда компилятор увидит, что это качество проблема реализации. Clang и gcc тупо слепы ко всему, кроме минимума, необходимого для того, чтобы сделать язык пригодным для использования, но...   -  person supercat    schedule 20.02.2021
comment
... при достаточно педантичном чтении правил в том виде, как они написаны, почти все, что код делает со структурами или членами объединения несимвольных типов, вызывает UB, но любой пригодный для использования компилятор расширит язык, обрабатывая конструкции, такие как someAggregate.array[i] с пользой, даже если Стандарт не требует этого.   -  person supercat    schedule 20.02.2021


Ответы (2)


Из стандарта C11:

(C11 §6.7.2.1.15: «Указатель на объект структуры, соответствующим образом преобразованный, указывает на его начальный член... и наоборот. Внутри объекта структуры может быть безымянное заполнение, но не в его начале».)

Это означает, что поведение, которое вы видите, разрешено и гарантировано.

person pmr    schedule 24.01.2013
comment
Однако это нарушает строгое правило псевдонимов. - person netcoder; 25.01.2013
comment
@netcoder Правда, приведет ли это к проблемам в данном случае пока непонятно. - person pmr; 25.01.2013
comment
+1 Краткий и четкий ответ ... Я мог бы добавить в качестве комментария, что вы можете найти множество примеров такого приведения типов в структуре GObject. - person Rerito; 26.01.2013
comment
Указатель может иметь тот же адрес, что и объект, но это не означает разрешения использовать указатель для фактического доступа к объекту. Если стандарт полностью определяет поведение программы при отсутствии строгого правила алиасинга N1570 6.5p7, но не соответствует требованиям этого раздела, то, что касается стандарта, программа вызывает неопределенное поведение. Компиляторы, которые написаны для платных клиентов, будут обрабатывать случаи, которые эти клиенты сочтут полезными, независимо от того, требует ли это Стандарт, но... - person supercat; 20.02.2021
comment
... поставщики компиляторов, которые не обязаны платить клиентам, успешно распространили миф о том, что программисты должны прыгать через обручи, чтобы успокоить их. - person supercat; 20.02.2021
comment
@netcoder: это не нарушает строгое правило псевдонимов. Так называемое строгое правило псевдонимов в C 2018 6.5 7 регулирует только тип выражения lvalue, используемого для доступа к объекту, а не способ получения указателя, используемого для формирования выражения. Если p является указателем на тип C, а в p находится объект C, то *p обращается к объекту C, используя выражение lvalue, правильное для типа C в соответствии с 6.5. 7. Тот факт, что p был получен преобразованием указателя в объект T для указателя на объект C не имеет значения. - person Eric Postpischil; 26.02.2021

Каждая версия Стандарта рассматривала поддержку многих конструкций псевдонимов как проблему качества реализации, поскольку было бы практически невозможно написать правила, которые поддерживали бы все полезные конструкции, не блокировали бы какие-либо полезные оптимизации и могли бы поддерживаться всеми компиляторами. без существенной доработки. Рассмотрим следующую функцию:

struct foo {int length; int *dat; };

int test1(struct foo *p)
{
  int *ip = &p->length;
  *ip = 2;
  return p->length;      
}

Я думаю, довольно ясно, что любой качественный компилятор должен обрабатывать возможность того, что объект типа struct foo может быть затронут присваиванием *ip. С другой стороны, рассмотрим функцию:

void test2(struct foo *p)
{
    int i;
    for (i=0; i < p->length; i++)
        p->dat[i] = 0;
}

Если от компилятора требуется учитывать возможность того, что запись в p->dat[i] может повлиять на значение p->length, например. путем перезагрузки значения p->length хотя бы после первой итерации цикла?

Я думаю, что некоторые члены Комитета, возможно, намеревались потребовать, чтобы компиляторы допускали такое допущение, но я не думаю, что все они это сделали, и написанные правила не требуют этого, поскольку они перечисляют типы lvalue, которые могут использоваться для получить доступ к объекту типа struct foo, а int среди них нет. Некоторые люди могут подумать, что упущение было случайным, но я думаю, что оно было основано на ожидании, что компиляторы интерпретируют правило как требующее, чтобы к объектам, к которым обращаются как к определенному типу в некотором контексте, обращались lvalues, которые имеют видимую ассоциацию с объектом одного из перечисленных типов в этом контексте. Вопрос о том, что составляет видимую ассоциацию, остался за пределами юрисдикции Стандарта как вопрос QoI, но ожидалось, что авторы компиляторов приложат разумные усилия для распознавания ассоциаций, когда это целесообразно.

В такой функции, как test1, lvalue типа p используется для получения ip, а p никаким другим образом не используется для доступа к p->length между формированием ip и его последним использованием. Таким образом, компиляторы должны без труда распознать, что сохранение в *ip не может быть переупорядочено при последующем чтении в p->length, даже без общего правила, дающего полное разрешение на использование указателей типа int* для доступа к int элементам несвязанных структур. Однако внутри test2 нет никаких видимых средств, с помощью которых адрес p->length мог бы использоваться при вычислении указателя p->dat, и, таким образом, для оптимизирующих компиляторов, предназначенных для наиболее распространенных целей, было бы разумно поднять чтение p->length перед циклом. в ожидании, что его значение не изменится.

Вместо того, чтобы пытаться распознать типы объектов, из которых получен указатель, clang и gcc вместо этого предпочитают вести себя так, как будто Стандарт дает общее разрешение на доступ к членам структуры (но не объединения!) с использованием указателей их типов. Это допустимо, но не требуется стандартом (соответствующая, но некачественная реализация может обрабатывать test1 произвольным бессмысленным образом), но слепота к получению указателя без необходимости ограничивает диапазон конструкций, доступных программистам, и заставляет отказаться от того, что должно быть полезные оптимизации, такие как примеры test2().

В целом, правильный ответ почти на любой вопрос, связанный с алиасингом в C, заключается в том, что это вопрос качества реализации. Наблюдения за тем, что делают clang и gcc, могут быть полезны для людей, которым нужно умиротворить режим -fstrict-aliasing этих компиляторов, но которые имеют мало общего с тем, что на самом деле говорит Стандарт.

person supercat    schedule 20.02.2021