从头构建一个表达式解析工具
最近在用反汇编工具的时候需要用特殊的表达式跳转至内存地址,用起来也挺方便的,就想着能不能用 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里写的,引用文件要注意一下,我已经提前注释好了,应该是没啥问题,报错就自己重新引用所需要的头文件