gcc, строгие псевдонимы и ужасные истории - PullRequest
47 голосов
/ 02 июня 2010

В gcc-strict-aliasing-and-casting-through-a-union Я спросил, не сталкивался ли кто-нибудь с проблемами с наложением союзов через указатели. Пока что, похоже, ответ Нет .

Этот вопрос является более широким: у вас есть какие-либо страшные истории о gcc и строгом псевдониме?

Справочная информация: Цитата из Ответ AndreyT в c99-strict-aliasing-rules-in-c-gcc :

"Строгие правила псевдонимов коренятся в частях стандарта, которые присутствовали в C и C ++ с начала [стандартизированных] времен. Пункт, который запрещает доступ к объекту одного типа через lvalue другого типа, присутствует в C89 / 90 (6.3), а также в C ++ 98 (3.10 / 15). ... Просто не все компиляторы хотели (или осмеливались) применять его или полагаться на него. "

Ну, gcc теперь решается сделать это с помощью переключателя -fstrict-aliasing. И это вызвало некоторые проблемы. См., Например, отличную статью http://davmac.wordpress.com/2009/10/ об ошибке Mysql и столь же превосходное обсуждение в http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html.

Некоторые другие менее релевантные ссылки:

Итак, повторюсь, у вас есть собственная история ужасов? Проблемы , а не , обозначенные -Wstrict-aliasing, конечно, будут предпочтительнее. И другие компиляторы Си также приветствуются.

Добавлено 2 июня : первая ссылка в ответ Майкла Барра , который действительно квалифицируется как ужасная история, возможно, немного устаревшая (с 2003 года). Я сделал быстрый тест, но проблема, по-видимому, ушла.

Источник:

#include <string.h>
struct iw_event {               /* dummy! */
    int len;
};
char *iwe_stream_add_event(
    char *stream,               /* Stream of events */
    char *ends,                 /* End of stream */
    struct iw_event *iwe,       /* Payload */
    int event_len)              /* Real size of payload */
{
    /* Check if it's possible */
    if ((stream + event_len) < ends) {
            iwe->len = event_len;
            memcpy(stream, (char *) iwe, event_len);
            stream += event_len;
    }
    return stream;
}

Конкретная жалоба:

Некоторые пользователи жаловались на то, что, когда код [выше] компилируется без -fno-strict-aliasing, порядок записи и memcpy инвертируется (что означает, что фиктивное len записывается в поток).

Скомпилированный код, используя gcc 4.3.4 на CYGWIN с -O3 (исправьте меня, если я ошибаюсь - мой ассемблер немного заржавел!):

_iwe_stream_add_event:
        pushl       %ebp
        movl        %esp, %ebp
        pushl       %ebx
        subl        $20, %esp
        movl        8(%ebp), %eax       # stream    --> %eax
        movl        20(%ebp), %edx      # event_len --> %edx
        leal        (%eax,%edx), %ebx   # sum       --> %ebx
        cmpl        12(%ebp), %ebx      # compare sum with ends
        jae L2
        movl        16(%ebp), %ecx      # iwe       --> %ecx
        movl        %edx, (%ecx)        # event_len --> iwe->len (!!)
        movl        %edx, 8(%esp)       # event_len --> stack
        movl        %ecx, 4(%esp)       # iwe       --> stack
        movl        %eax, (%esp)        # stream    --> stack
        call        _memcpy
        movl        %ebx, %eax          # sum       --> retval
L2:
        addl        $20, %esp
        popl        %ebx
        leave
        ret

И для второй ссылки в ответе Майкла,

*(unsigned short *)&a = 4;

gcc обычно (всегда?) Выдает предупреждение. Но я считаю верным решением для этого (для gcc ) является использование:

#define CAST(type, x) (((union {typeof(x) src; type dst;}*)&(x))->dst)
// ...
CAST(unsigned short, a) = 4;

Я спросил SO, все ли в порядке в gcc-строго-псевдонимах-и-каст-через-объединение , но пока никто не согласен.

Ответы [ 6 ]

29 голосов
/ 02 июня 2010

У меня нет ужасов, но вот несколько цитат из Линуса Торвальдса (извините, если они уже есть в одной из ссылок в вопросе):

http://lkml.org/lkml/2003/2/26/158:

Дата Ср, 26 февраля 2003 09:22:15 -0800 Тема Re: Неверная компиляция без -fno-strict-aliasing От Жана Туррильза <>

В среду, 26 февраля 2003 г., 16:38:10 +0100, Хорст фон Бранд написал:

Жан Туррильз <> сказал:

Для меня это похоже на ошибку компилятора ... Некоторые пользователи жаловались, что когда следующий код компилируется без -fno-строгого псевдонима, порядка записи и memcpy инвертирован (что означает, что фиктивный len записывается в поток). Код (из linux / include / net / iw_handler.h):

