Законно ли набирать тип с помощью указателя на объединение в C?

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

# include <stdio.h>
int main()
{
    char p[]={0x01,0x02,0x03,0x04};
    int *q = p;
    printf("%x",*q);
    return 0;
}

У меня вопрос: легальна ли следующая версия приведенного выше кода? Я совершенно не уверен в преобразовании указателя на char в указатель на объединение, содержащее массив char. Многие вопросы о каламбуре типов здесь, в SO, но я не нашел дубликата, который охватывает использование указателя таким образом.

#include <stdio.h>
#include <stdint.h>

union char_int {
    char p[4];
    int32_t q;
};

int main()
{
    char p[]={0x01,0x02,0x03,0x04};
    int *q = &(((union char_int *)p)->q);
    printf("%x",*q);
    return 0;
}

В связи с этим, я считаю, что эти байты сформируют законное значение int32_t для всех возможных представлений, разрешенных стандартом, но если кто-то может подтвердить эту дополнительную деталь, это тоже было бы здорово.


person hyde    schedule 06.10.2019    source источник
comment
Поведение (union char_int *)p в целом не определяется стандартом C из-за C 2018 6.3.2.3 7: «Указатель на тип объекта может быть преобразован в указатель на другой тип объекта. Если результирующий указатель неправильно выровнен для указанного типа, поведение не определено ... «если p оказывается выровненным, как необходимо для union char_int, тогда стандарт говорит:« при повторном преобразовании результат должен сравниваться с исходным указателем. . » В стандарте не говорится, что этот указатель действительно имеет какое-либо значение, которое иначе работает как union char_int *.   -  person Eric Postpischil    schedule 06.10.2019
comment
Другими словами, если у нас есть union char_int *x = (union char_int *) p;, и это успешно, потому что выравнивание происходит, стандарт ничего не говорит о значении x, кроме (char *) x, производит что-то, что сравнивается с p. Значение x не обязательно является допустимым адресом, иначе *x может относиться к совершенно другой памяти, чем p, например.   -  person Eric Postpischil    schedule 06.10.2019
comment
На самом деле это не вопрос законности или незаконности, а скорее неопределенное поведение. Первый - приводит к неопределенному поведению из-за нарушения строгого алиасинга. Оба результата приводят к неопределенному поведению из-за того, что на значение *q влияет базовое целочисленное представление реализации (в основном, порядок байтов, но потенциально платформа может не использовать дополнение до двух). И, как указывалось выше, оба значения не определены из-за алигментации.   -  person Graeme    schedule 06.10.2019
comment
@Graeme: вариации из-за порядка байтов определяются реализацией, а не неопределенными. Стандарт требует, чтобы реализации документировали свои представления в памяти, в C 2018 6.2.6.1 2: «За исключением битовых полей, объекты состоят из непрерывных последовательностей из одного или нескольких байтов, количество, порядок и кодировка которых либо указаны явно. или определяется реализацией ». После проблем с преобразованием указателя проблема заключается в алиасинге, а не в представлении.   -  person Eric Postpischil    schedule 06.10.2019
comment
@EricPostpischil Похоже, я представляю, или был ли какой-то язык об адресе структуры, которая может быть приведена к адресу ее первого типа элемента? Конечно, здесь у нас есть union, и мы все равно не приводим из его адреса, так что это ничего не говорит об этом случае.   -  person hyde    schedule 06.10.2019
comment
@hyde: Да, это в C 2018 6.7.2.1 15 (для структур) и 16 (для объединений): «… Указатель на объект структуры, преобразованный соответствующим образом, указывает на его начальный член (или если этот член является немного -field, затем на единицу, в которой он находится), и наоборот… »и«… Указатель на объект объединения, преобразованный соответствующим образом, указывает на каждый из его членов (или, если член является битовым полем, то на блок, в котором он находится), и наоборот ».   -  person Eric Postpischil    schedule 06.10.2019


Ответы (1)


