나는 오래 전에 포럼에서 흥미로운 질문을 우연히 발견했으며 그 답을 알고 싶습니다.
다음 C 함수를 고려하십시오.
f1.c
#include <stdbool.h>
bool f1()
{
int var1 = 1000;
int var2 = 2000;
int var3 = var1 + var2;
return (var3 == 0) ? true : false;
}
false
이후 항상 반환해야합니다 var3 == 3000
. main
기능은 다음과 같습니다 :
main.c
#include <stdio.h>
#include <stdbool.h>
int main()
{
printf( f1() == true ? "true\n" : "false\n");
if( f1() )
{
printf("executed\n");
}
return 0;
}
f1()
항상 반환해야 하므로 false
프로그램 이 화면에 하나의 거짓 만 인쇄 할 것으로 예상합니다 . 그러나 컴파일하고 실행 한 후에 실행 됨 도 표시됩니다.
$ gcc main.c f1.c -o test
$ ./test
false
executed
왜 그런 겁니까? 이 코드에는 일종의 정의되지 않은 동작이 있습니까?
참고 :로 컴파일했습니다 gcc (Ubuntu 4.9.2-10ubuntu13) 4.9.2
.
답변
다른 답변에서 언급했듯이 문제는 gcc
컴파일러 옵션을 설정하지 않고 사용한다는 것 입니다. 이렇게하면 기본값은 1990 년부터 철회 된 C90 표준의 비표준 구현 인 “gnu90″으로 기본 설정됩니다.
이전 C90 표준에서 C 언어의 주요 결함이 있었다 : 당신이 기능을 사용하기 전에 프로토 타입을 선언하지 않은 경우, 그것은 기본값 것 int func ()
(여기서 ( )
의미는 “어떤 매개 변수를 받아”). 이것은 함수의 호출 규칙을 변경 func
하지만 실제 함수 정의는 변경하지 않습니다. 의 크기 이후 bool
및int
가 다르기 코드가 함수를 호출 할 때 정의되지 않은 동작을 호출합니다.
이 위험한 말도 안되는 행동은 1999 년 C99 표준의 출시와 함께 수정되었습니다. 암시 적 함수 선언이 금지되었습니다.
불행히도, 버전 5.xx까지의 GCC는 여전히 기본적으로 이전 C 표준을 사용합니다. 표준 C 이외의 코드로 코드를 컴파일해야하는 이유는 없을 것입니다. 따라서 GCC에 25 년이 넘은 비표준 GNU 크랩 대신 최신 C 코드로 코드를 컴파일해야한다고 명시 적으로 명시해야합니다. .
항상 다음과 같이 프로그램을 컴파일하여 문제를 해결하십시오.
gcc -std=c11 -pedantic-errors -Wall -Wextra
-std=c11
(현재의) C 표준 (비공식적으로 C11)에 따라 컴파일을 반쯤 시도하도록 지시합니다.-pedantic-errors
C 표준을 위반하는 잘못된 코드를 작성할 때 컴파일러가 위의 작업을 수행하고 컴파일러 오류를 발생시킵니다.-Wall
나에게 좋은 경고를 알려줘-Wextra
나에게 좋을지도 모르는 다른 추가 경고를 알려주십시오.
답변
f1()
main.c에 선언 된 프로토 타입이 없으므로 암시 적으로로 정의됩니다 int f1()
. 즉, 알 수없는 수의 인수를 가져 와서int
.
경우 int
와 bool
다른 크기의 있습니다,이 발생합니다 정의되지 않은 동작 . 예를 들어, 내 컴퓨터에서 int
4 바이트이고 bool
1 바이트입니다. 이 함수는 return 으로 정의bool
되었으므로 반환 될 때 스택에 1 바이트를 넣습니다. 그러나 암시 적 으로 반환하도록 선언 되었으므로int
main.c에서 되었으므로 호출 함수는 스택에서 4 바이트를 읽으려고 시도합니다.
gcc의 기본 컴파일러 옵션은 이것이 수행 중임을 알려주지 않습니다. 그러나로 컴파일하면 다음을 -Wall -Wextra
얻을 수 있습니다.
main.c: In function ‘main’:
main.c:6: warning: implicit declaration of function ‘f1’
이 문제를 해결하려면 f1
main.c에서 에 대한 선언을 추가하십시오 main
.
bool f1(void);
인수 목록은 명시 적으로로 설정되어 void
있으며, 알 수없는 수의 인수를 의미하는 빈 매개 변수 목록과 달리 함수가 인수를 사용하지 않도록 컴파일러에 지시합니다. 정의 f1
f1.c에서도이를 반영하도록 변경해야합니다.
답변
Lundin의 훌륭한 답변에서 언급 된 크기 불일치가 실제로 발생하는 곳을 보는 것이 재미 있다고 생각합니다.
로 컴파일 --save-temps
하면 볼 수있는 어셈블리 파일을 얻게됩니다. 다음 f1()
은 == 0
비교를 수행하고 해당 값을 반환 하는 부분입니다 .
cmpl $0, -4(%rbp)
sete %al
반환 부분은 sete %al
입니다. C의 x86 호출 규칙에서 4 바이트 이하 ( int
및 포함 bool
)를 반환 하는 값 은 register를 통해 반환됩니다 %eax
. %al
의 가장 낮은 바이트입니다 %eax
. 따라서 상위 3 바이트%eax
는 제어되지 않은 상태로 남아 있습니다.
지금 main()
:
call f1
testl %eax, %eax
je .L2
이것은 전체 의 여부를 확인%eax
테스트하고 있다고 생각하기 때문에 가 0 .
명시 적 함수 선언을 추가하면 다음과 같이 변경 main()
됩니다.
call f1
testb %al, %al
je .L2
우리가 원하는 것입니다.
답변
다음과 같은 명령으로 컴파일하십시오 :
gcc -Wall -Wextra -Werror -std=gnu99 -o main.exe main.c
산출:
main.c: In function 'main':
main.c:14:5: error: implicit declaration of function 'f1' [-Werror=impl
icit-function-declaration]
printf( f1() == true ? "true\n" : "false\n");
^
cc1.exe: all warnings being treated as errors
이러한 메시지가 있으면이를 정정하기 위해 수행 할 작업을 알아야합니다.
편집 : (현재 삭제 된) 주석을 읽은 후 플래그없이 코드를 컴파일하려고했습니다. 글쎄, 이로 인해 컴파일러 오류 대신 컴파일러 경고가없는 링커 오류가 발생했습니다. 그리고 이러한 링커 오류는 이해하기가 더 어려우므로 -std-gnu99
필요하지 않더라도 적어도 항상 사용 -Wall -Werror
하십시오. 엉덩이에 많은 고통을 덜어줍니다.