Может ли вызов функции `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
, что в некоторых случаях это возможно.)
Если вы делаете "волшебство", тогда компилятор может сделать оптимизации, которые нарушают ваш код. Придерживайтесь правил и не используйте магию.
В примере, к которому вы привязались, код полагается на компилятор, который пытается указать конкретное значение в переменной, к которой никогда не обращаются, и немедленно прекращает свое существование. Компилятор не обязан делать что-либо, что не влияет на работу вашего кода.
Единственным способом, который может повлиять на код, является то, что он просматривал нераспределенные части стека или полагался на новые распределения в стеке, имеющие значения, которые они ранее имели. Требование компилятора сделать это сделало бы огромное количество оптимизаций невозможным, включая замену локальных переменных на регистры.