static inline char *
iwe_stream_add_event(char *   stream,     /* Stream of events */
                     char *   ends,       /* End of stream */
                    struct iw_event *iwe, /* Payload */
                     int      event_len)  /* Real size of payload */
{
  /* Check if it's possible */
  if((stream + event_len) < ends) {
      iwe->len = event_len;
      memcpy(stream, (char *) iwe, event_len);
      stream += event_len;
  }
  return stream;
}

ИМХО, компилятор должен иметь достаточно контекста, чтобы знать, что изменение порядка опасно Любое предложение сделать этот простой код более пуленепробиваемые приветствуется.

Компилятор может принять char * stream и struct iw_event * iwe point разделить области памяти из-за строгого совмещения имен.

Что является правдой, а не проблема, на которую я жалуюсь.

(Заметим задним числом: этот код в порядке, но реализация Linux memcpy была макросом, приведенным к long * для копирования большими кусками. С правильно определенным memcpy, gcc -fstrict-aliasing запрещается нарушать этот код, но это означает, что вам нужен встроенный asm для определения ядра memcpy, если ваш компилятор не знает, как превратить цикл байтового копирования в эффективный asm, что имело место для gcc до gcc7)

И комментарий Линуса Торвальда по этому поводу:

Жан Турриль писал: >

Для меня это похоже на ошибку компилятора ...

Как вы думаете, почему ядро ​​использует "-fno-strict-aliasing"?

gcc люди больше заинтересованы в том, чтобы выяснить, что может быть разрешено спецификациями c99, чем заставлять вещи на самом деле работать . в частности, псевдоним кода даже не стоит включать, просто можно здраво сказать gcc, когда некоторые вещи могут иметь псевдоним.

Некоторые пользователи жаловались, что когда следующий код компилируется без -fno-строгого псевдонима, порядка записи и memcpy инвертирован (что означает, что фиктивный len записывается в поток).

«Проблема» в том, что мы встроили memcpy (), после чего gcc не будет заботиться о том, что это может псевдоним, так что они будут просто изменить порядок все и утверждают, что это по своей вине. Хотя нет вменяемого способ для нас, чтобы даже сказать GCC об этом.

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

Я не собираюсь бороться с этим.

Linus

http://www.mail-archive.com/linux-btrfs@vger.kernel.org/msg01647.html:

Псевдоним на основе типа глуп . Это так невероятно глупо, что это даже не смешно. Оно сломано. И gcc взял нарушенное понятие и сделал его еще более значительным, сделав его «по буквам закона», что не имеет смысла.

...

Я знаю для факта , что gcc будет переупорядочивать обращения к записи, которые явно (статически) совпадают с одним и тем же адресом. Gcc вдруг подумает, что

unsigned long a;

a = 5;
*(unsigned short *)&a = 4;

можно было бы переупорядочить, чтобы сначала установить его на 4 (потому что явно они не псевдонимы - читая стандарт), а затем, потому что теперь назначение '= 5' было позже, назначение 4 могло быть полностью исключен! И если кто-то жалуется, что компилятор ненормальный, люди, отвечающие за компиляцию, скажут: «Ня, ня, специалисты по стандартам сказали, что мы можем это сделать», безо всякого самоанализа, чтобы спросить, имел ли он смысл.

7 голосов
/ 22 сентября 2011

SWIG генерирует код, который зависит от отключения строгого алиасинга, что может вызвать всевозможные проблемы .

SWIGEXPORT jlong JNICALL Java_com_mylibJNI_make_1mystruct_1_1SWIG_12(
       JNIEnv *jenv, jclass jcls, jint jarg1, jint jarg2) {
  jlong jresult = 0 ;
  int arg1 ;
  int arg2 ;
  my_struct_t *result = 0 ;

  (void)jenv;
  (void)jcls;
  arg1 = (int)jarg1; 
  arg2 = (int)jarg2; 
  result = (my_struct_t *)make_my_struct(arg1,arg2);
  *(my_struct_t **)&jresult = result;              /* <<<< horror*/
  return jresult;
}
5 голосов
/ 04 июня 2011

массивы gcc, aliasing и 2-D переменной длины: Следующий пример кода копирует матрицу 2x2:

#include <stdio.h>

static void copy(int n, int a[][n], int b[][n]) {
   int i, j;
   for (i = 0; i < 2; i++)    // 'n' not used in this example
      for (j = 0; j < 2; j++) // 'n' hard-coded to 2 for simplicity
         b[i][j] = a[i][j];
}

