왜 printf ( "% f", 0); 정의되지 않은 동작을 제공합니까?
진술
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)
여러 다른 답변에에 감동하지만 같은 첫째, 충분히 명확하게 밖으로 철자 내 마음에 : 그것은 수행 의 정수 제공하는 일을 대부분의 라이브러리 함수가 걸리는 상황 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
andint
may pass their values using different stacks (general vs. FPU stack.)A
double 0.0
might not be defined by an all zero bit pattern. (rare)
This is one of those great opportunities to learn from your compiler warnings.
$ 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);
^
or
$ 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.
So, printf
is producing undefined behavior because you are passing it an incompatible type of argument.
I'm not sure what's confusing.
Your format string expects a double
; you provide instead an int
.
Whether the two types have the same bit width is utterly irrelevant, except that it may help you avoid getting hard memory violation exceptions from broken code like this.
"%f\n"
guarantees predictable result only when the second printf()
parameter has type of double
. Next, an extra arguments of variadic functions are subject of default argument promotion. Integer arguments fall under integer promotion, which never results in floating-point typed values. And float
parameters are promoted to double
.
To top it off: standard allows the second argument to be or float
or double
and nothing else.
Why it is formally UB has now been discussed in several answers.
The reason why you get specifically this behaviour is platform-dependent, but probably is the following:
printf
expects its arguments according to standard vararg propagation. That means afloat
will be adouble
and anything smaller than anint
will be anint
.- You are passing an
int
where the function expects adouble
. Yourint
is probably 32 bit, yourdouble
64 bit. That means that the four stack bytes starting at the place where the argument is supposed to sit are0
, but the following four bytes have arbitrary content. That's what is used for constructing the value which is displayed.
The main cause of this "undetermined value" issue stands in the cast of the pointer at the int
value passed to the printf
variable parameters section to a pointer at double
types that va_arg
macro carries out.
This causes a referencing to a memory area that was not completely initialized with value passed as parameter to the printf, because double
size memory buffer area is greater than int
size.
Therefore, when this pointer is dereferenced, it is returned an undetermined value, or better a "value" that contains in part the value passed as parameter to printf
, and for the remaining part could came from another stack buffer area or even a code area (raising a memory fault exception), a real buffer overflow.
It can consider these specific portions of semplificated code implementations of "printf" and "va_arg"...
printf
va_list arg;
....
case('%f')
va_arg ( arg, double ); //va_arg is a macro, and so you can pass it the "type" that will be used for casting the int pointer argument of printf..
....
the real implementation in vprintf (considering gnu impl.) of double value parameters code case management is:
if (__ldbl_is_dbl) { args_value[cnt].pa_double = va_arg (ap_save, double); ... }
va_arg
char *p = (double *) &arg + sizeof arg; //printf parameters area pointer
double i2 = *((double *)p); //casting to double because va_arg(arg, double)
p += sizeof (double);
references
- gnu project glibc implementation of "printf"(vprintf))
- example of semplification code of printf
- example of semplification code of va_arg
참고URL : https://stackoverflow.com/questions/38597274/why-does-printff-0-give-undefined-behavior
'developer tip' 카테고리의 다른 글
PowerShell 기본 디렉토리를 설정하는 방법은 무엇입니까? (0) | 2020.09.13 |
---|---|
프로그래밍 방식으로 탐색 모음에 단추 추가 (0) | 2020.09.13 |
"."는 어떻게 사용합니까? (0) | 2020.09.13 |
문자열에서 n 번째 문자를 찾는 방법은 무엇입니까? (0) | 2020.09.13 |
/ etc / nginx를 어떻게 복원 할 수 있습니까? (0) | 2020.09.13 |