Значение фразы «Объект должен иметь свое сохраненное значение, доступ к которому может получить только выражение lvalue, которое имеет один из следующих типов ...» зависит от того, как определяются слова «объект» и «по», используемые в этом правиле. Насколько я могу судить, никогда не было ничего похожего на консенсус по поводу того, что означают эти слова, за исключением того факта, что авторы Стандарта предположительно ожидали, что реализации будут пытаться разумно интерпретировать правило. Обратите внимание, что при буквальном толковании правила это примерно так:

short volatile x;
int test(void)
{
  int y = x+1;
  return y;
}

вызовет UB, потому что время жизни y начинается, когда код входит в test, что, в свою очередь, происходит до чтения x, но он не может получить значение до тех пор, пока не будет прочитан x. Следовательно, значение y должно измениться в течение его времени жизни, но такое действие не включает в себя какое-либо выражение lvalue типа int или какой-либо другой допустимый тип.

Ясно, что такая интерпретация была бы абсурдной, но на правило, исключающее простые случаи из предположения, что реализации будут знать, что делать, нельзя полагаться при рассмотрении более сложных. Что касается рассматриваемой конструкции, некоторые компиляторы могут сказать, что в выражении lvalue, таком как someUnion.member = 23;, объект union модифицируется "выражением lvalue someUnion", но не обязательно делать поправку на возможность того, что такой объект может быть доступен где-либо еще с помощью lvalue типа члена, ни lvalue других типов объединения, содержащих тот же член. Однако без какой-либо ясности в отношении того, что должно означать слово «by», на самом деле невозможно охарактеризовать какую-либо конкретную интерпретацию как правильную или неправильную.

person supercat    schedule 09.10.2019
comment
правило строгого псевдонима является прямым, в выражении int y = x+1; нет строгого нарушения псевдонима, поскольку к сохраненному значению объекта y не обращается тип, не разрешенный правилом. Разговор о времени жизни и о том, когда переменная начинает свое время жизни и когда она назначается, - это ваша интерпретация, стандарт не говорит, что они связаны, и я не понимаю, как ваш пример - это UB. Кажется, ваше объяснение вызывает несуществующую связь между временем жизни и строгим алиасингом, но я буду рад узнать что-то новое, если вы можете добавить какое-то объяснение. - person user2162550; 13.10.2019
comment
@ user2162550: Правило в том виде, в котором оно написано, запрещает доступ не только для lvalue других типов, но и для всего, что не является lvalue надлежащего типа. Правильным исправлением было бы ограничить правило объектами, к которым ранее осуществлялся доступ в том же контексте, что и доступ lvalue, и потребовать, чтобы lvalue, используемое для доступа, было явно связано с более ранним объектом в этом контексте. При выполнении такого выражения, как someUnion.arrayMember[i]=x;, доступ осуществляется с помощью lvalue, явно производного от someUnion, но я бы не сказал, что к нему обращается lvalue someUnion. - person supercat; 13.10.2019
comment
@ user2162550: Я не думаю, что кто-то стал бы утверждать перед комитетом C89, что авторы компилятора должны иметь право умышленно игнорировать действия, которые формируют указатель на объект, и немедленно использовать его в контексте его формирования, но ни clang, ни gcc не будут надежно обрабатывать такие операции, кроме случаев использования оператора []. Даже (*((someUnion.arrayMember)+(i))) не распознается как доступ к someUnion или другим его членам, хотя это выражение является само определением someUnion.arrayMember[i] [буквально!]. - person supercat; 13.10.2019
comment
Вы говорите, что (*((someUnion.arrayMember)+(i))) s не распознается как доступ к someUnion или другим его членам, не могли бы вы поделиться соответствующим разделом для этого? Вы имеете в виду, что часть someUnion.arrayMember внутри (*((someUnion.arrayMember)+(i))) не является lvalue? это новость для меня - person user2162550; 15.10.2019
comment
@ user2162550: Ни clang, ни gcc не распознают (*((someUnion.arrayMember)+(i))) как доступ к someUnion. Выражение someUnion.arrayMember является lvalue, но когда оно является левым оператором оператора [] или +, оно раскладывается на значение указателя, отличное от l, которое идентифицирует первый элемент массива, а не обращается к объекту. - person supercat; 15.10.2019