C-函数栈帧

函数栈帧

int MyAdd(int a, int b)
{
	int c = 0;
	c = a + b;
	return c;
}
int main()
{
	int x = 0xA;
	int y = 0xB;
	int z =MyAdd(x,y);

	system("pause");
	return 0;
}

image-20230514122652130

--认识寄存器
eax:通用寄存器,保留临时数据,常用于返回值
ebx:通用寄存器,保留临时数据
ebp:栈底寄存器(bottom底部)
esp:栈顶寄存器
eip:指令寄存器,保存当前指令的下一条指令的地址
--汇编指令
mov:数据转移指令
push:数据入栈,同时esp栈顶寄存器也要发生改变
pop:数据弹出至指定位置,同时esp栈顶寄存器也要发生改变
sub:减法命令
add:加法命令
call:函数调用,1. 压入返回地址 2. 转入目标函数
jump:通过修改eip,转入目标函数,进行调用
ret:恢复返回地址,压入eip,类似pop eip命令
  • call压入返回地址?

根本原因是:函数是可能调用完毕的,就需要返回.所以需要将call命令下一条地址压栈.

  • EIP中被修改为jmp跳转函数的地址到jmp,jmp再跳转到函数地址.然后EIP中再是目标函数地址.
栈帧创建
栈帧销毁

image-20230514135353853

\1. 调用函数之前,需要先形成临时拷贝并且通过寄存器压入栈中,形参形成过程是从右向左的.
\2. 临时空间的开辟,是在对应函数栈帧内部开辟的
\3. 函数调用完毕,栈帧结构被释放掉
\4. 临时变量具有临时性的本质:栈帧具有临时性
\5. 调用函数是有成本的,成本体现在时间和空间上,本质是形成和释放栈帧有成本
\6. 函数调用,因拷贝所形成的临时变量,变量和变量之间的位置关系是有规律的

根据栈帧关系更改值

image-20230514135602519

int MyAdd(int a, int b)
{
	printf("Before:%d\n",b);
	*(&a + 1) = 100;
	printf("After:%d\n", b);

	/*int c = 0;
	c = a + b;*/
	return 0;
}
int main()
{
	int x = 0xA;
	int y = 0xB;
	int z =MyAdd(x,y);

	system("pause");
	return 0;
}
拓展

将main函数ebp地址栈帧相对位置更改实现回原本main函数时去调用其他函数.

只能通过控制停止时间观察现象,具体实现中间插入一个函数的运行还需记录main函数栈帧中原先的地址,由于随机栈等保护措施的存在,现在暂时无法完成.

void bug()
{
	printf("You can see me\n");
	Sleep(10000);
}
int MyAdd(int a, int b)
{
	printf("MyAdd be called!\n");
	*(&a - 1) = (int)bug;
	return 0;
}
int main()
{
	int x = 0xA;
	int y = 0xB;
	int z =MyAdd(x,y);

	system("pause");
	return 0;
}

image-20230514141530685

可变参数列表

函数传参求两个数较大值,形参可以确定的两个.

int FindMax(int x, int y)
{
    if (x > y)
    	return x;
    else
        return y;
} 
int main()
{
    int x = 0;
    int y = 0;
    printf("Please Eneter Two Data# ");
    scanf("%d %d", &x, &y);
    int max = FindMax(x, y);
    printf("max = %d\n", max);
    system("pause");
    return 0;
}

如果函数传参传多个值,需要在多个值中确定最大值,参数个数不确定,编写函数时形参个数无法确定,所以C提供了可变参数列表.

如果没有形式参数,可以给函数传参么?可以的

在C中,只要发生函数调用并且传递参数,必定形成临时拷贝

所谓的临时拷贝本质就是在栈帧中形成的,从右向左依次形成临时拷贝.

  • 可变参数列表至少要有一个参数
基本原理
  1. 通过指针操作以及栈帧中临时变量相对位置获取
