스탠포드 튜토리얼과 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
네, 초기화되지 않은 변수를1 사용하고 있기 때문에 정의되지 않은 동작입니다.
단, x86 아키텍처에서는2 이 실험이 유효합니다.이 값은 스택에서 "삭제"되지 않습니다.이것은, 이 값이 초기화되어 있지 않기 때문입니다.B()스택 프레임이 동일할 경우 같은 값이 계속 존재합니다.
감히 짐작해보죠, 왜냐면int a내부에서는 사용되지 않습니다.void B()컴파일러는 이 코드를 최적화하여 스택 상의 그 위치에 5를 기입하지 않았습니다. 한 번 더 .printfB()효과가 있을지도 모릅니다.
또한 컴파일러 플래그, 즉 최적화 수준도 이 실험에 영향을 미칠 수 있습니다.「」를 디세블로 해 .-O0gcc로 이동합니다.
편집: 방금 코드를 컴파일했습니다.gcc -O0(64비트), 실제로는 콜스택에 정통한 사람이 예상하는 대로 프로그램이 5를 출력합니다.사실, 그것은 심지어 이 모든것들 없이도 작동했다.-O032번입니다.
면책사항:절대, 절대 이런 걸 "진짜" 코드에 사용하지 마세요!
1 - 이것이 공식적으로 "UB"인지, 아니면 단지 예측할 수 없는 것인지에 대한 논쟁이 아래에서 진행되고 있다.
2 - x64 및 콜스택을 사용하는 다른 모든 아키텍처(최소한 MMU가 있는 아키텍처)도 지원
왜 효과가 없었는지 이유를 살펴봅시다.이것은 32비트로 표시되는 것이 가장 좋기 때문에, 다음과 같이 컴파일 합니다.-m32.
$ gcc --version
gcc (GCC) 4.7.2 20120921 (Red Hat 4.7.2-2)
i i했컴했 。$ 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]까지.
»A는 「」를 배치했습니다.int a[ebp-0xc]따라서 이 경우 우리의 지역 변수는 같은 위치에 있지 않습니다.를 추가함으로써printf()A 스택 .A ★★★★★★★★★★★★★★★★★」B하여 인쇄하다55.
그것은 정의되지 않은 행동이다.초기화되지 않은 로컬 변수는 값이 미확정이며 이 값을 사용하면 정의되지 않은 동작이 발생합니다.
기억해야 할 한 가지 중요한 것은 절대 그런 것에 의존하지 말고 이것을 실제 코드에 사용하지 말라는 것입니다.그것은 단지 흥미로운 것일 뿐이지, 특징 같은 것은 아니다.악몽이라는 그런 종류의 "특징"에 의해 생성된 버그를 찾으려고 노력하는 자신을 상상해 보세요.
그런데 - C와 C++는 그런 종류의 '보여주기'로 가득 차 있습니다.이 슬라이드 쇼에 대해서, http://www.slideshare.net/olvemaudal/deep-c 를 참조해 주세요.따라서, 보다 유사한 '보여주기' 를 참조하고 싶다면, 이 슬라이드 쇼를 봐 주세요.후회하지 않고, 경험이 풍부한 C/C++프로그래머도, 대부분의 프로그래머가 학습할 수 있습니다.이것으로부터 많은 것을.
함수에서A변수,a이 값을 인쇄하면 정의되지 않은 동작이 발생합니다.
일부 컴파일러에서는 변수가a에A그리고.a에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]
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
As @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
쓰레기!
언급URL : https://stackoverflow.com/questions/19559133/conflict-between-a-stanford-tutorial-and-gcc
'programing' 카테고리의 다른 글
| nginx가 있는 vue: 경로가 일치하지 않습니다. (0) | 2022.07.27 |
|---|---|
| Java에서 getPath(), getAbsolutePath() 및 getCanonicalPath()의 차이점은 무엇입니까? (0) | 2022.07.27 |
| 사이트 로드 시 nuxtServerInit이 여러 번 호출되는 이유 (0) | 2022.07.27 |
| 변환 커밋 콜이 실패하는 이유는 무엇입니까? (0) | 2022.07.27 |
| Vue 변환 변경 인스턴스(MyArray에서 어레이로) (0) | 2022.07.27 |