可以通过编译器删除`memset`函数调用吗?

我在这里读到,如果编译器知道传递的内存缓冲区从未再次使用过 ,则可以自由删除对memset调用。 怎么可能? 在我看来(从核心语言的角度来看) memset只是一个常规函数,编译器没有权利假设它内部发生的任何事情都没有副作用。

在链接文章中,他们展示了Visual C ++ 10如何删除memset 。 我知道Microsoft编译器在标准合规性方面并不领先,所以我问 – 它是按照标准,还是仅仅是msvc-ism? 如果符合标准,请详细说明;)

编辑: @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在Windows皮肤中真的是GCC,我想GCC也是这样做的 – 我会在重启到linux时检查它。

仍然无法找到这样的编译器?

EDIT2:

我刚刚发现了GCC的__attribute__ ((pure)) 。 因此,不是编译器知道关于memset的特殊内容并且忽略了它,它只是允许它在它的头部 – 使用它的程序员也应该看到它;)我的mingw在memset声明中没有这个属性,因此它不会从大会无论如何 – 正如我所料。 我将不得不对此进行调查。

“编译器没有权利假设其中发生的任何事情都没有副作用。”

那是对的。 但是如果编译器实际上知道它内部实际发生了什么并且可以确定它确实没有副作用,那么就不需要假设。

这几乎是所有编译器优化的工作方式。 代码说“X”。 编译器确定如果“Y”为真,则它可以用代码“Z”替换代码“X”,并且将没有可检测的差异。 它确定“Y”为真,然后用“Z”代替“X”。

例如:

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

编译器可以将其优化为foo(); bar(); foo(); bar(); 。 编译器可以看到foo不能合法地修改j的值。 如果foo()以某种方式神奇地指出j在堆栈中的位置并修改它,那么优化将改变代码的行为,但这是程序员使用“魔术”的错误。

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

现在它不能,因为foo可以在没有任何魔法的情况下合法地修改j的值。 (假设编译器无法查看foo内部,在某些情况下它可以。)

如果你做“魔术”,那么编译器可以进行破坏代码的优化。 坚持规则,不要使用魔法。

在您链接到的示例中,代码依赖于编译器在特定值中放置一个永远不会被访问并立即停止存在的变量。 编译器不需要执行任何对代码操作没有影响的操作。

可能影响代码的唯一方法是,如果它查看堆栈的未分配部分,或者依赖于堆栈上具有先前具有的值的新分配。 要求编译器这样做会使大量的优化变得不可能,包括用寄存器替换局部变量。