首页 > 其他分享 >中/后缀表达式

中/后缀表达式

时间:2023-05-11 14:04:32浏览次数:33  
标签:eax 后缀 int stack 表达式 ecx

目录

纸上得来终觉浅,绝知此事要躬行

0x01 什么是后缀表达式

人类语言数学表达式:(3+4)*2/(1-5)^2

计算机语言数学表达式:3 4 + 2 * 1 5 - / 2 ^

计算机通过栈的数据结构和对应的指令,实现数学表达式的运算

0x02 后缀表达式的具体应用

0x031 JAVA的后缀表达式

源码

    /**
     * 中序表达式:(a + b) * c / (d - e) ^ f
     * 后序表达式:a b + c * d e - / f ^ 
     */
    public int postfix(int a, int b, int c, int d, int e, int f){
        return  (a + b) * c / (d - e) ^ f;
    }

反编译

image

BYTECODE

image

数字含义如下

  • 25是方法开始的行号
  • 1/2/3/4/5/6是变量在参数列表中的索引
  • 3是最大压栈深度
  • 7是本地变量表长度

指令含义如下

字节码 助记符 指令含义
0x15 iload 将指定的int型本地变量推送至栈顶
0x60 iadd 将栈顶两int型数值相加并将结果压入栈顶
0x68 imul 将栈顶两int型数值相乘并将结果压入栈顶
0x64 isub 将栈顶两int型数值相减并将结果压入栈顶
0x6c idiv 将栈顶两int型数值相除并将结果压入栈顶
0x82 ixor 将栈顶两int型数值作“按位异或”并将结果压入栈顶
0xac ireturn 从当前方法返回int

实际已经转换完成:3 4 + 2 * 1 5 - / 2 ^

JAVAC的实现

二元表达式树节点对象

来源:com.sun.tools.javac.tree.JCTree.JCBinary
public static class JCBinary extends JCExpression implements BinaryTree {
    // 表示二元运算符
    private int opcode;
    // 二元表达式左边的表达式
    public JCExpression lhs;
    // 二元表达式右边的表达式
    public JCExpression rhs;
    // 操作符的符号
    public Symbol operator;
}

javac在语义分析阶段就将源码构建为一颗抽象语法树,并完成了对语法树的标注。语法树如下

image

运行时二元表达式树

image

iShot_2023-05-11_12

执行过程

来源:com.sun.tools.javac.jvm.Gen#visitBinary
// 访问二元表达式com.sun.tools.javac.jvm.Gen.visitBinary
public void visitBinary(JCBinary tree) {
    OperatorSymbol operator = (OperatorSymbol)tree.operator;
    if (operator.opcode == string_add) {
        // 对字符串的+进行处理
		...
    } else if (tree.getTag() == JCTree.AND) {
        // 特殊处理&&
		...
    } else if (tree.getTag() == JCTree.OR) {
        // 特殊处理||
        ...
    } else {
        // 处理二元表达式
        // 先处理左表达式
        Item od = genExpr(tree.lhs, operator.type.getParameterTypes().head);
        // 指令压栈
        od.load();
        // 当执行到这一行时,左表达式已经在栈中
        result = completeBinop(tree.lhs, tree.rhs, operator);
    }
}

来源:com.sun.tools.javac.jvm.Gen#completeBinop
Item completeBinop(JCTree lhs, JCTree rhs, OperatorSymbol operator) {
    // 运算符指令code
    int opcode = operator.opcode;
    ...
    // 处理右操作数,然后入栈
    genExpr(rhs, rtype).load();
    ...
    // 运算符入栈
    code.emitop0(opcode);
    ...
}

0x032 C的后缀表达式

源码

int postfix(int a, int b, int c, int d, int e, int f) {
	return  (a + b) * c / (d - e) ^ f;
}

int main()
{
	postfix(1, 2, 3, 4, 5, 6);
	return 0;
}

