栈帧应用

1、不通过调用函数来执行函数;//通过修改函数完成后返回其调用函数的断点地址

#include<stdio.h>
#include<windows.h>

void bug()
{
	printf("helloBug\n");
	system("pause");
}

int my_add(int a, int b)
{
	int z = 0;
	//分析栈帧可知返回main函数的地址在第一个临时变量的下一个地址(栈自上而下)
	//函数调用参数列表自右而左的实例化
	//所以此时返回main函数的地址在a的下一个地址
	int *p = &a;
	p--;//指针变量减1,实质是减去所指向变量类型的大小4
	//修改返回地址
	*p = (int)bug;
	z = a + b;
	printf("my_add run!\n");
	return z;
}

int main()
{
	int a = 0XAAAAAAAA;
	int b = 0xFFFFFFFF;
	int add = 0;

	printf("main run!\n");
	add = my_add(a,b);
	printf("函数调完返回!\n");

	printf("add = %d\n",add);
	system("pause");
	return 0;
}

运行结果:可以看出并没调用的bug函数也被执行了
但是!!!也出现了此错误

原因:没回到main函数这个调用其他的函数的函数

优化:在bug函数调完后返回到main函数的断点地址处(即为调函数的下一条指令的地址)
#include<stdio.h>
#include<windows.h>

int *mainBreakPoint;

void bug()
{
	int b;//声明在此函数的栈帧中,分析栈帧可知,&b+8即为存储返回地址的地址
	int *p = &b;
	p+=2;
	*p = mainBreakPoint;
	printf("helloBug\n");
	system("pause");
}

int my_add(int a, int b)
{
	int z = 0;
	//分析栈帧可知返回main函数的地址在第一个临时变量的下一个地址(栈自上而下)
	//函数调用参数列表自右而左的实例化
	//所以此时返回main函数的地址在a的下一个地址
	int *p = &a;
	p--;//指针变量减1,实质是减去所指向变量类型的大小4
	//在修改返回地址之前,先将之前的地址保存
	mainBreakPoint = *p;
	//修改返回地址
	*p = (int)bug;
	z = a + b;
	printf("my_add run!\n");
	return z;
}

int main()
{
	int a = 0XAAAAAAAA;
	int b = 0xFFFFFFFF;
	int add = 0;

	printf("main run!\n");
	add = my_add(a,b);
	printf("函数调完返回!\n");

	printf("add = %d\n",add);
	
	system("pause");
	return 0;
}

运行结果:可以看出在bug函数调完后又回到了main函数执行
但是!!!也出现了此错误。
原因:函数正常调用是通过call指令来调用的,
而call指令的作用就是将当前执行指令的下一条指令的地址压栈,再修改eip的值;
此bug函数没有通过call来调用而是直接修改返回地址的值;
但是在每个函数的最后一条指令ret指令的作用是将出栈并保存至eip;
所以:结论就是,bug函数只有ret没有call(即为没有push却有pop):导致栈帧结构错误

优化:将栈顶指针下移4;

运行结果:无错。

2实现一个函数,可以左旋字符串中的k个字符。
ABCD左旋一个字符得到BCDA
ABCD左旋两个字符得到CDAB
3判断一个字符串是否为另外一个字符串旋转之后的字符串。
例如:给定s1 = AABCD和s2 = BCDAA,返回1,给定s1=abcd和s2=ACBD,返回0.
AABCD左旋一个字符得到ABCDA
AABCD左旋两个字符得到BCDAA
AABCD右旋一个字符得到DAABC
#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<windows.h>

#pragma warning(disable:4996)

//左旋字符串中的k个字符
//此处注意左旋的k肯定是小于len的,所以:k = len%k;
//方法1
char *leftRotate(char *str, int len, int k)
{
	assert(str);
	assert(len > 0);

	//左旋次数
	k = len % k; 
	for (int i = 0; i < k; i++){
		char first = str[0]; //保存第一个
		int j = 0;
		for (; j < len - 1; j++){//再将后面的都前移1
			str[j] = str[j + 1];
		}
		str[j] = first;//将保存的第一个放在最后面
	}
	return str;
}

//逆置  
void reverse(char* pre, char* aft)
{
	assert(pre);
	assert(aft);

	while (pre < aft){
		*pre ^= *aft;
		*aft ^= *pre;
		*pre ^= *aft;
		pre++, aft--;
	}
}

//方法2:先局部逆置再整体逆置
char *leftRotate2(char *str, int len, int k)
{
	assert(str);
	assert(len > 0);

	k = len % k;
	reverse(str, str + k - 1);//根据k将字符串分为两部分;先将前一部分逆置
	reverse(str + k, str + len - 1);//再将后一部分逆置
	reverse(str, str + len - 1);//最后再将整体逆置
	return str;
}

//方法3:穷举
//abcdefabcdef:左旋3只需要从开始处获取len的字符即可得到结果
char *leftRotate3(char *str, int len, int k)
{
	assert(str);
	assert(len > 0);

	k = len % k;
	char *strres;
	strres = (char *)malloc(sizeof(char) * len * 2 + 1);
	if (!strres){//动态申请失败
		return;
	}
	strcpy(strres, str);
	strcat(strres, str);
	//memmove(str, strres + k, len);
	strncpy(str, strres + k, len);

	free(strres);//记得释放空间

	return str;
}

//判断一个字符串是否为另外一个字符串旋转之后的字符串
//右旋r = len - 左旋l
//方法1
int judge(char *s1, char *s2, int len)
{
	for (int i = 0; i < len; i++){//0:就代表s1字符串本身
		if (!strcmp(s2, leftRotate(s1, len, i))){//i:代表s1左旋个数
			return 1;
		}
	}
	return 0;
}

//方法2:根据上题方法3可知:构造出s1的双重字符串,判断s2是否为其子串即可
int judge2(char *s1, char *s2, int len)
{
	assert(s1);
	assert(s2);
	assert(len > 0);

	char *strres;
	strres = (char *)malloc(sizeof(char) * len * 2 + 1);
	if (!strres){//动态申请失败
		return;
	}
	strcpy(strres, s1);
	strcat(strres, s1);
	if (strstr(strres, s2)){//返回子串首次出现的首地址(找到)或NULL(未找到)
		return 1;
	}
	else{
		return 0;
	}
}

int main()
{
	//char str[] = "abcdef";
	//int len = strlen(str);
	//int k = 3;
	printf("%s\n",leftRotate(str, len, k));//真正的左旋字符数为k%len
	printf("%s\n", leftRotate2(str, len, k));
	//printf("%s\n", leftRotate3(str, len, k));

	char *s1 = "AABAD";
	char *s2 = "ADAAB";
	int len = strlen(s1);
	//printf("%d\n", judge(s1, s2, len));
	printf("%d\n", judge2(s1, s2, len));

	system("pause");
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值