Передача слишком большого количества аргументов в 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.