Строгий псевдоним и указатель на поля объединения

У меня вопрос о строгих правилах сглаживания, объединениях и стандарте. Предположим, у нас есть следующий код:

#include <stdio.h>

union
{
    int f1;
    short f2;
} u = {0x1};

int     * a = &u.f1;
short   * b = &u.f2;

int main()
{
    u.f1 = 1;
    *a += 1;
    u.f2 = 2;
    *b *= 2;

    printf( "%d %hd\n", *a, *b);

    return 0;
}

Теперь давайте посмотрим, как это работает:

$ gcc-5.1.0-x86_64 t.c -O3 -Wall && ./a.out 
2 4
$ gcc-5.1.0-x86_64 t.c -O3 -Wall -fno-strict-aliasing && ./a.out 
4 4

Мы видим, что строгое сглаживание разрывает зависимости. Более того, это кажется правильным кодом, не нарушающим правило строгого псевдонима.

  1. Получается, что в случае полей объединения объект, лежащий по адресу, совместим со всеми типами членов объединения?
  2. Если 1 верно, что компилятор должен делать с указателями на члены объединения? Это проблема стандарта, который разрешает такое поведение компилятора? Если нет - почему?
  3. Вообще говоря, иное поведение компилятора с корректным кодом недопустимо ни в коем случае. Так что, похоже, это тоже ошибка компилятора (особенно если обращение к полю объединения будет внутри функций, SA не нарушает зависимость).

person alexanius    schedule 12.08.2015    source источник
comment
Нет, это не правильный код. Строгое использование псевдонимов является требованием стандарта, а не компилятора. Это любезно предоставлено gcc, чтобы отключить это для сломанного кода.   -  person too honest for this site    schedule 12.08.2015
comment
@Olaf: Некоторые реализации определяют поведение приведенного выше кода, а некоторые нет. Код, ограниченный компиляторами, поддерживающими поведение, выходящее за рамки требований стандарта (т. е. наиболее полезные программы), может быть не полностью переносимым, но это не означает, что он в каком-то смысле неисправен. Напротив, поведение gcc во многих случаях (хотя, по общему признанию, не так, как указано выше) было бы совершенно нарушенным, если бы оно либо (1) не применяло правило одной программы таким образом, что сделало бы стандарт бессмысленным, либо (2) не документировало, что его соответствие требует -fno-strict-aliasing или -O0.   -  person supercat    schedule 21.06.2018


Ответы (2)


Стандарт C говорит, что псевдонимы через объединения явно разрешены.

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

void func(int *a, short *b)
{
     *a = 1; 
     printf("%f\n", *b);
}

Цель строгого правила псевдонимов состоит в том, что a и b не должны считаться псевдонимами. Однако вы можете позвонить func(&u.f1, &u.f2); .

Чтобы решить эту дилемму, решение, основанное на здравом смысле, состоит в том, чтобы сказать, что «разрешение на обход», которое союзы должны избегать строгого правила псевдонимов, применяется только тогда, когда доступ к членам союза осуществляется по имени.

Стандарт прямо не указывает на это. Можно возразить, что «Если член используется...» (6.5.2.3) на самом деле указывает, что «обход» происходит только при доступе к члену по имени, но это не на 100% ясно.

Однако трудно придумать какую-либо альтернативную и непротиворечивую интерпретацию. Одна из возможных альтернативных интерпретаций состоит в том, что запись func(&u.f1, &u.f2) вызывает UB, потому что перекрывающиеся объекты были переданы функции, которая «знает», что она не получает перекрывающиеся объекты — что-то вроде нарушения restrict.

Если мы применим эту первую интерпретацию к вашему примеру, мы бы сказали, что *a в вашем printf вызывает UB, потому что текущий объект, хранящийся в этом месте, является short, а 6.5.2.3 не срабатывает, потому что мы не используем член союза по имени.

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

Здесь уже обсуждалось, но сейчас не могу найти ветку.

