Во-первых, как было сказано в нескольких других ответах, но, на мой взгляд, недостаточно четко сказано: он действительно работает, чтобы предоставить целое число в большинстве контекстов, где библиотечная функция принимает аргумент double
или float
. Компилятор автоматически вставит преобразование. Например, sqrt(0)
он четко определен и будет вести себя точно так же sqrt((double)0)
, и то же самое верно для любого другого выражения целого типа, используемого в нем.
printf
отличается. Он отличается, потому что требует переменного количества аргументов. Его функциональным прототипом является
extern int printf(const char *fmt, ...);
Поэтому, когда вы пишете
printf(message, 0)
компилятор не имеет информации о том, какой тип printf
ожидает от второго аргумента. У него есть только тип выражения аргумента, которым int
нужно следовать. Поэтому, в отличие от большинства библиотечных функций, вы, программист, должны убедиться, что список аргументов соответствует ожиданиям строки формата.
(Современные компиляторы могут заглянуть в строку формата и сказать вам, что у вас есть несоответствие типов, но они не собираются начинать вставку преобразований, чтобы выполнить то, что вы имели в виду, потому что лучше ваш код должен сломаться сейчас, когда вы заметите , чем годы спустя, когда был перестроен с помощью менее полезного компилятора.)
Другая половина вопроса заключалась в следующем: учитывая, что (int) 0 и (float) 0.0 в большинстве современных систем представлены как 32 бита, все из которых равны нулю, почему это все равно не работает случайно? Стандарт C просто говорит: «Это не обязательно для работы, вы сами по себе», но позвольте мне указать две наиболее распространенные причины, по которым это не сработает; это, вероятно, поможет вам понять, почему это не требуется.
Во-первых, по историческим причинам, когда вы передаете a float
через список переменных аргументов, он продвигается к нему double
, который в большинстве современных систем имеет ширину 64 бита. Таким образом, printf("%f", 0)
вызываемому, ожидающему 64 из них, передается только 32 нулевых бита.
Вторая, не менее важная причина состоит в том, что аргументы функции с плавающей запятой могут передаваться в другом месте, чем целочисленные аргументы. Например, большинство процессоров имеют отдельные файлы регистров для целых чисел и значений с плавающей запятой, поэтому может быть правилом, что аргументы от 0 до 4 идут в регистры с r0 по r4, если они целые числа, но с f0 по f4, если они с плавающей запятой. Итак, printf("%f", 0)
ищем этот ноль в регистре f1, но его там совсем нет.
printf
ожидаетdouble
, и вы даете ейint
.float
иint
может иметь такой же размер на вашем компьютере, но0.0f
фактически преобразуется в adouble
при добавлении в список аргументов с переменным числом аргументов (иprintf
ожидает этого). Короче говоря, вы не выполняете свою часть сделки,printf
основываясь на спецификаторах, которые вы используете, и аргументах, которые вы предоставляете.