2009年8月18日 星期二

lex & yacc - lex introduction


lex被稱為Lexical Analyzer(中文要叫做語彙分析器?有點怪),用來產生辨識字詞的工具,透過regular expression定義pattern,當字詞符合某個pattern,就做特定的action。簡單的說就是切token。 lex檔分成三個部分: 1. definition section(declarations):用於初始化C和lex的,比如變數的宣告。 2. rule section(rules):定義pattern與相對應的action。 3. user subroutine section(programs):就是C code。 %{ /* comment: this is demo code * file name: 01.l */ %} %% [\t ]+ /* ignore space */ ; hello | world { printf("I can recognize the word \"%s\"\n", yytext); } %% int main() { yylex(); return 0; }
brook@debian:~/src/lex$ flex 01.l -o 01.yy.c
brook@debian:~/src/lex$ flex 01.l
brook@debian:~/src/lex$ gcc lex.yy.c -ll -Wall
lex.yy.c:1085: warning: 'yyunput' defined but not used
lex.yy.c:1128: warning: 'input' defined but not used
brook@debian:~/src/lex$ ./a.out
hello world
hello world! brook
I can recognize the word "hello"
I can recognize the word "world"
!brook
regular expression
.代表任何一個字元,但不含換行(\n)。
*重覆前一個比對零次以上。
[]比對[]中任一個字元。
[^][]的反向。
$每行的結尾。
{n,m}前一個比對至少重複n次,最多m次。
+重覆前一個比對一次以上。
?前一個比對可出現一次或零次。
|or
( )定義subexpression。
比如:  ".*":表示比對任何一個字元零次以上。 [0-9]+:表示比對數字一次以上,如0921等等。 -?[0-9]+:表示負號可出現可不出現,即表示正負數。 ([0-9]+) | ([0-9]+\.[0-9]+):( )分成兩個subexpression,|表示其中一個比對成功即可,也就是整數或小數。 [0-9]{1,3}:表示有1~3個數字。

2009年8月15日 星期六

為台灣致哀


看著電視媒體傳送著台灣50年來最大的災難,心中的難過總是一湧而上,淚水總是在自己的強忍下,停留在眼眶中打轉,望著那些長官們說的鬼話,心中的氣憤更是難平,平常這些辛勞的百姓為生活在大太陽底下工作,而這些高官卻只是在冷氣房裡決定政策,即便是錯誤的政策,而又或者是為了貪汙而制定了哪些莫名其妙的政策,百姓們也都只能苦笑買單。 但是今天,面對多少破碎的家庭,多少妻離子散的畫面,那怕是官員們演個戲也好,為這些苦難的百姓哀悼一下吧,不是他們不願撤離家園,而是誰都不知道有這麼大的災難。再者,如果今天人人都能在冷氣房裡辦公,誰還願意在大太陽底下,踩著滾燙的柏油路,辛勤的工作,再辛苦也是為了那口飯,還有那年幼的小孩,年邁的父母親。我也是一位農家子弟出身,小時候不管颳風下雨或者再大的太陽,總是要把田裡的工作完成,無奈靠天吃飯的農民,可能一個颱風就毀了這段時間的汗水,如果幸運逃過老天的考驗,到了收成的那刻,在田裡所受的大小傷都值得了,但是,這一切也都只能換取全家的溫飽,要多的是不可能的了。即便蔬菜水果再貴,永遠都是批發商在賺大錢,而農夫能感受到的只有曬在那刺痛的身體上的大太陽,還有寒風刺骨的寒流,這一切都夠可憐了,遠在市中心的長官們能感受到嗎?苦民所苦是騙人的。 看著電視,望著一幕一幕的救災畫面,面目可憎的名嘴們、政客們哪一個在第一時間到達災區?哪一個敢深入災區?騙人民的感情與信任,說再多都沒有用,因為災民們需要的是具體的行動,他們在挨餓的時候不會因為你們遠在電視機裡的一句話而飽了,但是他們的親人卻可能因為你晚來了一步而死了,每當時間經過一秒,他們內心的惶恐就多了一分,因為他們想像著親人被壓在土石堆裡,還有那一口氣等待救援,但是卻手無寸鐵,無法將親人在最後一刻救出。如果是你,你能承受嗎? 七天過了,即便人沒有被壓死,也該被活活的餓死渴死了,是政策殺人,卻沒有一個人為此負責,名嘴與政客也沒有在第一時間發揮他們的影響力前去救災,只有小兵們、小老百姓們還在為災民冒著生命危險持續搶救,這才是真正愛這片土地,這塊土地上的偉大英雄,每當畫面看到名嘴與政客的相互批判,讓我著實感到悲哀,為什麼還有人願意相信這群人,他們分不清誰才是真正愛他們的人嗎? 雨過就會天晴,但是人死不能復生,未來的路還相當的漫長,面對可能產生的孤兒,未來的路更顯的崎嶇,想到這心中又不免難過了起來。一夕之間,山河變色,希望的是,未來政府能多照顧這群災民,能讓她們面對自己未來的人生,更要想辦法照顧這些孤兒,因為他們真的是無辜的,他們的生命現在還很脆弱,很可能因為沒了父母,失了照顧,成了社會的邊緣人。

