진술
printf("%f\n",0.0f);
0을 인쇄합니다.
그러나 진술
printf("%f\n",0);
임의의 값을 인쇄합니다.
나는 내가 어떤 종류의 정의되지 않은 행동을 보이고 있다는 것을 알고 있지만, 그 이유를 구체적으로 알 수는 없습니다.
모든 비트가 0으로되는 부동 소수점 값은 여전히 유효 float
0의 값
float
과 int
(즉, 더욱 중요한 경우) 내 시스템의 동일한 크기이다.
부동 소수점 리터럴 대신 정수 리터럴을 사용 printf
하면이 동작이 발생 하는 이유는 무엇 입니까?
PS를 사용하면 동일한 동작을 볼 수 있습니다.
int i = 0;
printf("%f\n", i);
답변
"%f"
형식은 형식의 인수가 필요합니다 double
. 유형의 인수를 제공합니다 int
. 그것이 행동이 정의되지 않은 이유입니다.
이 표준은 모든 비트 제로는 유효한 표현은 보장하지 않습니다 0.0
(이 종종 있지만), 또는의 double
값, 또는 그 int
와 double
(그것의 기억 같은 크기 double
,하지 float
가 같은 경우에도, 나) 동일한 방식으로 가변 함수에 인수로 전달됩니다.
시스템에서 “작동”할 수 있습니다. 이는 정의되지 않은 동작의 최악의 증상입니다. 오류를 진단하기 어렵 기 때문입니다.
N1570 7.21.6.1 단락 9 :
… 인수가 해당 변환 사양에 대해 올바른 유형이 아닌 경우 동작이 정의되지 않습니다.
유형의 인수가 float
승격되는 double
이유입니다, printf("%f\n",0.0f)
작동합니다. 또는로 int
승격되는 것보다 좁은 정수 유형의 인수 . 이러한 프로모션 규칙 (N1570 6.5.2.2 단락 6에 의해 지정됨)은 .int
unsigned int
printf("%f\n", 0)
인수 0
를 예상하는 비가 변 함수에 상수 를 전달 double
하면 함수의 프로토 타입이 표시 된다는 가정하에 동작이 잘 정의됩니다. 예를 들어, sqrt(0)
(after #include <math.h>
)는 인수 0
를 int
에서 double
– 로 암시 적으로 변환합니다 . 컴파일러는 인수 가 필요 sqrt
하다는 선언에서 볼 수 있기 때문 double
입니다. 에 대한 정보가 없습니다 printf
. 와 같은 가변 함수 printf
는 특별하며 호출을 작성하는 데 더 많은주의가 필요합니다.
답변
여러 다른 답변에에 감동하지만 같은 첫째, 충분히 명확하게 밖으로 철자 내 마음에 : 그것은 수행 의 정수 제공하는 일을 대부분의 라이브러리 함수가 걸리는 상황 double
이나 float
인수를. 컴파일러는 자동으로 변환을 삽입합니다. 예를 들어, sqrt(0)
는 잘 정의되어 있고 정확히으로 작동 sqrt((double)0)
하며 여기에서 사용되는 다른 정수 유형 표현식에 대해서도 마찬가지입니다.
printf
은 다르다. 가변적 인 수의 인수를 취하기 때문에 다릅니다. 기능 프로토 타입은 다음과 같습니다.
extern int printf(const char *fmt, ...);
따라서 당신이 쓸 때
printf(message, 0);
컴파일러에는 두 번째 인수가 어떤 유형이 printf
될 것으로 예상 하는지에 대한 정보가 없습니다 . 인수 표현식의 유형 () 만 int
있습니다. 따라서 대부분의 라이브러리 함수와 달리 인수 목록이 형식 문자열의 예상과 일치하는지 확인하는 것은 프로그래머의 책임입니다.
(최신 컴파일러 는 형식 문자열을 조사하여 유형 불일치가 있음을 알려줄 수 있지만, 의미 한 바를 달성하기 위해 변환 삽입을 시작하지는 않을 것입니다. , 덜 유용한 컴파일러로 다시 빌드했을 때보 다 몇 년 후.)
이제 질문의 나머지 절반은 다음과 같습니다. 대부분의 최신 시스템에서 (int) 0과 (float) 0.0이 모두 0 인 32 비트로 표시된다는 점을 감안할 때 우연히 작동하지 않는 이유는 무엇입니까? C 표준은 “이것은 작동하는 데 필요하지 않습니다. 당신은 스스로 할 수 있습니다.”라고 말하고 있지만 작동하지 않는 가장 일반적인 두 가지 이유를 설명하겠습니다. 이것이 왜 필요하지 않은지 이해하는 데 도움이 될 것입니다 .
첫째, 역사적 이유로 float
변수 인수 목록 을 통과하면 대부분의 최신 시스템에서 64 비트 너비 인로 승격 됩니다 . 따라서 64 개를 예상하는 수신자에게 32 개의 0 비트 만 전달합니다.double
printf("%f", 0)
두 번째로 중요한 이유는 부동 소수점 함수 인수가 정수 인수 와 다른 위치에 전달 될 수 있다는 것입니다. 예를 들어, 대부분의 CPU에는 정수 및 부동 소수점 값에 대한 별도의 레지스터 파일이 있으므로 인수 0 ~ 4가 정수인 경우 레지스터 r0 ~ r4에 들어가고, 부동 소수점 인 경우 f0 ~ f4에 들어가는 것이 규칙 일 수 있습니다. 따라서 printf("%f", 0)
레지스터 f1에서 0을 찾습니다.하지만 전혀 없습니다.
답변
일반적으로를 예상하는 함수를 호출하지만를 double
제공 int
하면 컴파일러가 자동으로으로 변환됩니다 double
. printf
인수의 유형이 함수 프로토 타입에 지정되지 않았기 때문에에서는 발생하지 않습니다 . 컴파일러는 변환이 적용되어야한다는 것을 알지 못합니다.
답변
부동 리터럴 대신 정수 리터럴을 사용하면 왜 이런 동작이 발생합니까?
때문에 printf()
이외의 매개 변수를 입력하지 않습니다 const char* formatstring
제 1 회 하나. ...
나머지는 모두 c 스타일 줄임표 ( )를 사용합니다.
형식 문자열에 지정된 형식 지정 유형에 따라 전달 된 값을 해석하는 방법을 결정합니다.
당신은 시도 할 때와 같은 종류의 정의되지 않은 행동을 할 것입니다.
int i = 0;
const double* pf = (const double*)(&i);
printf("%f\n",*pf); // dereferencing the pointer is UB
답변
일치하지 않는 printf()
지정자 "%f"
와 유형을 사용 (int) 0
하면 정의되지 않은 동작이 발생합니다.
변환 사양이 유효하지 않으면 동작이 정의되지 않습니다. C11dr §7.21.6.1 9
UB의 후보 원인.
-
그것은 스펙 당 UB이고 컴파일은 고상합니다 .’nuf가 말했습니다.
-
double
그리고int
다른 크기의이다. -
double
및int
다른 스택을 사용하여 값을 전달할 수있다 (일반적인 대에 FPU의 스택). -
(A)은
double 0.0
수있는 모든 제로 비트 패턴으로 정의 할 수 없습니다. (드문)
답변
이것은 컴파일러 경고에서 배울 수있는 좋은 기회 중 하나입니다.
$ gcc -Wall -Wextra -pedantic fnord.c
fnord.c: In function ‘main’:
fnord.c:8:2: warning: format ‘%f’ expects argument of type ‘double’, but argument 2 has type ‘int’ [-Wformat=]
printf("%f\n",0);
^
또는
$ clang -Weverything -pedantic fnord.c
fnord.c:8:16: warning: format specifies type 'double' but the argument has type 'int' [-Wformat]
printf("%f\n",0);
~~ ^
%d
1 warning generated.
따라서 printf
호환되지 않는 유형의 인수를 전달하기 때문에 정의되지 않은 동작이 생성됩니다.
답변
무엇이 헷갈리는 지 잘 모르겠습니다.
형식 문자열에는 double
; 대신 int
.
두 유형이 동일한 비트 폭을 갖는지 여부는 완전히 관련이 없습니다. 단, 이와 같이 손상된 코드에서 하드 메모리 위반 예외가 발생하지 않도록하는 데 도움이 될 수 있습니다.