Ответ 1
Это неуказанное (тонко отличное от undefined) поведение для доступа к соединению любым другим элементом, кроме последнего. Это подробно описано в приложении C99 J:
Следующие неуказаны:
:
значение члена объединения, отличного от последнего, сохраненного в (6.2.6.1).
Однако, поскольку вы пишете на c
с помощью указателя, а затем читаете c
, этот конкретный пример хорошо определен. Неважно, как вы пишете элементу:
u.c = 'a'; // direct write.
*(&(u.c)) = 'a'; // variation on yours, writing through element pointer.
(&u)->c = 'a'; // writing through structure pointer.
Есть одна проблема, которая была поднята в комментариях, которые, кажется, противоречат этому, по крайней мере, по-видимому. Пользователь davmac
предоставляет пример кода:
// Compile with "-O3 -std=c99" eg:
// clang -O3 -std=c99 test.c
// gcc -O3 -std=c99 test.c
// On clang v3.5.1, output is "123"
// On gcc 4.8.4, output is "1073741824"
//
// Different outputs, so either:
// * program invokes undefined behaviour; both compilers are correct OR
// * compiler vendors interpret standard differently OR
// * one compiler or the other has a bug
#include <stdio.h>
union u
{
int i;
float f;
};
int someFunc(union u * up, float *fp)
{
up->i = 123;
*fp = 2.0; // does this set the union member?
return up->i; // then this should not return 123!
}
int main(int argc, char **argv)
{
union u uobj;
printf("%d\n", someFunc(&uobj, &uobj.f));
return 0;
}
который выводит разные значения для разных компиляторов. Однако я считаю, что это происходит потому, что он фактически нарушает правила здесь, потому что он записывает член f
, затем читает член i
и, как показано в Приложении J, это неуказано.
В 6.5.2.3
есть сноска 82, в которой говорится:
Если элемент, используемый для доступа к содержимому объекта объединения, не совпадает с элементом, который последний раз использовался для хранения значения в объекте, соответствующая часть представления объекта значения интерпретируется как представление объекта в новый тип.
Однако, поскольку это, похоже, противоречит комментарию к приложению J, а в сноске к разделу, посвященному выражению формы x.y
, оно может не применяться к обращениям через указатель.
Одной из основных причин, почему псевдонимы должны быть строгими, является предоставление компилятору большего объема для оптимизации. С этой целью стандарт диктует, что обращение с памятью другого типа к написанному не указано.
В качестве примера рассмотрим предоставленную функцию:
int someFunc(union u * up, float *fp)
{
up->i = 123;
*fp = 2.0; // does this set the union member?
return up->i; // then this should not return 123!
}
Реализация свободна предположить, что, поскольку вы не должны использовать память с псевдонимами, up->i
и *fp
- это два разных объекта. Поэтому можно предположить, что вы не изменяете значение up->i
после того, как вы установите его на 123
, чтобы он мог просто вернуть 123
, не просматривая фактическое содержимое переменной снова.
Если вместо этого вы изменили оператор установки указателя на:
up->f = 2.0;
то это сделает сноску 82 применимой, а возвращаемое значение будет повторной интерпретацией float как целого.
Причина, по которой я не думаю, что проблема в вопросе заключается в том, что ваша запись, а затем чтение того же типа, поэтому правила псевдонимов не вступают в игру.
Интересно отметить, что неуказанное поведение вызвано не самой функцией, а вызовом:
union u up;
int x = someFunc (&u, &(up.f)); // <- aliasing here
Если бы вы назовете это так:
union u up;
float down;
int x = someFunc (&u, &down); // <- no aliasing
это не проблема.