Является ли это ошибкой повторного использования хранилища (алиасинга) в gcc 6.2?

Из того, что я могу сказать, следующий код должен иметь 100% определенное поведение при любом разумном прочтении Стандарта для платформ, которые определяют int64_t и где long long имеет одинаковый размер и представление, независимо от того, распознается ли long long как совместимый с псевдонимом. .

#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 (GCC 6.2 x86-64 с использованием -std=c99 -x c -O2)

Правильный код для test3 должен выделить некоторое хранилище, тогда:

  1. Записывает long long со значением 1.
  2. Устанавливает эффективный тип хранилища на int64_t, записывая int64_t со значением 2.
  3. Считывает хранилище как int64_t (действующий тип), что должно дать 2
  4. Устанавливает эффективный тип хранилища на long long, сохраняя long long с вышеупомянутым значением (которое должно быть 2).
  5. Прочитайте хранилище как тип long long, что должно дать 2.

Однако gcc x86-64 6.2 на сайте godbolt не дает 2; вместо этого он дает 1. Я не нашел другой комбинации типов, для которых gcc ведет себя так. Я думаю, что происходит то, что gcc решает, что хранилище на *t1p2 может быть опущено, потому что оно не имеет никакого эффекта, но не может распознать, что хранилище действительно повлияло на изменение эффективного типа хранилища с int64_t на long long.

Хотя я считаю сомнительным решение не признавать int64_t и long long совместимыми с псевдонимами, я не вижу в стандарте ничего, что могло бы оправдать отказ gcc распознать повторное использование хранилища для хранения значения 2 после того, как оно ранее содержало значение 1. Ничто никогда не читается как любой другой тип, кроме того, с которым он был написан, но я думаю, что gcc решает, что два указателя, переданные «бла», не могут быть псевдонимом.

Я что-то пропустил или это явный баг?


person supercat    schedule 30.08.2016    source источник


Ответы (1)


Как вы объясняете, код не нарушает строгое правило псевдонимов. На самом деле, T1 и T2 могут быть любыми типами (такими, что назначения не являются несоответствиями типов), нет необходимости, чтобы они имели одинаковый размер или что-то в этом роде.

Я согласен, что вывод 1 является ошибкой компилятора. На сайте godbolt все версии gcc, кажется, имеют ошибку, тогда как clang выдает правильный вывод.

Однако при локальной установке gcc 4.9.2 (x86_64-win32-seh-rev1) я получаю правильный вывод 2. Таким образом, проблема существует только в некоторых сборках gcc.

person M.M    schedule 30.08.2016
comment
Ключевым условием появления ошибки, насколько я могу судить, является то, что компилятор должен (неправильно) рассматривать хранилище в *t1p2 как неоперативное. Я не знаю, используют ли все установки gcc одно и то же определение для int64_t, поскольку на платформе, где этот тип указан как long long, приведенный здесь код будет работать нормально, как показано здесь, но произойдет сбой, если T1 изменить на long. Интересно, предполагали ли когда-либо авторы Стандарта, что любой качественный компилятор не будет распознавать псевдонимы между типами с одинаковым размером и представлением, поскольку неспособность распознать его может сделать его очень сложным... - person supercat; 31.08.2016
comment
... для обмена данными определенным образом между API, которые используют разные именованные типы для одного и того же формата данных? В любом случае, я думаю, что происходит то, что gcc решает, что запись как T2 не может повлиять на *t1p из-за ее типа, а запись как T1 не может повлиять на нее, потому что она просто записывает то, что уже было записано (хотя предыдущий запись проигнорировали). - person supercat; 31.08.2016
comment
@supercat кажется правдоподобным, я думаю, это можно подтвердить, погрузившись в код оптимизатора. - person M.M; 31.08.2016