LINUX.ORG.RU

Снова type aliasing, и баг в gcc

 ,


0

2

Приветствую.
После последней type aliasing темы, решил я добить вопрос до конца и исследовать поведение оптимизатора когда он имеет дело с volatile аргументами. Ведь volatile должен запретить выполнять различные оптимизации над переменной и заставить не копить её в регистре. С такими убеждениями я набросал три функции и компилировал их в различных сочетаниях:

void f111111111(volatile int *i, short *s) {
  for(int cnt = 0;  cnt < 5;  ++ cnt) {
      *i += *s;
      s += 2;
    }
}

// аналогична первой, но i volatile
void f111111111(int *i, short *s) {
  for(int cnt = 0;  cnt < 5;  ++ cnt) {
      *i += *s;
      s += 2;
    }
}

int main() {
  int ar[5] = {1, 2, 3, 4, 5};
  f111111111(&ar[3], (short*)ar);
  cout << ar[3] << endl;
}


У меня gcc 5.4.0 (gcc 6.1 показывает аналогичное поведение). Ожидал следующего выхлопа от cout: если оптимизатор поработал, то 19; если нет, то 25. Вот что получилось:
// main() и void f111111111() в разных модулях; -O2,
// i не volatile; f111111111() не заинлайнина; cout: 19;
// работает правильно
_Z10f111111111PiPs:
// void f111111111(int *i, short *s)
    movl    12(%esp), %esi    // esi = i
    movl    16(%esp), %eax    // eax = s
    movl    (%esi), %edx      // edx = *i
    leal    20(%eax), %ebx    // ebx = &ar[5]
.L2:
    movswl  (%eax), %ecx      // ecx = *eax
    addl    $4, %eax          // eax += 4 байта
    addl    %ecx, %edx        // edx += ecx
    cmpl    %ebx, %eax
    jne	    .L2               // if(eax != ebx) goto .L2
    movl    %edx, (%esi)      // *i = edx  (*i = 19)


// main() и void f111111111() в разных модулях; -O2;
// i volatile; f111111111() не заинлайнина; cout: 25;
// работает правильно
_Z10f111111111PViPs:
// void f111111111(volatile int *i, short *s)
    movl    16(%esp), %eax    // eax = s
    movl    12(%esp), %ecx    // ecx = i
    leal    20(%eax), %esi    // esi = &ar[5]
.L2:
    movswl  (%eax), %ebx      // ebx = *eax
    movl    (%ecx), %edx      // edx = *ecx
    addl    $4, %eax          // eax += 4 байта
    addl    %ebx, %edx        // edx += ebx
    cmpl    %esi, %eax
    movl    %edx, (%ecx)      // *i = edx
    jne     .L2               // if(esi != eax) goto .L2


// main() и void f111111111() в одном модуле; -O2;
// i volatile; f111111111() инлайнится; cout: случайное число;
// ошибка
main:
    leal    4(%esp), %ecx
    andl    $-16, %esp
    pushl   -4(%ecx)
    pushl   %ebp
    movl    %esp, %ebp
    pushl   %ebx
    pushl   %ecx
    leal    -12(%ebp), %ebx // ebx = &ar[5]
    subl    $32, %esp
    movl    $4, -20(%ebp)
    movl    %gs:20, %eax
    movl    %eax, -12(%ebp)
    xorl    %eax, %eax
    leal    -32(%ebp), %eax // eax = &ar[0]
.L7:
    movswl  (%eax), %ecx    // ecx = *eax
    movl    -20(%ebp), %edx // edx = *i
    addl    $4, %eax        // eax += 4 байта
    addl    %ecx, %edx      // edx += ecx
    cmpl    %ebx, %eax
    movl    %edx, -20(%ebp) // *i = edx
    jne     .L7             // if(ebx != eax) goto .L7


