可以存儲在內存中的樹,或者你可以直接產生所需要的輸出代碼。存儲中間表格通常可以在生成輸出之前在較高級別上對代碼進行一些處理。
以你的情況爲例,很容易發現你的表達式不包含變量,因此結果是一個固定的數字。但是一次只能看到一個節點,但這是不可能的。如果在查看「2 *」後生成機器代碼來計算某些東西的二倍,那麼當其他部分例如爲「3」時,這些代碼會被浪費,因爲您的程序將計算「3」,然後計算雙倍的每次而只是加載「6」將是相當的,但更短,更快。
如果你想生成機器碼,那麼你首先需要知道代碼將被生成到什麼樣的機器......最簡單的模型是基於堆棧的方法。在這種情況下,您不需要寄存器分配邏輯,並且無需中間表示即可直接編譯爲機器碼。考慮這個只處理整數,四個操作,一元否定和變量的小例子......您會注意到根本沒有使用數據結構:讀取源代碼字符並將機器指令寫入輸出中......
#include <stdio.h>
#include <stdlib.h>
void error(const char *what)
{
fprintf(stderr, "ERROR: %s\n", what);
exit(1);
}
void compileLiteral(const char *& s)
{
int v = 0;
while (*s >= '0' && *s <= '9')
{
v = v*10 + *s++ - '0';
}
printf(" mov eax, %i\n", v);
}
void compileSymbol(const char *& s)
{
printf(" mov eax, dword ptr ");
while ((*s >= 'a' && *s <= 'z') ||
(*s >= 'A' && *s <= 'Z') ||
(*s >= '0' && *s <= '9') ||
(*s == '_'))
{
putchar(*s++);
}
printf("\n");
}
void compileExpression(const char *&);
void compileTerm(const char *& s)
{
if (*s >= '0' && *s <= '9') {
// Number
compileLiteral(s);
} else if ((*s >= 'a' && *s <= 'z') ||
(*s >= 'A' && *s <= 'Z') ||
(*s == '_')) {
// Variable
compileSymbol(s);
} else if (*s == '-') {
// Unary negation
s++;
compileTerm(s);
printf(" neg eax\n");
} else if (*s == '(') {
// Parenthesized sub-expression
s++;
compileExpression(s);
if (*s != ')')
error("')' expected");
s++;
} else {
error("Syntax error");
}
}
void compileMulDiv(const char *& s)
{
compileTerm(s);
for (;;)
{
if (*s == '*') {
s++;
printf(" push eax\n");
compileTerm(s);
printf(" mov ebx, eax\n");
printf(" pop eax\n");
printf(" imul ebx\n");
} else if (*s == '/') {
s++;
printf(" push eax\n");
compileTerm(s);
printf(" mov ebx, eax\n");
printf(" pop eax\n");
printf(" idiv ebx\n");
} else break;
}
}
void compileAddSub(const char *& s)
{
compileMulDiv(s);
for (;;)
{
if (*s == '+') {
s++;
printf(" push eax\n");
compileMulDiv(s);
printf(" mov ebx, eax\n");
printf(" pop eax\n");
printf(" add eax, ebx\n");
} else if (*s == '-') {
s++;
printf(" push eax\n");
compileMulDiv(s);
printf(" mov ebx, eax\n");
printf(" pop eax\n");
printf(" sub eax, ebx\n");
} else break;
}
}
void compileExpression(const char *& s)
{
compileAddSub(s);
}
int main(int argc, const char *argv[])
{
if (argc != 2) error("Syntax: simple-compiler <expr>\n");
compileExpression(argv[1]);
return 0;
}
例如與1+y*(-3+x)
運行編譯器爲輸入你的輸出
mov eax, 1
push eax
mov eax, dword ptr y
push eax
mov eax, 3
neg eax
push eax
mov eax, dword ptr x
mov ebx, eax
pop eax
add eax, ebx
mov ebx, eax
pop eax
imul ebx
mov ebx, eax
pop eax
add eax, ebx
然而編寫編譯器的這種方法不能很好地擴展到優化編譯器。
雖然可以通過增加在輸出級「窺孔」優化得到了一些優化,許多有用的優化是可能只能從更高的角度看代碼。
而且即使是裸機代碼生成可以通過看更多的代碼中受益,例如,以決定哪一個寄存器分配給什麼,或者決定哪些可能的彙編程序實現的是方便了特定的代碼模式。
例如相同的表達可以通過優化編譯器編譯爲
mov eax, dword ptr x
sub eax, 3
imul dword ptr y
inc eax