反汇编

 ;int postfix(int a, int b, int c, int d, int e, int f) {
 ... ; 参数内存分配,栈上下文检查
 ;	return  (a + b) * c / (d - e) ^ f;
 mov         eax,dword ptr [b]  ;将b挪到eax寄存器
 mov         ecx,dword ptr [a]  ;将a挪到ecx寄存器
 add         ecx,eax  			;eac+ecx 结果存到ecx中
 mov         eax,ecx   			;将结果挪到eax中
 imul        eax,dword ptr [c]  ;eax乘以c,结果放到eax中
 mov         ecx,dword ptr [e]  ;e挪到ecx中 
 mov         edx,dword ptr [d]  ;d挪到edx中
 sub         edx,ecx   			;edx-ecx,结果放到edx中
 mov         ecx,edx   			;结果挪到ecx中
 cdq   							;除法准备,符号扩展
 idiv        eax,ecx   			;eax除以ecx,结果放到eax中
 xor         eax,dword ptr [f]  ;eax与f做异或运算,结果放到eax中
 ;}
 add         rsp,28h  			;栈顶往下挪0x28个字节
 ret							;过程执行完成,返回

由反汇编可见,实际运算过程也是a b + c * d e - / f ^

0x03 简单实现

中缀转后缀表达式

  1. 从左到右遍历中缀表达式的每一个数字和符号。

  2. 若是数字就输出,即成为后缀表达式的一部分。

  3. 如果是符号,则判断其与栈顶符号的优先级,是*右括号**已有栈顶符号优先级(乘除优于加减)大于等于此符号*则栈顶元素依次出栈并输出,并将当前符号进栈,一直到最终输出后缀表达式为止。

package ylcComplieTest;

import java.util.Stack;

public class infixToPostfix {
    public static String infixToPostfix(String infix) {
        StringBuilder postfix = new StringBuilder();
        Stack<Character> stack = new Stack<>();
        for (int i = 0; i < infix.length(); i++) {
            char c = infix.charAt(i);
            // 如果是数字或字母,直接加入后缀表达式
            if (Character.isLetterOrDigit(c)) {
                postfix.append(c);
            }
            // 如果是左括号,入栈
            else if (c == '(') {
                stack.push(c);
            }
            // 如果是右括号,弹出栈顶元素,直到找到左括号
            else if (c == ')') {
                while (!stack.isEmpty() && stack.peek() != '(') {
                    postfix.append(stack.pop());
                }
                if (!stack.isEmpty() && stack.peek() != '(') {
                    return "Invalid Expression";
                } else {
                    stack.pop();
                }
            }
            // 如果是运算符
            else {
                while (!stack.isEmpty() && precedence(c) <= precedence(stack.peek())) {
                    postfix.append(stack.pop());
                }
                stack.push(c);
            }
        }
        // 将栈中剩余的运算符弹出,加入后缀表达式
        while (!stack.isEmpty()) {
            postfix.append(stack.pop());
        }
        return postfix.toString();
    }
    
    // 优先级判定
    public static int precedence(char c) {
        switch (c) {
            case '+':
            case '-':
                return 1;
            case '*':
            case '/':
                return 2;
            case '^':
                return 0;
        }
        return -1;
    }
}

后缀转中缀表达式

package ylcComplieTest;

import java.util.Stack;

public class postfixToInfix {
    public static String postfixToInfix(String postfix) {
        Stack<String> stack = new Stack<>();
        for (int i = 0; i < postfix.length(); i++) {
            char c = postfix.charAt(i);
            if (Character.isLetterOrDigit(c)) {
                stack.push(String.valueOf(c));
            } else {
                String operand2 = stack.pop();
                String operand1 = stack.pop();
                stack.push("(" + operand1 + c + operand2 + ")");
            }
        }
        return stack.pop();
    }
}

0x04 总结

  1. 更深入理解解释器原理
  2. 更深入理解计算机底层原理
  3. 解释器必备
  4. 虚拟机反编译引擎必备

标签:eax,后缀,int,stack,表达式,ecx
From: https://www.cnblogs.com/ylc0x01/p/17390822.html

