роль стека со строками формата

Хорошо, это будет несколько длинно.

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


#include <stdio.h>
#include <stdlib.h>

 int main() {

 int A = 5, B = 7, count_one, count_two;

 printf("The number of bytes written up to this point X%n is being stored in
 count_one, and the number of bytes up to here X%n is being stored in
 count_two.\n", &count_one, &count_two);

 printf("count_one: %d\n", count_one);
 printf("count_two: %d\n", count_two);

 printf("A is %d and is at %08x. B is %x.\n", A, &A, B);

 exit(0); 
 } 

вывод программы:

The number of bytes written up to this point X is being stored in count_one,and the number of bytes up to here X is being stored in count_two.
count_one: 46
count_two: 113
A is 5 and is at bffff7f4. B is 7.

Что касается этого printf

printf("A — это %d и находится в %08x. B — это %x.\n", A, &A, B);

Теперь аргументы помещаются в стек в обратном порядке: сначала значение B, затем адрес значения A, затем адрес значения A и, наконец, адрес строки формата...

Теперь, если только два аргумента помещаются в стек со строкой формата, которая использует три параметра формата?

Поэтому я удалил последний аргумент в приведенном выше коде, чтобы он соответствовал этому аргументу printf().

printf("A — это %d и находится в %08x. B — это %x.\n", A, &A);

и что происходит, когда я компилирую и выполняю...

The number of bytes written up to this point X is being stored in count_one, and the number of
bytes up to here X is being stored in count_two.
count_one: 46
count_two: 113
A is 5 and is at bffffc24. B is b7fd6ff4

Итак, я понимаю это b7fd6ff4, что это? Что это означает применительно к фрейму стека для функции форматирования?

Любое понимание или объяснение будут высоко оценены.


person Boschko    schedule 30.11.2017    source источник
comment
Единственное общее объяснение состоит из двух слов неопределенное поведение. Стеки и прочее зависят от реализации и не имеют значения.   -  person Eugene Sh.    schedule 30.11.2017
comment
размещенный код, как опубликовано, не компилируется! первый вызов printf() имеет недопустимую строку формата (первый параметр). Теперь, если бы эта 3-строчная строка была на одной строке...   -  person user3629249    schedule 30.11.2017
comment
второй спецификатор формата последнего вызова printf() должен быть «%p», а не «%08x».   -  person user3629249    schedule 30.11.2017
comment
в опубликованном коде отсутствует одно или несколько утверждений (скорее всего, «}») в конце кода   -  person user3629249    schedule 30.11.2017
comment
Вместо того, чтобы публиковать приблизительный код, лучше публиковать настоящий компилируемый код.   -  person chux - Reinstate Monica    schedule 30.11.2017
comment
@chux это верный код, сохраните его как .c, затем gcc что угодно.c, затем ./a.out, и он запустится   -  person Boschko    schedule 30.11.2017
comment
@user3629249 user3629249 нет, это должно быть %08x, почему это должно быть %p???   -  person Boschko    schedule 30.11.2017
comment
Выложенный код не компилируется. Попробуйте вырезать / вставить из поста, чтобы увидеть.   -  person chux - Reinstate Monica    schedule 30.11.2017
comment
"%08x" предназначен для печати unsigned, а не указателей. "%p" предназначен для печати void* указателей.   -  person chux - Reinstate Monica    schedule 30.11.2017
comment
для простоты чтения и понимания: следуйте аксиоме: только одно выражение в строке и (максимум) одно объявление переменной в выражении.   -  person user3629249    schedule 30.11.2017


Ответы (4)


Неправильное количество спецификаторов формата или несоответствие спецификаторов формата вызывает неопределенное поведение.

Из раздела 7.21.6.1 стандарта C относительно функции fprintf:

9 Если спецификация преобразования недействительна, поведение не определено.282) Если какой-либо аргумент не является правильным типом для соответствующей спецификации преобразования, поведение не определено.

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

person dbush    schedule 30.11.2017
comment
ооо, так что вы говорите, что, поскольку 3-е значение нужно поместить в стек, функция форматирования извлекает данные из того места, где должен был быть третий аргумент? если да, то это делается путем добавления к текущему указателю кадра?? если нет то как? - person Boschko; 30.11.2017
comment
@Boschko Да, это делается путем добавления к текущему указателю кадра. Предполагая, что вы работаете с x86, все аргументы передаются в стек, а вызываемая функция извлекает эти аргументы, проходя по стеку. Обычно компилятор выдает ошибку, когда функция вызывается с неправильным количеством аргументов, но printf() является функцией с переменным числом аргументов, и ее можно вызывать с любым количеством аргументов. - person David; 30.11.2017

