Почему printf ("%f", 0); дает неопределенное поведение?


заявление

printf("%fn",0.0f);

выводит 0.

, заявление
printf("%fn",0);

выводит случайные значения.

Я понимаю, что проявляю какое-то неопределенное поведение, но я не могу понять, почему именно.

значение с плавающей запятой, в котором все биты равны 0, по-прежнему является допустимым float со значением 0.
float и int имеют тот же размер на моей машине (если это еще актуально).

почему использует ли целочисленный литерал вместо литерала с плавающей запятой в printf причина такого поведения?

P. S. И все же поведение можно увидеть, если я использую

int i = 0;
printf("%fn", i);
10 85

10 ответов:

The "%f" формат требует аргумента типа double. Вы даете ему аргумент типа int. Вот почему поведение не определено.

стандарт не гарантирует, что all-bits-zero является допустимым представлением 0.0 (хотя это часто бывает), или любого double значением, или int и double имеют одинаковый размер (помните, что это double, а не float), или, даже если они имеют одинаковый размер, что они передаются в качестве аргументов для переменной функции в точно так же.

это может случиться, чтобы "работать" на вашей системе. Это худший из возможных симптомов неопределенного поведения, потому что это затрудняет диагностику ошибки.

N1570 7.21.6.1 пункт 9:

... Если какой-либо аргумент не является правильным типом для соответствующего спецификация преобразования, поведение не определено.

аргументы типа float звание double, вот почему printf("%f\n",0.0f) строительство. Аргументы целочисленных типов уже, чем int звание int или unsigned int. Настоящие правила акции (указанные в пункте 6.5.2.2 N1570) не помогают в случае printf("%f\n", 0).

во-первых, как было затронуто в нескольких других ответах, но не, на мой взгляд, достаточно четко прописано: это тут работать, чтобы обеспечить целое число в большинство контексты, в которых библиотечная функция принимает double или

обычно при вызове функции, которая ожидает 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

используя mis-matched printf() спецификатор "%f"и типа (int) 0 приводит к неопределенному поведению.

если спецификация преобразования является недопустимым, поведение неопределено. C11dr §7.21.6.1 9

причины кандидата UB.

  1. это UB за спецификацию, и компиляция является злобной - сказал Нуф.

  2. double и int имеют разные размеры.

  3. double и int может передавать свои значения, используя различные стеки (общие против 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.

независимо от того, имеют ли два типа одинаковую битную ширину, это совершенно не имеет значения, за исключением того, что это может помочь вам избежать получения исключений из жесткого нарушения памяти из сломанного кода, подобного этому.

"%f\n" гарантирует предсказуемый результат только при втором

почему это формально UB теперь обсуждается в нескольких ответах.

причина, по которой вы получаете именно такое поведение, зависит от платформы, но, вероятно, заключается в следующем:

  • printf ожидает своих доводов в соответствии со стандартом распространения количеством аргументов. Это означает float будет double и что-нибудь меньше, чем int будет int.
  • вы передаете int где функция ожидает double. Ваш int вероятно, 32 бит, ваш double 64 бит. Это означает, что четыре байта стека, начиная с места, где должен находиться аргумент, являются 0, но следующие четыре байта имеют произвольное содержимое. Это то, что используется для построения значения, которое отображается.

основная причина этой проблемы "неопределенное значение" стоит в приведении указателя на int значение printf раздел переменных параметров к указателю на double типы va_arg макрос выполняет.

это вызывает ссылку на область памяти, которая не была полностью инициализирована значением, переданным в качестве параметра printf, потому что double размер буферной области памяти больше, чем int размер.

поэтому, когда этот указатель разыменовывается, возвращается неопределенное значение или лучше "значение", которое частично содержит значение, переданное в качестве параметра в printf, а для остальной части может исходить из другой буферной области стека или даже кодовой области (вызывая исключение ошибки памяти),реальное переполнение буфера.


он может рассмотреть эти конкретные части семификаторных реализаций кода "printf" и "как 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..
.... 


реальное осуществление в vprintf для расширенных символов (с учетом гну осущ.) случая кода параметров двойного значения менеджмент-это:

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



ссылки

  1. gnu project glibc реализация "printf"(vprintf))
  2. пример кода семплификации printf
  3. пример кода семплификации va_arg