Функция printf
просматривает форматирующую строку и выводит каждый символ как он есть, буквально, пока не встретит спецификацию преобразования. Поскольку функция printf заранее не знает, сколько параметров будет ей передано, то каждый параметр считывается из стека по мере обработки форматирующей строки в соответствии с типом каждой спецификации преобразования. В рассматриваемом примере в форматирующей строке единственная спецификация преобразования – спецификация вывода целого числа со знаком %i, обеспечивает вставку в формируемую строку переменной целого типа. Функция ожидает, что переменная, соответствующая спецификации преобразования, будет передана функции printf вторым параметром. В архитектуре Intel (по крайней мере) параметры функций помещаются в стек до того, как будет создан стековый фрейм. Поэтому когда функция printf считывает свои параметры из стека, они ссылаются на данные в стеке, находящиеся ниже стекового фрейма....
Примечание
В этой главе термин «ниже» («под») применяется к данным, которые были помещены в стек раньше каких-то других. Для описания данных, помещенных позже, применяется термин «выше» («над»). В архитектуре Intel стек растет вниз. В архитектурах с растущим вниз стеком адрес вершины уменьшается по мере роста стека, поэтому данные, расположенные «ниже», хранятся в областях памяти с большими адресами, чем данные, расположенные «выше».
Тот факт, что область памяти с большим адресом логически располагается в стеке ниже, может вызвать путаницу. Когда про область стека говорят, что она выше другой, это означает, что эта область находится ближе к вершине стека, чем другая.
В примере второй аргумент функции printf
– целое число, которое включается в формируемую строку в соответствии со спецификацией вывода целого числа со знаком %i. В результате в формируемой строке на месте спецификации формата будет помещено значение переменной integer в формате целого десятичного числа, которое равно 10.В соответствии со спецификацией вывода целого числа со знаком функция printf
для формирования выводимой строки использует содержимое области памяти, размер которой совпадает с размером переменной целого типа и которая расположена в нужном месте стека. Сначала в соответствии со спецификацией формата двоичное представление содержимого выбранной области стека преобразуется в символьное представление, а затем включается в формируемую строку. Как будет показано позже, это происходит независимо от того, передан на самом деле второй параметр функции printf или нет. Если ни одного параметра, соответствующего спецификациям формата форматирующей строки, не было передано функции printf, то стековые данные вызывающей функции будут трактоваться как параметры, поскольку они занимают в стеке место предполагаемых параметров вызванной функции printf. Вернемся к примеру. Допустим, что впоследствии было решено выводить только статическую строку, но при этом забыли указать переменную, соответствующую спецификации формата. В конечном счете функция printf
вызывается следующим образом:printf(“this is the skeleton of the string, %i”); /* note: no argument. only a format string. */
Во время своего выполнения функция не знает, что ей забыли указать переменную, соответствующую спецификации вывода целого числа со знаком %i.
Поэтому при формировании строки функция printf прочтет целое число из области стека, в которую должен быть помещен второй параметр и которая занимает 4 байта под ее стековым фреймом. Если виртуальная память размещения второго параметра доступна, то программа продолжит свою работу и любые байты, оказавшиеся в области размещения второго аргумента, при работе функции будут проинтерпретированы и выведены как целое число. В результате будет напечатано следующее:[dma@victim server]$ ./format_example this is the skeleton of the string, -1073742952