[c++] 왜 printf ( “% f”, 0); 정의되지 않은 동작을 제공합니까?

진술

printf("%f\n",0.0f);

0을 인쇄합니다.

그러나 진술

printf("%f\n",0);

임의의 값을 인쇄합니다.

나는 내가 어떤 종류의 정의되지 않은 행동을 보이고 있다는 것을 알고 있지만, 그 이유를 구체적으로 알 수는 없습니다.

모든 비트가 0으로되는 부동 소수점 값은 여전히 유효 float0의 값
floatint(즉, 더욱 중요한 경우) 내 시스템의 동일한 크기이다.

부동 소수점 리터럴 대신 정수 리터럴을 사용 printf하면이 동작이 발생 하는 이유는 무엇 입니까?

PS를 사용하면 동일한 동작을 볼 수 있습니다.

int i = 0;
printf("%f\n", i);



답변

"%f"형식은 형식의 인수가 필요합니다 double. 유형의 인수를 제공합니다 int. 그것이 행동이 정의되지 않은 이유입니다.

이 표준은 모든 비트 제로는 유효한 표현은 보장하지 않습니다 0.0(이 종종 있지만), 또는의 double값, 또는 그 intdouble(그것의 기억 같은 크기 double,하지 float가 같은 경우에도, 나) 동일한 방식으로 가변 함수에 인수로 전달됩니다.

시스템에서 “작동”할 수 있습니다. 이는 정의되지 않은 동작의 최악의 증상입니다. 오류를 진단하기 어렵 기 때문입니다.

N1570 7.21.6.1 단락 9 :

… 인수가 해당 변환 사양에 대해 올바른 유형이 아닌 경우 동작이 정의되지 않습니다.

유형의 인수가 float승격되는 double이유입니다, printf("%f\n",0.0f)작동합니다. 또는로 int승격되는 것보다 좁은 정수 유형의 인수 . 이러한 프로모션 규칙 (N1570 6.5.2.2 단락 6에 의해 지정됨)은 .intunsigned intprintf("%f\n", 0)

인수 0를 예상하는 비가 변 함수에 상수 를 전달 double하면 함수의 프로토 타입이 표시 된다는 가정하에 동작이 잘 정의됩니다. 예를 들어, sqrt(0)(after #include <math.h>)는 인수 0int에서 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 비트 만 전달합니다.doubleprintf("%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의 후보 원인.

  1. 그것은 스펙 당 UB이고 컴파일은 고상합니다 .’nuf가 말했습니다.

  2. double그리고 int다른 크기의이다.

  3. doubleint다른 스택을 사용하여 값을 전달할 수있다 (일반적인 대에 FPU의 스택).

  4. (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.

두 유형이 동일한 비트 폭을 갖는지 여부는 완전히 관련이 없습니다. 단, 이와 같이 손상된 코드에서 하드 메모리 위반 예외가 발생하지 않도록하는 데 도움이 될 수 있습니다.