Почему printf берет последний напечатанный номер? - PullRequest
4 голосов
/ 05 ноября 2011

РЕДАКТИРОВАТЬ: я уже знал, что printf не безопасен, я просто ищу объяснение того, что именно произошло (я имею в виду описать неопределенное поведение)

Почему, если я печатаю «7» во втором printf, программа печатает 9.334354. Я знаю, что если я не напишу 7.0, это не будет напечатано, но почему вместо этого берется первое записанное число?.

#include <stdio.h>  

int main()  
{  
    printf("%.2f\n", 9.334354);    
    printf("%.5f\n", 7);  
    printf("%03d\n", 9);  
    getchar();  
}

Это вывод

    9.33
    9.33435
    009

Ответы [ 6 ]

16 голосов
/ 05 ноября 2011

Повторите это для себя в течение двух недель один раз перед сном:

printf небезопасен. printf небезопасен. printf небезопасен.

Функция будет работать, только если вы передадите ей аргумент того типа, который вы обещаете. Все остальное - неопределенное поведение. Вы обещаете double (через %f), но предоставляете int (тип литерала 7), так что это неопределенное поведение. Позор вам.

(я однажды углубился в подробности , чтобы объяснить фактический результат, если вам интересно.)


Обновление: Поскольку вас интересует объяснение этого конкретного поведения, вот (соответствующая) сборка для этого кода на моем x86 / GCC4.6.2 / -O3:

Первые разделы данных:

.LC0:
        .long   1921946325
        .long   1076013872   // 0x 4022AB30 728E92D5 is the binary rep of 9.334354
.LC1:
        .string "%.2f\n"
.LC2:
        .string "%.5f\n"
.LC3:
        .string "%03d\n"

Теперь код:

        fldl    .LC0              // load number into fp register
        fstpl   4(%esp)           // put 64-bit double on the stack
        movl    $.LC1, (%esp)     // first argument (format string)
        call    printf            // call printf

        movl    $7, 4(%esp)       // put integer VA (7) onto stack
        movl    $.LC2, (%esp)     // first argument (format string)
        call    printf            // call printf

        movl    $9, 4(%esp)       // put integer VA (9) onto stack
        movl    $.LC3, (%esp)     // first argument (format string)
        call    printf            // call printf

Причина, по которой вы видите то, что вы видите, проста сейчас. Давайте на минутку переключимся на полный 17-значный вывод:

  printf("%.17f\n", 9.334354);
  printf("%.17f\n", 7);

Получаем:

9.33435399999999937
9.33435058593751243

Теперь давайте заменим целое число на «правильный» двоичный компонент:

 printf("%.17f\n", 9.334354);
 printf("%.17f\n", 1921946325);

И вуаля:

9.33435399999999937
9.33435399999999937

В результате double занимает 8 байтов в стеке со значением 0x4022AB30728E92D5. Целое число занимает только 4 байта, и, как это бывает, наименее значимые четыре байта перезаписываются, поэтому значение с плавающей запятой остается почти таким же. Если вы перезаписываете четыре байта теми же байтами, которые встречаются в исходном плавающем значении, то вы получите точно такой же результат.

Могу добавить, что это просто удача, что старшие четыре байта остаются нетронутыми. При других обстоятельствах они могли быть перезаписаны чем-то другим. Короче говоря, "неопределенное поведение".

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

Как намекал Барри Браун, ваши 7 перезаписывают 4 байта 8-байтового двойного числа, ранее сохраненного в стеке.В зависимости от того, каким образом растет стек и каков порядок ваших двойников, 7 могут просто перезаписывать наименее значимые биты мантиссы двойника.Следовательно, удвоение в стеке фактически не идентично тому, что было в предыдущем вызове printf ();просто разница не видна в формате% .5f.

Printf () не является строго типобезопасным, но некоторые компиляторы проверяют оператор формата по аргументам и предупреждают вас, если вы сделаете что-то подобное

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

Вы используете неправильные спецификаторы формата.Вы передаете число с плавающей точкой (которое превращается в двойное число), поэтому printf ожидает 8 байтов в стеке, но вы передаете int, что составляет 4 байта.Напишите 7.0 и 9.0, чтобы превратить литералы в двойные.Или, как сказал Даниэль в комментариях, оптимизатор может вызвать все виды странного поведения.

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

7 - это int, но printf ожидает, что аргумент будет плавающим.Аргумент из предыдущего printf (9.334354) все еще находится в стеке, и только 7 недостаточно для перезаписи.

Если вы измените 7 на 7.0, он будет работать правильно.

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

Вы говорите printf, что вы передаете ему double (из-за спецификатора формата f), но вы фактически передаете ему int.Это неопределенное поведение , и буквально все может произойти.

0 голосов
/ 05 ноября 2011

7 - это целое число. 7.0 - это число с плавающей запятой / double.

printf не типобезопасен, поэтому он ожидает двойное число, но вы передаете ему целое число. Это означает неопределенное поведение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...