Нарушение строгого сглаживания даже без приведения?

Думаю, я действительно спрашиваю: является ли псевдоним «транзитивным»? Если компилятор знает, что A может быть псевдонимом B, а B может быть псевдонимом C, то, конечно же, он должен помнить, что A, следовательно, может быть псевдонимом C. Возможно, эта «очевидная» транзитивная логика не требуется?

Пример, для наглядности. Самый интересный для меня пример проблемы со строгим псевдонимом:

// g++    -fstrict-aliasing -std=c++11 -O2
#include <iostream>

union
{   
    int i;
    short s;
} u;
int     * i = &u.i;

int main()
{   

    u.i = 1; // line 1
    *i += 1; // line 2

    short   & s =  u.s;
    s += 100; // line 3

    std::cout
        << " *i\t" <<  *i << std::endl // prints 2
        << "u.i\t" << u.i << std::endl // prints 101
        ;

    return 0;
}

g++ 5.3.0 на x86_64 (но не clang 3.5.0) дает приведенный выше вывод, где *i и u.i дают разные числа. Но они должны давать точно такое же число, потому что i определяется как int * i = &u.i;, а i не изменяется.

У меня есть теория: при «предсказании» значения u.i компилятор спрашивает, какие строки могут повлиять на содержимое u.i. Это включает в себя строку 1, очевидно. И строка 2, потому что int* может быть псевдонимом int члена союза. И строка 3 также, потому что все, что может повлиять на одного члена союза (u.s), может повлиять на другого члена того же союза. Но при прогнозировании *i он не понимает, что строка 3 может повлиять на int lvalue в *i.

Эта теория кажется разумной?

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


person Aaron McDaid    schedule 28.09.2016    source источник
comment
en.cppreference.com/w/cpp/language/union   -  person Baum mit Augen    schedule 28.09.2016
comment
Во-первых, каламбур типов на основе объединения разрешен только в C. Во-вторых, разрешение на каламбур типов дается только тогда, когда к членам объединения обращаются непосредственно как к членам объединения. В противном случае ваша транзитивность немедленно уничтожила бы все ограничения на псевдонимы, поскольку компилятор обычно должен был бы предположить, что два несвязанных указателя могут указывать на члены одного объекта объединения.   -  person AnT    schedule 28.09.2016
comment
(Сейчас я пытаюсь удалить этот вопрос. Я никогда не знал, что союзы C++ настолько отличаются от C. Но я не могу его удалить. Извините за глупый вопрос!)   -  person Aaron McDaid    schedule 28.09.2016
comment
Я только что задал здесь версию этого вопроса на основе C: кастинг" title="нарушение строгого алиасинга в c даже без кастинга">stackoverflow.com/questions/39757658/   -  person Aaron McDaid    schedule 29.09.2016


Ответы (2)


Чтение из неактивного члена объединения не определено в C++. (Это допустимо в C99 и C11).

Итак, в целом, компилятору не требуется ничего предполагать/запоминать.

Стандартный:

N4140 §9.5[class.union]/1

В объединении не более одного из нестатических элементов данных может быть активным в любой момент времени, то есть значение не более одного из нестатических элементов данных может быть сохранено в объединении в любое время.

person krzaq    schedule 28.09.2016
comment
Я думал, что много читал о строгом псевдониме на SO, но никогда раньше этого не замечал. Я думаю, мне нужно отдохнуть от вопроса о строгом псевдониме :) - person Aaron McDaid; 28.09.2016
comment
Мне только что удалось воспроизвести ту же проблему в C. Думаю, мне следует задать еще один вопрос. Я не могу удалить этот вопрос, и я думаю, что мне не следует менять этот вопрос, чтобы он касался C - person Aaron McDaid; 28.09.2016
comment
Мне не хватает необходимых знаний, чтобы сказать, должен ли компилятор учитывать псевдоним допустимой в других отношениях переменной приведения в C. - person krzaq; 28.09.2016
comment
@AaronMcDaid Просто задайте новый вопрос, он тоже не тот плохой. - person Baum mit Augen; 28.09.2016
comment
Спасибо @BaummitAugen. Я только что задал еще один вопрос на C. stackoverflow.com/questions/39757658/ - person Aaron McDaid; 29.09.2016

Разрешено только чтение из члена объединения, в которое последний раз записывались на C++.

Псевдонимы внешних союзов разрешены только между «похожими» типами (подробности см. В этом вопросе) и char/unsigned char. Псевдоним другого типа допускается только через char/unsigned char, но не разрешается использовать псевдоним char/unsigned char через другие типы. Если бы последнее было разрешено, то все объекты должны были бы рассматриваться как возможные псевдонимы любого другого объекта, потому что они могут быть «транзитивно псевдонимами», как вы описываете через char/unsigned char.

