Функция “printf” использует для этой цели строку спецификаторов, и ее вызов может выглядеть, например, так: “printf(“Name: %s\nAge: %d\nIndex: %x\n”, amp;s[0],age,index)”. Количество спецификаторов должно быть равно количеству передаваемых функции переменных. Но что произойдет, если равновесие нарушится?
Возможно два варианта - переменных больше, чем спецификаторов и переменных меньше, чем спецификаторов. Пока количество спецификаторов не превышает количества переданных параметров, не происходит ничего интересного, поскольку, из стека аргументы удаляются не самой функцией, а вызывающим ее кодом (который уж наверняка знает, сколько аргументов было передано) разбалансировки стека не происходит и все работает нормально. Но если количество спецификаторов превышает количество требуемых аргументов, функция, пытаясь прочитать очередной аргумент, обратится к «чужим» данным! Конкретное поведение кода зависит от компилятора и содержимого стека на момент вызова функции “printf”.
Сказанное будет рассмотрено ниже на примере следующей программы (на диске, прилагаемом к книге, она находится в файле “/SRC/printf.bug”):
· #include «stdio.h»
·
· main()
· {
· int a=0x666;
· int b=0x777;
· printf("%x %x\n",a);
·
·}
·
Если ее откомпилировать с помощью Microsoft Visual Studio 5.0-6.0, результат работы окажется следующий:
· 666 777
Программа выдала два числа, несмотря на то, что ей передавали всего одну переменную ‘a’. Каким же образом она сумела получить значение ‘b’? (а в том, что ‘777’ это действительно значение переменной ‘b’ сомневаться не приходится). Ответить на этот вопрос помогает дизассемблирование:
·.text:00401000 main proc near.text:00401000
·.text:00401000 var_8 = dword ptr -8
·.text:00401000 var_4 = dword ptr -4
·.text:00401000
·.text:00401000 push ebp
·.text:00401001 mov ebp, esp
·.text:00401001; Открывается кадр стека
·.text:00401003 sub esp, 8
·.text:00401003; Относительное значение esp равно 0 ( условно)
·.text:00401006 mov [ebp+var_4], 666h
·.text:00401006 ; var_4 - это переменная a , которую компилятор расположил в стеке
·.text:0040100D mov [ebp+var_8], 777h
·.text:0040100D ; var_8 - это переменная b
·.text:00401014 mov eax, [ebp+var_4]
·.text:00401014 ; В регистр eax загружается значение переменной ' a’ для передачи его функции printf
·.text:00401017 push eax
·.text:00401017 ;В стек заносится значение переменной eax
·.text:00401018 push offset aXX; "%x %x\n"
·.text:00401018; В стек заносится указатель на строку спецификаторов
·.text:00401018; Содержимое стека на этот момент такого
·.text:00401018; +8 off aXX (‘%x %x’) (строка спецификаторов)
·.text:00401018; +4 var_4 (‘a’) (аргумент функции printf)
·.text:00401018; 0 var_8 (‘b’) (локальная переменная)
·.text:00401018; -4 var_4 (‘a’) (локальная переменная)
·.text:0040101D call printf
·.text:0040101D; Вызов функции printf
·.text:00401022 add esp, 8
·.text:00401022; Выталкивание аргументов функции из стека
·.text:00401025 mov esp, ebp
·.text:00401025; Закрытие кадра стека
·.text:00401027 pop ebp
·.text:00401028 retn
·.text:00401028 main endp
Итак, содержимое стека на момент вызова функции printf такого (смотри комментарии к дизассемблированному листингу) [315]:
· +8 off aXX ( ‘%x %x’) (строка спецификаторов)
· +4 var_4 (‘a’) (аргумент функции printf)
· 0 var_8 (‘b’) (локальная переменная)
· -4 var_4 (‘a’) (локальная переменная)
Но функция не знает, что ей передали всего один аргумент, - ведь строка спецификаторов требует вывести два (“%x %x). А поскольку аргументы в Си заносятся слева на право, самый левый аргумент расположен в стеке по наибольшему адресу. Спецификатор “%x” предписывает вывести машинное слово [316], переданное в стек по значению. Для сравнения - вот как выглядит стек на момент вызова функции “printf” в следующей программе (на диске, прилагаемом к книге, она расположена в файле “/SRC/printf.demo.c”):
· main()
· {
· int a=0x666;
· int b=0x777;
· printf("%x %x\n",a,b);
·
·}
·
· +12 off aXX ( ‘%x %x’) (строка спецификаторов)
· +08 var_4 (‘a’) (аргумент функции printf)
· +04 var_8 (‘ b’) (аргумент функции printf)
· 00 var_8 (‘b’) (локальная переменная)
· -04 var_4 (‘a’) (локальная переменная)
Дизассемблированный листинг в книге не приводится, поскольку он практически ни чем не отличается от предыдущего (на диске, прилагаемом к книге, он расположен в файле “/SRC/printf.demo.lst”). В стеке по относительному смещению [317]+4 расположен второй аргумент функции. Если же его не передать, то функция примет за аргумент любое значение, расположенное в этой ячейке.
Читать дальше