Как различные вариационные функции, такие как printf, обнаруживают количество аргументов, которые они получили?
Количество аргументов, очевидно, не передается как (скрытый) параметр (см. вызов printf в примере asm здесь).
Какой трюк?
Как различные вариационные функции, такие как printf, обнаруживают количество аргументов, которые они получили?
Количество аргументов, очевидно, не передается как (скрытый) параметр (см. вызов printf в примере asm здесь).
Какой трюк?
Фокус в том, что вы рассказываете им как-то иначе. Для printf
вам необходимо указать строку формата, которая даже содержит информацию о типе (которая может быть некорректна, хотя). Способ предоставления этой информации в основном зависит от пользователя и часто подвержен ошибкам.
Как для конвенций вызова: Обычно аргументы вставляются в стек слева направо и затем, наконец, обратный адрес. Вызывающая процедура очищает стек. Поэтому нет технической необходимости для вызываемой подпрограммы знать количество параметров.
EDIT: в С++ 0x существует безопасный способ (даже typafe!) для вызова вариативных функций!
Неявно, из строки формата. Обратите внимание, что 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) действительно передает переменные числового вектора (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