Как функция printf обрабатывает спецификацию% f? - PullRequest
0 голосов
/ 22 ноября 2011

У меня есть пара программ, вывод которых я не могу понять:

Программа 1

#include <stdio.h>
int main(void)
{
  int i=1;
  float k=2;
  printf("k is %f \n",k);
  printf("i is %f \n",i);
  return 0;
}

Выход на http://codepad.org/ZoYsP6dc

k is 2.000000 
i is 2.000000

Программа 2

Теперь еще один пример

#include <stdio.h>

int main(void)
{
  char i='a';
  int a=5;
  printf("i is %d \n",i);  // here %d type cast char value in int
  printf("a is %f \n",a);  // hete %f dont typecast float value
  printf("a is %f \n",(float)a);  // if we write (float) with %f then it works
  return 0;
}

Здесь вывод на http://codepad.org/XkZVRg64

i is 97 
a is 2.168831 
a is 5.000000 

Вопрос

Что здесь происходит? Почему я получаю показанные результаты?

Ответы [ 5 ]

8 голосов
/ 22 ноября 2011

Возможно, вы запускаете это приложение на 64-битной архитектуре x86.В этой архитектуре аргументы с плавающей запятой передаются в регистрах XMM, тогда как целочисленные аргументы передаются в регистрах общего назначения.См. Система V Соглашение AMD64 ABI .

Поскольку %f ожидает значение с плавающей запятой:

printf("i is %f \n",i);

печатает значение из регистра XMM0, который являетсязначение k присвоено ранее, а не i передано в регистр RSI.Сборка выглядит следующим образом:

movl    $.LC1, %edi       # "k is %f \n"
movsd   .LC0(%rip), %xmm0 # float k = 2
call    printf
movl    $1, %esi          # int i = 1
movl    $.LC2, %edi       # "i is %f \n"
call    printf            # prints xmm0 because of %f, not esi

Если переупорядочить назначения следующим образом:

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

float k = 2;
printf("k is %f \n",k);

Она печатает:

i is 0.000000 
k is 2.000000 

Поскольку регистр XMM0 имеет значение0.

[Update] Воспроизводится также на 32-битном x86.На этой платформе printf() в основном приводит int* к double*, а затем читает double.Давайте изменим пример, чтобы было легче увидеть:

int main() {
    float k = 2;
    int i = -1;
    printf("k is %f \n",k);
    printf("i is %f \n",i,i);
}

64-битный вывод:

k is 2.000000 
i is 2.000000 

32-битный вывод:

k is 2.000000 
i is -nan 

То есть, 2 int s со значением -1 выглядят как double 0xffffffffffffffff, что является значением NaN.

8 голосов
/ 22 ноября 2011

Во-первых, для любой переменной функции, такой как printf(), все целочисленные значения типа короче int передаются как int (или unsigned int в некоторых случаях на некоторых платформах) и все float значения передаются как double. Поэтому ваш (float)a разыгрывается во второй раз до double.

Во-вторых, printf() сам поверит вам на слово. Если вы пропустите мусор, вы получите мусор. Точнее, если вы передадите целое число, в котором вы скажете printf() ожидать, что double, printf() попытается прочитать double из списка параметров. Что происходит дальше, это неопределенное поведение.

Некоторые компиляторы, в частности GCC, сообщают о несоответствиях типов между форматами литеральных строк и соответствующим списком параметров. Если ваш обычный компилятор не выполняет этот анализ, рассмотрите возможность использования компилятора, который - по крайней мере, для предварительной компиляции, чтобы разобраться в ошибках компиляции.

2 голосов
/ 22 ноября 2011

Компилятор не выполняет никаких преобразований типов на основе спецификаторов формата, таких как %f. Каждый параметр передается обычным способом, и предоставление аргумента, который не соответствует его спецификатору формата, является ошибкой в ​​вашей программе, приводящей к неопределенному поведению.

1 голос
/ 22 ноября 2011

Это еще интереснее. Я протестировал его на трех разных машинах Linux и получил два разных результата для второй строки вывода:

gcc 4.5.1     32bit: a is -0.000000
gcc 4.5.1 -Ox 32bit: a is 0.000000
gcc 4.5.1     64bit: a is 0.000000
gcc 4.6.2     64bit: a is 0.000000 
1 голос
/ 22 ноября 2011
printf("i is %d \n",i); 

Ничего странного здесь.Символ a является числом 97 в ASCII .

printf("a is %f \n",a);

a - это целое число со значением 5. В памяти это будут байты [0x5 0x0 0x0 0x0].Однако эта команда лежит на printf.Он говорит: «Просто поверь мне, что a указывает на число с плавающей точкой».printf верит тебе.Поплавки довольно странные, но в основном они работают как научная нотация, но в базе 2, а не в базе 10. Случайным образом способ, которым вы задаете 2.1688 в качестве числа с плавающей запятой, оказывается [0x5 0x0 0x0 0x0].Так вот что printf показывает вам.

printf("a is %f \n",(float)a);

Здесь вы сказали компилятору, что хотите преобразовать a в число с плавающей точкой, прежде чем printf увидит его.Компилятор C знает, как изменить выражение на 5 в странном формате с плавающей точкой.Таким образом, printf получает то, что ожидает, и вы видите 5.0000

Последующий крутой эксперимент: Хотите увидеть что-то еще аккуратное?Попробуйте

union data {
    int i;
    float f;
    char ch[4];
};
union data d;
d.i = 5;
printf("How 5 is represented in memory as an integer:\n");
printf("0x%X 0x%X 0x%X 0x%X\n", v.ch[0], v.ch[1], v.ch[2], v.ch[3]);
d.f = 5.0;
printf("How 5 is represented in memory as a float:\n");
printf("0x%X 0x%X 0x%X 0x%X\n", v.ch[0], v.ch[1], v.ch[2], v.ch[3]);

// OUTPUT:
// How 5 is represented in memory as an integer:
// 0x5 0x0 0x0 0x0
// How 5 is represented in memory as a float:
// 0x0 0x0 0xFFFFFFA0 0x40

Вы действительно можете увидеть, как компилятор C меняет данные для вас (при условии, что это работает ...)

...