==============================================java
copyright: KIRA-lznios
==============================================c++
轉載請註明出處,這篇是我原創,翻版必究!!!!!!!!!!!!!!!!!!!程序員
==============================================正則表達式
若是以爲寫個好,請留個言,點個贊。redis
最喜歡吳軍博士的一句話,和我本人的學習理念比較接近,因此對他的書也很是着迷:技術分爲術 和 道,術 是具體作事的算法,道是其背後隱藏的根本機理。算法
就像吳軍博士說的那樣,sql
1.高大上的天然語言處理背後模型機理盡然如此簡單(固然細節不簡單)數據庫
2.怎麼像你奶奶解釋搜索引擎?其實搜索引擎的背後機理其實簡單的不能再簡單了,就是布爾運算!!!三句話就能講明白,一是下載儘量多的網頁,二是創建索引,三是根據相關性給網頁排序!沒了,這就是搜索引擎,任何智能的搜素引擎都逃不出布爾運算的框架。express
3…..
以我我的愚見,首先得深入理解道,而後再去發揚術會比較好。由於只有深入理解道,而後才能舉一反十!!!而後在你接觸新東西的時候,能對之前學的知識加以聯繫,發現其中的隱含機理的類似性。並能把一個領域的經典研究方法帶到另外一個研究領域。
先交代一下:
1.這是我第一篇,忽然想寫點有質量的文章,來和你們分享知識,寫的很差的地方歡迎拍磚。
2.本人寫過編譯器,編譯器根本不是什麼高大上的東西,本質就是一種數據(信息/語言)處理的方法而已,和處理其餘數據同樣,並和處理天然語言進行對比
3.下一篇是關於學完編譯器以後,應該掌握的技能,即進階信息安全的基礎:
關於一段c/c++代碼,編譯以後,生成怎麼樣的x86,calling convention,prolog/epilog,caller-saved/callee-saved register,堆棧平衡,全部變量的內存分佈,函數符號修飾成什麼樣,靜態連接,動態連接,地址修正,連接指示對編譯過程的影響,如dllimport,dllexport,#pragma,函數聲明順便提一下連接器,以及windows下病毒的運行機理,我不會重點寫什麼是動態連接,而是解釋爲何動態連接,及其背後隱藏的緣由
4.下一篇關於OO object model,本人對OO有必定了解,封裝,繼承(單一,多重,怎麼解決菱形多重繼承數據二義性問題,微軟怎麼解決?gcc怎麼辦?分析咱們用的 prefix算法 實現對象模型的繼承 ,並給出拓展),多態,這篇以c++爲基本,講述c++ object model,並給出c++爲何轉換指針會變化(Base* b = new Derived();編譯器怎麼理解對象模型的,怎麼就能多態??對象模型長成什麼樣,怎麼樣會形成覆蓋,遮蔽?和多態在對象模型上有什麼區別?遮蔽,覆蓋爲毛就不能多態了?),並分析一下c++對象模型優缺點,容易受到什麼攻擊(堆溢出,堆噴射),雖然hook 函數指針本質不是c++語言自己形成的。。可是c++對象模型若是對於你們都是好人的狀況下,是很優秀的對象模型,but。。。
5.下一篇準備寫關於高級(多核)操做系統內核的理解,固然是基於MIT的 xv6 和 Yale的pios ,關於 Vitrual Memory:邏輯地址->線性地址->物理地址, fork/join/ret ,copy-on-write…..
6.再下一篇多是關於 內存數據庫 新存儲方式的新實現(本人拍腦殼想的),並和 sqlite3,nosql,redis 等內存數據庫進行 性能,實現方式 的比較
本文參考了數學之美,編譯器(虎書 和 龍書),和在USTC老師教的,加上我本身寫編譯器過程的理解,
最後加上我本身設計的final project:code generation(minijava->x86,AT&T,IA32)
本人花了一個學期的時間,認真的寫了一個編譯器,差很少由如下部分組成:
miniJava compiler ->
implement: lexer,parser,AST,elabrator,garbage collector(Gimple algorithm),
code generation(minijava->C), code generation(minijava->java bytecode),
code generation(minijava->x86,AT&T,IA32),
object model(encapsulation,single inherit,polymorphism)
theory: exception,closure,SSA(static single assignment),
register allocation(graph coloring)
optimiztion:CFG(liveness analysis,Reaching Definition analysis),DFG,SSA, Lattice, register allocation(graph coloring)
寫本文的目的:
寫完編譯器,發現編譯器更多的是一種數據處理的方法,而不是什麼高大上的東西,我寫這篇文章的目的,是想任何讀完我文章的人,知道編譯器到底在幹嘛,編譯器到底能幹些什麼?學了編譯器以後有神馬好處?學完編譯器應該掌握什麼技能???
我會不斷提出問題,引起讀者的思考,我喜歡有邏輯的思考問題,但願這樣能讓文章更有邏輯性。
並且我寫東西,不喜歡記流水帳,好比這個應該怎麼怎麼樣,而是寫爲何要這樣,我喜歡搞清楚其背後的緣由。
本文可能會很長,我會從背後隱含的原理去寫,而不去探討高大上的技術。
好了,廢話很少說,正文開始。
1. 先說說天然語言處理吧(本人不是很懂),一些基本概念,懂行的人直接跳過,謝謝。
a.首先古老的文明爲何會出現文字?
由於文字僅僅是信息的一種載體,意圖仍是想把信息記錄下來,本質仍是信息,古代沒有文字的時候,人們好比到了冬天冷,會發出一些 ,"嗖嗖"的聲音,肚子餓了,會發出一些什麼什麼聲音,而後因爲聲音太多,信息太多,人們沒法記住,也沒法統一,如此纔出現了文字,沒有爲何,就是由於沒有人能記住全部的聲音,
這樣就須要一種文字,去記錄那些信息。
b.有了文字,就必定會有語句,N個文字用不一樣的語法規則去拼湊生成的語句,不一樣的語法規則,生成不一樣的語言,這個很好理解。
c.隨着文明的發展,信息愈來愈多,可是文字的數量不能成倍的增加,不然也不便於記憶,這樣就出現了聚類,把一些相同概念的意思,概括用一個字(詞)去表示,好比一次多義,"日"表示太陽,表示太陽早上從東邊升起,從西邊落下,因此又能夠表示一天,等等。
第c條就是所謂的一詞多義,絕對是困擾古今中外語言學者,包括計算機科學家的一個大問題,也就是理解這個詞的意思,須要參照上下文(context)
d.常識。
The pen is in the box.
The box is in the pen.
第一句正常人都懂,第二句有點坑了,不過外國人很容易理解,因爲外國人的常識,經驗,因此外國人立馬就明白,第二句的pen的意思是圍欄。
天然語言處理,想分析語句的語義就又多了一坑。
其實我就是想說 c 和 d 是基於 編譯器技術的 lexer+parser分析 天然語言的語義 上的一個大坑, 這個就是困擾計算機科學家,語言學家多年,以及阻礙處理天然語言的緣由之一。
e.爲何要分詞?
像英語這種基本不須要,由於空格就是活生生的分隔符(可是對於手寫識別英文,空格不明顯,仍是須要分詞的),可是對於 中,日,韓,泰 等語言,好比 今天我學會了開汽車,中間沒有分隔符,因此須要分詞。
分詞其實也是一坑,好比:
此地\安能\居住,其人\好不\悲傷
此地安\能居住,其人好\不悲傷
2.爲何要扯天然語言處理,這個和編譯器到底有什麼關係?
聽我慢慢道來。
天然語言處理,其實就是處理好比,今天\我\學會了\開\汽車。 you \ are \ so \ cool.
而基於編譯器技術的 lexer + parser ,則也是同樣, 今天\我\學會了\開\汽車,不過一般是處理計算機語言,相似,static void main(string[] args)等等。
so:
a.天然語言處理,處理的是天然語言,好比上面舉得例子,The box is in the pen. 定義的上下文相關文法,即其中詞語的意思不能肯定(一次多義),須要結合相應的語境才能知道pen的意思,和你們作的英文完形填空是差很少的。
b.編譯器的如java語法,static void main()定義的是上下文無關文法,注意,上下文無關文法的好處就是,只要你定義的好,不會發生歧義,由於不存在一次多義,稍稍舉個小例子。
exp -> NUM
-> ID
-> exp + exp
-> exp * exp
碰見exp就能夠無條件分解爲後面這四種狀況,而後再不斷的遞歸降低(recursive decendent parser/ top-down parsing/predicative parsing )迭代,解析語句。
爲何說只要定義的好呢?由於咱們lab用的是ll(k),也就是說,只支持從左到右parser,若是出現左遞歸就會出現永遠循環下去,由於是無條件分解。
定義左遞歸上下文無關文法坑:
a.左遞歸->右遞歸
b.歧義->提取公因式
一些其餘編譯器應該支持lr(k)
到這裏看不懂不要緊,這裏只是隨便提提。
我只是想說,像編譯器編譯的 c/c++/java...,包括sql語句,都是上下文無關文法,均可以用基於編譯器的技術,lexer + parser 去解決問題
ok,有的人就要問了,那爲何基於編譯器的技術,lexer + parser 把天然語言,先分解爲一系列的token,以後生成語法樹,而後用llk or lrk 去遍歷這棵樹,而後進行 語義分析, 爲何不能很好的處理天然語言?
誤區:本來科學家覺得,隨着語言學家對天然語言語法的歸納愈來愈完備,計算機計算能力又在逐漸提升,基於編譯器的技術應該可以很好的解決天然語言處理。
but:一條很簡單的上下文相關的語句,卻能分析出很龐大複雜的 AST(parser 返回結果是 語法樹), 若是再複雜一點,基於語法樹的分析根本行不通。
考慮一句很長的文言文,此處省略100字。
結論:因此說,基於編譯器技術的lexer + parser 只適合解決上下文無關文法 定義出的語言。
那上下文無關文法 就不能定義 天然語言了??要不試試看?
反正我不試。。緣由以下:
a.想要經過上下文無關文法定義漢語的50%語句,語言學家不只會累死,並且因爲一詞多義,須要結合語境,因此還要在文法裏定義各類語境,能夠想象那個工做量 嗎
b.定義的上下文無關文法越多,越容易出現歧義(提取公因式),並且會出現左遞歸(改爲右遞歸),如此,如此,會瘋掉的。因此 沒法涵蓋全部語言語法不說,還有歧義,這個是要作成實際應用的,這樣能忍嗎?
如此說來,20世紀50年代到70年代,用 基於編譯器技術 lexer + parser 分析天然語言的語義,絕對是科學家們走的彎路。
直到20世紀70年代,纔有先驅從新認識這個問題,基於數學模型和統計,天然語言處理進入第二個階段。
再總結一下結論: 基於編譯器技術 lexer + parser 分析語言的語義, 只適合 上下文無關文法, 而上下文無關文法 沒法(不容易)定義 天然語言,so,不能用lexer + parser 去分析天然語言的語義。
3. 那到底怎麼處理天然語言呢?(本文不會詳細寫怎麼處理,只寫基本原理),懂行的請自覺跳過,謝謝。
從規則到統計,用數學的方法去描述語言規律。
注意,統計語言模型的產生初衷是爲了解決語音識別問題,也就是說 一句話,讓你分析,這句話究竟是不是具備正確意義的天然語言。
用統計的思想思考:一個句子,由特定的單詞串組成,s = w1,w2,...,wn ,一個句子有意義的機率是 P(s) ,
由條件機率很容易獲得 P(s) = P(w1) * P(w2 | w1) * ..... * P(wn | w1,w2,...,wn-1)
只要算出這個語句有意義的機率,不就能判斷到底這句話有木有意義了呢
可是越到後面這個條件機率越難算了,怎麼破?
不要緊,馬爾可夫爲咱們想了一個偷懶並且頗爲有效的方法就是,假設 一個詞 wi 出現的 機率 只和它前面的那個詞 wi-1 有關係,
因此公式就簡化爲 P(S) = P(w1) * P(w2 | w1) * P(w3 | w2) * ..... * P(wn | wn-1)
固然,這個模型,不少人第一次見到,確定會問,就這東東,能分析這麼難文法的天然語言。。。。嗎?
答案是確定的,Google 的 羅塞塔 系統,僅僅開發2年,就是基於相似這種數學統計模型,就一舉成名的得到了 NIST 評測的第一。
固然,對於高階一點的語言模型,其餘模型,模型的訓練,零機率問題,我在本文不想深刻討論,討論的重點,主要仍是想放在編譯器上面。
一點點思考:
說到這裏,說一點題外話。本人還寫過內存數據庫,因此,須要支持sql語句,爲sql語句也寫過 lexer 和 parser,用的也是上下文無關文法。
考慮若是sql語言,若是發展足夠強大,就像天然語言同樣,語法愈來愈多,會不會出現 聚類(一詞多義) ?若是出現聚類,那根據個人結論,
lexer + paser這種方法不work了,那是否是得用到 天然語言處理的 某些方法,或者其餘方法???
因爲目前的語言c/c++/java/sql 仍是處於上下文無關文法就能夠定義的語言,有個度(界限)的問題,若是跨越到天然語言,則之前的方法根本不能用了,是否是得考慮新的技術。
嘖嘖,隨便說說。
4.關於天然語言處理 和 編譯器相關技術處理 的淺薄關係 在上面已經說過了,接下來就是我要講清楚,編譯器到底在幹什麼?
我以前說過,編譯器也是對一種語言的處理過程,因此上文和天然語言處理進行了對比,而後引起了一點點小思考。
ok,書上說編譯器就是把高級語言翻譯成低級語言,忘了,書上好像是這麼寫的。
不過我理解的編譯器應該是這樣,
a. 編譯器會通過 lexer + parser + elabraor + code generation : IR(N種) for optimization + 可能還連接一個garbage collector
->而後生成object file(目標文件),注意目標文件仍是不能運行,可是就差那麼一點點,這一點點是什麼(對於外部符號,編譯器不知道,只能進入符號表,等待連接器來修正)?
好比你 cl /c main.c 這樣只編譯不連接,若是出現編譯器不認識的符號,不要緊,反正生成目標文件,那些符號就進入了符號表,等連接器下一步工做。
可是你 cl main.c ,這樣既編譯又連接,若是有不認識的符號,直接報錯(假設你其餘目標文件也木有這個符號)
總結:等連接器,把其餘的目標文件link到一塊兒(主要是地址修正),而後生成可執行文件(靜態連接/動態連接/動態連接靜態加載/動態連接動態加載,不同), 這樣就生成了可執行文件 .exe / a.out ... 芯片上跑去吧
詳細細節留給下一篇吧,要寫就停不下來。。。
b. 編譯器確實是把高級語言翻譯成低級語言,可是其中會通過不少種IR(中間代碼),大部分緣由是由於優化,像gcc就通過N種優化,而後生成一個最簡的x86機器碼,而後跑在intel的芯片上,固然ARM,MIPS均可以。。。固然你翻譯成java的bytecode ,在虛擬機上跑,都是能夠的。
IR嘛,舉個例子,好比
第一步我就要對AST進行優化,優化一般有 常量摺疊,代數簡化,標量代替聚量, 常量傳播,拷貝傳播,死代碼刪除,公共子表達式刪除等等
class TreeVisitor{
public static void main(String[] a){
System.out.println(new TV().Start());
}
}
lexer的輸出,很明顯是,a stream of lexical tokens :class | TreeVisitor | { | public | static | void | main | ( | String | [ | ] | a | ) | { | System | . | out | . | println | ( | new | TV | ( | ) | . | Start | ( | ) | ) | ; | } | }
看一下 Token結構體長成神馬樣子?
class Token{
public Kind kind; // kind of the token
public String lexeme; // extra lexeme for this token, if any
public Integer lineNum; // on which line of the source file this token appears 目前能夠忽略,只是爲了輸出
......}
看下輸出,你們就明白了:
TOKEN_CLASS: class : at line 5
TOKEN_ID: TreeVisitor : at line 5
TOKEN_LBRACE: <NONE> : at line 5
TOKEN_PUBLIC: public : at line 6
TOKEN_STATIC: static : at line 6
TOKEN_VOID: void : at line 6
TOKEN_MAIN: main : at line 6
TOKEN_LPAREN: <NONE> : at line 6
TOKEN_STRING: String : at line 6
TOKEN_LBRACK: <NONE> : at line 6
TOKEN_RBRACK: <NONE> : at line 6
TOKEN_ID: a : at line 6
TOKEN_RPAREN: <NONE> : at line 6
TOKEN_LBRACE: <NONE> : at line 6
..................
ok,分解爲了 a stream of lexical tokens ,很明顯用一個 隊列 去存儲它們。
note:
很是建議用隊列去存儲,爲何?
1.咱們lab用的是直接在parser裏面一個一個直接讀取lexer分解出來的Token,即不能回滾,即上一個Token還得用一個value記錄下來,固然你能夠
定義回滾幾個,而後記錄 rollbackToken1,rollbackToken2,rollbackToken3....等
2.用隊列雖然浪費了存儲空間,可是能夠任意回滾任意個數的Token
so,建議看具體須要。
神馬狀況下會遇到回滾Token?
好比,
MyVisitor v ;
root = new Tree();
因爲是遞歸降低分析(在paser中詳細討論,看完paser再回來理解),只能像微軟的編譯器同樣,寫c語言的時候,定義放在語句前面,若是你在中間某個地方寫了,int a = fun(1,2);則微軟編譯器會報錯,可是一個這樣的小錯誤,微軟的優化器會爆出各類錯。。。讓你根本就不知道哪錯了
回到正題:因爲和c語言同樣,本編譯器算法是,前面是定義,後面是語句。
so,檢測到root 的時候 Token是個ID,沒問題,可是後面發現Token 是 = 號,也就是你進入 定義和語句的 臨界區域了,so,你的代碼還在分析定義的代碼裏,怎麼破?你得回滾,而後跳出整個 分析 定義的代碼,進入分析語句的代碼,而後 current Token 得回滾到 root (原來在=)。
note:
可是gcc支持語句中有定義,不是由於 ANSI c 支持,而是gcc進行的拓展。
gcc怎麼實現的?其實很容易,和c++/java 同樣,加減符號表運算便可
gcc的c還支持bool呢,呼呼。
note:
吐槽微軟編譯器:
void fun(){}
這樣的空函數,微軟還不優化,
fun:
push ebp
mov ebp,esp
push ebx
push esi
push edi
這三個是 callee-saved 寄存器,微軟還要入棧保存,是否是有點懶了,別說寄存器分配了,若是 寄存器分配(好比圖着色) 只用到一個寄存器,這樣入棧保存一個不就好了嗎?
note:算了,仍是表揚下微軟的編譯器吧,好比你看到,
push ebp
mov ebp,esp
push ecx // 而不是 sub esp,4
爲何不用sub esp,4 ? 。。。。。。。這個緣由很深入,由於,一樣是往下開闢4個byte, push ecx 用的(指x86,ARM不知道)機器碼更少哦
其實我是想解釋,爲何 lexer 要 translates the source program into a stream of lexical tokens ?而不分解爲其餘結構 ?
想一想中文爲何要分詞? eg,今天我學會了開汽車,你用指針去掃源代碼的時候,掃到 unicode "今" ,你能把它做爲一個Token嗎?明顯不行,由於"今天"纔是一個Token。。。那怎麼樣斷句呢?即,怎麼分詞呢?最簡單的方法就是查字典,這種方法最先是由北航的梁南元教授提出的。即,字典裏有的詞就表示出來,遇到複合詞就最長匹配。
可是最長匹配也有問題。
好比, 上海大學城書店,你怎麼分?
最長匹配是: 上海大學/城/書店?
顯然不對,應該是 上海/大學城/書店
這裏不進一步討論。
好了,以前說過,像英文這樣 I am so cool. 語句之間有標點符號,語句之中有空格,因此,不須要分詞,Token很容易找到!!!!!!
代碼也是這樣,大部分是有分隔符(以空格分開)的,可是也有例外,好比,
/
//
/*
遇到一個/,你能武斷說這個Token是 / 嗎?嗯,得看看後面跟的是啥。
回到正題,爲何要分解爲a stream of lexical tokens?
由於好比天然語言是由一個一個單詞組成的,單詞組成的順序,則是語法。
你只有先把一個一個單詞分解出來,而後去分析每一個單詞之間爲何這樣排列(這就是分析這句話是神馬語法 -> 找出它的語法規則 ),而後生成一棵語法樹,存儲起來。
分詞就是lexer乾的事情,它的輸出就是給 parser 的輸入,parser 則負責生成 AST(抽象語法樹),並傳給 elabrator。
note:
說道分詞,編譯器技術已經完美解決了這個問題(僅僅針對上下文無關文法),即用 正則表達式。 NFA -> DFA
我不想延伸,由於內容太多,之後有機會再寫。
固然lexer有不少,好比 flex, sml-lex, Ocaml-lex, JLex, C#lex ......
說道這裏,lexer我是否已經講清楚了呢??我以爲差很少了,之後有機會補充。
b. parser -> 根據 遞歸降低 分析算法,生成語法樹
class TreeVisitor{
public static void main(String[] a){
System.out.println(new Visitor().Start());
}
}
class Visitor {
Tree l ;
Tree r ;
public int Strat(Tree n){
int nti ;
int a;
while(n < 10)
a = 1;
if (n.GetHas_Right())
a = 3;
else
a = 12 ;
return a;
}
}
遞歸降低,能夠用一個詞來來歸納,其實就是 while循環。
若是說要返回一個AST,這樣固然須要先定義全部抽象語句的類,而後生成其對象,而後reference相互連起來,造成一棵樹。
parser 輸出返回一棵AST -> theAst = parser.parse();
ast.program.T prog = parseProgram();
.......
ast.mainClass.MainClass mainclass = parseMainClass();
java.util.LinkedList<ast.classs.T> classes = parseClassDecls();
......
java.util.LinkedList<ast.classs.T> classes = new java.util.LinkedList<ast.classs.T>();
ast.classs.T oneclass = null;
while (current.kind == Kind.TOKEN_CLASS) {
oneclass = parseClassDecl();
classes.add(oneclass);
}
注意,我爲何說,遞歸降低就是while循環,上面漂綠的字體很明顯了,當你分析某一種語法的時候,不斷用while探測,若是進入下一個語法,則跳出while循環。
再說細一點:
int nti ;
int a;
while(n < 10)
a = 1;
if (n.GetHas_Right())
a = 3;
else
a = 12 ;
函數開始的時候,先分析 "定義" ,分析到 int nti; 沒問題,是 "定義" ,而後到 int a; 也沒問題,是 "定義"。
可是到了 while 語句,則 編譯器代碼跳出 分析 「定義」 的代碼,進入 分析 "語句" 的代碼。
注意一點便可,我上面舉得例子。
MyVisitor v ;
root = new Tree();
OK,返回了AST,好辦了,能夠直接 pretty print 出來了,由於你已經有了AST,即一棵樹,全部這段程序的語義都存儲起來了,你想怎麼打印,
不就怎麼打印了?
好比:
@Override
public void visit(ast.stm.If s)
{
this.sayln("");//if語句前換個行先
this.printSpaces();
this.say("if (");
s.condition.accept(this);
this.sayln(")");
this.indent();
s.thenn.accept(this);
this.unIndent();
this.printSpaces();
this.sayln("else");
this.indent();
s.elsee.accept(this);
this.unIndent();
return;
}
理論聯繫下實際:
假設定義語義 : int + int -> int
@Override
public void visit(ast.exp.Add e)
{
e.left.accept(this);
if (!this.type.toString().equals("@int"))
error("operator '+' left expression must be int type",e.addleftexplineNum);
ast.type.T leftty = this.type;
e.right.accept(this);
if (!this.type.toString().equals("@int"))
error("operator '+' right expression must be int type" ,e.addrightexplineNum);
this.type = new ast.type.Int(); //表示當前操做 add,完成以後,「返回」一個操做數類型爲 int
return;
}
note:
這裏不討論關於繼承(多態),function call等再難一點的語義分析,不是本文重點。
OK, elabrator 的工做,總結下,就是先掃一遍AST,而後生成相應的符號表(多態涉及prefix算法計算繼承後的對象模型中虛函數表的函數指針排列順序,這裏不討論),而後進行類型系統的判斷,報出一些語句出錯的信息,或者警告信息。
elabrator我是否已經講清楚了呢?
d. code generation -> 生成 IR
本人作了 minijava -> java bytecode / c / x86
minijava 直接 -> x86,我幾回推翻重寫,不過最後完成了,仍是很 happy (minijava沒有很高深的java語法,僅僅是封裝,繼承,多態,我用x86模擬了而已)
仍是有必定難度的,用匯編這種低級語言去模擬封裝,繼承,多態,仍是有必定難度的,放在之後討論吧,寫不完了。
e. 討論 exception,closure,SSA(static single assignment) 是怎麼樣實現的
exception:其實編譯器一般有2種方法,
1.基於異常棧
2.基於異常表:pay as you go
細節,不想在本文討論了。
closure: 我會討論在java非要支持nested function以後,一步一步逃逸變量是怎麼樣不可以存儲,而後引出closure的解決方法的,還會給出closure 和 object model 有什麼區別?
SSA(static single assignment) 真心是一種牛逼的IR,讓不少優化變得很是簡單。可是內容太多,寫不完了。自從有的這個SSA,gcc版本從某一個版本,忘了,開始所有把基於 CFG , DFG 的優化,變成SSA了
5. 用編譯器知識理解語言小細節
1.好比到底應該寫成 char* p; 還應該寫成 char *p; 這種問題其實很好理解,爲何,編譯器怎麼處理指針? 即,碰到類型後面碰到*,就把後面的變量當作指針,好理解了嗎,這就是爲神馬 char* p1,p2; p2不是指針的緣由
我我的喜愛,就把 char* 當作一個類型,只須要注意 char* p1,p2; p2 這種狀況便可,不少人不是喜歡這樣寫typedef char* pchar嗎,這不就是赤裸裸的認爲char*就是一個類型嗎?沒錯,我就喜歡把它當作一個類型。
2.好比 const 修飾的 變量,老是分不清 ,
const char* p;
char const* p;
char* const p;
const char* const p;
我說一句話,你就能永遠分清,信不信?固然這個是我從effective c++裏面學的,
const 出如今*左邊修飾的是指針指向的value,而出如今右邊則是修飾的是指針,
沒錯,你已經會了。
前面2個一個意思,都是修飾value是const,第三個是修飾指針是const,第四個是2個都修飾。
3.好比神馬 前加加,後加加 ,搞不清楚 ++a;a++;....
int a = 1;
printf("%d",a++); 爲毛答案仍是1 ?
int a = 1;
printf("%d",++a); 爲毛答案就是2了 ?
你若是學過編譯器,你就懂了,你能夠這樣理解:
printf("%d",a++); 其實會被編譯成2句話
printf("%d",a);
a++;(a = a + 1;)
這樣,答案是神馬,不用我說了吧。這個就叫作後加,懂了吧
printf("%d",++a);實際上也是會被編譯成2句話
a++;(a = a + 1;)
printf("%d",a);
爲何是2?一目瞭然,之後還分不清前加後加嗎?嘻嘻。
4. 其實 循環語句,其實對於x86來講就1種->跳轉,固然跳轉有2種,
結語:
note:
其實編譯器技術,還有不少不少,我只是討論了其中的九牛一毛,並且因爲篇幅限制,我寫不下太多。做爲第一篇文章,暫時先這樣吧,之後再更新。
本人對信息安全也略懂,因此對底層的一些東西有一些本身的理解,其實這些都是基礎,作安全最最重要的基礎,是在課本上根本學不到的東西,最最精華的東西,在之後的文章中我會陸續提到:
學完編譯器,對語言的理解又更深了一步,好比你看到以下東西,
int c = 4;
int d;
void fun(int a,int b)
{ int n = 4;
int i;
for(i=0;i<n;i++)
printf("a+b=%d\n",a+b);
}
int main()
{
fun(1,2);
return 0;
}
要思考,編譯以後,生成怎麼樣的x86,calling convention,prolog/epilog,caller-saved/callee-saved register,堆棧平衡,全部變量的內存分佈,函數符號修飾成什麼樣,靜態連接,動態連接,地址修正,連接指示對編譯過程的影響,如dllimport,dllexport,#pragma,函數聲明等等
之後的文章我會陸續解釋。
其實學完編譯器的真正效果,就是你看到上面的c,能想到,其實它就是神馬。。。