__fastcall
Блок, относящийся только к системам Microsoft
__fastcall Соглашение о вызовах указывает, что аргументы функции должны передаваться в регистры, если это возможно. Это соглашение о вызовах применяется только к архитектуре x86. В следующем списке показана реализация этого соглашения о вызовах.
| Элемент | Реализация |
|---|---|
| Порядок передачи аргументов | Первые два значения DWORD или меньшие аргументы, найденные в списке аргументов слева направо, передаются в регистрах ECX и EDX; все остальные аргументы передаются в стек справа налево. |
| Обязанность по обслуживанию стека | Вызываемая функция извлекает аргументы из стека. |
| Соглашение об оформлении имен | Знак «@» добавляется к именам как префикс. Знак «@», за которым следует количество байтов (в десятичной системе счисления) в списке параметров, добавляется к именам как суффикс. |
| Соглашение о преобразовании регистра | Изменение регистра не выполняется. |
В следующих версиях компилятора могут использовать другие регистры для сохранения параметров.
__fastcall Ключевое слово принимается и игнорируется компиляторами, предназначенными для архитектуры ARM и x64; в микросхеме x64 по соглашению первые четыре аргумента передаются в регистры по возможности, а дополнительные аргументы передаются в стек. Дополнительные сведения см. в разделе соглашение о вызовах x64. На микросхеме ARM можно передавать в регистрах до четырех целочисленных аргументов и до восьми аргументов с плавающей запятой; дополнительные аргументы передаются в стеке.
Если используется внестрочное определение нестатической функции класса, то модификатор соглашения о вызовах не должен быть задан во внестрочном определении. То есть для нестатических методов-членов считается, что соглашение о вызовах, указанное во время объявления, было сделано в точке определения. Рассмотрим следующее определение класса:
В этом случае следующий код:
Пример
В следующем примере аргументы передаются в функцию DeleteAggrWrapper в регистрах.
Завершение блока, относящегося только к системам Майкрософт
Русские Блоги
Познакомьтесь с соглашениями о вызовах Visual Studio __cdecl, __stdcall и __fastcall
Те, кто имеет определенный опыт разработки на C ++, должны быть знакомы с «__cdecl, __stdcall, __fastcall»! Но ты действительно понимаешь? Да, я собрал здесь бесчисленные ямы, посадил бесчисленные каблуки и, наконец, у меня нет другого выбора, кроме как подвести итог (хотя у меня уже есть возможность решить большинство из этих проблем)!
Что называется конвенция
функциональнаяСоглашение о вызовахКак следует из названия, это ограничение и спецификация (спецификация) для вызовов функций, описывающая, как передаются параметры функций и кто очищает стек. Он определяет следующее: (1) порядок, в котором параметры функции помещаются в стек, (2) извлекает ли вызывающий или вызываемый объект параметры из стека, и (3) и метод генерации имени модификатора функции.
Мы знаем, что функция состоит из следующих частей: Тип возвращаемого значения Имя функции (список параметров), например:
【code1】
Выше перечислены хорошо известные компоненты, на самом деле, есть еще некоторые компоненты функции, то есть соглашение о вызовах. следующим образом:
【code2】
Соглашение о вызовах должно быть одинаковым в объявлении и определении
В VC ++ соглашение о вызовах является частью типа функции,Следовательно, соглашение о вызовах объявления и определения функции должно быть одинаковым. Вы не можете просто иметь соглашение о вызовах в объявлении, и определение не отличается от объявления или не отличается от него.следующим образом:
[code3] Неправильно используйте один:
error C2373: ‘add’: redefinition; different type modifiers
error C2440: ‘initializing’: cannot convert from ‘int (__stdcall *)(int,int)’ to ‘int’
[Code4] Два неправильных использования:
error C2373: ‘add’: redefinition; different type modifiers
error C2440: ‘initializing’: cannot convert from ‘int (__cdecl *)(int,int)’ to ‘int’
[Code5] Три неправильных использования:
error C2373: ‘add’: redefinition; different type modifiers
error C2440: ‘initializing’: cannot convert from ‘int (__stdcall *)(int,int)’ to ‘int’
[Code6] Правильное использование:
Процесс вызова функции
Процесс вызова функции может быть описан следующим образом:
(1) Сначала поместите базовый адрес (ebp) стека вызывающего абонента (A) в стек, чтобы сохранить информацию предыдущей задачи.
(2) Затем присвойте значение указателя верхнего стека (esp) вызывающей стороны (A) для ebp в качестве нового базового адреса (то есть нижнего стека вызываемого B).
(3) Затем по этому базовому адресу (в нижней части стека вызываемого абонента B) соответствующее пространство (обычно с использованием подинструкции) используется в качестве пространства стека вызываемого абонента B.
(4) После возврата из функции B ebp текущего кадра стека восстанавливается до вершины стека вызывающей стороны A (esp), так что вершина стека восстанавливается до позиции, предшествующей вызову функции B, затем Затем вызывающая сторона A может извлечь предыдущее значение ebp с вершины восстановленного стека (это можно сделать, поскольку это значение было помещено в стек за один шаг до вызова функции). Таким образом, ebp и esp восстанавливают позицию перед вызовом функции B, то есть состояние до вызова функции восстановления стека B.
Этот процесс завершается в сборке AT & T двумя инструкциями, а именно:
Особенности __cdecl
(1). Чтобы убедиться, что параметры помещены в стек справа налево, мы можем увидеть следующий код: Отладка для одношаговой отладки, мы можем видеть, что наш стек вызовов сначала войдет в GetC (), а затем в GetB ( ) и, наконец, введите GetA ().
С точки зрения отладки кода и программ, нам не нужно обращать слишком много внимания на порядок передачи параметров и очистки стека, потому что это определяется компилятором, и мы не можем его изменить. Но третий момент часто сбивает нас с толку, потому что, если мы не понимаем этот момент, часто возникают необъяснимые ошибки при вызове и использовании друг друга между несколькими библиотеками (такими как dll, lib, exe). Я подробно расскажу об этом в следующих главах.
Особенности __stdcall
Особенности __fastcall
__thiscall
подводить итоги
Вот краткое изложение различий между _cdecl, _stdcall и __fastcall:
| Основные положения | __cdecl | __stdcall | __fastcall |
|---|---|---|---|
| Метод передачи параметров | Правша> левый | Правша> левый | Два параметра, не превышающие 4 байта (DWORD), начиная слева, помещаются в регистры ECX и EDX, а оставшиеся параметры передаются справа налево и передаются в стек |
| Очистить стек | Уборка номера | Вызывается функция очистки | Вызывается функция очистки |
| Приложения | Режим по умолчанию C / C ++ и MFC; используется, когда параметры являются переменными; | Win API | Высокая скорость |
| Соглашение о модификации компиляции Си | _functionname | [email protected] | @[email protected] |
Перепечатано в:https://blog.csdn.net/luoweifu/article/details/52425733
Вызов функции с «неизвестным» именем на C++. Часть 1 — cdecl
The cdecl calling convention is used by many C systems for the x86 architecture. In cdecl, function parameters are pushed on the stack in a right-to-left order. Function return values are returned in the EAX register (except for floating point values, which are returned in the x87 register ST0). Registers EAX, ECX, and EDX are available for use in the function.
В общем, параметры передаются через стек в обратном порядке, результирующее значение будет в EAX кроме чисел с плавающей точкой — они будут в псевдо-стеке x87.
Составим план работы:
1) Сгенерировать некий буфер в памяти, который можно будет без изменений, пословно(4 байта) поместить в стек.
2) Узнать адрес функции, которую будем вызывать
3) Поместить в стек буфер по словам
4) Вызвать функцию
5) Выдернуть результат
Поехали!
Что же у нас есть:
1) char* sName — тут находится имя функции
2) int N — количество параметров
3) enum CParamType
4) CParamType Params[] — список типов параметров
5) void* ParamList[] — собственно, указатели на переменные с параметрами
6) CParamType RetType — тип данных результата
7) void* Ret — указатель на память, куда нужно скинуть результат
8) enum CCallConvention
9) CCallConvention conv — соглашение вызова. Для начала будем вызывать только cdecl функции
Это необходимый и достаточный список объявлений, которые нам нужны для вызова.
На C/C++ нету средств для осуществления этой операции, поэтому придется обратиться к ассемблеру.
1. Создаем буфер
Посчитали. Выделяем память:
void* Buffer = new char[4*WordCount];
Заполняем буфер: void*, int — помещаем без изменений, а в double меняем слова местами.
Думаю, тут комментировать нечего. offset — смещение по буферу.
2. Узнаем адрес функции
3. Помещаем в стек буфер по словам
Мы делаем цикл: пока ecx (WordCount) не станет 0, кладем в стек слово и уменьшаем ecx.
4. Вызываем функцию
Делаем
l2: call *%1;
после заполнения стека. %1 — указатель на функцию (addr).
5. Возвратить результат
Тут 2 варианта: целый результат или дробный. Согласно соглашению, по умолчанию результат будет в %eax, но если с плавающей точкой — то в всевдо-стеке x87.
1) Целый результат
movl %%eax, %0;
где %0 — переменная результата.
2) Вариант с плавающей точкой
По идее здесь нужно изъять из ST(0) ответ. Пока что у меня не получилось этого сделать. Хотелось бы увидеть в комментариях возможные решения. Заранее спасибо.
Ну вот и все! Задача была действительно не тривиальная. Надеюсь, этот пост кому-то понадобится.
PS Нужно все это для написания интерпретатора.
_________
Текст подготовлен в ХабраРедакторе
Русские Блоги
Соглашения о вызовах __cdecl, __stdcall и __fastcall
Что такое соглашение о вызовах
функцияСоглашение о вызовахКак следует из названия, это ограничение и спецификация (спецификация) для вызовов функций, описывающая, как передаются параметры функций и кто очищает стек. Он определяет следующее: (1) порядок размещения параметров функции, (2) извлекает ли вызывающий или вызываемый объект параметры из стека и (3) метод, который генерирует измененное имя функции.
Историческая справка
До появления микрокомпьютеров производители компьютеров почти предоставили операционную систему и компиляторы, написанные для разных языков программирования. Соглашения о вызовах, используемые платформой, определяются программной реализацией поставщика. До появления Apple early ранние микрокомпьютеры были почти полностью «голыми», и была одна ОС или компилятор, даже IBM PC. Единственный стандарт оборудования для компьютеров, совместимых с IBM PC, был определен процессорами Intel (8086, 80386) и распространен IBM. Аппаратные расширения и все программные стандарты (соглашения о вызовах BIOS) открыты для конкуренции на рынке. Группа независимых компаний-разработчиков программного обеспечения предоставляет операционные системы, компиляторы для разных языков и некоторое прикладное программное обеспечение. Исходя из разных потребностей, исторических практик и творческого подхода разработчиков, все эти компании используют разные соглашения о вызовах, которые часто сильно различаются. После перетасовки рынка совместимых машин IBM доминировали операционные системы и инструменты программирования Microsoft (с различными соглашениями о вызовах). В это время компании второго уровня, такие как Borland и Novell, и проекты с открытым исходным кодом, такие как GCC, также сохранили свои собственные Ваши собственные стандарты. Правила совместимости в конечном итоге были приняты поставщиками оборудования и программными продуктами, что упростило задачу выбора допустимого стандарта.
Уборка номера
В этих соглашениях вызывающая сторона очищает аргументы в стеке, что позволяет реализовать списки переменных параметров, таких как printf ().
cdecl
cdecl (объявление C)Язык CСоглашение о вызовах также является стандартом де-факто для C. В архитектуре x86 его содержимое включает в себя:
Visual C++Определяет возвращаемое значение функции, если оноЗначение PODИ если длина не превышает 32 бита, она передается в регистр EAX, в диапазоне 33-64 бита она передается в регистр EAX: EDX, если длина превышает 64 бита или значение не POD, вызывающая сторона заранее выделяет пространство для возвращаемого значения функции, Адрес пространства передается как неявный параметр вызываемой функции.
GCCВозвращаемое значение функции выделяется вызывающей стороной, а адрес пространства передается в качестве неявного параметра вызываемой функции без использования регистра EAX. Начиная с версии 4.5 GCC, при вызове функций данные в стеке должны быть выровнены в 16B (в предыдущих версиях требовалось только 4B выравнивания).
Рассмотрим следующий фрагмент кода C:
На x86 создается следующий код сборки (синтаксис AT & T):
После возврата из функции вызываемая функция очищает стек. Есть некоторые различия в понимании cdecl, особенно в том, как возвращать значения. В результате программы x86 становятся несовместимыми после компиляции разными компиляторами на разных платформах ОС, даже если они используют правило «cdecl» и не используют системные вызовы. Некоторые компиляторы возвращают простые структуры данных, которые занимают приблизительно два регистра и помещаются в пару регистров EAX: EDX; более крупные структуры и объекты класса требуют некоторой специальной обработки обработчиком исключений (например, определенный конструктор, анализ Конструктор или присваивание), хранящиеся в памяти. Чтобы поместить его в память, вызывающей стороне необходимо выделить некоторую память и позволить указателю указать на эту память, этот указатель используется в качестве первого скрытого параметра, вызываемый использует эту память и возвращает всплывающие указатели при возврате. Скрытый указатель В Linux / GCC значения с плавающей точкой помещаются в стек через псевдостек x87. Как это:
Использование этого метода гарантирует, что стек может быть передан в правильном формате. Соглашение о вызовах cdecl обычно используется в качестве правила вызова по умолчанию для компилятора x86 C. Многие компиляторы также предоставляют возможность автоматического переключения соглашения о вызовах. Если вам нужно вручную указать правило вызова как cdecl, компилятор может поддерживать следующий синтаксис:
Модификатор _cdecl должен быть указан в прототипе функции, а другие параметры будут перезаписаны в объявлении функции.
syscall
Подобно cdecl, параметры помещаются в стек справа налево. EAX, ECX и EDX не сохраняют значения. Размер списка параметров помещается в регистр AL (?). syscall является стандартом для 32-битного OS / 2 API.
optlink
Уборка улиц
Если вызываемый объект хочет очистить параметры в стеке, он должен знать, сколько байтов в стеке нужно обработать на этапе компиляции. Поэтому этот тип соглашения о вызовах не совместим со списками переменных параметров, такими как printf (). Однако это соглашение о вызовах может быть более эффективным, потому что код, который необходимо разложить, не должен генерироваться при каждом вызове. Функции, использующие это правило, легко распознаются в коде asm, потому что перед возвращением они разбивают стек. Инструкция x86 ret позволяет опциональному 16-битному параметру указать количество байтов стека, используемых для разборки стека перед возвратом его вызывающей стороне. Код выглядит так:
pascal
Основываясь на соглашении о вызовах языка Pascal, параметры помещаются в стек слева направо (в отличие от cdecl). Вызываемый отвечает за очистку стека перед возвратом. Это соглашение о вызовах обычно встречается в компиляторах для следующих 16-битных платформ: OS / 2 1.x, Microsoft Windows 3.x и Borland Delphi версии 1.x.
register
Псевдоним для быстрого вызова Borland.
stdcall
Инструменты компиляции Microsoft предусматривают: PASCAL, WINAPI, APIENTRY, FORTRAN, CALLBACK, STDCALL, __far __pascal, __fortran, __stdcall Все относятся к таким соглашениям о вызовах.
fastcall
Это соглашение не было стандартизировано, и реализации не согласованы между компиляторами.
Соглашение __fastcall от Microsoft или GCC (то есть __msfastcall) передает первый (слева направо) параметр, который не превышает 32 бита, через регистр ECX / CX / CL, а второй параметр, который не превышает 32 бита, через регистр EDX. / DX / DL, остальные параметры помещаются в стек в порядке справа налево.
Слева направо передайте три параметра в EAX, EDX и ECX. Остальные параметры помещаются в стек слева направо. В 32-разрядном компиляторе Embarcadero Delphi это соглашение о вызовах по умолчанию, которое в компиляторе называется регистром. Некоторые версии на i386LinuxЭто соглашение также используется.
Вызов вызывающего абонента
thiscall
призваниеC++Используйте это соглашение для нестатических функций-членов. Существует две основные версии этого вызова, основанные на том, являются ли используемые компилятор и функция varargs. Для компилятора GCC этот вызов почти эквивалентен cdecl: вызывающая сторона очищает стек, а параметры передаются справа налево. Разница заключается в указателе this, thiscall помещает указатель this в стек в конце, что эквивалентно неявному параметру first-left в прототипе функции.
вMicrosoftVisual C++составительВ этом указатель this передается через регистр ECX, а остальное такое же, как в соглашении cdecl. Когда функция принимает переменный аргумент, вызывающая сторона отвечает за очистку стека (см. Cdecl). Это соглашение о вызовах явно указано только в Microsoft Visual C ++ 2005 и более поздних версиях. В других компиляторах thiscall не является ключевым словом (дизассемблеры, такие как IDA, используют __thiscall).
Intel ABI
Согласно Intel ABI, EAX, EDX и ECX могут свободно использоваться в процедурах или функциях и не требуют сохранения.
Соглашение о вызовах x86-64
Соглашение о вызовах x86-64 имеет больше регистров, которые можно использовать для передачи параметров. Более того, меньше несовместимых соглашений о вызовах, но есть еще два основных правила.
Соглашение о вызовах Microsoft x64
Например, функция имеет 5 целочисленных параметров, с первого по четвертый помещаются в регистры, а пятый помещается в верхнюю часть стека за пределами теневого пространства. Когда функция вызывается, этот стек используется для составления возвращаемого значения теневого пространства 32 бита + пятый параметр.
В архитектуре x86-64 Visual Studio 2008 хранит числа с плавающей запятой в XMM6 и XMM7 (также от XMM8 до XMM15). В результате для подпрограмм на языке ассемблера, написанных пользователем, XMM6 и XMM7 должны быть сохранены (эти два регистра не требуются для x86), что означает, что при переносе подпрограмм на ассемблере между x86 и x86-64 необходимо обратить внимание на / После этого XMM6 и XMM7 должны быть сохранены / восстановлены.
System V AMD64 ABI
Это соглашение в основном используется в Solaris, GNU / Linux, FreeBSD и других ОС, не относящихся к Microsoft. Первые шесть целочисленных параметров помещаются в регистры RDI, RSI, RDX, RCX, R8 и R9, в то же время от XMM0 до XMM7 используются для размещения аргументов с плавающей запятой. Для системных вызовов вместо RCX используется R10. Как и в соглашении Microsoft x64, другие дополнительные параметры помещаются в стек, а возвращаемое значение сохраняется в RAX. В отличие от Microsoft, нет необходимости предоставлять теневое пространство. При входе в функцию возвращаемое значение находится рядом с седьмым целочисленным параметром в стеке.
Мы знаем, что функция состоит из следующих частей: имя функции типа возвращаемого значения (список параметров), например:
【code1】
Вышеприведенный компонент является хорошо известным компонентом, на самом деле есть еще одна часть состава функции, то есть соглашение о вызовах. Следующим образом:
【code2】
Декларация и определение соглашения о вызовах должны быть одинаковыми
В VC ++ соглашение о вызовах является частью типа функции,Следовательно, соглашение о вызовах при объявлении и определении функции должно быть одинаковым. Соглашение о вызовах не может быть только в объявлении, и в определении нет или другое определение.Следующим образом:
[code3] Неправильно используйте один:
Сообщение об ошибке:
error C2373: ‘add’: redefinition; different type modifiers
error C2440: ‘initializing’: cannot convert from ‘int (__stdcall *)(int,int)’ to ‘int’
Выше хорошо, потому что по умолчанию __cdecl.
[Code4] Неправильное использование 2:
Сообщение об ошибке:
error C2373: ‘add’: redefinition; different type modifiers
error C2440: ‘initializing’: cannot convert from ‘int (__cdecl *)(int,int)’ to ‘int’
[Code5] Неправильное использование три:
Сообщение об ошибке:
error C2373: ‘add’: redefinition; different type modifiers
error C2440: ‘initializing’: cannot convert from ‘int (__stdcall *)(int,int)’ to ‘int’
[Code6] Правильное использование:
Вызов функции
Процесс вызова функции может быть описан следующим образом:
(1) Сначала поместите базовый адрес (ebp) стека вызывающего абонента (A) в стек, чтобы сохранить информацию предыдущей задачи.
(2) Затем присвойте значение верхнего указателя (esp) вызывающей стороны (A) для ebp в качестве нового базового адреса (то есть нижней части стека вызываемой стороны B).
(3) Затем откройте (обычно подинструкции) соответствующее пространство на этом базовом адресе (в нижней части стека вызываемого B) как пространство стека вызываемого B.
(4) После возврата из функции B ebp из текущего кадра стека восстанавливается до верхней части вызывающей стороны A (esp), так что вершина стека восстанавливает позицию до вызова функции B, затем Вызывающий объект A может вытолкнуть предыдущее значение ebp с вершины восстановленного стека (это можно сделать, потому что это значение помещается в стек перед вызовом функции). Таким образом, ebp и esp оба восстанавливают позицию перед вызовом функции B, то есть стек восстанавливает состояние до вызова функции B.
Этот процесс выполняется в сборке AT & T с помощью двух инструкций, а именно:
__cdecl Особенности
(1). Чтобы убедиться, что параметры помещены в стек справа налево, мы можем взглянуть на следующий код: Отладка для одношаговой отладки, вы можете видеть, что наш стек вызовов сначала вводит GetC (), затем GetB ( ) И, наконец, введите GetA ().
С точки зрения отладки кода и программ нам не нужно обращать внимание на порядок добавления стека и очистки стека, потому что это определяется компилятором, и мы не можем его изменить. Но третий момент часто беспокоит нас, потому что, если мы этого не понимаем, часто возникают необъяснимые ошибки, когда несколько библиотек (таких как dll, lib, exe) вызывают и зависят друг от друга. Я подробно расскажу об этом в следующих главах.
__stdcall Особенности
__fastcall Особенности
__thiscall
резюме
Вот краткое изложение различий между _cdecl, _stdcall и __fastcall:



