Как функция printf работает в C?

Я столкнулся с проблемой при проверке функции printf:

Сначала я пишу код следующим образом:

int main(void)
{
    char a = 'a';
    printf("a = %f\n", a);
    return 0;
}

Выход

enter image description here

А потом я пишу код:

int main(void)
{
    float b = 'a';
    printf("b = %f\n", b);
    return 0;
}

Выход

enter image description here

А потом я пишу код:

int main(void)
{
    char a = 'a';
    float b = 'a';
    printf("b = %f\n", b);
    printf("a = %f\n", a);
    return 0;
}

Выход

enter image description here

Поэтому я смущен, почему в первой программе a = 0.000000 и в третьей программе a = 97.000000 ? < ш> Как работает функция printf() ?
Как работает символ % f , % d ?

5
RTFM, например. прочитайте printf (3) и/или printf
добавлено автор Basile Starynkevitch, источник
Вы запутались в printf, но float b = 'a'; подходит для вас? ;)
добавлено автор sebastian, источник
@sebastian извините, я не знаю, что вы имеете в виду.
добавлено автор Mr.CodeMonkey, источник

6 ответы

Update: after doing some more research on this, it seems that the differences between the float and int memory representations are not the ones responsible for the behaviour of the three programs.

Я просмотрел объектный код для третьей программы, и я нашел причину нечетного поведения: аргументы с плавающей запятой отправляются в другой реестр/стек, чем целые. И printf полагается на это и просматривает их в других местах, кроме тех, которые вызывающий вызов printf (т. Е. Метод main ) помещает аргументы.

Вот соответствующая разборка третьей программы (для архитектуры x86_64):

0000000100000f18    leaq    0x71(%rip), %rdi        ## literal pool for: "b = %f\n"
0000000100000f1f    movsd   0x61(%rip), %xmm0      ## b variable gets sent to xmm0
0000000100000f27    movl    $0x0, -0x4(%rbp)
0000000100000f2e    movb    $0x61, -0x5(%rbp)      ## a variable gets placed on the callee stack
0000000100000f32    movsd   %xmm0, -0x10(%rbp)
0000000100000f37    movsd   -0x10(%rbp), %xmm0
0000000100000f3c    movb    $0x1, %al
0000000100000f3e    callq   0x100000f66             ## symbol stub for: _printf
0000000100000f43    leaq    0x4e(%rip), %rdi        ## literal pool for: "a = %f\n"
0000000100000f4a    movsbl  -0x5(%rbp), %esi
0000000100000f4e    movl    %eax, -0x14(%rbp)
0000000100000f51    movb    $0x0, %al
0000000100000f53    callq   0x100000f66             ## symbol stub for: _printf

И printf полагается на это, он предполагает, что вызываемый пользователь разместил аргументы % f в xmm0 / xmm1 /etc регистры, а поведение трех программ выглядит так:

  1. в первой программе printf ищет аргумент % f в регистре xmm0, однако, поскольку мы находимся в начале программы, регистр является чистым и main разместил a в eax , поэтому xmm0 содержит нулевое значение, и это то, что printf печатает
  2. во второй программе main правильно помещает b в xmm0 , а printf берет его оттуда, печатает правильное значение
  3. в третьей программе из-за того, что сначала печатается b , регистр xmm0 будет удерживать это значение, а поскольку printf doesn 't беспорядок с регистром, когда он называется во второй раз, он снова извлекает из xmm0 , который остался неповрежденным после первого вызова printf .

So it's all about caller/callee conventions on where integers and floats are being send by the caller and from where the callee tries to pick them up.


Original response: In the first program you are trying to print a float, but you pass an int (char is a smaller int). Due to the fact that ints and floats have different binary representations, the int 97 (corresponding to the character 'a') corresponds to a very small float: 1.36E-43, that gets printed as zero.

Here is the binary representation of 97 (the compiler expands any 1-byte char to a 4-byte argument when calling a function)
00000000 00000000 00000000 01100001

