2016-08-30 3 views
1

Из того, что я могу сказать, следующий код должен иметь 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 следует выделить некоторое хранение, то:

  1. Записывает long long со значением 1.
  2. Устанавливает эффективный тип хранилища до int64_t путем записи int64_t со значением 2.
  3. Считывает хранение как int64_t (его эффективный тип), который должен давать 2
  4. Устанавливает эффективный тип хранения для long long путем сохранения в long long с вышеупомянутым значением (который должен быть 2).
  5. 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 решает, что два указателя, переданные в «бла», не могут быть псевдонимом.

Я пропустил что-нибудь или это ошибка?

ответ

0

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

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

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

+0

Ключевым условием появления ошибки, насколько я могу судить, является то, что компилятор должен (неправильно) рассматривать хранилище на '* t1p2' как no-op. Я не знаю, используют ли все установки gcc одно и то же определение для 'int64_t', так как на платформе, где этот тип указан как' long long', код здесь будет работать нормально, как показано здесь, но не сработает, если T1 изменено на 'long'.Интересно, были ли авторы Стандарта когда-либо предполагали, что любой качественный компилятор не будет распознавать псевдонимы между типами с одинаковым размером и представлением, поскольку неспособность распознать его может сделать его очень трудным ... – supercat

+0

... для обмена данными в определенных мода между API, которые используют разные именованные типы для одного и того же формата данных? В любом случае, я думаю, что происходит то, что gcc решает, что запись как 'T2' не может повлиять на' * t1p' из-за его типа, а запись как 'T1' не может повлиять на нее, потому что она просто пишет, что было «уже написано» (хотя предыдущая запись проигнорирована). – supercat

+0

@supercat кажется правдоподобным, я думаю, это может быть подтверждено погружением в код оптимизатора –