следующий предлагаемый код:

  1. исправляет проблемы, перечисленные в комментариях к вопросу
  2. чисто компилирует
  3. выполняет желаемую функцию

а теперь предлагаемый код:

#include <stdio.h>
#include <stdlib.h>

int main( void )
{
    int A = 5;
    int B = 7;
    int count_one;
    int count_two;

    printf("The number of bytes written up to this point X%n is being stored"
        " in count_one, and the number of bytes up to here X%n is being"
        " stored in count_two.\n",
        &count_one,
        &count_two);

    printf("count_one: %d\n", count_one);
    printf("count_two: %d\n", count_two);

    printf("A is %d and is at %p. B is %x.\n", A, (void*)&A, B);

    exit(0);
}

типичный запуск программы приводит к:

The number of bytes written up to this point X is being stored in count_one, and the number of bytes up to here X is being stored in count_two.
count_one: 46
count_two: 113
A is 5 and is at 0x7fff19c28ea8. B is 7.
person user3629249    schedule 30.11.2017
comment
При компиляции всегда включайте предупреждения, а затем исправьте эти предупреждения (для gcc при минимальном использовании: -Wall -Wextra -Wconversion -pedantic -std=gnu11 ) - person user3629249; 30.11.2017
comment
Попробуйте удалить последний аргумент из printf, чтобы он соответствовал printf("A is %d and is at %08x. B is %x.\n", A, &A);, и вы не получите B равно 7 - person Boschko; 30.11.2017
comment
и это не имеет смысла, если у вас есть 3 параметра формата и вводятся только 2 аргумента, как 7 может быть первым значением, найденным ниже кадра стека для функции формата - person Boschko; 30.11.2017
comment
@Boschko, количество спецификаторов формата должно соответствовать количеству следующих параметров в вызове printf(). В противном случае результатом будет неопределенное поведение. Вы «никогда» не хотите, чтобы ваш код содержал неопределенное поведение. То, что именно произойдет, когда вы нарушите это правило, зависит от окружающих данных в стеке и конкретной реализации. - person user3629249; 30.11.2017

В соответствии с соглашением о вызовах для 32-разрядных систем x86 все параметры функции передаются в стеке. Если вы вызовете printf() с большим количеством спецификаторов формата, чем аргументов, функция с радостью продолжит подниматься по стеку и захватывать любые значения, чтобы найти что-то для отображения.

Конкретное содержимое стека будет переменным — обычно это локальные переменные функции, указатели возврата функции или указатели стека.

person David    schedule 30.11.2017
comment
так что в основном 3-е значение не было помещено в стек? верно, тогда откуда функция форматирования брала данные? - person Boschko; 30.11.2017
comment
Разве используемое соглашение о вызовах не определяется компилятором, а не платформой? Конечно, у платформы есть ограничения, но должно быть много соглашений о вызовах, которые может выбрать компилятор. - person chux - Reinstate Monica; 30.11.2017
comment
@ Бошко Верно. Когда вы вызываете printf с тремя спецификаторами формата и двумя аргументами (после строки), то третье значение в стеке будет найдено там, где оно обычно ожидается. Размер аргументов определяется размером данных, указанных в спецификаторе формата. Например. int соответствует 4-байтовому значению, double соответствует 8-байтовому значению и т. д. - person David; 30.11.2017
comment
@chux Вы правы, соглашение о вызовах определяется языком/компилятором. Но в контексте вопроса о C/C++ соглашение о вызовах x86 может означать только соглашение о вызовах x86 в стиле C. - person David; 30.11.2017

printf предполагает, что вы поместили все 4 аргумента в стек. Ожидается, что B будет в стеке независимо от того, предоставили ли вы его или нет. Если вы не предоставили его, он будет использовать все, что находится в этой области памяти, и распечатает его.

person dernst    schedule 30.11.2017
comment
printf предполагает, что вы поместили все 4 аргумента в стек, безусловно, является деталью реализации. Это не указано С. - person chux - Reinstate Monica; 30.11.2017
comment
ну, это будет что-то вроде {верхняя часть стека} Строка формата адреса, значение A, значение A, значение B {нижняя часть стека}, как правило, функция форматирования выполняет итерацию по строке формата по одному символу за раз. .. поэтому, если он не находится в начале параметра формата, символ копируется в вывод. Правильно? - person Boschko; 30.11.2017