Об общем указателе на char и строгом псевдониме

Я не знаю, почему следующий код работает нормально, без ошибок gcc (-fstrict-aliasing -Wstrict-aliasing=1).

#include <stdio.h>

int 
main(void) 
{
    char  n = 42;
    char *p = &n;
    int  *q = (int *)p;

    *q = 10;

    printf("%d|%d\n", *p, *q);

    return 0;
}

Если я буду следовать строгому правилу псевдонима:

n1570, § 6.5 Выражения

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

- тип, совместимый с эффективным типом объекта,

- квалифицированная версия типа, совместимого с действующим типом объекта,

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

- тип, который является типом со знаком или без знака, соответствующим квалифицированной версии действующего типа объекта,

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

- символьный тип.

Но *q не имеет типа, совместимого с *p, ни квалифицированной версии, ни соответствующего знакового, ни беззнакового типа, ни символьного типа.

Итак, почему это разрешено?


person md5    schedule 09.09.2012    source источник
comment
- тип, совместимый с эффективным типом объекта - оба являются целочисленными типами, и вы даже не теряете точности, потому что конвертируете из char в int.   -  person    schedule 09.09.2012
comment
Это неопределенное поведение, *q указывает на выделенное пространство для одного символа, вы записываете там sizeof(int) данные.   -  person Mat    schedule 09.09.2012
comment
Типом символа является char, unsigned char или signed char: этот код не нарушает строгих правил псевдонима из-за последнего предложения. Тем не менее, у него есть другие проблемы, которые приводят к неопределенному поведению.   -  person cmaster - reinstate monica    schedule 23.12.2014
comment
cmaster: насколько я понимаю, выражение lvalue имеет тип int. Я не вижу ничего, что позволяло бы получить доступ к объектам символьного типа через int lvalue.   -  person md5    schedule 24.12.2014


Ответы (1)


Это не разрешено. В опубликованной вами части Стандарта показано, какие типы вам разрешено использовать псевдонимы для объектов.

Тот факт, что код скомпилирован, не означает, что он правильный. У вас есть три проблемы с кодом, из-за которых программа демонстрирует неопределенное поведение.

Во-первых, вы назначили указатель char на указатель int. Стандарт не требует их выравнивания и представления, поэтому результирующий указатель недействителен.

int  *q = (int *)p;

Затем вы интерпретировали объект char n как целое число, нарушая строгий псевдоним (обратите внимание на цитату из Стандарта в вашем вопросе).

*q;

Наконец, вы записали int в память объекта char (char n), что вызывает переполнение, потому что размер int всегда больше, чем размер char.

*q = 10;
person 2501    schedule 23.12.2014
comment
Можем ли мы использовать reinterpret_cast, чтобы правильно выровнять его? - person Unheilig; 23.12.2014
comment
@Unheilig Этот вопрос помечен тегом C, который не имеет reinterpret_cast. - person 2501; 23.12.2014
comment
C (++) или правильно. Дефицит кофеина с моей стороны. - person Unheilig; 23.12.2014
comment
Это кажется правильным, за исключением второй части о строгом псевдониме: в последнем пункте стандартной цитаты есть исключение именно для доступа к любым данным через char*, и наоборот. Это исключение необходимо для того, чтобы такие функции, как memcpy(), не нарушали строгие правила псевдонима. - person cmaster - reinstate monica; 23.12.2014
comment
@cmaster OP обращается к char с помощью указателя int. Это не определено. Но наоборот, доступ к int с помощью указателя char есть. - person 2501; 23.12.2014
comment
@ 2501 Я бы понял, что доступ к его сохраненному значению будет означать доступ для чтения и для записи. В формулировке строгих правил псевдонима нет разницы в как вы получаете один int* и один char*, здесь говорится только об их использовании для доступа к объекту памяти. Проблема с кодом в том, что объект памяти слишком мал, чтобы разрешить доступ через int*, и что int* не обязательно правильно выровнен; не то чтобы это было запрещено строгими правилами псевдонима. - person cmaster - reinstate monica; 23.12.2014
comment
@cmaster Покажите мне, где определяется, что вы можете получить доступ к char как через целочисленный указатель. - person 2501; 23.12.2014
comment
@ 2501 Я никогда не говорил, что вы можете получить доступ к char через указатель int. Это не работает, потому что sizeof(char) < sizeof(int). Тем не менее, вы можете сгенерировать указатель char через char* charPtr = malloc(sizeof(int));, заполнить массив данными char, преобразовать его в int* и, наконец, прочитать данные как int. Это не нарушает никаких правил, которые я знаю (кроме правил хорошего стиля). - person cmaster - reinstate monica; 23.12.2014
comment
Спасибо за Ваш ответ. Я задал этот вопрос давным-давно, но мне кажется, я пытался понять, было ли это неправильным пониманием стандарта или довольно простым примером, в котором использовалась опция предупреждения -Wstrict-aliasing (которая должна улавливать общие ловушки). - person md5; 24.12.2014
comment
@ md5 Я наткнулся на это и увидел неправильный ответ. - person 2501; 24.12.2014
comment
@cmaster: Исключение символьного типа никогда не было необходимо для работы memcpy. Что необходимо, так это признать, что (1) важно не то, имеет ли lvalue правильный тип, а скорее, получено ли оно только что из одного из правильных типов, и что (2) lvalue, которое не является вообще используется в конкретном выполнении функции, не может быть псевдонимом чего-либо еще в том же выполнении этой функции. Их объединение позволит не только memcpy, но и подавляющее большинство другого кода, который в противном случае потребовал бы -fno-strict-aliasing. - person supercat; 15.06.2018
comment
Однако, несмотря на эти преимущества, его было бы легче обрабатывать, чем существующие правила, при этом допуская больше оптимизаций, чем они делают [некоторый код может быть нарушен дополнительными оптимизациями и, таким образом, потребовать -fno-strict-aliasing, хотя в настоящее время это не так, но это было бы реже, чем код, который в настоящее время требует -fno-strict-aliasing, но будет работать по производным правилам lvalue.] - person supercat; 15.06.2018