Как функции 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?