GCC (4.4.1)之C Preprocessor part I


C Preprocessor系列,其實就是CPP manual的讀書心得。

initial processing 在初始化的處理上,CPP主要有四個任務:
  1. 讀取檔案到記憶體中。
  2. 如果有啟動trigraphs,那麼會做trigraph轉換,如??/轉換成\,??-轉成~等等。
  3. 合併連續行成一行,就是每行後面有\當結尾的,會將該行和下一行合併成一行。
  4. 將註解的部分用一個空白取代。
Tokenization CPP使用空白區分token,如果沒有token之間沒有空白,那麼CPP會使用greedy法則,也就是盡可能的找最長字串的token,比如a++++b會被解釋成a++ ++ +b,而不是a++ + ++b,而token之間預設也是一個空白,比如: #define foo() bar foo()baz 會被解釋成bar baz,不管#define中的foo()和bar有幾個空白。
brook@debian:~$ echo -e "#define foo() bar\nfoo()baz"|gcc -E -
# 1 "<stdin>"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "<stdin>"

bar baz

The preprocessing language 所謂的preprocessing language,就是以#開頭的那些preprocessing directive,這些指令是固定的,無法讓使用者自訂新的。這些指令主要有四種用途:
  1. include of header file(比如#include)。
  2. Macro expansion(比如將#define展開)。
  3. Conditional Compilation(比如#if,決定哪些要被編譯,哪些不被編譯)。
  4. Line control(我不是很清楚,只知道#line num下一行就會由num當行數重新計算,也可以簡寫成# num)。
  5. Diagnostic(比如#error)。


Header files Header files主要分成兩類:
  1. System header files:提供OS部分的interface,<xx.h>。
  2. Your own header file:提供source files的interface,"xx.h"。
傳統上,C的header files都使用.h當結尾,當您在#include 時,其實就是將xx.h複製到該行上,並利用Line control,重新計算後面的行數,比如:
brook@desktop:~$ cat a.h
#ifndef A_H
#define A_H
int hello(char *str);
#endif
brook@desktop:~$ cat a.c
#include 
#include "a.h"

int main(int argc, char * argv[])
{
    printf("hello world\n", __LINE__);

    return 0;
}
brook@desktop:~$ cpp -I./ a.c
... 略 ...
# 2 "a.c" 2
# 1 "a.h" 1


int hello(char *str);
# 3 "a.c" 2

int main(int argc, char * argv[])
{
    printf("hello world\n", 6);

    return 0;
}
看到把a.h中的int hello(char *str)複製到source file中了嗎?隨後又重新計算行數# 3,而header files並沒有規定只能放啥東西,不過通常就是放置一些宣告。 include syntax #include有兩種變形:
  1. #include <file>:for system header files,就是會尋找系統路徑。
  2. #include "file":for your own header files,會先尋找目前目錄,找不到就依循的search path尋找。
注意:有些OS會使用'\'當pathname separator,比如M$,但是還是請使用'/',因為GCC一律都使用'/'當pathname separator。

search path 一般在UNIX底下,system header files的search path為 /usr/local/include libdir/gcc/target/version/include /usr/target/include /usr/include 透過GCC實際觀察一下吧
brook@desktop:~$ gcc -I./ -v a.c
... 略  ...
ignoring nonexistent directory "/usr/local/include/i486-linux-gnu"
ignoring nonexistent directory "/usr/lib/gcc/i486-linux-gnu/4.3.2/../../../../i486-linux-gnu/include"
ignoring nonexistent directory "/usr/include/i486-linux-gnu"
#include "..." search starts here:
#include <...> search starts here:
 ./
 /usr/local/include
 /usr/lib/gcc/i486-linux-gnu/4.3.2/include
 /usr/lib/gcc/i486-linux-gnu/4.3.2/include-fixed
 /usr/include
End of search list.

... 略  ...
您可以透過-I新增search path,新增的search path會優先被尋找,但是如果新增的search path已經在預設的(系統的)search path中,該search path會被忽略,避免影響預設的search path順序。有興趣可以再研究一下-I-和-iquote。

Once-Only headers 一個header files很可能被include兩次,進而造成重複define,引起compile error,標準做法應該是在header file中,使用conditional compilation防止被引入兩次,如:
#ifndef XX_H
#define XX_H
 the entire file
#endif /* !XX_H */
XX_H我們稱為controlling macro或guard macro。macro name不應該使用'_'開頭的命名方式,而在system header files中應該使用'__'開頭,避免與user program造成衝突。 computed include 有時候我們會需要根據不同的configure引入不同的header files,比如:
#if SYSTEM_1
#include "system1.h"
#elif SYSTEM_2
#include "system2.h"
#elif
... 略 ...
#endif
像這樣需要經過計算然後才引入的行為就稱為computed include,你可以發現很就會被許多的#elif淹沒,你可以使用簡單的方式替代,比如:
#define SYSTEM1_H <stdio.h>
#define SYSTEM2_H "system3.h"
... 略 ...
#include SYSTEM1_H
#include SYSTEM2_H
甚至SYSTEM_H可以由Makefile傳遞進來。

System Headers是用來宣告OS和runtime libraries的interface,system header所產生的warning除了#warning會顯示出來,其他的都會被抑制住。一般而言,當GCC在編譯時就已經設定哪些目錄會被當成system header存放的目錄了,不過我們還是可以透過-isystem#pragma GCC system_header兩種方式,將一般的header file當成system header file。
  • -isystem後面接的目錄會被當成system header,用法上和-I一樣。
  • #pragma GCC system_header這個指令告訴GCC把這個header file當成system header。

brook@ubuntu:~$ cat syshdr.c 
#include <stdio.h>
#include "syshdr1.h"
#include "syshdr2.h"
#include "syshdr3.h"

int main(int argc, char *argv[])
{
    syshdr1();
    syshdr2();
    syshdr3();
    return 0;
}
brook@ubuntu:~$ cat syshdr/syshdr1.h 
#ifndef SYSHDR1_H
#define SYSHDR1_H
int syshdr1(void) { printf("%s\n", __FUNCTION__); }
#endif
brook@ubuntu:~$ cat syshdr2.h 
#ifndef SYSHDR2_H
#define SYSHDR2_H
#pragma GCC system_header
int syshdr2(void) { printf("%s\n", __FUNCTION__); }
#endif
brook@ubuntu:~$ cat syshdr3.h 
#ifndef SYSHDR3_H
#define SYSHDR3_H
int syshdr3(void) { printf("%s\n", __FUNCTION__); }
#endif
brook@ubuntu:~$ gcc -Wall -isystem syshdr syshdr.c -o my_syshdr
syshdr.c: In function ‘syshdr3’:
syshdr3.h:3: warning: control reaches end of non-void function



熱門文章