Может ли вызов функции `memset` быть удален компилятором?

Я прочитал здесь, что компилятор может удалить вызов memset, если он знает, что переданный буфер памяти больше никогда не используется. Как это возможно? Мне кажется, что (с точки зрения основного языка) memset - это просто регулярная функция, и компилятор не имеет права предполагать, что все, что происходит внутри него, не будет иметь побочных эффектов.

В связанной статье они показывают, как Visual С++ 10 удалил memset. Я знаю, что компиляторы Microsoft не ведут стандартное соответствие, поэтому я спрашиваю - это в соответствии со стандартом, или это просто msvc-ism? Если это соответствует стандарту, пожалуйста, уточните;)

EDIT: @Cubbi

Следующий код:

void testIt(){
    char foo[1234];
    for (int i=0; i<1233; i++){
        foo[i] = rand()%('Z'-'A'+1)+'A';
    }
    foo[1233]=0;
    printf(foo);
    memset(foo, 0, 1234);
}

Скомпилирован под mingw с линиями:

g++ -c -O2 -frtti -fexceptions -mthreads -Wall -DUNICODE -o main.o main.cpp
g++ -Wl,-s -Wl,-subsystem,console -mthreads -o main.exe main.o
objdump -d -M intel -S main.exe > dump.asm

Вывести вывод:

 4013b0:    55                      push   ebp
 4013b1:    89 e5                   mov    ebp,esp
 4013b3:    57                      push   edi
 4013b4:    56                      push   esi
 4013b5:    53                      push   ebx
 4013b6:    81 ec fc 04 00 00       sub    esp,0x4fc
 4013bc:    31 db                   xor    ebx,ebx
 4013be:    8d b5 16 fb ff ff       lea    esi,[ebp-0x4ea]
 4013c4:    bf 1a 00 00 00          mov    edi,0x1a
 4013c9:    8d 76 00                lea    esi,[esi+0x0]
 4013cc:    e8 6f 02 00 00          call   0x401640
 4013d1:    99                      cdq    
 4013d2:    f7 ff                   idiv   edi
 4013d4:    83 c2 41                add    edx,0x41
 4013d7:    88 14 1e                mov    BYTE PTR [esi+ebx*1],dl
 4013da:    43                      inc    ebx
 4013db:    81 fb d1 04 00 00       cmp    ebx,0x4d1
 4013e1:    75 e9                   jne    0x4013cc
 4013e3:    c6 45 e7 00             mov    BYTE PTR [ebp-0x19],0x0
 4013e7:    89 34 24                mov    DWORD PTR [esp],esi
 4013ea:    e8 59 02 00 00          call   0x401648
 4013ef:    81 c4 fc 04 00 00       add    esp,0x4fc
 4013f5:    5b                      pop    ebx
 4013f6:    5e                      pop    esi
 4013f7:    5f                      pop    edi
 4013f8:    c9                      leave  
 4013f9:    c3                      ret   

В строке 4013ea есть вызов memset, поэтому mingw не удалил его. Поскольку mingw на самом деле является GCC в окнах, я полагаю, что GCC делает то же самое - я проверю его при перезагрузке в linux.

По-прежнему возникают проблемы с поиском такого компилятора?

EDIT2:

Я только что узнал о GCC __attribute__ ((pure)). Так что не то, что компилятор знает что-то особенное о memset и elides он, это просто, что он разрешил в нем заголовок - там, где его следует использовать программист;) My mingw не имеет этого атрибута в объявлении memset, поэтому он не выходя из собрания независимо от того, что я ожидал. Мне придется исследовать это.

Ответы

Ответ 1

"компилятор не имеет права предполагать, что все, что происходит внутри него, не будет иметь побочных эффектов."

Это правильно. Но если компилятор действительно знает, что на самом деле происходит внутри него, и может определить, что у него действительно нет побочных эффектов, тогда предположение не требуется.

Вот как работают почти все оптимизаторы компилятора. Код говорит "X" . Компилятор определяет, что если "Y" истинно, тогда он может заменить код "X" кодом "Z", и не будет обнаруженной разницы. Он определяет, что "Y" истинно, а затем он заменяет "X" на "Z".

Например:

void func()
{
  int j = 2;
  foo();
  if (j == 2) bar();
   else baz();
}

Компилятор может оптимизировать это значение до foo(); bar();. Компилятор может видеть, что foo не может юридически изменить значение j. Если foo() каким-то образом волшебным образом определяет, где j находится в стеке и изменяет его, тогда оптимизация изменит поведение кода, но ошибка программиста в использовании "магии".

void func()
{
  int j = 2;
  foo(&j);
  if (j == 2) bar();
   else baz();
}

Теперь это невозможно, потому что foo может законно изменять значение j без магии. (Предполагая, что компилятор не может заглянуть внутрь foo, что в некоторых случаях это возможно.)

Если вы делаете "волшебство", тогда компилятор может сделать оптимизации, которые нарушают ваш код. Придерживайтесь правил и не используйте магию.

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

Единственным способом, который может повлиять на код, является то, что он просматривал нераспределенные части стека или полагался на новые распределения в стеке, имеющие значения, которые они ранее имели. Требование компилятора сделать это сделало бы огромное количество оптимизаций невозможным, включая замену локальных переменных на регистры.