[c] Stanford 자습서와 GCC 간의 충돌

영화 (약 38 분) 에 따르면 동일한 로컬 변수를 가진 두 개의 함수가 있으면 동일한 공간을 사용합니다. 따라서 다음 프로그램은5 . gcc결과 와 함께 컴파일 -1218960859. 왜?

프로그램:

#include <stdio.h>

void A()
{
    int a;
    printf("%i",a);
}

void B()
{
    int a;
    a = 5;
}

int main()
{
    B();
    A();
    return 0;
}

요청한대로 디스어셈블러의 출력은 다음과 같습니다.

0804840c <A>:
 804840c:   55                      push   ebp
 804840d:   89 e5                   mov    ebp,esp
 804840f:   83 ec 28                sub    esp,0x28
 8048412:   8b 45 f4                mov    eax,DWORD PTR [ebp-0xc]
 8048415:   89 44 24 04             mov    DWORD PTR [esp+0x4],eax
 8048419:   c7 04 24 e8 84 04 08    mov    DWORD PTR [esp],0x80484e8
 8048420:   e8 cb fe ff ff          call   80482f0 <printf@plt>
 8048425:   c9                      leave
 8048426:   c3                      ret

08048427 <B>:
 8048427:   55                      push   ebp
 8048428:   89 e5                   mov    ebp,esp
 804842a:   83 ec 10                sub    esp,0x10
 804842d:   c7 45 fc 05 00 00 00    mov    DWORD PTR [ebp-0x4],0x5
 8048434:   c9                      leave
 8048435:   c3                      ret

08048436 <main>:
 8048436:   55                      push   ebp
 8048437:   89 e5                   mov    ebp,esp
 8048439:   83 e4 f0                and    esp,0xfffffff0
 804843c:   e8 e6 ff ff ff          call   8048427 <B>
 8048441:   e8 c6 ff ff ff          call   804840c <A>
 8048446:   b8 00 00 00 00          mov    eax,0x0
 804844b:   c9                      leave
 804844c:   c3                      ret
 804844d:   66 90                   xchg   ax,ax
 804844f:   90                      nop



답변

예, 예, 이것은 uninitialized 1 변수를 사용하고 있기 때문에 정의되지 않은 동작 입니다.

그러나 x86 아키텍처 2 에서는 이 실험이 작동 합니다. 값은 스택에서 “삭제”되지 않으며에서 초기화되지 않았기 때문에 B()스택 프레임이 동일하다면 동일한 값이 여전히 존재해야합니다.

나는 내부에서 사용int a 되지 않기 때문에 컴파일러가 해당 코드를 최적화했으며 5는 스택의 해당 위치에 기록되지 않았다고 생각합니다. 추가 시도 의를void B()printfB() 그냥 작동 할 수 있습니다 – 물론.

또한 컴파일러 플래그 (즉, 최적화 수준)도이 실험에 영향을 미칠 수 있습니다. -O0gcc 로 전달 하여 최적화를 비활성화하십시오 .

편집 : 방금 코드를 gcc -O0(64 비트)로 컴파일 했으며 실제로 프로그램은 호출 스택에 익숙한 사람이 예상하는 것처럼 5를 인쇄합니다. 실제로 -O0. 32 비트 빌드는 다르게 동작 할 수 있습니다.

면책 조항 : 음주하지 영원히, 영원히 “진짜”코드 같은 것을 사용!

1- 이것이 공식적으로 “UB”인지 아니면 단지 예측할 수 없는지 에 대한 논쟁이 계속되고 있습니다 .

2-또한 x64 및 호출 스택을 사용하는 다른 모든 아키텍처 (최소한 MMU가있는 아키텍처)


작동 하지 않는 이유를 살펴 보겠습니다 . 이것은 32 비트에서 가장 잘 볼 수 있으므로 -m32.

$ gcc --version
gcc (GCC) 4.7.2 20120921 (Red Hat 4.7.2-2)

$ gcc -m32 -O0 test.c(최적화 비활성화)로 컴파일했습니다 . 이것을 실행하면 쓰레기가 인쇄됩니다.

보고 $ objdump -Mintel -d ./a.out:

080483ec <A>:
 80483ec:   55                      push   ebp
 80483ed:   89 e5                   mov    ebp,esp
 80483ef:   83 ec 28                sub    esp,0x28
 80483f2:   8b 45 f4                mov    eax,DWORD PTR [ebp-0xc]
 80483f5:   89 44 24 04             mov    DWORD PTR [esp+0x4],eax
 80483f9:   c7 04 24 c4 84 04 08    mov    DWORD PTR [esp],0x80484c4
 8048400:   e8 cb fe ff ff          call   80482d0 <printf@plt>
 8048405:   c9                      leave
 8048406:   c3                      ret

08048407 <B>:
 8048407:   55                      push   ebp
 8048408:   89 e5                   mov    ebp,esp
 804840a:   83 ec 10                sub    esp,0x10
 804840d:   c7 45 fc 05 00 00 00    mov    DWORD PTR [ebp-0x4],0x5
 8048414:   c9                      leave
 8048415:   c3                      ret

