Из того, что я могу сказать, следующий код должен иметь 100% определенное поведение при любом разумном чтении стандарта на платформы, которые определяют int64_t
, и где long long
имеет тот же размер и представление, независимо от того, или нет long long
распознается как псевдоним -Совместим.Является ли это ошибкой повторного использования памяти (aliasing) в gcc 6.2?
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
typedef long long T1;
typedef int64_t T2;
#define T1FMT "%lld"
#define T1VALUE 1
#define T2VALUE 2
T1 blah3(void *p1, void *p2)
{
T1 *t1p, *t1p2;
T2 *t2p;
T1 temp;
t1p = p1;
t2p = p2;
*t1p = T1VALUE; // Write as T1
*t2p = T2VALUE; // Write as T2
temp = *t2p; // Read as T2
t1p2 = (T1*)t2p; // Visible T2 to T1 pointer conversion
*t1p2 = temp; // Write as T1
return *t1p; // Read as T1
}
T1 test3(void)
{
void *p = malloc(sizeof (T1) + sizeof (T2));
T1 result = blah3(p,p);
free(p);
return result;
}
int main(void)
{
T1 result = test3();
printf("The result is " T1FMT "\n", result);
return 0;
}
См код на https://godbolt.org/g/75oLGx (ССЗ 6.2 x86-64 использованием -std=c99 -x c -O2
)
Правильный код test3
следует выделить некоторое хранение, то:
- Записывает
long long
со значением 1. - Устанавливает эффективный тип хранилища до
int64_t
путем записиint64_t
со значением 2. - Считывает хранение как
int64_t
(его эффективный тип), который должен давать 2 - Устанавливает эффективный тип хранения для
long long
путем сохранения вlong long
с вышеупомянутым значением (который должен быть 2). - Read хранение в качестве типа
long long
, которая должна уступить 2.
НКУ x86-64 6.2 на сайте godbolt, однако, не дает 2; вместо этого он дает 1. Я не нашел никакой другой комбинации типов, для которых gcc ведет себя . Я думаю, что происходит то, что gcc решает, что хранилище в * t1p2 можно опустить, потому что оно не имеет никакого эффекта, но не признает, что хранилище действительно повлияло на изменение Эффективного типа хранилища от int64_t
до long long
.
Хотя я считаю сомнительным не решение признать int64_t
и long long
как псевдоним-совместимый, я не вижу ничего в стандарте, который оправдывал бы отказ GCC распознавать повторное использование хранилища для хранения значения 2 после того, как он ранее занимал значение 1. Ничто никогда не читается как любой тип, отличный от того, с которым он был написан, но я думаю, что gcc решает, что два указателя, переданные в «бла», не могут быть псевдонимом.
Я пропустил что-нибудь или это ошибка?
Ключевым условием появления ошибки, насколько я могу судить, является то, что компилятор должен (неправильно) рассматривать хранилище на '* t1p2' как no-op. Я не знаю, используют ли все установки gcc одно и то же определение для 'int64_t', так как на платформе, где этот тип указан как' long long', код здесь будет работать нормально, как показано здесь, но не сработает, если T1 изменено на 'long'.Интересно, были ли авторы Стандарта когда-либо предполагали, что любой качественный компилятор не будет распознавать псевдонимы между типами с одинаковым размером и представлением, поскольку неспособность распознать его может сделать его очень трудным ... – supercat
... для обмена данными в определенных мода между API, которые используют разные именованные типы для одного и того же формата данных? В любом случае, я думаю, что происходит то, что gcc решает, что запись как 'T2' не может повлиять на' * t1p' из-за его типа, а запись как 'T1' не может повлиять на нее, потому что она просто пишет, что было «уже написано» (хотя предыдущая запись проигнорирована). – supercat
@supercat кажется правдоподобным, я думаю, это может быть подтверждено погружением в код оптимизатора –