При необходимости для форума с именем Переполнение стека причина неожиданного вывода printf()
связана с смещением стека.Несоответствие размера между спецификацией преобразования %x
и аргументом функции a
вызывает смещение.
При компиляции оператора
printf("a is %x & b is %llx & c is %llx",a,b,c);
компилятор генерирует машинный код, который помещает аргументы функции встек в порядке справа налево.
Компилятор использует объявления переменных, а не строку формата, для определения размера данных каждого аргумента в стеке (за исключением, возможно, генерации предупреждений).В процессоре x86 (как и на большинстве машин) указатель стека уменьшается с каждым нажатием.Поэтому при входе в библиотечную функцию printf()
стек имеет следующую компоновку:
00A4: ...
00A0: 00000000
009C: 11111111 Variable 'c' pushed on the stack as uint64
0098: 00000000
0094: 87654321 'b' pushed on the stack as uint64
0090: 00000000
008C: 12345678 'a' pushed on the stack as uint64
0088: <pointer to format string>
0084: <return address>
В этом примере произвольно указан адрес вершины стека 0084.
Поскольку все трипеременные объявлены как uint64_t
, скомпилированный код помещает эти переменные в стек как 64-битные значения.Для машин с прямым порядком байтов, таких как процессор x86, старшие байты каждого значения uint64
заканчиваются старшими адресами.
Реализация printf()
использует строку формата для определения числа иразмеры аргументов в стеке.В отличие от компилятора, printf()
не получает информации об исходных объявлениях переменных.Первая спецификация преобразования - %x
, поэтому printf()
ожидает, что a
будет 32-разрядным значением, и поэтому printf()
анализирует макет стека следующим образом:
00A4: ...
00A0: 00000000
009C: 11111111
0098: 00000000 '%llx' reads 'c' as uint64, but from the wrong address
0094: 87654321
0090: 00000000 '%llx' reads 'b' as uint64, but from the wrong address
008C: 12345678 '%x' causes printf() to read 'a' as a uint32
0088: <pointer to format string>
0084: <return address>
Смещение стекаобъясняет, почему a
печатает 12345678, как и ожидалось, но b
и c
были эффективно сдвинуты влево на 32 бита до 8765432100000000 и 1111111100000000.
Исправление первой %x
спецификации преобразования или аргумента приведения a
до uint32 должны решить проблему.