Почему 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 ответов:
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.
это UB за спецификацию, и компиляция является злобной - сказал Нуф.
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
.независимо от того, имеют ли два типа одинаковую битную ширину, это совершенно не имеет значения, за исключением того, что это может помочь вам избежать получения исключений из жесткого нарушения памяти из сломанного кода, подобного этому.
почему это формально 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"...
printfva_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_argchar *p = (double *) &arg + sizeof arg; //printf parameters area pointer double i2 = *((double *)p); //casting to double because va_arg(arg, double) p += sizeof (double);
ссылки