우리는 점에서 볼 B, 컴파일러는 스택 공간을 0x10 바이트를 예약하고, 우리의 초기화 int a에 변수를 [ebp-0x4]5.

에서 A그러나, 컴파일러는 배치 int a에서 [ebp-0xc]. 따라서이 경우 우리의 지역 변수 같은 위치에 있지 않았습니다 ! 추가하는 것으로 써 printf()의 통화 A뿐만위한 스택 프레임 발생할하면서 AB동일하고, 인쇄 55.


답변

그건 정의되지 않은 동작 . 초기화되지 않은 지역 변수에는 불확실한 값이 있으며이를 사용하면 정의되지 않은 동작이 발생합니다.


답변

기억해야 할 한 가지 중요한 사항은 절대 그런 것에 의존 하지 말고 실제 코드에서 절대 사용 하지 마십시오 ! 그것은 단지 흥미로운 것입니다 (항상 사실은 아닙니다), 기능이나 그런 것이 아닙니다. 그런 종류의 “기능”-악몽에 의해 생성 된 버그를 찾으려고한다고 상상해보십시오.

Btw. -C와 C ++는 그런 종류의 “기능”으로 가득 차 있습니다. 여기에 대한 훌륭한 슬라이드 쇼가 있습니다.
http://www.slideshare.net/olvemaudal/deep-c 따라서 더 유사한 “기능”을보고 싶다면 무엇이 있는지 이해하십시오. 이 슬라이드 쇼를 보시면 후회하지 않으실 것입니다. 경험 많은 C / C ++ 프로그래머들도 이것으로부터 많은 것을 배울 수있을 것입니다.


답변

함수 A에서 변수 a는 초기화되지 않고 값을 인쇄하면 정의되지 않은 동작이 발생합니다.

일부 컴파일러에서는 ain Aain 변수 가 B동일한 주소에 있으므로 인쇄 5할 수 있지만 정의되지 않은 동작에 의존 할 수 없습니다.


답변

코드를 컴파일 gcc -Wall filename.c하면 다음과 같은 경고가 표시됩니다.

In function 'B':
11:9: warning: variable 'a' set but not used [-Wunused-but-set-variable]

In function 'A':
6:11: warning: 'a' is used uninitialized in this function [-Wuninitialized]

In c 초기화되지 않은 변수를 인쇄하면 정의되지 않은 동작이 발생합니다.

섹션 6.7.8 C99 표준 초기화에 따르면

자동 저장 기간이있는 개체가 명시 적으로 초기화되지 않은 경우 해당 값은 확정되지 않습니다. 정적 저장 기간이있는 객체가 명시 적으로 초기화되지 않은 경우 :

if it has pointer type, it is initialized to a null pointer;
— if it has arithmetic type, it is initialized to (positive or unsigned) zero;
— if it is an aggregate, every member is initialized (recursively) according to these rules;
— if it is a union, the first named member is initialized (recursively) according to these rules.

편집 1

@Jonathon Reinhart로 -O플래그 gcc-O0 를 사용하여 최적화를 비활성화 하면 출력 5를 얻을 수 있습니다.

그러나 이것은 전혀 좋은 생각이 아니며 프로덕션 코드에서 절대 사용하지 마십시오.

-Wuninitialized 이것은 귀중한 경고 중 하나입니다.이 경고를 고려해야합니다. 데몬을 실행하는 동안 충돌을 일으키는 것과 같이 프로덕션에서 막대한 손상을 초래하는이 경고를 비활성화하거나 건너 뛰지 마십시오.


편집 2

Deep C 슬라이드는 왜 결과가 5 / 가비지인지 설명했습니다.이 답변을 좀 더 효과적으로 만들기 위해 약간의 수정을 통해 해당 슬라이드에서이 정보를 추가합니다.

사례 1 : 최적화없이

$ gcc -O0 file.c && ./a.out
5

아마도이 컴파일러에는 재사용하는 명명 된 변수 풀이 있습니다. 예를 들어 변수 a가에서 사용되고 해제 된
B()
다음 A()정수 이름 a이 필요할 때 변수가 동일한 메모리 위치를 가져옵니다. 당신이 변수의 이름을 변경하는 경우 B()에, 말 b, 그때 당신이 얻을 것이라고 생각하지 않습니다 5.

사례 2 : 최적화

옵티마이 저가 시작될 때 많은 일이 발생할 수 있습니다.이 경우 B()부작용이 없기 때문에 호출을 건너 뛸 수 있다고 생각합니다 . 또한에 A()인라인 main(), 즉 함수 호출이없는 경우에는 놀라지 않을 것 입니다 . (그러나 A
()
링커 가시성이 있으므로 다른 개체 파일이 함수와 연결하려는 경우에만 함수에 대한 개체 코드를 만들어야합니다.) 어쨌든 코드를 최적화하면 인쇄 된 값이 다른 것이 될 것이라고 생각합니다.

gcc -O file.c && ./a.out
1606415608

찌꺼기!


답변