C++中裸露的函数调用
大家好!今天我们要学习的是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
裸露的函数调用
当一个函数用裸体属性声明时,不会产生prolog或epilog代码,允许你使用内联汇编器来创建你自己独特的prolog/epilog序列。
一个高级功能是裸函数的可用性。它们为你提供了一个选项,可以声明一个从C或C++以外的环境中调用的函数,允许你对参数的位置或寄存器的保存进行不同的假设。
像中断处理程序这样的路由就是例子。虚拟设备驱动(VxD)的创建者会发现这个功能尤其非常方便或容易使用。
naked属性用于指定函数,当为这种函数生成代码时,不包括prolog和epilog。
使用内联汇编程序代码,你可以利用这个功能来创建你自己的prolog/epilog代码序列。编写虚拟设备驱动程序可以很好地利用裸露的功能。请记住,x64平台不支持naked属性;它只在x86和ARM上有效。
语法
_delspec (naked)
裸函数必须使用扩展的属性语法和__declspec关键字,因为裸属性只影响函数的声明,而不是一个类型修改器。
即使一个函数被标记为__force inline关键字和裸属性,编译器也不能为该函数创建一个内联函数。
如果裸属性被用于非成员方法的定义之外的任何地方,编译器会抛出一个错误。
例子
_declspec (naked) float fun ( initial parameters ) { }
Or
#define Naked _declspec (naked)
Naked float fun ( initial parameters ) { }
只有由编译器生成的序言和尾声序列的性质受到裸露属性的影响。
它对为调用这些函数而创建的代码没有影响。因此,函数指针不能包含裸属性,因为它不被看作是函数类型的一个组成部分。此外,数据定义不能使用裸露属性。
规则和限制
以下是C++中裸函数调用的规则和限制。
- 在这种类型的函数调用中不能使用返回关键字
- 在这种类型的函数调用中不能使用_alloca函数
- 在函数范围内不允许初始化局部变量,以保证局部变量的初始化代码不会在prolog序列之前进入。特别是,函数作用域不允许定义C++对象。然而,一个嵌套的作用域可以包括初始化的数据。
- C++中的裸函数调用必须在堆栈帧上展开。所以,C++的异常处理结构和结构化异常处理是不允许的。
- C++中的裸露函数调用必须在堆栈框架上解开。所以,setjmp不能用于C++中的裸函数调用。
- 每次在C/C++代码中引用__fastcall裸函数的一个寄存器参数时,prolog代码应该将该寄存器的值保存到该变量的堆栈位置。
- 如果裸函数调用是在词法范围内,就不能声明C++类对象。但是你仍然可以在嵌套块中定义对象。
- 尽管不建议这样做,但对于裸函数来说,帧指针优化(/ Oy编译器选项)会自动被抑制。
- 当用/ clr来构建裸函数时,裸关键字可以被忽略。
例子
// 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中扣除合适的量来计算从ebp的偏移量,以便访问局部变量。
局部大小
为了在函数prolog代码的内联汇编块中使用,编译器提供了一个叫做__LOCAL SIZE的符号。在自定义prolog代码中,这个符号被用来为堆栈框架上的局部变量分配空间。
__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++语言中的裸函数调用。