Что происходит с переменной float, когда% d используется в printf? - PullRequest
15 голосов
/ 20 сентября 2011

Я пытаюсь выучить C, читая Язык программирования C, 2-е издание .У меня есть некоторый опыт программирования, но не с C.

Я в настоящее время в Главе 1. У меня есть следующий код:


  float f;
  for (f = 0.0; f <= 3; f += 1.1)
      printf("A: %3f B: %6.2f\n", f, f + 0.15);

Он печатает вывод:

A: 0.000000 B:   0.15
A: 1.100000 B:   1.25
A: 2.200000 B:   2.35

Выглядит нормально.


Теперь я изменяю printf следующим образом:

printf("A: %3d B: %6.2f\n", f, f + 0.15);

Новый вывод

A:   0 B:   0.00
A: -1610612736 B:   0.00
A: -1610612736 B: -625777476808257557292155887552002761191109083510753486844893290688350183831589633800863219712.00

Что происходитЗдесь?Я ожидаю, что float будет преобразован в int, потому что я использовал% d, но это не то, что случилось.Кроме того, почему значение B также пошло не так?Что здесь произошло?

Ответы [ 3 ]

16 голосов
/ 20 сентября 2011

Когда вы позвонили:

printf("A: %3d B: %6.2f\n", f, f + 0.15);

C автоматически преобразует значения float в double (это стандартное преобразование, выполняемое при вызове функции, которая принимает переменные аргументы, такие как int printf(const char *fmt, ...);). В качестве аргумента предположим, что sizeof(int) равно 4, а sizeof(double) равно 8 (есть исключения, но они немногочисленны и далеко друг от друга).

Вызов, следовательно, поместил указатель на стек, плюс 8-байтовый дубль для f и еще один 8-байтовый дубль для f + 0.15. Когда он обрабатывает строку формата, %d сообщает printf(), что вы поместили 4-байтовый int в стек после строки формата. Поскольку это не то, что вы сделали, вы вызвали неопределенное поведение; что бы ни случилось дальше, все в порядке в соответствии со стандартом C.

Однако наиболее вероятная реализация беспечно читает 4 байта и печатает их, как если бы они были int (это позволяет вам сказать правду). Затем встречается формат %6.2f; он будет читать 8 байт со стека как double. Существует внешняя вероятность того, что это приведет к ошибке памяти для не выровненного доступа (для этого потребуется 64-разрядный компьютер с требованием выравнивания double на 8-байтовой границе, такой как SPARC), или будет показано 4 байты от f и 4 байта от f + 0.15, соединяя их вместе для создания довольно неожиданного значения double - как показывает ваш пример.

8 голосов
/ 20 сентября 2011

Printf будет обрабатывать указанную вами память так, как вы говорите.Там нет преобразования не происходит.Он обрабатывает память, которая представляет число с плавающей точкой как int.Поскольку эти два хранятся по-разному, вы получите то, что по сути является случайным числом.

Если вы хотите вывести ваш float в виде целого числа, вы должны сначала разыграть его:

printf("A: %3d B: %6.2f\n", (int)f, f + 0.15); 
0 голосов
/ 21 марта 2019

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

  printf("A: %d B: %6.2f\n", f, f + 0.15);

Вот как вы можете печатать произвольные целые числа на архитектуре Intel:

  int print_it(int, int /* nameless but printed */, float f)
  {
      printf("A: %d B: %6.2f\n", f, f + 0.15);
  }
  int main()
  {
      print_it(0, 12 /* will be printed */, 0.0);
      print_it(0, 123 /* printed */, 1.1);
      print_it(0, 1234 /* printed */ , 2.2);
  }

Этот вывод:

A: 12 B:   0.00
A: 123 B:   1.10
A: 1234 B:   2.20

Объяснение: Очевидно, что несовпадающая строка формата и параметры приводят к неопределенному поведению. Тем не менее, иногда это можно предсказать. В архитектуре Intel первые несколько параметров передаются регистрами. Значения с плавающей запятой передаются в разные регистры.

Несмотря на то, что инструкция printf такая же, как и в вопросе, вывод отличается. Что происходит, так это то, что 12, 123, 1234 передаются через регистр общего назначения, отвечающий за второй параметр без плавающей запятой. Поскольку printf имеет только один параметр без плавающей запятой, регистр второго параметра без плавающей запятой не изменяется. Этот регистр сохраняет значение, полученное из второго параметра print_it(0, int_value, fp_value).

Но оригинал дает мусор:

  for (f = 0.0; f <= 3; f += 1.1)
      printf("A: %3f B: %6.2f\n", f, f + 0.15);

Это дает различный мусор, потому что printf вызывает другие функции внутри. Эти функции уничтожают регистр общего назначения, который читает printf("... %d ...", ...).

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

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