表达式解析

从头构建一个表达式解析工具

最近在用反汇编工具的时候需要用特殊的表达式跳转至内存地址,用起来也挺方便的,就想着能不能用 C++ 也实现一个类似的小工具

[[0x00458888]+0x12*4]+0x8
[[[0x08684FFF]]+0x255]

大概就是这种格式,中括号内的是内存地址,可以轻松读出内存并计算出偏移
我原本的思路是用正则表达式把字符串都提出来然后再计算,但这种对结构简单的表达式还比较奏效,如果表达式比较复杂,或者是多层嵌套外加很多计算,那逻辑写起来就相当的不方便,然后我开始构想其他方案

中缀表达式和逆波兰式(后缀表达式)

我在思考如何处理表达式的时候才意识到,windows自带的计算器和各种语言的编译器,不都是有这样一个解析过程吗,我们所能理解的,或者说约定的书写格式,并不是计算机能直接运行的格式,只有经过编译解释,转换成了相应的格式才能跑得起来
那我们那拿简单的 计算器 来说,它到底是怎么一个实现原理呢

(A+B)*C+D/E

假设我们要计算如上一个表达式,正常推算的话,有括号先算括号内的,有乘除先算乘除,加减是最后算,引入一个优先级系统倒也是一个方案,但数据处理起来还是相对麻烦,应该还有更科学更标准的算法方案
这时候 逆波兰式 就要派上用场了
百度介绍了一大堆巴拉巴拉,我是没仔细看转换原理是啥,反正能用就行
我们要做的就是将我们写的中缀表达式转换成后缀表达式,然后通过后缀表达式来计算出值
我们先看一下两者之间的一个转换逻辑

输入:中缀表达式串
中转:临时符号堆栈
输出:后缀表达式串

PROCESS BEGIN:

从左往右扫描中缀表达式串s,对于每一个操作数或操作符,执行以下操作;
	1.IF (扫描到的s[i]是操作数DATA)          
		将s[i]添加到输出串中;
	2.IF (扫描到的s[i]是开括号'(')
		将s[i]压栈;
	3.WHILE (扫描到的s[i]是操作符OP)
		IF (栈为空 或 栈顶为'(' 或 扫描到的操作符优先级比栈顶操作符高)
			将s[i]压栈;
			BREAK;
		ELSE
			出栈至输出串中
	4.IF (扫描到的s[i]是闭括号')')
		栈中运算符逐个出栈并输出,直到遇到开括号'(';
		开括号'('出栈并丢弃;
	5.返回第1步
	6.WHILE (扫描结束而栈中还有操作符)
		操作符出栈并加到输出串中

PROCESS END
————————————————
原文链接:https://blog.csdn.net/linraise/article/details/20459751

假设我们输入表达式

(A+B)*C+D/E

那输出的后缀表达式应为

AB+C*DE/+

后缀表达式计算

用转换完的后缀表达式计算就非常方便了

输入:后缀表达式
中转:临时数值堆栈
输出:表达式计算结果

PROCESS BEGIN:

从左往右扫描后缀表达式串s,对于每一个操作数或操作符,执行以下操作;

	1.IF (扫描到的s[i]是操作数DATA)
		将s[i]压入堆栈
	2.IF (扫描到的s[i]是操作符)
		从数值堆栈弹出两个操作数并执行相应的计算
		压入计算结果

PROCESS END

最后堆栈里的数值就是表达式的计算结果

代码实现

已知我们的思路的是:
将输入的中缀表达式以字符串的形式解析成后缀表达式,然后再由后缀表达式计算出数值
如果该表达式需要经常调用,可以将该表达式的解析式存储,需要时直接计算结果,就不需要反复解析,以节省cpu开销

首先自己构建一个堆栈工具类

