C++ 裸函数调用
大家好!今天我们要学习关于在C++中的裸函数调用。我们可能会怀疑为什么在C++中函数被称为裸函数。在了解它之前,我们应该先了解什么是函数调用。
C++中的函数调用
在C++程序中,激活函数并允许它在我们需要执行的地方运行的过程被称为函数调用。
C++中有两种类型的函数调用,它们是:
- 带参数的函数调用
在这种函数调用中,我们将参数分配或传递给C++程序。这些参数在程序的运行中起着关键的作用。
- 不带参数的函数调用
在这种函数调用中,我们不将任何参数分配或传递给C++程序。这些参数在程序的运行中起着关键的作用。
示例
文件名:withParameters1.cpp
代码
/* This is a program written to find the factorial of a number using function call which is parameterized or we can say that parameters are passed into the program */
#include
#include
using namespace std;
int factorial (int number)
{
int i;
int f=1;
for(i = 1; i <= number; i++)
{
f = f * i;
}
return f;
}
int main()
{
int n;
cout <<"Enter any number: " << endl;
cin >>n;
int fact;
fact = factorial(n);
printf("%d is the factorial of %d",fact,n);
return 0;
}
输入:
Enter any number:
10
输出:
3628800 is the factorial of 10
示例
文件名称:withoutParameters1.cpp
代码
/* This is a program written to find the factorial of a number using function call which is not parameterized or no parameter passing */
#include
#include
using namespace std;
void factorial ()
{
int number;
cout <<"Enter any number: " << endl;
cin >>number;
int i;
int f=1;
for(i = 1; i <= number; i++)
{
f = f * i;
}
printf("%d is the factorial of %d",f,number);
}
int main()
{
int fact;
factorial();
return 0;
}
输入:
Enter any number:
15
输出:
2004310016 is the factorial of 15
裸函数调用
当一个函数被声明为裸函数时,不会生成任何的前导码或尾码,允许您使用内联的汇编器来创建您自己独特的前导码/尾码序列。
一种高级功能是裸函数的可用性。它们为您提供了在C或C++之外的上下文中调用函数的选项,允许您对参数的位置或寄存器的保存作出不同的假设。
中断处理程序是其中的一个示例。虚拟设备驱动程序(VxDs)的创建者将会发现这个功能特别方便和容易使用。
裸属性用于指定函数,当为这样的函数生成代码时,不包括前导码和尾码。
通过使用内联汇编代码,您可以利用这个功能来创建您自己的前导码/尾码代码序列。编写虚拟设备驱动程序可以很好地利用裸函数。请记住,x64平台不支持裸属性;它只在x86和ARM上有效。
语法
_delspec (naked)
裸函数必须使用扩展属性语法和__declspec关键字,因为裸属性只影响函数的声明,而不是类型修饰符。
即使函数被标记为__forceinline关键字和裸属性,编译器也无法为该函数创建内联函数。
如果裸属性在非成员方法的定义以外的任何地方使用,编译器会抛出错误。
示例
_declspec (naked) float fun ( initial parameters ) { }
Or
#define Naked _declspec (naked)
Naked float fun ( initial parameters ) { }
只有编译器生成的prolog和epilog序列的性质受到naked属性的影响。
它对于调用这些函数所创建的代码没有影响。因此,函数指针不能包含naked属性,因为它不是函数类型的组成部分。另外,数据定义也不能使用naked属性。
规则和限制
以下是C ++中Naked函数调用的规则和限制:
- 这种类型的函数调用中不能使用return关键字
- 这种类型的函数调用中不能使用_alloca函数
- 在函数作用域中不允许初始化局部变量,以确保没有初始化代码进入prolog序列之前。特别是,函数作用域不允许定义C ++对象。然而,嵌套作用域可以包括初始化数据。
- C ++中的Naked函数调用必须沿着堆栈帧进行展开。因此,不允许使用C ++异常处理结构和结构化异常处理。
- C ++中的Naked函数调用必须沿着堆栈帧进行展开。因此,不能在C ++中使用setjmp来进行Naked函数调用。
- 每当在C / C ++代码中引用__fastcall裸函数的寄存器参数之一时,prolog代码应将该寄存器的值保存到该变量的堆栈位置上。
- 如果Naked函数调用在词法作用域中,不能声明C ++类对象。但是你仍然可以在嵌套块中定义对象。
- 虽然不建议这样做,但对于naked函数,帧指针优化(/ Oy编译器选项)会自动停止。
- 当使用/ clr完成Naked函数调用构建时,可以忽略naked关键字。
示例
// nkdfastcl.cpp
// compile with: /c
// processor: x86
__declspec(naked) int __fastcall power(int i, int j) {
// This code is written to calculate the value of x EXOR y, assumes that j >= 0
// prolog
__asm {
push ebp
mov ebp, esp
sub esp, __LOCAL_SIZE
// Place ECX and EDX in the x and y stack places.
mov x, ecx
mov y, edx
}
{
int s = 1; // return value
while (y-- > 0)
s * = x;
__asm {
mov eax, k
};
}
// epilog
__asm {
mov esp, ebp
pop ebp
ret
}
}
编写Prolog/Epilog代码时需要注意的思路
在创建自己的prolog和epilog代码序列之前,了解堆栈帧的结构非常重要。了解如何利用__LOCAL SIZE符号也是有帮助的。
堆栈帧布局
这个图示展示了一个32位函数中可能使用的典型prolog代码:
示例:
push ebp ; Save ebp
sub esp, localbytes ; Allocate space for locals
mov ebp, esp ; Set stack frame pointer
push <registers> ; Save registers
“registers”变量是占位符,表示应保存在堆栈上的寄存器列表,“localbytes变量表示需要为局部变量在堆栈上分配的字节数。在推送寄存器后,您可以将任何其他相关数据放在堆栈上。相关的尾声代码如下所示:
尾声代码
pop ; Restore registers
pop ebp ; Restore ebp
mov esp, ebp ; Restore stack pointer
ret ; Return from function
栈始终变得较小(从高地址到低地址)。推入的ebp的值是基指针(ebp)所指的位置。在ebp-4处开始居民区域。通过从ebp中减去正确的数量来计算偏移量,以便访问局部变量。
局部大小
在函数展开代码的内联汇编块中使用时,编译器提供了一个名为__LOCAL SIZE的符号。在自定义展开代码中,该符号用于为栈帧上的局部变量分配空间。
__LOCAL SIZE的值由编译器设置。所有用户定义的局部变量和编译器生成的临时变量的总和构成其值。__LOCAL SIZE只能用作瞬时操作数,不能在表达式中使用。您不能更改或重新解释此符号的含义。例如:
mov eax, [ebp - __LOCAL_SIZE] ;Error
mov eax, __LOCAL_SIZE ;Immediate operand--Okay
在使用唯一的前奏和尾奏序列的裸函数的前奏序列中,使用 __LOCAL SIZE 符号的使用方法如下:
示例
// the__local_size_symbol.cpp
// processor: x86
__declspec ( naked ) int main() {
int x;
int y;
__asm { /* prolog */
push ebp
mov ebp, esp
sub esp, __LOCAL_SIZE
}
/* Function body */
__asm { /* epilog */
mov esp, ebp
pop ebp
ret
}
}
这就是关于C++语言中的Naked Function Calls的所有内容。