Нарушает ли этот код правило строгого псевдонима?

Вопросы:

  • Означает ли этот код ниже правила строгого сглаживания? То есть, интеллектуальному компилятору будет разрешено печатать 00000 (или какой-либо другой неприятный эффект), поскольку к нему сначала обращается буфер, к которому обращаются как к другому типу, через int*?

  • Если это не так, будет перемещено только определение и инициализация ptr2 перед фигурными скобками (так что ptr2 будет уже определено, когда ptr1 попадет в область видимости) сломать его?

  • Если это не так, удаляем скобки (так что ptr1 и ptr2 находятся в одной области) сломать его?

  • Если да, как может быть исправлен код?

Бонусный вопрос: если код в порядке, а 2. или 3. не сломайте его, как изменить его, чтобы он нарушил строгие правила псевдонимов (например, конвертируйте скобку в цикл для использования int16_t)?


int i;
void *buf = calloc(5, sizeof(int)); // buf initialized to 0

{
    char *ptr1 = buf;    
    for(i = 0; i < 5*sizeof(int); ++i)
        ptr1[i] = i;
}

int *ptr2 = buf;
for(i = 0; i < 5; ++i)
    printf("%d", ptr2[i]);

Ищем подтверждение, поэтому короткий (ish), экспертный ответ об этом конкретном коде, в идеале с минимальными стандартными кавычками, является тем, чем я занимаюсь. Я не буду долго объяснять строгие правила псевдонимов, а только те части, которые относятся к этому коду. И было бы здорово, если бы ответ явно перечислил пронумерованные вопросы выше.

Также предположим, что ЦПУ общего назначения без целых значений ловушки, а также сказать, что int - 32 бита и два дополнения.

Ответы

Ответ 1

Нет, это не так, но это происходит только потому, что память была выделена и записана в использование типа символа.

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

Затем код обращается и изменяет объект, используя тип char. Поскольку тип 2char и объект с эффективным типом не скопирован 5 копирование не устанавливает эффективный тип char для этого и последующего доступа, но устанавливает эффективный тип char, только на время доступа 3. После доступа объект больше не имеет эффективного типа.

Затем тип int используется для доступа и только чтения этого объекта. Поскольку объект не имеет эффективного типа, он становится 3int, на время чтения. После доступа объект больше не имеет эффективного типа. Поскольку int был явно совместим с эффективным типом int, поведение определяется.

(Предполагая, что считанные значения не являются ловушками для int.)


Если вы получили доступ и изменили объект, используя несимвольный тип, который также несовместим с int, поведение будет undefined.

Скажем, ваш пример был (предполагая sizeof(float)==sizeof(int)):

int i;
void *buf = calloc(5, sizeof(float)); // buf initialized to 0

{
    float *ptr1 = buf;    
    for(i = 0; i < 5*sizeof(float); ++i)
        ptr1[i] = (float)i;
}

int *ptr2 = buf;
for(i = 0; i < 5; ++i)
    printf("%d", ptr2[i]);

Эффективный тип объекта, когда float записывается в, становится типа float, на время записи и всех последующих обращений к объекту, который его не модифицирует 2. Когда эти объекты затем обращаются к int, эффективный тип остается float, так как значения только считаются не модифицированными. Предыдущая запись с использованием float постоянно устанавливает эффективный тип float, пока следующая запись в этот объект (что в этом случае не произошло). Типы int и float не совместимы 4 поэтому поведение undefined.


(Весь текст приведен ниже: ISO: IEC 9899: 201x)

1 (6.5 Выражения 6)
Эффективным типом объекта для доступа к его сохраненному значению является объявленный тип объекта, если таковой имеется. 87) Выделенные объекты не имеют объявленного типа.

2 (6.5 Выражения 6)
Если значение хранится в объекте, не имеющем объявленного типа, через lvalue, имеющем тип, который не является типом символа, тогда тип lvalue становится эффективным типом объекта для этого доступа и для последующих обращений, которые не изменяют сохраненное значение.

3 (6.5 Выражения 6)
Для всех других доступов к объекту, не имеющему объявленного типа, эффективным типом объекта является просто тип lvalue, используемый для доступа.

4 (6.5 Выражения 8)
Объект должен иметь сохраненное значение, доступ к которому имеет только выражение lvalue, которое имеет один из следующие типы: 88) - тип, совместимый с эффективным типом объекта, - квалифицированная версия типа, совместимая с эффективным типом объекта, - тип, который является подписанным или неподписанным типом, соответствующим эффективному типу объект, - тип, который является подписанным или неподписанным типом, соответствующим квалифицированной версии эффективный тип объекта, - совокупный или объединенный тип, который включает один из вышеупомянутых типов среди его членов (включая рекурсивно, члена подгруппы или объединенного союза) или - тип символа.

5 (6.5 Выражения 6)
Если значение копируется в объект, не имеющий объявленного типа с использованием memcpy или memmove, или копируется как массив типа символа, тогда эффективный тип измененного объекта для этого доступа и для последующих обращений, которые не изменяют значение, является эффективный тип объекта, из которого копируется значение, если оно есть.

Ответ 2

Нет. Это не нарушает строгий псевдоним.

Из Стандарт C, 6.2.5 Типы, пункт 28:

Указатель на void должен иметь такое же представление и требования к выравниванию в качестве указателя на тип символа. 48

Обратите внимание на 48. Это относится к сноске 48:

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

Таким образом, вы можете получить доступ к памяти calloc() 'd с помощью указателя char * (при условии, что ваш ptr должен быть ptr1) без проблем.

Хотя это действительно дополнительно, поскольку 7.22.3 Функции управления памятью, в пункте 1 указано:

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

Таким образом, вы можете безопасно обращаться к памяти calloc() 'd с помощью указателя int, а также указателя char. И указатель double для загрузки (предполагая, что вы остаетесь в пределах выделенной памяти).