@dandropov95

Почему неправильный вывод значений в printf?

В книге С.Прата. Язык программирования С представлен пример кода, который показывает что в определенных случаях даже с использованием правильного спецификатора возможен не тот результат который ожидался изначально*.
Вроде как общая логика понятна, но непонятны некоторые моменты в объяснении. Там приводится следующий пример:
#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <conio.h>

int main(void)
{
	float a = 3.0;
	double b = 3.0;
	long c = 2000000000;
	long d = 1234567890;

	printf("%ld %ld %ld %ld", a, b, c, d);

	_getch();

	return 0;
}


И показываются результаты выполнения. Не совсем понятно откуда такой результат после объяснения. Там говорится что значения аргументов записываются в стек руководствуясь типами а не спецификаторами. Следовательно в стек сначала запишется "а" (64 бита), далее "b" (64 бита), потом "с" (32 бит) и в конце "d" (32 бита). Но при выводе, функция будет руководствоваться спецификаторами (везде %ld (32 бита)). Начнет считывать стек. Вот тут непонятно. Стек считывается как бы с конца, а спецификаторы записаны в порядке записи в стек аргументов. Вот встретился первый спецификатор, его нужно заменить на первый аргумент, но первым считываться из стека будет как раз таки последний аргумент. Вот этот момент не понятен, что происходит в момент считывания стека и замены спецификаторов.
5b75c4aa3ab02844925938.png
Там представлена такая картинка. Она еще больше непонятностей создает. Вроде как запись в стек отображена правильно, а вот считывание непонятно, не с того конца началось. Получается что это и не стек вовсе а очередь.

В итоге у меня получилось что вещественные числа со спецификатором %ld вывелись как нули, а переменные с типом long, вывелись как надо. (В книге же результат получился таким: 0 неожиданное число 0 неожиданное число).
Что то все не сходится у меня в голове. Объяснение в книге, результат в книге и мои результаты, все отличается и не сходится.

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

PS. Используя разные компиляторы, получаются разные результаты вывода. Получается даже разная последовательность значений. В одном случае числа с типом long выводятся первыми, а далее неправильные значения чисел double. В другом же случае наоборот.
PSS. Вычитал что стандарт такое поведение не определяет. Но все равно хочется понять как все таки компиляторы обрабатывают данную ситуацию.
  • Вопрос задан
  • 432 просмотра
Пригласить эксперта
Ответы на вопрос 1
@Mercury13
Программист на «си с крестами» и не только
У меня вышло: 0 1074266112 0 1074266112
Или, в 16-й системе, 0 40080000 0 40080000

Связано это с такими вещами.
1. Аргументы типа float записываются в стеке как double.
2. На x86 порядок байтов Intel (обратный).
3. Дробные числа хранятся без ведущего разряда (который всегда 1), в формате мантисса-порядок-знак (на порядке байтов Intel).
4. Для единицы (xxx·20) порядок будет 011…11.

3 = 1,10…02·2¹, и с учётом отброшенного ведущего разряда мантисса будет 10…0.
Порядок 011…11 + 1 = 10…0.

С учётом обратного порядка байтов double 3,0 будет выглядеть так
• 6 нулевых байтов — мантисса
• 0000.1000: нижние полбайта — это мантисса, верхние — уже порядок
• 0100.0000: бит знака и ещё семь битов порядка

Получается 00.00.00.00.00.00.08.40.
Разбиваем на два куска памяти по 4 байта.
[00.00.00.00] [00.00.08.40]
Опять-таки не забудем, что у целых тоже обратный порядок байтов — и получается 0 и 40080000.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы