2013-03-21 2 views
15

컴파일러가 memset에 대한 호출을 제거 할 수 있다고 읽었을 때 here을 읽었습니다. 전달 된 메모리 버퍼가 결코 다시 사용되지 않는다는 것을 알고있는 경우입니다. 어떻게 가능합니까? 그것은 (핵심 언어의 관점에서) memset은 단지 일반적인 함수이며, 컴파일러는 내부에서 일어나는 일이 부작용이 없다고 가정 할 수있는 권리가 없다고 생각됩니다.`memset` 함수 호출을 컴파일러에서 제거 할 수 있습니까?

linked article에는 Visual C++ 10이 어떻게 memset을 제거했는지 보여줍니다. Microsoft 컴파일러가 표준 준수를 이끌어 내지 못한다는 것을 알고 있습니다. 표준에 따라 또는 단순히 msvc-ism입니까? 이 표준에 따라라면, 정교한하시기 바랍니다)

편집 :

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에서 컴파일 : 코드에 따라 @Cubbi

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 skin의 GCC이기 때문에, GCC도 똑같은 일을한다고 생각합니다. linux로 재부팅 할 때 검사 할 것입니다.

그런 컴파일러를 찾는 데 여전히 문제가 있습니까?

EDIT2 : 난 그냥 GCC의 __attribute__ ((pure))에 대해 알게

. 그래서 컴파일러가 memset에 대해 특별한 것을 알고 있지 않고 그것을 헤더에서 제외시킬 수 있습니다 - 프로그래머가 사용하는 곳에서도 볼 수 있습니다.) 내 mingw는 memset 선언에이 속성이 없으므로 어셈블리가 무엇이든간에 - 내가 예상했던대로. 나는 이것을 조사해야 할 것이다.

+8

그러나'memset()'은 정규 함수가 아닙니다. 컴파일러는 부작용이 없다는 것을 알고 있기 때문에 종종 특별한 대우를받습니다. – Mysticial

+0

그런 경우 memset을 제거하지 않는 컴파일러를 찾는 데 문제가 있습니다. – Cubbi

+0

@Mysticial 그 대답이 될 것입니다. –

답변

11

"컴파일러는 내부에서 일어나는 일이 아무런 부작용이 없다고 생각할 권리가 없습니다."

맞습니다. 그러나 실제로 컴파일러가 이 실제로이 발생하는 것을 알고 있고 실제로 이 없다는 것을 확인할 수 있다면 아무런 가정이 필요하지 않습니다.

거의 모든 컴파일러 최적화가 작동하는 방식입니다. 코드는 "X"라고 말합니다. 컴파일러는 "Y"가 참이면 코드 "X"를 코드 "Z"로 대체 할 수 있으며 감지 할 수있는 차이가 없음을 결정합니다. "Y"가 참이라고 판단한 다음 "X"를 "Z"로 바꿉니다. 예를 들어

:

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

컴파일러 foo(); bar();이 최적화 할 수있다. 컴파일러는 fooj의 값을 합법적으로 수정할 수 없음을 알 수 있습니다. foo()이 어쨌든 마법을 가지고 j이 스택에 있는지 확인하고 수정하면 최적화가 코드 동작을 변경하지만 이는 "마법"사용에 대한 프로그래머의 잘못입니다.

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

는 이제 foo은 법적으로 어떤 마법없이 j의 값을 수정할 수 없습니다 때문입니다. 컴파일러가 foo 내부를 볼 수없는 경우를 가정합니다.

"magic"을 수행하면 컴파일러가 코드를 손상시키는 최적화를 수행 할 수 있습니다. 규칙에 충실하고 마법을 사용하지 마십시오.

예를 들어 연결된 코드에서 컴파일러는 특정 값을 액세스하지 못하고 즉시 존재하지 않는 변수에 넣으려고합니다. 컴파일러는 코드 작동에 영향을주지 않는 모든 작업을 수행 할 필요가 없습니다.

코드를 적용 할 수있는 유일한 방법은 스택의 할당되지 않은 부분을 엿보거나 이전에 갖고 있던 값을 가진 스택에 새로운 할당을 사용하는 경우입니다. 컴파일러가 그렇게하도록 요구하면 로컬 변수를 레지스터로 바꾸는 것을 포함하여 많은 수의 최적화가 불가능합니다.

+0

j가 모두 로컬로 처리되는 예제 코드에서 코드 사이에 차이가 있으며 memset과 같은 함수에 넣습니다. memset은 언어 구문이 아닙니다. 실제로 컴파일 된 파일에 존재하는 실제 코드입니다 (모든 컴파일러에는 헤더가 함께 묶여 있습니다). 인라인이 아니기 때문에 컴파일러는 어떤 값을 넣고 있다고 가정 할 권리가 없습니다. 어떤 매개 변수를 전달해야하는지 알 필요가 있습니다. 내 코드를 실제 memset 구현에 연결하는 링커입니다. –

+0

오른쪽 속성이 함수 선언에 주어지면 컴파일러에 의해서만 제거 될 수 있습니다. 그래서'memset'는 특별한 대우를받지 못합니다. 내 두 번째 편집을 참조하십시오. –

+0

@j_kubik 그게 사실 일 필요는 없습니다. 이 속성은 컴파일러가'memset'에 대한 호출을 최적화하는 데 필요하지 않습니다. intelinsics처럼'memset'과'strlen'과 같은 함수를 구현하는 컴파일러가 많이 있습니다 (또는 적어도 내장 함수로 처리하는 옵션을 사용 가능하게 만드십시오). 이 경우 컴파일러는 이러한 함수와 함수에 대해 특별한 지식을 갖고 있으므로 호출에 가시적 인 부작용이 없다고 판단하면 해당 함수를 "데드 코드"로 제거 할 수 있습니다. –

관련 문제