int FindMax(int num, ...)
{
	va_list arg;//char* 类型指针
	va_start(arg,num);//让arg指针指向可变参数部分
	int max = va_arg(arg,int);
	for (int i = 0; i < num - 1; i++)
	{
		int cur = va_arg(arg, int);
		if (max < cur)
		{
			max = cur;
		}
	}
	va_end(arg);//指针置空
	return max;
}
int main()
{
	int max = FindMax(5, 0x11, 0x22, 0x33, 0x44, 0x65);
	printf("max=%d\n", max);
	return 0;
}

image-20230515104040388

整形提升
  1. 可变参数中,如果是短整型,一般都需要进行int整形提升.movsx.即使是char类型,在压栈的时候都是4字节.

    通过查看汇编,我们看到,在可变参数场景下:
    \1. 实际传入的参数如果是char,short,float,编译器在编译的时候,会自动进行提升(通过查看汇编,我们都能看到)
    \2. 函数内部使用的时候,根据类型提取数据,更多的是通过int或者double来进行

image-20230515104848125

int FindMax(int num, ...)
{
	va_list arg; //定义可以访问可变参数部分的变量,其实是一个char*类型
	va_start(arg, num); //使arg指向可变参数部分
	int max = va_arg(arg, int); //根据类型,获取可变参数列表中的第一个数据
	for (int i = 0; i < num - 1; i++) {//获取并比较其他的
		int curr = va_arg(arg, int);
		if (max < curr) {
			max = curr;
		}
	} 
	va_end(arg); //arg使用完毕,收尾工作。本质就是讲arg指向NULL
	return max;
}
int main()
{
	char a = '1'; //ascii值: 49
	char b = '2'; //ascii值: 50
	char c = '3'; //ascii值: 51
	char d = '4'; //ascii值: 52
	char e = '5'; //ascii值: 53
	int max = FindMax(5, a, b, c, d, e);
	printf("max = %d\n", max);//max=53
	system("pause");
	return 0;
}
  • 可变参数必须从头到尾逐个访问。如果你在访问了几个可变参数之后想半途终止,这是可以的,但是,如果你想一开始就访问参数列表中间的参数,那是不行的。
  • 参数列表中至少有一个命名参数。如果连一个命名参数都没有,就无法使用 va_start 。
  • 这些宏是无法直接判断实际存在参数的数量。
  • 这些宏无法判断每个参数的是类型。
  • 如果在 va_arg 中指定了错误的类型,那么其后果是不可预测的。

\1. 可变参数列表对应的函数,最终调用也是函数调用,也要形成栈帧
\2. 栈帧形成前,临时变量是要先入栈的,根据之前所学,参数之间位置关系是固定的
\3. 通过上面汇编的学习,发现了短整型在可变参数部分,会默认进行整形提升,那么函数内部在提取该数据的时候,就要考虑提升之后的值,如果不加考虑,获取数据可能会报错或者结果不正确

_INTSIZEOF(n)的意思:计算一个最小数字x,满足 x>=n && x%4==0,其实就是一种4字节对齐的方式是什么:
比如n是:1,2,3,4 对n进行向 sizeof(int) 的最小整数倍取整的问题 就是 4
比如n是:5,6,7,8 对n进行向 sizeof(int) 的最小整数倍取整的问题 就是 8
所以,简洁版:(n+4-1) & ~(4-1)
原码版:( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )

image-20230515123051944

image-20230515123029720

命令行参数

main函数也是一个函数,其实也可以携带参数的
int main( int argc, char *argv[ ], char *envp[ ] )
{
		program-statements
}

那这里是有三个参数的。

第一个参数: argc 是个整型变量,表示命令行参数的个数(含第一个参数)。

第二个参数: argv 是个字符指针的数组,每个元素是一个字符指针,指向一个字符串。这些字符串就是命令行中的每一个参数(字符串)。

第三个参数: envp 是字符指针的数组,数组的每一个原元素是一个指向一个环境变量(字符串)的字符指针.

image-20230515123859289

image-20230515124211158

打印环境变量
int main(int argc, char* argv[], char* env[])
{
	for (int i = 0; env[i]; i++)//env[i]结尾就是NULL
	{
		printf("env[%d]:%s\n", i, env[i] );
	}
}

image-20230515124928101

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值