Как преобразование float/double в int обрабатывается в printf?

Рассмотрим эту программу

int main()
{
        float f = 11.22;
        double d = 44.55;
        int i,j;

        i = f;         //cast float to int
        j = d;         //cast double to int

        printf("i = %d, j = %d, f = %d, d = %d", i,j,f,d);
        //This prints the following:
        // i = 11, j = 44, f = -536870912, d = 1076261027

        return 0;
}

Может кто-нибудь объяснить, почему кастинг из double/float в int работает правильно в первом случае и не работает, когда выполняется в printf?
Эта программа была скомпилирована на gcc-4.1.2 на 32-битной Linux-машине.


EDIT: Ответ Zach кажется логичным, то есть использовать спецификаторы формата, чтобы выяснить, что нужно удалить из стека. Однако рассмотрим следующий вопрос:

int main()
{

    char c = 'd';    // sizeof c is 1, however sizeof character literal
                     // 'd' is equal to sizeof(int) in ANSI C

    printf("lit = %c, lit = %d , c = %c, c = %d", 'd', 'd', c, c);
    //this prints: lit = d, lit = 100 , c = d, c = 100
    //how does printf here pop off the right number of bytes even when
    //the size represented by format specifiers doesn't actually match 
    //the size of the passed arguments(char(1 byte) & char_literal(4 bytes))    

 return 0;
}

Как это работает?

Ответ 1

Функция printf использует спецификаторы формата, чтобы выяснить, что нужно удалить из стека. Поэтому, когда он видит %d, он выталкивает 4 байта и интерпретирует их как int, что неверно (двоичное представление (float)3.0 не совпадает с (int)3).

Вам нужно либо использовать спецификаторы формата %f, либо применить аргументы к int. Если вы используете новую версию gcc, то включение более сильных предупреждений вызывает такую ​​ошибку:

$ gcc -Wall -Werror test.c
cc1: warnings being treated as errors
test.c: In function ‘main’:
test.c:10: error: implicit declaration of function ‘printf’
test.c:10: error: incompatible implicit declaration of built-in function ‘printf’
test.c:10: error: format ‘%d’ expects type ‘int’, but argument 4 has type ‘double’
test.c:10: error: format ‘%d’ expects type ‘int’, but argument 5 has type ‘double’

Ответ на отредактированную часть вопроса:

C целочисленных правил продвижения говорят, что все типы, меньшие, чем int, получают до int при передаче в качестве vararg. Таким образом, в вашем случае 'd' получает повышение до int, а затем printf выталкивает int и бросает на char. Лучшей ссылкой, которую я смог найти для этого поведения, была эта запись в блоге.

Ответ 2

Нет такой вещи, как "приведение к int в printf". printf не выполняет и не может делать кастинг. Несогласованный спецификатор формата приводит к поведению undefined.

На практике printf просто получает необработанные данные и интерпретирует его как тип, подразумеваемый спецификатором формата. Если вы передадите значение double и укажите спецификатор формата int (например, %d), printf примет значение double и слепо переинтерпретирует его a int. Результаты будут полностью непредсказуемыми (вот почему это формально вызывает поведение undefined в C).

Ответ 3

Ответ Джека объясняет, как исправить вашу проблему. Я объясню, почему вы получаете неожиданные результаты. Ваш код эквивалентен:

float f = 11.22;
double d = 44.55;
int i,j,k,l;

i = (int) f;
j = (int) d;
k = *(int *) &f;         //cast float to int
l = *(int *) &d;         //cast double to int

printf("i = %d, j = %d, f = %d, d = %d", i,j,k,l);

Причина в том, что f и d передаются в printf как значения, а затем эти значения интерпретируются как int s. Это не изменяет двоичное значение, поэтому отображаемый номер является двоичным представлением float или double. Фактическое отбрасывание от float до int намного сложнее в сгенерированной сборке.

Ответ 4

Поскольку вы не используете спецификатор формата float, попробуйте:

printf("i = %d, j = %d, f = %f, d = %f", i,j,f,d);

В противном случае, если вы хотите 4 ints, вам нужно передать их перед передачей аргумента printf:

printf("i = %d, j = %d, f = %d, d = %d", i,j,(int)f,(int)d);

Ответ 5

Причина, по которой ваш последующий код работает, заключается в том, что символьная константа продвигается до int до того, как она будет перенесена в стек. Поэтому printf выдает 4 байта для% c и для% d. На самом деле символьные константы имеют тип int, а не тип char. C странно так.

Ответ 6

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

Ответ 7

Стоит отметить, что printf, являясь функцией с списком аргументов переменной длины, никогда не получает float; Поплавковые аргументы - это "старая школа", которая удваивается.

В недавнем стандартном проекте сначала предлагаются акции "старой школы" по умолчанию (n1570, 6.5.2.2/6):

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

Затем он обсуждает списки переменных аргументов (6.5.2.2/7):

обозначение многоточия в деклараторе прототипа функции вызывает аргумент преобразование типа для остановки после последнего объявленного параметра. Значение по умолчанию Продвижение аргументов выполняется по завершающим аргументам.

Следствием для printf является то, что невозможно "напечатать" настоящий поплавок. Выражение float всегда повышается до double, что является 8-байтным значением для реализации IEEE 754. Это продвижение происходит на вызывающей стороне; printf уже будет иметь 8-байтовый аргумент в стеке при его запуске.

Если мы присваиваем 11.22 двойнику и проверяем его содержимое, с моим x86_64-pc-cygwin gcc я вижу последовательность байтов 000000e0a3702640.

Это объясняет значение int, напечатанное с помощью printf: Ints на этой цели все еще имеют 4 байта, так что оцениваются только первые четыре байта 000000e0 и снова в little endian, то есть как 0xe0000000. Это -536870912 в десятичном формате.

Если мы отменим все 8 байтов, так как процессор Intel хранит двойные символы в маленьком endian, мы получаем 402670a3e0000000. Мы можем проверить значение, которое эта последовательность байтов представляет в формате IEEE на этом веб-сайте; он близок к 1.122E1, то есть 11.22, ожидаемый результат.