__cdecl приводит к большему исполняемому файлу, чем __stdcall? - PullRequest
4 голосов
/ 07 февраля 2012

Я нашел это:

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

Предположим, я получил 2 функции:

void __cdecl func1(int x)
{
    //do some stuff using x
}

void __stdcall func2(int x, int y)
{
    //do some stuff using x, y
}

и здесь в main():

int main()
{
    func1(5);
    func2(5, 6);
}

ИМО, main() несет ответственность за очистку стека вызова до func1(5), а func2 очистит стек вызова до func2(5,6), верно?

Четыре вопроса:

1. Для вызова func1 в main() ответственность main лежит на очистке стека, поэтому компилятор вставит некоторый код(код для очистки стека) до и после вызова func?Например:

int main()
{
    before_call_to_cdecl_func(); //compiler generated code for stack-clean-up of cdecl-func-call
    func1(5);
    after_call_to_cdecl_func(); //compiler generated code for stack-clean-up of cdecl-func-call

    func2(5, 6);
}

2. Для вызова func2 в main(), это func2 собственная работа по очистке стека, поэтому я предполагаю, что код не будет вставлен вmain() до или после вызова func2, верно?

3. Потому что func2 равно __stdcall, поэтому я предполагаю, что компилятор автоматически вставит код (для очистки стека) следующим образом:

void __stdcall func1(int x, int y)
{
    before_call_to_stdcall_func(); //compiler generated code for stack-clean-up of stdcall-func-call
    //do some stuff using x, y
    after_call_to_cdecl_func(); //compiler generated code for stack-clean-up of stdcall-func-call
}

Полагаю, верно?

4. Наконец, вернемся к цитируемым словам, почему __stdcall приводит к меньшему количеству исполняемых файлов, чем __cdecl?И нет такого понятия, как __stdcall в Linux, верно?Означает ли это, что linux elf всегда будет больше чем exe в win?

Ответы [ 4 ]

5 голосов
/ 07 февраля 2012
  1. Он будет вставлять код только после вызова, то есть для сброса указателя стека, до тех пор, пока там находятся аргументы вызова. *
  2. __stdcall не генерирует код очистки на сайте вызоваоднако следует отметить, что компиляторы могут накапливать очистку стека из нескольких вызовов __cdecl в одну очистку или могут задерживать очистку для предотвращения остановок конвейера.
  3. Игнорирование инвертированного порядка в этом примере, нет,он будет вставлять код только для очистки функции __cdecl, настройка аргументов функции - это что-то другое (разные компиляторы генерируют / предпочитают разные методы).
  4. __stdcall было больше для Windows, см. это .размер двоичного файла зависит от количества вызовов функции __cdecl, большее количество вызовов означает больший код очистки, где __stdcall имеет только 1 единичный экземпляр кода очистки.однако, вы не должны видеть такого большого увеличения размера, поскольку самое большее у вас есть несколько байтов на вызов.

* Важно различать очистку и настройку параметров вызова.

3 голосов
/ 07 февраля 2012

Исторически сложилось, что первые компиляторы C ++ использовали эквивалент __stdcall.С точки зрения качества реализации, я ожидаю, что компилятор C будет использовать __cdecl соглашения, а компилятор C ++ - __stdcall (которые тогда были известны как соглашения Pascal).Это одна вещь, которую ранние компиляторы Zortech получили правильно.

Конечно, функции vararg должны все еще использовать __cdecl соглашения.Вызываемый не может очистить стек, если он не знает, сколько нужно очистить.