class Stack 
{
private:
	int SP = -1;						//初始化栈顶
	DWORD OUTStr;						//声明弹出值
	DWORD TOPValue;						//声明栈顶值
public:
	DWORD Stack[50];					//4字节且最大高度为50
	DWORD Stack_top();					//返回栈顶的值
	void Stack_push(DWORD numb);		//压入一个数
	DWORD Stack_pop();					//弹出栈顶
	bool StackisEmpty();				//判断堆栈是否清空
	bool ComparePriority(char op);		//比较栈顶符号优先级
};
void Stack::Stack_push(DWORD temp)
{
	Stack[SP + 1] = temp;
	SP++;
}
DWORD Stack::Stack_top()
{
	TOPValue = Stack[SP];
	return TOPValue;
}
DWORD Stack::Stack_pop()
{
	OUTStr = Stack_top();
	SP--;
	return OUTStr;
}
bool Stack::StackisEmpty()
{
	if (SP >= 0) {
		return false;
	}
	else
	{
		return true;
	}
}
bool Stack::ComparePriority(char op)
{
	if (SP == -1)										//堆栈为空,直接存储
	{
		return true;
	}
	return priority(op) > priority(Stack_top());
}

然后是支撑运算的一些小功能

//定义符号优先级
char priority(char op)									
{
	switch (op)
	{
	case '*':
	case '/':
		return 5;
	case '+':
	case '-':
		return 4;
	case 'B':
	case 'W':
		return 2;
	case '[':
	case '(':
		return 1;
	case ']':
	case ')':
		return 0;
	}
}
//将字符串转换为具体数值
DWORD StringtoVlaue(char *p)		
{
	DWORD value;
	if (p[0] == '0')				//首字母为0默认为16进制
	{
		sscanf(p, "%x", &value);
	}
	else							//否则10进制
	{
		sscanf(p, "%d", &value);
	}
	return value;
}
//判断是否为数字
bool isnumb(char a) {
	if (a >= '0'&&a <= '9')
	{
		return true;
	}
	if (a >= 'a'&&a <= 'f')
	{
		return true;
	}
	if (a >= 'A'&&a <= 'F')
	{
		return true;
	}
	if (a == 'x')
	{
		return true;
	}
	return false;
}

然后是代码的关键部分:中缀表达式转逆波兰式

DWORD* postfixExpression(const char *nfexp)							//逆波兰式
{
	Stack *pfexp = new Stack;										//如果不需要缓存则不需要new
	Stack operators;
	char numb[20];													//一个临时的数组
	int i;
	while (*nfexp != NULL) {
		if (*nfexp >= '0' && *nfexp <= '9')							//读取的是数字
		{
			ZeroMemory(numb, 20);
			i = 0;
			while (isnumb(*nfexp))
			{
				numb[i] = *nfexp;
				i++;
				nfexp++;
			}
			pfexp->Stack_push(0);
			pfexp->Stack_push(StringtoVlaue(numb));
		}
		else if (priority(*nfexp) > 2)								//为普通运算符的情况"+-*/"
		{
			while (true)
			{
				if (operators.ComparePriority(*nfexp))
				{
					operators.Stack_push(*nfexp);
					nfexp++;
					break;
				}
				else
				{
					pfexp->Stack_push(1);
					pfexp->Stack_push(operators.Stack_pop());
				}
			}
		}
		else if (priority(*nfexp) == 1 || priority(*nfexp) == 2)	//特殊运算符类型1,2 "[(BW"
		{
			operators.Stack_push(*nfexp);
			nfexp++;
		}
		else if (priority(*nfexp) == 0)								//特殊运算符类型0 ")]"
		{
			while (priority(operators.Stack_top()) != 1)			//遍历到 "(["
			{
				pfexp->Stack_push(1);
				pfexp->Stack_push(operators.Stack_pop());			//依次弹出符号堆栈到后缀表达式
			}
			if (operators.Stack_top() == '[')
			{
				pfexp->Stack_push(1);
				pfexp->Stack_push(operators.Stack_pop());			//把符号'['也推进后缀表达式
			}
			else
			{
				operators.Stack_pop();								//把符号'('从堆栈里弹出
				if (priority(operators.Stack_top()) == 2)			//如果之前有特殊运算符 "BW" 
				{
					pfexp->Stack_push(1);
					pfexp->Stack_push(operators.Stack_pop());		//弹出特殊运算符到后缀表达式
				}
			}
			nfexp++;
		}
		else
		{
			nfexp++;												//可能是空格或者其他未知指令直接跳过
		}
	}
	while (!operators.StackisEmpty())								//将剩余的符号也推入后缀表达式
	{
		pfexp->Stack_push(1);
		pfexp->Stack_push(operators.Stack_pop());
	}
	return pfexp->Stack;
}

