В C ++, обязательно ли функции variadic (с ... в конце списка параметров) следуют соглашению о вызовах __cdecl? - PullRequest
7 голосов
/ 25 марта 2010

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

Ответы [ 5 ]

8 голосов
/ 25 марта 2010

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

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

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

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

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

2 голосов
/ 25 марта 2010

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

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

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

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

1 голос
/ 13 апреля 2016

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

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

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

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

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

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

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

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

нажмите здесь

0 голосов
/ 25 марта 2010

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

0 голосов
/ 25 марта 2010

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

...