이 영화 (약 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()
printf
B()
그냥 작동 할 수 있습니다 – 물론.
또한 컴파일러 플래그 (즉, 최적화 수준)도이 실험에 영향을 미칠 수 있습니다. -O0
gcc 로 전달 하여 최적화를 비활성화하십시오 .
편집 : 방금 코드를 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
뿐만위한 스택 프레임 발생할하면서 A
와 B
동일하고, 인쇄 55
.
답변
그건 정의되지 않은 동작 . 초기화되지 않은 지역 변수에는 불확실한 값이 있으며이를 사용하면 정의되지 않은 동작이 발생합니다.
답변
기억해야 할 한 가지 중요한 사항은 절대 그런 것에 의존 하지 말고 실제 코드에서 절대 사용 하지 마십시오 ! 그것은 단지 흥미로운 것입니다 (항상 사실은 아닙니다), 기능이나 그런 것이 아닙니다. 그런 종류의 “기능”-악몽에 의해 생성 된 버그를 찾으려고한다고 상상해보십시오.
Btw. -C와 C ++는 그런 종류의 “기능”으로 가득 차 있습니다. 여기에 대한 훌륭한 슬라이드 쇼가 있습니다.
http://www.slideshare.net/olvemaudal/deep-c 따라서 더 유사한 “기능”을보고 싶다면 무엇이 있는지 이해하십시오. 이 슬라이드 쇼를 보시면 후회하지 않으실 것입니다. 경험 많은 C / C ++ 프로그래머들도 이것으로부터 많은 것을 배울 수있을 것입니다.
답변
함수 A
에서 변수 a
는 초기화되지 않고 값을 인쇄하면 정의되지 않은 동작이 발생합니다.
일부 컴파일러에서는 a
in A
및 a
in 변수 가 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
찌꺼기!