最后一步,计算后缀表达式
补充:细心的同学应该发现了,我在符号和数字入栈的时候都加了对应的标识符(0=数字,1=操作符),这是防止最后读取的时候误把数值当成操作符处理了,毕竟谁也不能保证写入数值会不会和操作符数值撞上

DWORD ExpressionValue(DWORD pfexp[])
{
	int i = 0;
	Stack temp;
	DWORD num1, num2;
	BYTE bytevalue;
	WORD wordvalue;
	while (true)
	{
		if (pfexp[i] == 0)
		{
			i++;
			temp.Stack_push(pfexp[i]);
			i++;
		}
		else if (pfexp[i] == 1)
		{
			i++;
			switch (pfexp[i])
			{
			case '+':
				temp.Stack_push(temp.Stack_pop() + temp.Stack_pop());
				break;
			case '*':
				temp.Stack_push(temp.Stack_pop() * temp.Stack_pop());
				break;
			case '-':
				num1 = temp.Stack_pop();
				num2 = temp.Stack_pop();
				temp.Stack_push(num2 - num1);
				break;
			case '/':
				num1 = temp.Stack_pop();
				num2 = temp.Stack_pop();
				temp.Stack_push(num2 / num1);
				break;
			case '[':
				temp.Stack_push(*(DWORD *)temp.Stack_pop());
				break;
			case 'B':
				bytevalue = *(DWORD *)temp.Stack_pop();
				temp.Stack_push(bytevalue);
				break;
			case 'W':
				wordvalue = *(DWORD *)temp.Stack_pop();
				temp.Stack_push(wordvalue);
				break;
			}
			i++;
		}
		else
		{
			return temp.Stack_pop();
		}
	}
}

运行测试

简单运算一下几个表达式
在这里插入图片描述
结果没有任何问题,读内存的功能也试了一下,同样可以正常运行,需要注意的是读内存功能没有加入异常处理,如果读入的是非法地址,程序是会直接崩掉的

表达式规则

[addresss]	= 以DWORD的形式读取存地址的值
B(address)	= 以BYTE形式读取内存地址的值
W(address)	= 以WORD形式读取内存地址的值

以0开头的立即数被认为是16进制数值,如

0xFF,012A,012

不加 0 的话,表达式 12A 只会读取 12 ,并认为是10进制
所有未定义的字符都会被忽略
可以直接当个小计算器来用
可以自己添加语法规则,假如你能看懂我代码逻辑的话

!没有对语法格式进行校验,如果格式错误的话我也不知道会产生什么样的后果

反思和总结

其实我的这些代码是干了编译器的活,而且处理的效率还不是很高,尤其是数据处理方面,那个后缀表达式解析后的存储,我用DWORD数组类型来当堆栈存储数值,char型的符号标志位和操作符都会单独占据一个位置,属实有点浪费,然后就是计算的时候还是需要堆栈和多个判断语句协同处理,也会对速度造成影响

  • 源文件还在审核,要用可以自己先复制,代码全在这
  • 因为我的代码是在MFC里写的,引用文件要注意一下,我已经提前注释好了,应该是没啥问题,报错就自己重新引用所需要的头文件

源文件下载

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值