Считаю, что в 3 случаи gcc показал наличие у него бага (cout выдаёт случайное число, например 32670). Поковырялся под оталдчиком, ar[5] не проинициализирован (содержит случайные значения), но сам цикл вполне корректен. clang в третьем случаи выдаёт 25 - всё правильно.
Господа, если согласны с тем, что это баг, то может вызовется кто-нибудь сообщить куда следует (товарищам из gcc)? Описывать суть на английском для меня трудновато будет, читать умею, а говорить нет.

Так работа с int как с short это же нарушение strict aliasing и вызывает UB, не? Хоть volatile, хоть как. Когда оно в одном модуле, GCC просто может этим воспользоваться при инлайне.

xaizek ★★★★★ ()
Ответ на: комментарий от xaizek

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

movl    -20(%ebp), %edx // edx = *i
edx == 4. Где не было касата, там инициализация массива производилась строкой
movl    $4, -20(%ebp)

pavlick ★★ ()
Ответ на: комментарий от tailgunner

Это однозначно он, пытался перехитрить его volatile'ом.

pavlick ★★ ()

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

anonymous ()
Ответ на: комментарий от anonymous

Без ассемблера невозможно понять ни volatile, ни strict aliasing, ни memory order (когда я в него в первый раз пытался въехать, то чуть сам не поехал :) ). Все эти абстрактные заумные словечки типа sequenced before и happens-before как-то не особо способствуют пониманию. Лично у меня так.

pavlick ★★ ()
Ответ на: комментарий от pavlick

Без ассемблера невозможно понять ни volatile, ни strict aliasing, ни memory order

Можно и нужно. Более того, strict aliasing вообще не имеет отношения к ассемблеру.

tailgunner ★★★★★ ()
Ответ на: комментарий от tailgunner

Более того, strict aliasing вообще не имеет отношения к ассемблеру

А как вы поймёте то, как компилятор использует эти правила? Я вот вообще не понимал сути запретов на кастованиие до того как спустился на уровень ниже. Можно выучить правила, но не понимать их. Уверен, что многие пишут говнокод с неправильным кастом, хотя что-то слышали о запретах. В msvc вон вообще не действует strict aliasing rule, чтобы ноги не поодстреливали, наверное. Всякие ide задают по-умолчанию -O2, не согласен, умолчательным должно быть -O, если человек чётко понимает оптимизатор, то пусть явно задаёт нужный уровень.

pavlick ★★ ()
Ответ на: комментарий от pavlick

Нафиг не нужен тут ассемблер. Volatile - переменная ведет себя в строгом соответствии с абстрактной семантикой программы, strict aliasing - доступ к объекту разрешен только через перечисленные в стандарте типы.

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

А что слова непонятные - так надо хаскель учить^W^W что-то надьязыковое читать, по семантикам ЯП там.

anonymous ()
Ответ на: комментарий от pavlick

Да не надо, блин, понимать компилятор-оптимизатор! Надо понимать язык. У тебя это вроде третья тема про сабж, и ты все еще думаешь что ты его понял?

anonymous ()
Ответ на: комментарий от pavlick

А как вы поймёте то, как компилятор использует эти правила?

Как я понял проблему, описанную в хедпосте. Мне не понадобилось смотреть ассемблер.

Можно выучить правила, но не понимать их

Еще раз - для понимания этих правил знать ассемблер не обязательно.

tailgunner ★★★★★ ()
Ответ на: комментарий от anonymous

«Понимать язык» - красиво, конечно. Это типа стандарт от корки до корки проштудировать? В псих больнице можно оказаться, хотя не пробовал.

pavlick ★★ ()
Ответ на: комментарий от pavlick

Ничего не пожелаешь, это C++ :D

В реале обычно хватает книжек Страуструпа-Саттера-Майерса и кто-там-еще. Потом, если не надоело, их же и прочих статьи и блоги, а через время и стандарт не страшен будет.

anonymous ()
Ответ на: комментарий от pavlick

Уверен, что многие пишут говнокод с неправильным кастом, хотя что-то слышали о запретах.

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

dzidzitop ★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.