Мне потребовалось некоторое время, чтобы понять, в чем суть проблемы. DR236 обсуждает это. На самом деле проблема заключается в передаче указателей на функцию, которые указывают на перекрывающееся хранилище; и разрешено ли компилятору предполагать, что такие указатели могут создавать псевдонимы друг друга или нет.
Если мы просто обсуждаем псевдонимы членов объединения, то это было бы проще. В следующем коде:
u.i = 5;
u.l = 6;
printf("%d\n", u.i);
поведение не определено, поскольку эффективным типом u
является long
; то есть хранилище u
содержит значение, которое было сохранено как long
. Но доступ к этим байтам через lvalue типа int
нарушает правила псевдонимов 6.5p7. Текст о неактивных членах союза, имеющих неуказанные значения, не применяется (IMO); правила псевдонимов превосходят это, и этот текст вступает в игру, когда правила псевдонимов не нарушаются, например, при доступе через lvalue символьного типа.
Если мы поменяем порядок первых двух строк выше, тогда программа будет четко определена.
Однако все меняется, когда доступ «спрятан» за указателями на функцию.
DR236 решает эту проблему с помощью двух примеров. Оба примера имеют check()
, как в этом посте. Пример 1 malloc
занимает немного памяти и передает h
и k
, указывающие на начало этого блока. В примере 2 есть союз, аналогичный этому посту.
Их вывод состоит в том, что пример 1 является «неразрешенным», а пример 2 — UB. Однако в этом превосходном сообщении в блоге указывается, что логика, используемая DR236, в достижении этих выводов непоследовательно. (Спасибо Тору Клингбергу за это).
В последней строке DR236 также говорится:
Обе программы вызывают неопределенное поведение, вызывая функцию f с указателями qi
и qd
, которые имеют разные типы, но обозначают одну и ту же область памяти. Переводчик имеет полное право изменить доступ к *qi
и *qd
по обычным правилам псевдонимов.
(очевидно, в противоречии с более ранним утверждением, что пример 1 не был решен).
Эта цитата предполагает, что компилятору разрешено предполагать, что два указателя, переданные функции, имеют значение restrict
, если они имеют разные типы, однако я не могу найти в стандарте никакой формулировки на этот счет или даже решения проблемы переупорядочивания доступа компилятором через указатели.
Было высказано предположение, что правила псевдонимов позволяют компилятору сделать вывод, что int *
и long *
не могут получить доступ к одной и той же памяти. Однако примеры 1 и 2 этому категорически противоречат.
Если бы указатели имели один и тот же тип, то я думаю, мы согласны с тем, что компилятор не может изменить порядок доступа, потому что они оба могут указывать на один и тот же объект. Компилятор должен предположить, что указатели не являются restrict
, если они специально не объявлены как таковые.
Тем не менее, я не вижу разницы между этим случаем и случаями примеров 1 и 2.
DR236 также говорит:
Общее понимание состоит в том, что объявление объединения должно быть видно в единице перевода.
что снова противоречит утверждению о том, что пример 2 является UB, потому что в примере 2 весь код находится в одной и той же единице перевода.
Мой вывод: мне кажется, что формулировка C99 указывает на то, что компилятору не следует разрешать переупорядочивать *h = 5;
и *k = 6;
в случае, если они перекрывают хранилище. Несмотря на то, что DR236 противоречит формулировке C99 и ничего не проясняет. Но чтение *h
после этого должно привести к неопределенному поведению, поэтому компилятору разрешено генерировать вывод 5
или 6
или что-то еще.
По моему мнению, если вы измените check()
на *k = 6; *h=5;
, тогда должно быть четко определено, чтобы напечатать 5
. Было бы интересно посмотреть, делает ли компилятор что-то еще в этом случае, а также обоснование компилятора, если он это делает.
person
M.M
schedule
07.04.2014
u.l
, а затем читаете из*h
, что указывает наu.i
, что делает то, что утверждает этот пост, неуказанным поведением. - person Turix   schedule 06.04.2014printf()
(поскольку это значение все равно нужно там снова). Вы не можете сказать наверняка (поскольку вы полагаетесь на неопределенное поведение), пока вы не проверяете сгенерированную сборку. - person mfro   schedule 06.04.2014-fstrict-aliasing
, которая включается с помощью-O2
,-O3
и-Os
. Вы можете отключить его с помощью-fno-strict-aliasing
для проверки. - person rici   schedule 06.04.2014char
для псевдонимов других типов, однако в этом постеint
иlong
используются псевдонимы, поэтому unspecified больше не является правильным ответом IMO; нарушение правил алиасинга важнее этого. - person M.M   schedule 07.04.2014