相关文章

  • 【C++学习笔记】C++ 正则表达式不完全支持零断宽言
    最近需要解析配置文件,遇到从@STARTDATA@END中提取DATA的正则,按照C#的操作,直接(?<=@START)[\W\w]?(?=@END),就能提取的,可是在C++中,regexe("(?<=@START)[\W\w]?(?=@END)")报错了,找了很多说法,最终结论:支持先行断言,不支持后行断言即:(?<=@START)和(?<!@START)。好在C++支持子匹......
  • linux 中 正则表达式* 和 ?
     *表示匹配前一个字符0次或者多次;?表示匹配前一个字符0次或者1次,且只在扩展正则表达式中生效。 001、root@DESKTOP-IDT9S0E:/home/test#echo"ik"|grep"ie?k"root@DESKTOP-IDT9S0E:/home/test#echo"ik"|sed-n'/ie*k/p'##*表示匹配0次或者多次ikroot@DESK......
  • 6个在线正则表达式工具
    正则表达式可以让开放人员更加有效的操纵文本内容,在各种各样的开发中经常会遇到需要正则表达式解决的问题,比如验证邮箱,验证网址,一些小偷程序的批量替换等等。熟练的应用正则表达式可以方便于很多文本的操作,加快开发的进度。但是正则表达式并不是一个非常简单......
  • 正则表达式的使用方法
    引用单元:uses System.RegularExpressions1、TRegEx.Match方法Match()方法总是获取满足条件的第一个匹配,而不关心满足条件的匹配有多少个。Match()方法都回一个Match对象,其中包含了匹配的各种细节.Match()方法的取值方法varm:TMatch;beginm:=TRegEx.Match('a......
  • 正则表达式语法及其在python的应用
    一、语法参考:https://www.liujiangblog.com/course/python/731、普通字符:正则表达式中的普通字符在进行匹配的时候只会匹配与自身相同的一个字符。2、元字符:.小数点;|逻辑或;[]匹配字符集中的一个字符;[^]对字符集求反;-定义字符集中的字符区间;\对紧跟其后的一个字符进行转义;()对表......
  • 正则表达式详解
    一、正则表达式概述正则表达式是一组由字母和符号组成的特殊文本,它可以用来从文本中找出满足你想要的格式的句子。通俗的讲就是按照某种规则去匹配符合条件的字符串一个正则表达式是一种从左到右匹配主体字符串的模式。“Regularexpression”这个词比较拗口,我们常使用缩写......
  • 现代 C++:Lambda 表达式
    Lambda表达式(LambdaExpression)是C++11引入的一个“语法糖”,可以方便快捷地创建一个“函数对象”。从C++11开始,C++有三种方式可以创建/传递一个可以被调用的对象:函数指针仿函数(Functor)Lambda表达式函数指针函数指针是从C语言老祖宗继承下来的东西,比较原始,功能也比......
  • C++ Lambda表达式的完整介绍
    c++在c++11标准中引入了lambda表达式,一般用于定义匿名函数,使得代码更加灵活简洁。lambda表达式与普通函数类似,也有参数列表、返回值类型和函数体,只是它的定义方式更简洁,并且可以在函数内部定义。什么是Lambda表达式最常见的lambda的表达式写法如下autoplus=[](intv1,int......
  • C++11 lambda表达式精讲
    lambda表达式是C++11最重要也最常用的一个特性之一,C#3.5和Java8中就引入了lambda表达式。 lambda来源于函数式编程的概念,也是现代编程语言的一个特点。C++11这次终于把lambda加进来了。 lambda表达式有如下优点:声明式编程风格:就地匿名定义目标函数或函数对......
  • Django笔记二十三之case、when操作条件表达式搜索、更新等操作
    本文首发于公众号:Hunter后端原文链接:Django笔记二十三之条件表达式搜索、更新等操作这一篇笔记将介绍条件表达式,就是如何在model的使用中根据不同的条件筛选数据返回。这个操作类似于数据库中ifelifelse的逻辑。以下是本篇笔记的目录:model和数据准备When和Case......