移动指令数据移动指令mov,把数据从一个位置移动到另一个位置mov r0, #1 ; 移动立即数1到寄存器r0 mov r1, r2 ; 移动寄存器r2的内容到寄存器r1算术指令加法指令add r3, r1, r2 ; r3 = r1 + r2 add r4, r4, #5 ; r4 = r4 + 5减法指令sub r5, r5, #3 ; r5 = r5 - 3分支指令跳转到指定标签或者地址b designated_label ; 跳转到指定分支designated_label条件跳转beq equal ; 如果相等就跳转 bne not_equal ; 如果不等就跳转 bgt greater ; 如果大于就跳转 blt less ; 如果小于就跳转加载存储指令加载指令,从内存加载数据到寄存器ldr r1, [r2] ; 加载寄存器r2里的地址指向的内容到寄存器r1存储指令, 把寄存器的内容存到内存str r6, [r7] ; 把寄存器r6的内容保存到r7寄存器里地址指向的内存比较指令cmp r8, r9 ; 比较寄存

源代码经过编译后,变成二进制文件,这个二进制文件是由一条一条指令组成的。操作系统加载这个二进制文件到内存。CPU按照内存里的指令一条一条执行,CPU加载一条指令,执行一条指令,然后接着加载下一条指令,再执行。一直重复这个过程。

add' -> + mul add' | ε对于这条规则里的递归,有个术语叫尾递归。即产生式箭头的右边在尾部调用产生式的箭头左边。尾递归改写为循环是优化写法。因为循环比递归在系统开销上更少。计算n的阶乘,n*(n-1)*(n-2)*...*1记f(n)表示n的阶乘,则f(n) = n * f(n-1)int factorial(int n) { if(n == 1) { return 1; } return n * factorial(n-1); }这个就是典型的尾递归。改写成循环int factorial(int n) { int result = 1; while(n > 1) { result *= n; n--; } return result; }

对于表达式规则:expr -> add add -> mul | add + mul mul -> literal | mul * literal literal -> 0|1|2|3|4|5|6|7|8|9会有左递归的问题,比如:1+2,针对这个表达式,应用产生式:add -> mul | add + mul,得到 add + mul,继续应用产生式:add -> mul | add + mul,得到add + mul + mul,发现最左边一直都是add,每应用一次产生式都多一个 + mul,一直重复下去,无穷无尽,无法推导出最终的表达式。这个问题就是左递归问题。问题的解决办法是修改规则,增加一条规则,变成以下这样:add -> mul add' add' -> + mul add' | ε这个规则可以改写成下面的形式add -> mul (+ mul)*其中*表示零个或多个 + mul,这样就解决了左递归问题。

源代码文本经过词法分析后,变成一串token。语法分析就是处理这串token,使之变成树状的token。这个树状的token就是AST。比如源代码文本1 + 2 * 3经过语法分析,变为后序遍历这颗树就可以求出这个表达式的值。怎么构造这颗树呢?使用自顶向下算法。首先构造第一个表达式节点,然后遇到1构造一个1节点,作为第一个表达式的子节点。然后遇到+构造+号节点,也作为第一个表达式的子节点。后面要么是一个表达式,要么是一个数字,所以也构造一个表达式节点作为第一个表达式节点的子节点。然后遇到2,构造一个2节点,作为第二个表达式节点的子节点,然后遇到*号,构造一个*节点作为第二个表达式的子节点,然后遇到3,构造一个3节点,作为第二个表达式的子节点。什么是表达式呢?假设expr表示表达式,并且expr只有加法和乘法,则有如下定义:expr -> add add -> mul | add + mul mul -> literal | mul * literal literal -> 0|1|2|3|4|5|6|7|8|9上面的式子叫产生式,可以读作expr由add组成。a