programing

printf("%f",0);가 정의되지 않은 동작을 하는 이유는 무엇입니까?

goodcopy 2022. 7. 24. 23:06
반응형

printf("%f",0);가 정의되지 않은 동작을 하는 이유는 무엇입니까?

스테이트먼트

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

0 을 인쇄합니다.

단, 이 문장은

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

랜덤한 값을 출력합니다.

제가 뭔가 불명확한 행동을 보이는 건 알지만, 왜 그런지 정확히 알 수가 없어요.

합니다.float0으로 하다
float ★★★★★★★★★★★★★★★★★」int을 사용하다

리터럴을 입니까?printf작작작 원인 ??? ???

추신, 같은 행동을 볼 수 있습니다.

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

"%f"에는 형식 합니다.doubleint그래서 그 행동이 정의되지 않은 거야

가 all-bits-zero의 .0.0) 또는 의 (종종 그렇긴 하지만)double 그 「」, 「」를 참조해 주세요.int ★★★★★★★★★★★★★★★★★」double크기입니다(double 아니라, 이에요.float같은 크기라도 같은 방법으로 변수 함수에 인수로 전달되는 경우도 있습니다.

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

N1570 7.21.6.1 단락 9:

... 대응하는 변환 사양의 올바른 타입이 아닌 인수는 정의되어 있지 않습니다.

" " 의 floatdouble 「」라고 하는 것이 됩니다.printf("%f\n",0.0f) 보다 좁은 intint 「」로unsigned int2.2은 N1570 6.5.2.2의 경우 printf("%f\n", 0).

해 주세요.0하여 비변수 함수는 비변수 함수로 변환됩니다.double인수, 함수의 프로토타입을 볼 수 있다고 가정하면 동작이 잘 정의됩니다.예를 들어, 예를들면,sqrt(0)((이훈 후)#include <math.h>)암시적으로)는암묵적으로 변환합니다 인수를이 주장합니다.0부터에서int로.double입니다 그 컴파일러, 컴파일러는 이 선언에서 볼 수 있기 때문입니다의 선언을 알 수 있어.sqrt는 기대하다를 기대했다.double논쟁.논쟁. 그것은 에대한 정보는 없습니다에 대한 그러한 정보를 가지고 있습니다.printf좋아합니다.Variadic 기능. 다음과 같은 다양한 기능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 표준에는 "이것이 작동하는데 필요하지 않고, 독자적으로 작동해야 합니다."라고만 되어 있지만, 이것이 작동하지 않는 가장 일반적인 두 가지 이유를 설명하겠습니다.그것이 왜 필요하지 않은지를 이해하는 데 도움이 될 것입니다.

째, 사, 신, 신, 신, 신, 신, 신, 신, 신, 신, 신, first, first, first, first, first, first, first, first.float변수 인수 목록을 통해 승격됩니다.double대부분의 현대 시스템에서는 64비트 입니다.그렇게printf("%f", 0)는, 그하고 있는 .

두 번째 마찬가지로 중요한 이유는 부동소수점 함수 인수가 정수 인수와는 다른 장소에서 전달될 수 있기 때문입니다.예를 들어 대부분의 CPU에는 정수값과 부동소수점 값에 대한 별도의 레지스터 파일이 있기 때문에 0 ~4 인수가 정수인 경우에는 레지스터 r0 ~r4 에 들어가지만 부동소수점인 경우에는 f0 ~f4 에 들어가는 것이 규칙일 수 있습니다.그렇게printf("%f", 0)f1을 0으로 하다

으로는, 「」, 「」, 「」를 상정하는 함수를 할 때,double, ,를 int으로 '컴파일러'로double그런printf인수 유형은 함수 프로토타입에 지정되어 있지 않기 때문에 컴파일러는 변환이 적용되어야 하는지 알 수 없습니다.

플로트 리터럴이 아닌 정수 리터럴을 사용하면 왜 이런 동작이 발생합니까?

★★★★★★★★★★★★★★★★★★printf()에는 입력된 const char* formatstring첫 번째 곡으로.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이며 컴파일은 ornery라고 nuf는 말했다.

  2. double ★★★★★★★★★★★★★★★★★」int사이즈가 다릅니다.

  3. double ★★★★★★★★★★★★★★★★★」int는 다른 스택을 사용하여 값을 전달할 수 있습니다(일반과FPU 스택).

  4. A double 0.0 all 제로 비트패턴에 의해 정의되지 않을 수 있습니다.(예:)

이는 컴파일러 경고에서 배울 수 있는 좋은 기회 중 하나입니다.

$ 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.

so,는,printf호환되지 않는 유형의 인수를 전달했기 때문에 정의되지 않은 동작이 생성되고 있습니다.

뭐가 헷갈리는지 모르겠어요.

은 형음음음음음음음음음음 a a a a a a a a a를 요구합니다.double ; ;를 제공하세요.int.

두 유형의 비트 폭이 동일한지 여부는 이와 같이 손상된 코드에서 하드 메모리 위반 예외가 발생하는 것을 방지하는 데 도움이 될 수 있다는 점을 제외하고는 전혀 관련이 없습니다.

"%f\n"는, 의 「예측 가능한 결과」가 있는 경우에만, 예측 를 보증합니다.printf()은 「」입니다.double다음으로, 변수 함수의 추가 인수는 기본 인수 승격의 대상입니다.integer 인수는 정수 승격에 속하므로 부동소수점 형식의 값은 생성되지 않습니다. ★★★★★★★★★★★★★★★★★.float는 「모수」로 됩니다.double.

는 두 또는 standard 인수가 될 수 .또는float ★★★★★★★★★★★★★★★★★」double그리고 그 외 아무것도 없다.

왜 그것이 공식적으로 UB인지 이제 몇 가지 답변에서 논의되었다.

특히 이 동작이 발생하는 이유는 플랫폼에 의존하지만 다음과 같습니다.

  • printf바라르그라고 합니다., ,, ,, ,, ,, ,, ,.float 되다double 그고 an an an than than than 보다 작은 것int 되다int.
  • 합격하고 있습니다.int는 ""를 합니다.double.당신의.int아마 32비트일 거예요double64비트즉, 인수가 배치되어야 하는 위치에서 시작하는4개의 스택바이트는0단, 다음 4바이트에는 임의의 내용이 포함되어 있습니다.표시되는 값을 구성하는 데 사용됩니다.

이 "미정 값" 문제의 주요 원인은 포인터의 캐스트에 있습니다.int에 전달된 값printf변수 매개 변수 섹션의 포인터 위치double라고 입력합니다.va_arg매크로가 실행됩니다.

이로 인해 printf에 파라미터로 전달된 값으로 완전히 초기화되지 않은 메모리 영역이 참조됩니다.double크기 메모리 버퍼 영역이 다음보다 큽니다.int크기.

따라서 이 포인터가 참조되지 않으면 미결정 값이 반환되거나 파라미터로 전달된 값의 일부가 포함된 "값"이 반환됩니다.printf나머지 부분은 다른 스택버퍼 영역 또는 코드 영역(메모리 장애 예외 발생)에서 발생할 수 있습니다.실제 버퍼 오버플로우입니다.


printf 및 va_arg의 semplicated code 구현의 이러한 특정 부분을 고려할 수 있습니다.

인쇄물

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


vprintf(gnu incommending gnu incapt.)에서의 코드 케이스 관리의 실제 실장은 다음과 같습니다.

if (__ldbl_is_dbl)
{
   args_value[cnt].pa_double = va_arg (ap_save, double);
   ...
}



va_module

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);



레퍼런스

  1. gnu project glibc의 printf(vprintf) 구현
  2. printf의 semplification 코드 예시
  3. va_module의 semplification 코드 예제

언급URL : https://stackoverflow.com/questions/38597274/why-does-printff-0-give-undefined-behavior

반응형