Алиасинг через союзы - PullRequest
       49

Алиасинг через союзы

3 голосов
/ 20 марта 2019

У 6.5(p7) есть пуля около union с и aggregate с:

Объект должен иметь свое сохраненное значение, доступное только через lvalue выражение, которое имеет один из следующих типов:

[...]

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

Это не совсем понятно, что это значит. Требуется ли для хотя бы одного члена или всех членов для выполнения строгого правила наложения имен. Особенно о union с:

union aliased{
    unsigned char uint64_repr[sizeof(uint64_t)];
    uint64_t value;
};

int main(int args, const char *argv[]){
    uint64_t some_random_value = 123;
    union aliased alias;
    memcpy(&(alias.uint64_repr), &some_random_value, sizeof(uint64_t));
    printf("Value = %" PRIu64 "\n", alias.value);
}

DEMO

Хорошо ли определено поведение программы? Если нет, что означает пуля?

Ответы [ 2 ]

3 голосов
/ 20 марта 2019

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

Возьмем, к примеру, unsigned и float, как правило, оба 32-битных, а в некоторых случаях может потребоваться просмотр сохраненного значения из unsigned* или float*.Например, вы не можете сделать:

    float f = 3.3;
    // unsigned u = *(unsigned *)&f;  /* violation */

После 6.5 (p7) вы можете использовать union между обоими типами и получать доступ к той же информации, что и unsigned или floatбез указания типа на указатель или выполнения строгого правила псевдонима, например

typedef union {
    float f;
    unsigned u;
} f2u;
...    
    float f = 3.3;
    // unsigned u = *(unsigned *)&f;  /* violation */
    f2u fu = { .f = f };
    unsigned u = fu.u;                /* OK - no violation */

Таким образом, правило строгого псевдонима предотвращает доступ к памяти с эффективным типом через указатель другого типа, если только этот указатель не равен char тип или указатель на член объединения между двумя типами.

( примечание: этот раздел стандарта - это не что иное, как пример ясности. (Выможет прочитать его 10 раз и все равно почесать голову) Его цель - обуздать злоупотребление типами указателей, при этом признавая, что блок памяти в любой форме должен иметь возможность доступа через символьный тип (и unionсреди других допустимых способов доступа).)

За последние несколько лет компиляторы стали намного лучше отмечать нарушения правил.

2 голосов
/ 20 марта 2019

Пуля служит для двух целей.Прежде всего, если кто-то признает, что доступ к lvalue, который визуально основан или может быть основан на lvalue определенного типа, должен быть распознан как lvalue или возможное lvalue последнего типа, то ему присваивается что-то вроде:

union U {int x[10]; float y[10];} u;

lvalue, который явно получен из u, будет разрешен доступ ко всем содержащимся в нем объектам.Диапазон ситуаций, в которых реализация распознает, что lvalue основано на другом, является проблемой качества реализации, поскольку некоторые качественные компиляторы, такие как icc, могут распознавать, учитывая что-то вроде:

int load_array_element(int *array, int i) { return array[i]); }
...
int test(int i) { return load_array_element(&u.x, i); }

что все, что определенный вызов load_array_element может сделать с *array, будет сделано с u (ему в конце концов присваивается адрес lvalue, непосредственно сформированный из u), и другиекомпиляторы, такие как clang и gcc, неспособны распознать даже такую ​​конструкцию, как *(u.x+i), как lvalue, основанную на u.

Вторая цель пули - предложить, что даже если компилятор слишком примитивен для сохраненияотслеживая вывод lvalue в прямолинейном коде, он должен признать, что данные декларации:

int *p,i;
struct foo { int x;} foo;

, если он видит *p=1; i=foo.x;, не обращая никакого внимания на то, откуда пришел p, он должен убедиться, чтозапись в *p выполняется до чтения foo.x.Даже если это действительно необходимо только в тех случаях, когда компилятор, который потрудился обратить внимание, смог бы увидеть, что p был сформирован из foo, описание вещей в этих терминах увеличило бы кажущуюся сложность компилятора по сравнениюс предоставлением доступа к foo.x принудительное завершение любых ожидающих записей в целевые указатели целых чисел.

Обратите внимание, что если кто-то заинтересован только в тех случаях, когда доступ к элементу структуры или объединения осуществляется через только что полученный указатель, нет необходимости включать общее разрешение для доступа к структуре или объекту объединения через lvalue типа члена.Учитывая последовательность кодов: foo.x = 1; p = &foo.x; i=*p;, принятие адреса foo.x должно привести к тому, что компилятор завершит любые ожидающие записи в foo.x, прежде чем запускать любой код, который может использовать адрес (компилятор, который не имеет представления о том, что ниже).код будет делать с адресом может просто завершить запись немедленно).Если бы кодовая последовательность была foo.x = 1; i = *p;, то обращение к foo.x через lvalue foo означало бы, что любой существующий указатель, который мог бы идентифицировать это хранилище, был бы «устаревшим», и, таким образом, компилятор не был бы обязан распознатьчто такой указатель может идентифицировать то же хранилище, что и foo.x.

. Обратите внимание, что, несмотря на сноску 88, в которой четко сказано, что цель "правила строгого алиасинга" состоит в том, чтобы указать, когда объектам разрешается псевдоним, интерпретацияgcc и clang интерпретируют правило как оправдание для игнорирования случаев, когда к объектам получают доступ lvalue, которые довольно явно получены из них.Возможно, в ретроспективе авторам стандарта следовало бы включить положение «Обратите внимание, что это правило не делает попыток запретить некачественным компиляторам вести себя тупо, но не имеет намерения поощрять такое поведение», но авторы C89 не имели никаких основанийожидать, что правило будет интерпретировано так, как оно есть, и авторы clang и gcc почти наверняка наложат вето на любое предложение добавить такой язык сейчас.

...