Передача слишком большого количества аргументов в printf

Любой программист на Си, который работал более недели, сталкивался со сбоями, возникающими в результате вызова printf с большим количеством спецификаторов формата, чем фактических аргументов, например:

printf("Gonna %s and %s, %s!", "crash", "burn");

Однако есть ли подобные плохие вещи, которые могут произойти, если вы передаете слишком много аргументов в printf?

printf("Gonna %s and %s!", "crash", "burn", "dude");

Мои знания по сборке x86/x64 позволяют мне верить в то, что это безвредно, хотя я не уверен, что я пропускаю ни одно граничное условие, и я понятия не имею о других архитектурах. Гарантируется ли это условие безопасным, или здесь также есть потенциально опасная ловушка?

5 ответов

Решение

Вы, вероятно, знаете прототип функции printf примерно так

int printf(const char *format, ...);

Более полная версия этого на самом деле будет

int __cdecl printf(const char *format, ...);

__cdecl определяет "соглашение о вызовах", которое, наряду с другими вещами, описывает, как обрабатываются аргументы. В этом случае это означает, что аргументы помещаются в стек и что стек очищается функцией, выполняющей вызов.

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

Короче говоря, в случае __cdecl функции безопасны для передачи сколько угодно аргументов, так как очистка выполняется в коде, выполняющем вызов. Если бы вы как-то передать слишком много аргументов __stdcall Функция это приводит к повреждению стека. Один из примеров того, где это может произойти, - если у вас был неправильный прототип.

Более подробную информацию о соглашениях о вызовах можно найти в Википедии здесь.

Проект стандарта C на сайте (n1256), раздел 7.19.6.1, пункт 2:

Функция fprintf записывает вывод в поток, на который указывает поток, под управлением строки, на которую указывает формат, которая указывает, как последующие аргументы преобразуются для вывода. Если для формата недостаточно аргументов, поведение не определено. Если формат исчерпан, пока остаются аргументы, избыточные аргументы оцениваются (как всегда), но в противном случае игнорируются. Функция fprintf возвращает, когда встречается конец строки формата.

Поведение для всех остальных *printf() функции одинаковы по отношению к избыточным аргументам, за исключением vprintf() (Очевидно).

Все аргументы будут помещены в стек и удалены, если будет удален кадр стека. это поведение не зависит от конкретного процессора. (Я помню только мэйнфрейм без стека, созданный в 70-х годах). Итак, второй пример не провалится.

printf предназначен для принятия любого количества аргументов. Затем printf читает спецификатор формата (первый аргумент) и извлекает аргументы из списка аргументов по мере необходимости. Вот почему происходит сбой слишком небольшого числа аргументов: код просто начинает использовать несуществующие аргументы, обращаясь к несуществующей памяти или к чему-то другому. Но при слишком большом количестве аргументов дополнительные аргументы будут просто игнорироваться. Спецификатор формата будет использовать меньше аргументов, чем было передано.

Комментарий: и gcc, и clang выдают предупреждения:

$ clang main.c 
main.c:4:29: warning: more '%' conversions than data arguments [-Wformat]
  printf("Gonna %s and %s, %s!", "crash", "burn");
                           ~^
main.c:5:47: warning: data argument not used by format string 
                      [-Wformat-extra-args]
  printf("Gonna %s and %s!", "crash", "burn", "dude");
         ~~~~~~~~~~~~~~~~~~                   ^
2 warnings generated.
Другие вопросы по тегам