(Обратите внимание, что стандарт C был тщательно разработан, чтобы разрешить также соглашения __stdcall в C. Я толькооднако известно об одном компиляторе, который воспользовался этим преимуществом: объем существующего в то время кода, который вызывал функции vararg без представления прототипа, был огромен, и хотя стандарт объявил, что он не работает, разработчики компиляторов не хотели ломать своих клиентов'code.)

Во многих средах, кажется, существует очень сильная тенденция настаивать на том, что соглашения C и C ++ совпадают, что можно взять адрес функции extern "C++",и передать его функции, написанной на C, которая вызывает его.IIRC, например, g ++ не обрабатывает

extern "C" void f();

и

void f();

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

Многие компиляторы имеют расширения для поддержки других соглашений.(Почему они не используют стандарт extern "xxx", я не знаю.) Однако синтаксис для этих расширений очень разнообразен.Microsoft помещает атрибут непосредственно перед именем функции:

void __stdcall func( int, int );

, g ++ помещает его в специальное предложение атрибута после объявления функции:

void func( int, int ) __attribute__((stdcall));

C ++ 11 добавилстандартный способ указания атрибутов:

void [[stdcall]] func( int, int );

Он не определяет stdcall как атрибут, но действительно указывает, что дополнительные атрибуты (отличные от определенных в стандарте) могутбыть указанным и зависит от реализации.Я ожидаю, что и g ++, и VC ++ принимают этот синтаксис в своих последних версиях, по крайней мере, если C ++ 11 активирован.Однако точное имя атрибута (__stdcall, stdcall и т. Д.) Может отличаться, поэтому, вероятно, вы захотите обернуть это в макрос.

Наконец: в современном компиляторе с включенной оптимизациейРазница в соглашениях о вызовах, вероятно, незначительна.Такие атрибуты, как const (не путать с ключевым словом C ++ const), regparm или noreturn, вероятно, окажут большее влияние как с точки зрения размера исполняемого файла, так и производительности.

1 голос
/ 07 февраля 2012

Эта группа соглашений о вызовах - это история нового 64-битного ABI .

http://en.wikipedia.org/wiki/X86_calling_conventions#x86-64_calling_conventions

Существует также сторона ABI для разных архитектур. (как ARM ) Не все выполняется одинаково для всех архитектур. Так что не думайте об этом соглашении о вызовах!

http://en.wikipedia.org/wiki/Calling_convention

Улучшение размера EXE незначительно (возможно, отсутствует), не беспокойтесь ...

__cdecl гораздо более гибок, чем __stdcall. Переменная гибкость количества аргументов, незначительность кода очистки (инструкции), функция __cdecl может быть вызвана с неправильным количеством аргументов, и это не обязательно вызывает серьезные проблемы! Но та же самая ситуация с __stdcall всегда идет не так!

0 голосов
/ 31 августа 2015

Другие ответили на другие части вашего вопроса, поэтому я просто добавлю свой ответ о размере:

4. Наконец, вернемся к цитируемым словам, почему __stdcall приводит к меньшему исполняемому файлу, чем __cdecl?

Кажется, это не правда. Я проверил это, скомпилировав libudis с соглашением о вызовах stdcall и без него. Сначала без:

$ clang -target i386-pc-win32 -DHAVE_CONFIG_H -Os -I.. -I/usr/include -fPIC -c *.c && strip *.o
$ du -cb *.o
6524    decode.o
95932   itab.o
1434    syn-att.o
1706    syn-intel.o
2288    syn.o
1245    udis86.o
109129  totalt

и с. Это переключатель -mrtd, который включает stdcall:

$ clang -target i386-pc-win32 -DHAVE_CONFIG_H -Os -I.. -I/usr/include -fPIC -mrtd -c *.c && strip *.o
7084    decode.o
95932   itab.o
1502    syn-att.o
1778    syn-intel.o
2296    syn.o
1305    udis86.o
109897  totalt

Как видите, cdecl превосходит stdcall несколькими сотнями байтов. Это может быть моя методология тестирования, которая ошибочна, или генератор кода stdcall clang слаб. Но я думаю, что с современными компиляторами дополнительная гибкость, обеспечиваемая очисткой вызывающих, означает, что они всегда будут генерировать лучший код с помощью cdecl, а не stdcall.

...