person M.M    schedule 12.08.2015
comment
Я думаю, что основным ключом к пониманию правила является признание того, что авторы Стандарта не считали необходимым определять язык, подходящий для целей системного программирования, поскольку любой, кто хочет сделать реализацию подходящей для этой цели, может указать, что правила псевдонимов не применяются (хотя, как ни странно, мне кажется, что я видел больше компиляторов, авторы которых делают вид, что правило не существует, чем явно отказываются от его использования для оптимизации). Жаль, что Комитет так и не определил барьер наложения псевдонимов типов, поскольку это сделало бы язык пригодным для системного программирования... - person supercat; 21.11.2015
comment
... и устранил необходимость исключения символьного типа из правил псевдонимов. - person supercat; 21.11.2015
comment
@supercat ну компиляторы такую ​​штуку реализовали (-fno-strict-aliasing) - person M.M; 21.11.2015
comment
Существует ли какой-либо стандартный способ, с помощью которого программа может указать, что она должна иметь возможность гарантировать, что все записи в блок памяти с использованием любого типа до того, как определенная точка должна предшествовать всем операциям записи в эту память с использованием любой тип после этой точки? Многие программы, не требующие строгого сглаживания, на самом деле прекрасно работали бы с включенной оптимизацией сглаживания 99%, если бы программист мог заблокировать 1%, который может вызвать проблемы. - person supercat; 21.11.2015

Техническое исправление 3 C99 разъясняет каламбур типов на основе метода объединения, заявляя в разделе 6.5.2.3:

Если член, используемый для доступа к содержимому объекта объединения, не совпадает с членом, который в последний раз использовался для хранения значения в объекте, соответствующая часть объектного представления значения переинтерпретируется как объектное представление в новом типе как описан в 6.2.6 (процесс, который иногда называют «каламбуром»).

См. здесь с 1042 по 1044.

person Eugene Sh.    schedule 12.08.2015
comment
Это положение имеет довольно ограниченную полезность, учитывая, что единственными типами членов союза, к которым можно получить доступ без UB, являются char, signed char и unsigned char. - person supercat; 15.06.2018
comment
@supercat, не могли бы вы уточнить? - person Jerry Jeremiah; 20.06.2018
comment
@JerryJeremiah: согласно N1570 6.5p7, к объекту агрегатного типа можно получить доступ только с использованием lvalue его собственного типа, символьного типа или охватывающего агрегатного типа. Не существует правила, разрешающего доступ к объекту агрегатного типа с использованием lvalue типа члена. Авторы Стандарта, вероятно, полагали, что составители компиляторов поймут, что при наличии чего-то простого, например int *p = unionPtr->intMember; *p = 1;, качественный компилятор распознает связь между *p и объединением независимо от того, требует этого Стандарт или нет, но... - person supercat; 20.06.2018
comment
... gcc и clang более умны и будут генерировать более эффективный код, который не может надежно обработать возможность того, что объединение также может быть доступно как какой-либо другой тип. - person supercat; 20.06.2018
comment
@JerryJeremiah: хотя авторы компиляторов, кажется, распознают большинство выражений, которые используют форму unionPtr->intMember напрямую, они не могут надежно обрабатывать последовательность, подобную unionPtr->member = expression1; memberType *p = &unionPtr->member; *p = expression2; memberType result = unionPtr->member;, хотя я не уверен, как можно осмысленно сказать, что оператор & дает указатель типа члена если компилятор не может надежно обработать такой простой случай использования. - person supercat; 20.06.2018
comment
@JerryJeremiah: Наш резидент «Я знаю все лучше» снова вводит в заблуждение. Он забыл прочитать сноску, которая разъясняет это: port70.net/~nsz/ c/c11/n1570.html#note95 Как я уже писал, тайп-каламбур под union является единственным строго законным способом. Обратите внимание, что это по-прежнему оставляет интерпретацию данных как определенную реализацию, и в некоторых реализациях это может привести к UB. Но это должно быть указано реализацией, например. заявляя, что целые числа могут иметь представление ловушки, поэтому его можно проверить в расширенном режиме и гарантировать работу для конкретной реализации. - person too honest for this site; 21.06.2018
comment
@JerryJeremiah: И да, эта часть стандарта обязательна для каждого компилятора C. Я был бы очень удивлен, что gcc или clang потерпят неудачу именно в этом. Однако, если они это сделают, это будет ошибкой, и о ней следует сообщить (если это еще не сделано). Оптимизатор не в первый раз выходит из-под контроля. В примере во втором комментарии даже не используется union. Это распространенное заблуждение, что указатель на union-member является указателем на union. На данный момент действительно действует 6.5p6 ff. Но это совсем другая тема. - person too honest for this site; 21.06.2018
comment
@toohonestforthissite: То, как написан N1570 p6.5p7, лишь незначительное меньшинство программ, использующих структуры или объединения, действительно имеют определенное поведение; способность запускать их является проблемой качества реализации. Реализации, которые прилагают добросовестные усилия, чтобы не быть тупыми, будут поддерживать большинство таких программ, однако, в том числе многие, которые сломаются оптимизаторами тупых компиляторов. - person supercat; 15.08.2018