Но поскольку это не так, компилятор может с уверенностью предположить, что только объекты «похожих» типов и char/unsigned char псевдонимов друг друга.

person alain    schedule 28.09.2016
comment
Применение псевдонимов нетранзитивно в тех случаях, когда это имеет смысл, было бы более полезным, чем блокирование полезных форм псевдонимов из-за опасения транзитивности. Например, имело бы смысл сказать, что если T1 и T2 появляются вместе в одном объединении, а T1 и T3 вместе появляются в другом, то доступы через T1* следует предполагать к грязным вещам типов T2 и T3, а доступы через T2* или T3* должны загрязнять вещи типа T1, но доступ через T2* не должен загрязнять вещи T3*, и наоборот. - person supercat; 05.10.2016
comment
@supercat не должен был бы компилятор знать все союзы во всех TU, если бы это было разрешено? - person alain; 05.10.2016
comment
Правило использования common-initial-sequences, которое авторам gcc не нравится и которое они беспечно игнорируют, потребует от компиляторов учитывать только те объединения, полное объявление которых было видно в момент использования. Это правило распространяется только на доступ к общим начальным последовательностям структур внутри объединения (а не на использование объединений для других видов каламбуров типов), но этот принцип можно применять и в других местах (обмен оптимизацией на семантическую выразительность). - person supercat; 05.10.2016
comment
Если правило было интерпретировано таким образом, чтобы разрешить использование S в качестве T, если полное объявление союза было видно в местах, где объект использовался в качестве S, или< /i>, где он использовался как T, что позволило бы использовать многие полезные конструкции, которые раньше поддерживались, но поддерживаются только в -fno-strict-aliasing диалекте gcc. Например, если многие структуры имеют общую начальную последовательность, и каждая такая структура сопровождается определением объединения этого типа и определенного типа, который просто содержит эту последовательность, то функция, которая принимает указатель на последнюю структуру... - person supercat; 05.10.2016
comment
... сможет работать с любым из связанных с ним типов структур. Авторы gcc, по-видимому, думают, что распознавание псевдонимов в этом случае полностью испортит оптимизацию, но наличие кода, который принимает общий тип, предполагающий, что он может быть псевдонимом с другими, повредит оптимизации гораздо меньше, чем если бы от программистов требовалось писать его таким образом, чтобы компилятор должен предположить, что он способен использовать псевдоним для любого объекта любого типа в любом месте, чей адрес когда-либо был открыт для внешнего мира. - person supercat; 05.10.2016
comment
У меня нет проблем с идеей, что компиляторы не должны предполагать, что алиасинг может произойти в тех случаях, когда у них нет причин его ожидать. У меня есть большая проблема с идеей, что компиляторы, получившие что-то вроде uint32_t get_float_bits(float *f) { return *(uint32*)f; }, должны использовать правила, чтобы сделать вывод, что f не может указывать на float, а не использовать приведение типов как указание на то, что float, вероятно, будет прочитано как uint32_t. - person supercat; 05.10.2016
comment
Чего мне больше всего не хватает в C++, так это возможности использовать объединение для повторной интерпретации буфера как структуры. C это позволяет, а C++ нет, чего я действительно не понимаю, потому что это заставляет программиста использовать «ненужный», возможно, дорогой memcpy. - person alain; 05.10.2016
comment
Я не знаю, насколько на практике такие структуры менее безопасны в C++, чем в C; clang и gcc выдвигают очень агрессивные интерпретации стандарта, которые сильно ограничивают то, что может быть сделано на 100% надежно (иногда неясно, какое поведение при взломе кода является результатом ошибок или дизайна, но если шаблон не поддерживается надежно, он ненадежен ). Напротив, в C++ я думаю, что должна быть возможность заполнить хранилище как один PODS, затем вручную вызвать деструктор и использовать новое размещение для размещения нового объекта в старом хранилище. - person supercat; 05.10.2016
comment
Да, думаю, на практике некоторые приемы, не задействующие memcpy, хорошо работают, проблема в стандарте. У меня сложилось впечатление, что memcpy — единственный санкционированный стандартом способ, но я не уверен. - person alain; 05.10.2016
comment
Я думаю, что в C++ явное сочетание тривиального деструктора и размещения new будет иметь определенное поведение. В C даже memmove не гарантирует безопасность. В C, если memmove используется для копирования чего-либо с объявленным типом в область выделенного хранилища, Стандарт позволяет компилятору предположить, что адресат не может использовать псевдоним для чего-либо другого типа, и, по крайней мере, в некоторых случаях — будь то ошибка или дизайн - gcc, похоже, использует эту свободу. - person supercat; 06.10.2016