Как функции vararg обнаруживают количество аргументов в машинных кодах?
Как различные вариационные функции, такие как printf, обнаруживают количество аргументов, которые они получили?
Количество аргументов, очевидно, не передается как (скрытый) параметр (см. вызов printf в примере asm здесь).
Какой трюк?
Ответы
Ответ 1
Фокус в том, что вы рассказываете им как-то иначе. Для printf
вам необходимо указать строку формата, которая даже содержит информацию о типе (которая может быть некорректна, хотя). Способ предоставления этой информации в основном зависит от пользователя и часто подвержен ошибкам.
Как для конвенций вызова: Обычно аргументы вставляются в стек слева направо и затем, наконец, обратный адрес. Вызывающая процедура очищает стек. Поэтому нет технической необходимости для вызываемой подпрограммы знать количество параметров.
EDIT: в С++ 0x существует безопасный способ (даже typafe!) для вызова вариативных функций!
Ответ 2
Неявно, из строки формата. Обратите внимание, что stdarg.h не содержит макросов, чтобы получить общее количество "переменных" переданных аргументов. Это также является одной из причин, по которым конвенция вызова C требует, чтобы вызывающий пользователь очищал стек, хотя это увеличивает размер кода.
Ответ 3
Вот почему аргументы вытесняются в обратном порядке в 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");
Это приведет к удалению части стека.
Ответ 4
AMD64 System V ABI (Linux, Mac OS X) действительно передает переменные числового вектора (SSE/AVX) в al
(младший байт RAX), в отличие от любых стандартных соглашений о вызовах IA-32. См. также: Почему% eax обнуляется перед вызовом printf?
Но только до 8 (максимальное количество регистров для использования). И IIRC, ABI позволяет al
быть больше, чем фактическое количество аргументов XMM/YMM/ZMM, но оно не должно быть меньше. Так что, как правило, он не всегда показывает количество аргументов FP; Вы не можете сказать, сколько больше 8, и al
разрешено пересекать.
Это возможно только из соображений производительности, чтобы пропустить сохранение ненужных векторных регистров в "Область сохранения регистров", упомянутую в "3.5.7 Списки аргументов переменных". Например, GCC создает код, который тестирует al!=0
, а затем выгружает XMM0..7 в стек или ничего. (Или если функция где-нибудь использует VA_ARG
с __m256
, то YMM0..7.)
На уровне C есть и другие методы, помимо синтаксического анализа строки формата, как упоминалось другими. Вы также можете:
передайте дозорного (void *)0
, чтобы указать последний аргумент, как execl.
Вы захотите использовать атрибут функции sentinel
, чтобы помочь GCC принудительно применить его во время компиляции: C предупреждение Отсутствует страж в вызове функции
передать его в качестве дополнительного целочисленного аргумента с числом varargs
используйте атрибут функции format
, чтобы помочь GCC применять строки формата известных типов, таких как printf
или strftime
Related: Как переменные аргументы реализованы в gcc?