int main(int argc, char *argv[]) {
   int a[2][2] = {{1, 2},{3, 4}};
   int b[2][2];
   copy(2, a, b);    
   printf("%d %d %d %d\n", b[0][0], b[0][1], b[1][0], b[1][1]);
   return 0;
}

С gcc 4.1.2 на CentOS я получаю:

$ gcc -O1 test.c && a.out
1 2 3 4
$ gcc -O2 test.c && a.out
10235717 -1075970308 -1075970456 11452404 (random)

Я не знаю, является ли это общеизвестным, и я не знаю, это ошибка или особенность. Я не могу продублировать проблему с gcc 4.3.4 на Cygwin , поэтому она может быть исправлена. Некоторые обходные пути:

  • Используйте __attribute__((noinline)) для копирования ().
  • Используйте переключатель gcc -fno-strict-aliasing.
  • Измените третий параметр copy () с b[][n] на b[][2].
  • Не используйте -O2 или -O3.

Дополнительные примечания:

  • Это ответ через год и день на мой собственный вопрос (и я немного удивлен, что есть только два других ответа).
  • Я потерял несколько часов из-за этого на своем реальном коде, фильтре Калмана. Казалось бы, небольшие изменения будут иметь радикальные последствия, возможно, из-за изменения автоматического встраивания gcc (это предположение; я все еще не уверен). Но это, вероятно, не квалифицируется как страшная история .
  • Да, я знаю, что вы бы не написали copy(), как это. (И, кроме того, я был немного удивлен, увидев, что gcc не развернул двойной цикл.)
  • Нет gcc предупреждающих переключателей, включая -Wstrict-aliasing=, здесь все сделано.
  • 1-D массивы переменной длины выглядят нормально.

Обновление : Вышесказанное на самом деле не отвечает на вопрос ОП, так как он (то есть я) спрашивал о случаях, когда строгое совмещение имен «законно» нарушало ваш код, тогда как просто кажется, что это ошибка компилятора садового сорта.

Я сообщил об этом GCC Bugzilla , но они не интересовались старой версией 4.1.2, хотя (я полагаю) это ключ к RHEL5 стоимостью в 1 миллиард долларов. Это не происходит в 4.2.4.

И у меня есть немного более простой пример подобной ошибки, только с одной матрицей. Код:

static void zero(int n, int a[][n]) {
   int i, j;
   for (i = 0; i < n; i++)
   for (j = 0; j < n; j++)
      a[i][j] = 0;
}

int main(void) {
   int a[2][2] = {{1, 2},{3, 4}};
   zero(2, a);    
   printf("%d\n", a[1][1]);
   return 0;
}

дает результаты:

gcc -O1 test.c && a.out
0
gcc -O1 -fstrict-aliasing test.c && a.out
4

Кажется, именно комбинация -fstrict-aliasing с -finline вызывает ошибку.

2 голосов
/ 20 августа 2016

Правило общей начальной последовательности C раньше интерпретировалось как создание можно написать функцию, которая могла бы работать на ведущей части большое разнообразие типов конструкций, если они начинаются с элементов соответствия типы. Под C99 правило было изменено так, чтобы оно применялось только если структура участвующие типы были членами одного и того же объединения, чье объявление complete было видно в момент использования.

Авторы gcc настаивают на том, что данный язык применим, только если доступ осуществляется через тип объединения, несмотря на факты что:

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

  2. Хотя правило СНГ было описано в терминах союзов, его основной Полезность заключалась в том, что это подразумевает о том, как структуры были выложены и доступны. Если S1 и S2 были структурами, которые разделяли СНГ, не было бы способа, чтобы функция, которая приняла указатель на S1 и S2 из внешнего источника может соответствовать правилам СНГ C89 не позволяя тому же поведению быть полезным с указателями на структуры, которые на самом деле не были внутри объекта объединения; с указанием СНГ таким образом, поддержка структур была бы излишней, учитывая, что уже указано для союзов.

2 голосов
/ 17 декабря 2011

вот мой:

http://forum.openscad.org/CGAL-3-6-1-causing-errors-but-CGAL-3-6-0-OK-tt2050.html

это вызвало неправильную прорисовку определенных фигур в программе CAD. Слава Богу, что руководители проекта работают над созданием пакета регрессионных тестов.

ошибка проявлялась только на определенных платформах, с более старыми версиями GCC и более старыми версиями определенных библиотек. и только после включения -O2. -fno-строго-псевдонимы решили это.

2 голосов
/ 09 октября 2010

Следующий код возвращает 10 в gcc 4.4.4. Что-то не так с методом union или gcc 4.4.4?

int main()
{
  int v = 10;

  union vv {
    int v;
    short q;
  } *s = (union vv *)&v;

  s->v = 1;

  return v;
}
...