В C++ обязательно ли вариативные функции (с … в конце списка параметров) следуют соглашению о вызовах __cdecl?

StackOverflow https://stackoverflow.com/questions/2512746

Вопрос

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

Это было полезно?

Решение

Соглашение о вызовах должно быть таким, при котором вызывающая сторона очищает аргументы из стека (поскольку вызываемая сторона не знает, что будет передано).

Однако это не обязательно соответствует тому, что Microsoft называет «__cdecl».Например, в SPARC аргументы обычно передаются в регистрах, потому что SPARC предназначен для работы — его регистры в основном действуют как стек вызовов, который переносится в основную память, если вызовы становятся достаточно глубокими, что они больше не вписываются в реестр.

Хотя я менее уверен в этом, я ожидаю примерно того же от IA64 (Itanium) — у него также огромный набор регистров (пару сотен, если мне не изменяет память).Если я не ошибаюсь, использование регистров немного более либерально, но я ожидаю, что они будут использоваться одинаково, по крайней мере, большую часть времени.

Почему это важно для вас?Смысл использования stdarg.h и его макросов состоит в том, чтобы скрыть различия в соглашении о вызовах из вашего кода, чтобы он мог переносимо работать с переменными аргументами.

Редактировать, основываясь на комментариях:Хорошо, теперь я понимаю, что вы делаете (по крайней мере, достаточно, чтобы улучшить ответ).Учитывая, что у вас уже (по-видимому) есть код для обработки изменений в ABI по умолчанию, все становится проще.Остается только вопрос, всегда ли вариативные функции используют «ABI по умолчанию», каким бы он ни был для конкретной платформы.Учитывая, что «stdcall» и «default» являются единственными вариантами, я думаю, что ответ на этот вопрос — да.Например, в Windows wsprintf и wprintf нарушает эмпирическое правило и использует соглашение о вызовах cdecl вместо stdcall.

Другие советы

Самый точный способ определить это — проанализировать соглашения о вызовах.Для работы вариативных функций вашему соглашению о вызовах требуется несколько атрибутов:

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

stdcall не будет работать, поскольку вызываемый объект отвечает за извлечение параметров из стека.Во времена старой 16-битной Windows pascal не сработало бы, потому что параметры помещались в стек слева направо.

Конечно, как упоминалось в других ответах, многие платформы не дают вам никакого выбора с точки зрения соглашения о вызовах, что делает этот вопрос неактуальным для них.

Рассмотрим следующую функцию в системе x86:

void __stdcall что-то (char *, ...);

Функция объявляет себя как __stdcall, что соответствует соглашению об очистке вызываемого абонента.Но вариативная функция не может быть чистой для вызываемого объекта, поскольку вызываемый объект не знает, сколько параметров было передано, поэтому он не знает, сколько параметров ему следует очистить.

Компилятор Microsoft Visual Studio C/C++ разрешает этот конфликт, автоматически преобразуя соглашение о вызовах в __cdecl, которое является единственным поддерживаемым соглашением о вызовах с переменным числом вариантов для функций, которые не принимают скрытый параметр this.

Почему это преобразование происходит автоматически, а не генерирует предупреждение или ошибку?

Я предполагаю, что это сделано для того, чтобы сделать параметры компилятора /Gr (установить соглашение о вызовах по умолчанию __fastcall) и /Gz (установить соглашение о вызовах по умолчанию __stdcall) менее раздражающими.

Автоматическое преобразование функций с переменным числом аргументов в __cdecl означает, что вы можете просто добавить переключатель командной строки /Gr или /Gz к параметрам компилятора, и все по-прежнему будет компилироваться и выполняться (только с новым соглашением о вызовах).

Другой способ взглянуть на это — не думать о компиляторе как о преобразуя переменный __stdcall в __cdecl, а просто сказав: «Для функций с переменным числом аргументов __stdcall чист от вызывающего абонента».

кликните сюда

Вы имеете в виду «платформы, поддерживаемые MSVC» или как правило?Даже если вы ограничитесь платформами, поддерживаемыми MSVC, у вас все равно возникнут такие ситуации, как IA64 и AMD64 где существует только «одно» соглашение о вызовах, и это соглашение о вызовах называется __stdcall, но это конечно не то же самое __stdcall у тебя х86.

AFAIK, разнообразие соглашений о вызовах уникально для DOS/Windows на x86.На большинстве других платформ компиляторы поставлялись вместе с ОС и стандартизировали соглашение.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top