IEEE 754 is the standard format for binary representations of float/double numbers, you can play with an online converter here, and you can see how the same binary number has different values when its interpreted as an int or as a float.

6
добавлено
Ну, я думаю, что это одно из тех неопределенных действий, которые вы получаете, когда не соответствуют строке формата и списку аргументов, переданных printf. Радуйтесь, что у вас не было крушения :)
добавлено автор Cristik, источник
Спасибо за ваш ответ. Но как объяснить третью программу?
добавлено автор Mr.CodeMonkey, источник
Я думаю, что выходной буфер выводит на выход в третьей программе. Потому что, когда я обмениваюсь двумя предложениями printf, выход выдает a = 0,000000 b = 97.000000.
добавлено автор Mr.CodeMonkey, источник

% f здесь представляет собой заменяющий токен для поплавка.

Чтобы заменить символ, вам понадобится % c .

Here is a list that tells you what is the appropriate replacement token for each type.

5
добавлено
@ Mr.CodeMonkey, то, что вы делаете, является неопределенным поведением, поэтому anything является допустимым результатом, вплоть до полного уничтожения Вселенной. Другими словами, не делайте этого :-)
добавлено автор paxdiablo, источник
Я это знаю. Но я хочу знать, когда я использую% f вместо% c, что будет делать функция printf? Как он читает регистр?
добавлено автор Mr.CodeMonkey, источник

Согласно последнему стандарту «C» это неопределенное поведение. Проверьте 7.21.6.1 п. 9 из стандартного черновика.

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

Поэтому, когда говорят, что есть неопределенное поведение, все возможно, и поведение может варьироваться от одного компилятора к другому. «C» давайте вам вырезать нос топором, но это не значит, что вы должны это сделать.

1
добавлено

% f для Float  % c для символов

The 97 which you have got is the ASCII value for 'a'
1
добавлено

Разница между

printf("%f\n, 97.0);

а также

printf("%c\n, 'a');

is that the printf function reads its parameters from the stack based on the %X you give, а также interprets them (for display) as such.

For %c printf expects a char as parameter, so it will read a char (a byte, but often actually a int, it's implementation dependant) а также displays it (it displays the less significant byte if an int is provided).

Для % f printf ожидает float (размер в байтах sizeof (float) , обычно 4 байта на процессорах gcc/Intel).

If you compile with gcc use the -Wall option that would give a warning when the %X format а также the type of the parameter do not match.

0
добавлено
Прочитайте отличный ответ от Cristik (выше, я думаю), который объясняет, почему вы получаете 0 в этом случае.
добавлено автор Ring Ø, источник
Я понимаю, что вы сказали. Но он не может объяснить мою программу. Когда я определяю как char a = 'a'; и printf ("a =% f \ n", a); Имеет ли printf 4 байта данных, начиная с адреса a? Если да, то выход не может быть 0, не так ли?
добавлено автор Mr.CodeMonkey, источник

%f is for float. You must use %c for characters.

Если вы используете

    printf("a = %c\n", a);

Вы получите персонажа.

Итак, если вы измените свой первый код на

int main(void)
{
    char a = 'a';
    printf("a = %c\n", a);
    return 0;
}

Вы получите результат как

a
0
добавлено
@ Mr.CodeMonkey, вы можете проверить вывод, который я получил здесь
добавлено автор Arun A S, источник
@ Mr.CodeMonkey, разница в выходе, вероятно, связана с тем, как интерпретирует его компилятор.
добавлено автор Arun A S, источник
Я знаю. Но я хочу знать, в чем разница между% f и% c?
добавлено автор Mr.CodeMonkey, источник
Ну почему? Я использую clodeblocks13.12. Мой os - Windows7 64bit. Вы знаете, почему наша продукция отличается?
добавлено автор Mr.CodeMonkey, источник
Я думаю, это зависит от данных в неинициализированной памяти.
добавлено автор Mr.CodeMonkey, источник