Подтвердить что ты не робот

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

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

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

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

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

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

4b9b3361

Ответ 1

Вероятно, вы знаете прототип функции printf как-то вроде этого

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

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

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

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

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

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

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

Ответ 2

Стандарт веб-проекта (n1256), раздел 7.19.6.1, параграф 2:

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

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

Ответ 3

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

Ответ 4

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

Ответ 5

Комментарий: 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.