Как функции vararg узнают количество аргументов в машинном коде?
Как различные функции, такие как printf, могут узнать количество полученных аргументов?
Количество аргументов, очевидно, не передается как (скрытый) параметр (см. Вызов printf в примере asm здесь).
В чем подвох?
4 ответа
Хитрость в том, что вы говорите им как-то еще. За printf
Вы должны предоставить строку формата, которая даже содержит информацию о типе (которая может быть неправильной, хотя). Способ предоставления этой информации в основном заключается в пользовательском контракте и часто подвержен ошибкам.
Что касается соглашений о вызовах: Обычно аргументы помещаются в стек слева направо, а затем, наконец, адрес обратного адреса. Вызывающая процедура очищает стек. Таким образом, нет никакой технической необходимости для вызываемой подпрограммы знать количество параметров.
РЕДАКТИРОВАТЬ: В C++0x есть безопасный способ (даже безопасное для типов!) Для вызова функций с переменными!
Неявно, из строки формата. Обратите внимание, что stdarg.h не содержит макросов для получения общего "переменного" числа переданных аргументов. Это также одна из причин, по которой соглашение о вызовах C требует, чтобы вызывающая сторона очищала стек, даже если это увеличивает размер кода.
Это причина, почему аргументы выдвигаются в обратном порядке в соглашении о вызовах C, например:
Если вы позвоните:
printf("%s %s", foo, bar);
Стек заканчивается как:
...
+-------------------+
| bar |
+-------------------+
| foo |
+-------------------+
| "%s %s" |
+-------------------+
| return address |
+-------------------+
| old frame pointer | <- frame pointer
+-------------------+
...
Аргументы принимаются косвенно, используя его смещение от указателя кадра (указатель кадра может быть опущен умными компиляторами, которые знают, как вычислять вещи из указателя стека). Первый аргумент всегда находится по общеизвестному адресу в этой схеме, функция получает столько аргументов, сколько ей говорят ее первые аргументы.
Попробуйте следующее:
printf("%x %x %x %x %x %x\n");
Это сбросит часть стека.
AMD64 System V ABI (Linux, Mac OS X) передает переменные вектора чисел (SEE / AVX) в
rax
в отличие от ИА-32. Смотрите также: Почему%eax обнуляется перед вызовом printf?ТОДО, почему это требуется? Я думаю, что это только из соображений производительности, чтобы избежать сохранения ненужных регистров SSE в "области сохранения регистров", упомянутой в "3.5.7 Списках аргументов переменной".
На уровне C есть и другие методы, помимо синтаксического анализа строки формата, как упоминалось другими. Вы также можете:
передать стражу
(void *)0
чтобы указать последний аргумент, как делает execl.Вы хотите использовать
sentinel
Атрибут функции, чтобы помочь GCC обеспечить это во время компиляции: C предупреждение Отсутствует страж в вызове функциипередать его как дополнительный целочисленный аргумент с числом varargs
использовать
format
Атрибут функции, чтобы помочь GCC применять строки формата известных типов, таких какprintf
или жеstrftime