Подтвердить что ты не робот

Является ли строгое правило псевдонимов "улицей с двусторонним движением"?

В эти комментарии пользователь @Deduplicator настаивает на том, что правило строгого псевдонима разрешает доступ через несовместимый тип, если любой из псевдонимов или указатель псевдонимов является указателем-указателем, (квалифицированный или неквалифицированный, подписанный или неподписанный char *). Итак, его утверждение в основном состоит в том, что оба

long long foo;
char *p = (char *)&foo;
*p; // just in order to dereference 'p'

и

char foo[sizeof(long long)];
long long *p = (long long *)&foo[0];
*p; // just in order to dereference 'p'

соответствуют и имеют определенное поведение.

В моем прочитанном, однако, это только первая форма, которая действительна,, то есть когда указатель сглаживания является указателем на char; однако этого нельзя сделать в другом направлении, т.е. е. когда указатель сглаживания указывает на несовместимый тип (кроме символьного типа), указатель с псевдонимом - char *.

Итак, второй фрагмент выше имел бы поведение undefined.

В чем дело? Это верно? Для записи я уже прочитал этот вопрос и ответ, и там принятый ответ явно заявляет, что

Правила допускают исключение для char *. Он всегда предполагал, что char * псевдонимы других типов. Однако это не сработает по-другому, нет предположения, что ваша структура псевдонизирует буфер символов.

(акцент мой)

4b9b3361

Ответ 1

Вы можете сказать, что это неверно. Как вы сами цитировали (поэтому я не буду перекодировать здесь) гарантированный действительный листинг только от любого другого типа до char *.

Другая форма действительно соответствует стандарту и вызывает поведение undefined. Однако, как небольшой бонус, давайте немного поговорим об этом стандарте.

Шары на каждой существенной архитектуре являются единственным типом, который допускает полностью неприсоединенный доступ, это связано с тем, что инструкции чтения байтов должны работать на любом байте, иначе они были бы почти бесполезны. Это означает, что косвенное чтение char всегда будет действительным для каждого процессора, о котором я знаю.

Однако другой путь вокруг этого не применим, вы не можете прочитать uint64_t, если указатель не выровнен с 8 байтами на большинстве арок.

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

struct x *mystruct = MakeAMyStruct();
char * foo = (char *)mystruct;
struct x *mystruct2 = (struct mystruct *)foo;

И mystruct2 будет равнозначно. Это также гарантирует правильность выравнивания структуры для нее.

В принципе, если вы хотите указатель на char и указатель на другой тип, всегда указывайте указатель на другой тип, а затем наберите char. Или даже лучше использовать союз, вот что они в основном для...

Обратите внимание, что есть заметное исключение из правила. Некоторые старые реализации malloc используются для возврата char *. Этот указатель всегда гарантированно может быть спрятан для любого типа, не нарушая правил псевдонимов.

Ответ 2

Дедупликатор правильный. Поведение undefined, которое позволяет компиляторам реализовывать оптимизацию "строгого сглаживания", не применяется, когда символьные значения используются для создания представления объекта.

Определенные представления объектов не обязательно должны представлять значение типа объекта. Если хранимое значение объекта имеет такое представление и считывается с помощью выражения lvalue который не имеет типа символа, поведение undefined. Если такое представление создается побочным эффектом, который изменяет всю или любую часть объекта на lvalue выражение, которое не имеет характера, поведение undefined. Такой представление называется ловушечным представлением.

Однако ваш второй пример имеет поведение undefined, потому что foo не инициализирован. Если вы инициализируете foo, то он имеет только поведение, определенное реализацией. Это зависит от реализации определенных требований к выравниванию long long и имеет ли long long какие-либо конкретные биты для реализации.

Рассмотрим, если вы измените свой второй пример на это:

long long bar() {
    char *foo = malloc(sizeof(long long));
    char c;
    for(c = 0; c < sizeof(long long); c++)
        foo[c] = c;
    long long *p = (long long *) p;
    return *p;
}

Теперь выравнивание перестает выдаваться, и этот пример зависит только от представления, определенного реализацией long long. Какое значение возвращается, зависит от представления long long, но если это представление определено как не имеющее битов пэда, эта функция всегда должна возвращать одно и то же значение и всегда должна быть допустимым значением. Без битов бит эта функция не может генерировать представление ловушки, и поэтому компилятор не может выполнять на нем строгие алгоритмы псевдонимов.

Вы должны выглядеть довольно сложно, чтобы найти стандартную совместимую реализацию C, которая имеет реализованные по умолчанию биты пэдов в любом из своих целых типов. Я сомневаюсь, что вы найдете тот, который реализует любой тип строгого псевдонима оптимизации. Другими словами, компиляторы не используют поведение undefined, вызванное доступом к представлению ловушки, чтобы обеспечить оптимизацию сглаживания, поскольку ни один компилятор, который реализует оптимизацию с использованием строкового сглаживания, не определил никаких ловушных представлений.

Обратите внимание, что если buf был инициализирован всеми нулями ('\0' символов), то эта функция не имела бы никакого поведения undefined или реализации. Представление целочисленного нуля целочисленного типа гарантируется, что оно не является ловушным представлением и гарантированно имеет значение 0.

Теперь для строго соответствующего примера, который использует значения char для создания гарантированного действительного (возможно ненулевого) представления значения long long:

#include <stdio.h>
#include <stdlib.h>

int
main(int argc, char **argv) {
    int i;
    long long l;
    char *buf;

    if (argc < 2) {
        return 1;
    }
    buf = malloc(sizeof l);
    if (buf == NULL) {
        return 1;
    }
    l = strtoll(argv[1], NULL, 10);
    for (i = 0; i < sizeof l; i++) {
        buf[i] = ((char *) &l)[i];
    }
    printf("%lld\n", *(long long *)buf);
    return 0;
}

Этот пример не имеет поведения undefined и не зависит от выравнивания или представления long long. Это тип кода, для которого было создано исключение типа символа при доступе к объектам. В частности, это означает, что Standard C позволяет реализовать собственную функцию memcpy в переносном коде C.