2009年12月5日 星期六

Undefine Behavior 和 GCC

有個編譯器的測試程式是這樣寫的,基本上這些測試程式會利用執行程式的回傳值來判斷編譯器是否能通過測試。換句話說,只要編譯並執行了這個測試程式而且執行程式的回傳值是0,就代表編譯器通過這個測驗,反之,就代表編譯器無法通過這個測驗。( 註: 呼叫abort將回傳1,也就是代表測試失敗。)
void f(int i)
{
  i = i > 0 ? i : -i;   if (i<0)
    return;
  abort ();
}
int main(int argc, char *argv[])
{
  f(INT_MIN);
  exit (0);
}
上述程式在進行的其實就是 abs (absolute value) 的測試,看起來如此簡單的一個程式,長的雖然如此甜美,可是卻是包藏禍心的。跟電腦上的數值表示法熟的人就會發現其實有某個數字是沒有辦法正確的做"數學上的" absolute value,也不難猜的,他就是那個整數的最小值。對整數的最小值來說,他並沒有對應的正數值,所以整數的最小值進行絕對值運算後會溢位。以在ISO C99 spec 當中,對整數的絕對值相關函式的解釋來說,這樣的行為將會是未定義的。既然是未定義的,就會取決於硬體的實作。一般而言,硬體實作絕對值時,通常會有兩種實作方式,第一種就是直接運算,也就是整數的最小時做絕對值後的結果還會是他原本的值(以32位元來說0xf0000000取補數+1還是0xf0000000),第二種狀況在DSP比較常出現,就是硬體會幫忙做飽和運算,也就是結果會是正的最大值(0x7fffffff)。
編譯器的核心是IR(intermediate representation),IR對一個編譯器進行最佳化時的重要程度,是可以用"IR是編譯器的生命"來形容的。 上述程式粗體表示的部分,其實出乎大部分人(?)意料之外的,如果硬體有支援ABS指令的話,大部分的主流編譯器是能產生ABS指令來最佳化這個運算式的。簡單來說,GCC 為了進行最佳化,定義了許多低階的指令範本(Instruction Pattern),用來match C 語言所代表的運算式,當然中間還兼過了許多跟平台無關的最佳化過程(像Loop Optimization),如果目標平台的硬體有定義abs的Instruction Pattern,GCC就會拿它來match上面那個運算式,也就是為什麼GCC能選擇性的用上abs指令來最佳化那條運算式。不過就一個未定義的行為而言,有必要去測試它的正確性嗎?
很可惜的是GCC是支援多個程式語言的前端,這個未定義的表示式在某些語言中是有定義(對,我就是在說你,Java)。JLS (Java Langauge Sepc) 中定義了有號算數運算如果有溢位的話,必須要使用2的補數表示法來 wraps around 。也就是說,在Java中,剛剛那個絕對值運算"必須"也應該要維持原本的值。GCC為了同時支援兩種不相容的Spec,所以就定義了一個編譯器選項-fwrapv來指定編譯器要不要wraps around有號運算的結果。所以上面那支測式程式還是有它存在的意義,其實編譯器在編譯這個程式時,就是要測試-fwrapv這個編譯器選項有沒有實作正確,這對一些只有 saturates 版本ABS指令的CPU/DSP/GPU造成一些困擾,也就是說當-fwrapv這個選項打開的時候,編譯器要清楚的知道不能使用abs的Instruction Pattern去match那個運算式了。

2009年11月2日 星期一

Signed不Signed還是問題所在 之 C# Remix 版

每個男人都想當慣C,可是在2009年的今天也有不少男人不想當慣C,想當慣西夏(C#)也說不定,跟C和C++比起來,C#是個純物件導向的程式語言,在Everything is object的設計前提上,這些Signed 不Signed 的問題當然使用者不是很想去沾惹,總是希望Framework能把事情處理的很好,這樣王子和公主就能過這幸福快樂的日子了。在這個那麼美好的世界當中,會不會有那些令人惱怒的轉換規則呢?在前篇中提到的C 語言中Signed Type 跟 Unsigned Type 容易造成的誤會和運算轉換的規則,在現行的純物件導向語言當中,轉換的規則、Signed Type 跟 Unsigned Type 之間的交互作用,又全然是另外一個故事了,本篇以C#為例來探討一下所謂的新世紀Signed 問題。

當然這整件事情的緣起,是在某朋友的部落閣當中,看到了一個程式。這個程式這這樣寫的。

unsigned int i=1;
int j=-1;
if(i>j) printf("i>j");
else printf(“j>i");

慣C的人看到一定馬上就看出問題所在,大聲說出:「閃開,讓專業的來」,或是「放開那女孩」,然後新警察就會出來說,依造前篇的規則,這個轉換會根據C99 Spec中的6.3.1.8的Rule 3(a),得到 "j>i"這個慣C的人看起來很自我感覺良好,可是大家感覺卻不太好的結果。有沒有念過國小的人都知道1是比-1來得大,不過"數學是數學","數字要在程式中表現出來"就是會有這樣的結果,數值的表示本來就是有極限,所以才需要訂規則消除這種模糊的狀況,這個應該是程式工程師應該小心的,這是他們的責任。不過把這個看起來詭異的程式拿去C語言的後繼者,也是2000之後才出現的新世紀語言C#去做驗證,卻得到了看起來正常,卻沒有那麼正常的答案"i>j"

看起來正常是法理上是"i>j" 應該,"j>i"會是悲哀,可是如果仔細去想的話,這樣的結果又會令人覺得奇怪,畢竟在C#中什麼都是物件,那個運算子'=='其實會對應到某物件的成員函式,那現在 i 跟 j 的型態並不一樣,那到底是哪個比較函式會被叫起來呢?不過對程式語言稍有理解的人其實會了解,這樣的情境下你的編譯器勢必會幫你動一些手腳,如果你不懂你的編譯器幫你做了些什麼,那可能會是個災難。一個看起來合理的想法可是卻是錯誤的想法就是編譯器會幫你把unsigned int i (C# 中 uint i) 會被轉成 Signed Type,讓這個轉換看起來會得到這樣的結果,可是C#是個號稱自己是個強型態的語言,除了隱性轉換之外(所謂的隱性轉換就是設計者覺得合理的轉換(笑)),使用者是必須在程式碼顯性的說出:「我就是要這個轉換。」然而unsigned int i 轉 int 是會丟失資料的,這樣一個轉換的確不該是一個隱性轉換。會設計成這樣就是不希望使用者管這問題,如果是顯性轉換的話,其實使用者是在意了。

另外一個合理的實作是把每個基本形態的比較運算子成員函式做不同型態的比較,這樣的話unsigned int 就能正確的跟int 做相比了,不過這樣的排列組合會挺多的,所以並不是一個很好的解法。另外一個合理的轉換方式就是把unsigned int i 跟 int j 都轉成64bit int 再來進行比較,這樣就可以得到合理的結果了,事實上C#的Language Specification中是使用這種解法的,所以C#會得到"i>j"也非常正常。其實就如同其他的語言,不同型態的運算難免不了要做 promotion,只是這樣的作法就見仁見智了。最後,這部份的轉換是定義在C# Spec中的 7.2.6 中,其中的第二節Binary numeric promotions就是在這個範例程式碼當中,C#編譯器所套用的轉換規則。

2009年10月26日 星期一

淺談C++例外處理 (中篇)

前言: 經過了漫長的混戰當中,終於把GNU G++例外處理的機制實作在某32位元CPU上了,為了避免富姦化,個人考慮多發表一些另類的文章才是阿!!

前文淺談了C++例外處理的機制,根據前篇的內容,我們可以初略的了解到例外處理機制在語言中的定位和C++例外處理機制中容易產生的迷失。然而,有志者總是喜歡更"深入"的去了解更底層的行為,看似平凡的東西有時候會使人驚艷,而像C++ 例外處理機制這個從Spec 看來就如此複雜機制該如何來實作呢?相信大家心中都有一樣的疑惑。編譯器是個複雜的東西,然而在GNU Toolchain還沒問世之前,要講例外處理的實作通常只能從產生出來的代碼來探討,然而一個開放原始碼的編譯器卻能讓程式工作者能以"編譯器"的角度出發,深入研究C++的例外處理機制,這也是開放原始碼偉大的地方。本文就以開放原始碼的編譯器 GNU G++為出發點,來探討C++例外處理的實作。

註: 本文探討的原始碼以GCC 4.4.2 為主

GNU Toolchain支援的C++例外處理模型的實作有兩種,與其說例外處理的實作有兩種,其實正確的說法是Stack Unwinder的實作方式有兩種。第一種叫Set Jump Long Jump Exception Handling (簡稱SJLJ) 另一種叫Dawrf 2 Stack Unwinder Exception Handling。如果要用簡單的幾句話來介紹一下兩者皆的差別,其實就是Set Jump Long Jump Exception Handling 這種方式實作起來比較簡單,可攜性也較高,可是效能和產生的代碼大小都差強人意。 而 Dawrf 2 Stack Unwinder是利用Table Driven的方式來進行例外處理,效能和速度都比前者好,想當然是可攜性比較差,而且需要其他二元工具組一些額外的支援,實作上也複雜許多了。本篇的探討會以Set Jump Long Jump Exception Handling為主,而下一篇再探討Dawrf 2 Stack Unwinder。

Set Jump Long Jump Exception 的原理很簡單,顧名思義就是用到setjmp和longjmp這兩個函式,這兩個函式是C標準函式庫的一員,原本是用來處理non-local goto 的堆疊平衡。所以用他們來處理C++的例外也是蠻單純的。就學理上來說,就是進入到需要被偵測例外的區域就呼叫setjmp來保持執行狀態,在丟出例外時使用longjmp來復原原本的執行狀態。然而事實上這部份的處理確沒那麼簡單,而且比想像中的複雜很多,因為C++程式語言規定了離開例外處理區塊的時候必須要正確的解構auto object,這造成許多麻煩。一個簡單的方法就是把例外處理區塊製造出來需要解構的物件都塞到一個list內,如果例外發生就能依序解構他們。如此一來想當然效能是非常差的,最致命的是,不論例外有沒有發生,這些保存的動作都是必須的。會採取這種實作有許多理由,除了簡單之外,可攜性也是當初在實作的一個重大考量,因為他在那些把C++原始碼轉成C原始碼的早期C++編譯器中也能夠運行的很好,

回到實作的層面,C++程式語言中用來作例外處理主要的關鍵字有三個,分別是try, catch, 和throw。如果大家用C++程式語言的角度來看,他們的作用我就不贅述,相信C++程式語言的書籍中都有介紹。如果想從標準文件的角度對這三個關鍵字的作用和描述更深入的研究,他們在ISO C++文件的第15章。然而,如果大家用的是更低階的角度來看例外處理,就會發現其實組合語言是沒辦法對這個行為進行原生的支援的。也就是說編譯器其實會根據那些關鍵字,在他們的程式區塊(Code Block)中產生特定的代碼和函式呼叫。關於這個假設,我們可以用GNU G++產生組合語言的功能(-S)來驗證。經過觀察我們就可以輕鬆的發現GNU G++會分別在這三種區塊內加入函式呼叫的代碼。稍微對G++有些了解的讀者會知道其實這些函式名稱其實就是GNU G++的"約定",也就是在運行時期函式庫中必須要有這些函式,才有辦法正確的進行例外處理。以下為例外處理模型設定成sjlj-exception 的 G++ 編譯器對特定的例外處理關鍵字產生的函式。

try-block
    _Unwind_SjLj_Register
    _Unwind_SjLj_Unregister
    _Unwind_SjLj_Resume
handler: (catch block)
    __cxa_begin_catch
    __cxa_end_catch
throw-expesssion
    __cxa_allocate_exception
    __cxa_throw

註: 以上的結果必須要將編譯器都設定成 “-wtih-sjljexception=yes “ 才有辦法看到一樣的結果,其中要注意的是大部分主流處理器的編譯器和主流Linux發行版預設的編譯器,預設都是使用Dawrf 2 Exception Handling,所以無法看到相同的結果。

_Unwind_SjLj_Register 和_Unwind_SjLj_Unregister分別用來加入或移除一個到Function_Context到unwinding-chain當中。_Unwind_SjLj_Resume是用來恢復例外的傳遞(也就是說用來做cleanup),前兩個函式的原始碼在<gcc>/gcc/unwind-sjlj.c 中,後面一個在<gcc>/gcc/unwind.inc中。如果大家有去觀看看原始碼會發現其實_Unwind_SjLj_Resume有一個兄弟函式叫_Unwind_SjLj_Resume_or_Rethrow,他是用來處理FORCE_UNWIND例外和實作rethrow的功能__cxa_begin_catch 和__cxa_end_catch就沒那麼複雜了,他們主要用來處理C++例外處理語意中一些housekeeping的動作,他們在libstdc++中libspu++的 eh_catch.cc 內。值得一提的是,C++運行函式庫最容易被問題的問題是有沒有支援thread-safe,然而G++在例外處理的實作是有考慮到thread-safe的。

throw的處理就複雜很多了,因為throw的處理牽扯到了執行狀態的恢復、例外處理函式的尋找和程式碼控制權的轉移。以下為__cxa_throw的原始碼(在libstdc++/libspu++/eh_throw.cc當中)

extern "C" void
__cxxabiv1::__cxa_throw (void *obj, std::type_info *tinfo, void (*dest) (void *))
{
  // Definitely a primary.
  __cxa_refcounted_exception *header
    = __get_refcounted_exception_header_from_obj (obj);
  header->referenceCount = 1;
  header->exc.exceptionType = tinfo;

… 中間省略 … 

  _Unwind_SjLj_RaiseException (&header->
exc.unwindHeader);
 

  __cxa_begin_catch (&header->exc.unwindHeader);
  std::terminate ();
}

看過原始碼後我們會發現,其實__cxa_throw並不複雜,比較值得注意的就是藍色的部份,就是如果unwind失敗的時候呼叫std::terminate離開,這也說明了大部分的工作就落是_Unwind_SjLj_RaiseException當中,而且這個函式如果執行成功的話是不會返回到__cxa_throw的。

_Unwind_SjLj_RaiseException的部份就是分成兩個階段,第一個階段是尋找例外處理函式的部份,這部份的程式會翻閱frame的資訊來尋找是否有例外處理函式被註冊,如果這部份有找到例外處理函式就會進入第二階段,反之就會返回__cxa_throw,這會造成程式被結束。第二階段會進行恢復到有例外處理函式的那個frame的執行狀態,並且取出他的Function_Context並且longjmp回到例外處理函式去。如果某一個函式有多個例外處理函式,那編譯器會在那個函式產生一段充滿跳躍指令的程式碼叫landing pad,而_Unwind_SjLj_RaiseException的會先longjmp回到landing pad後再判斷要跳躍到哪個例外處理函式中。

2009年8月21日 星期五

我宅 故我在 ~ 餘玹杯心得 [COSCUP 2009]

今年是小弟一次參加這個台灣開源/自由軟體界的大活動,也因為今年有不少中意的議程,所以小弟很注意報名資訊,也很幸運的也有報到名,不然4個小時內把450個名額搶購一空的熱浪也太驚人了。由於在活動前一週台灣發生了一件大事,而小弟的老家正好在災區近郊,也蒙受了一點損失,然而比起其他沿海地區算是非常微小了。小的雖然不是政治人物,且能先天下之憂而憂,後天下之樂而樂,曾經那麼熟悉的地方遭受到如此嚴重的天災地變,令人噓唏。也因為心情一直難以平復,就這樣渾渾噩噩過了一個禮拜馬上就到了需要上台北的周末了。

由於小弟是早上才從新竹趕往台北,加上前晚沒有很早睡(迷之音: 誰叫你要研究HOI3研究到半夜),又加上路途客運塞車,炎熱的天氣,漫長的椰林道,所以到會場的時候已經午時了,也漏掉了一些議程。不過我第一天最主要的目標是聽人稱人帥真好的Jserv前輩的場次Linux Virtualization Goes Mobile,所以我就在外面廠商攤位抽獎聊天順便看美女,正好呼應Jserv前輩那場的主題:「我宅、 我色、我舒服」,不過話說回來,雖然Jserv那場充滿的搞笑的橋段,就Introdutuion 來說是一個的好的演說,不過我猜大部分還是對征服和舒服比較有印象XD。

第二天的演講對我來說就蠻有吸引力的,早上Clutter - The Animation UI Framework on Mobile Device開放源碼軟體授權分析工具就還蠻精采的。Happy Build with OpenEmbedded這個主題就比較難發揮了,講者也試圖把他講的精采,開發嵌入式Linux軟體的就會知道OE的好XD。下午的講題就比較硬了,由於個人背景的關係,廖博士那場"Smaller and Faster Android: Optimization and Toolchain Perspective"讓我感覺非常"舒服",如果Coscup有Best Section Award,我想我會投給他一票。

總結來說,這個活動辦的很用心,也相當的成功,小小的美中不足點就是以三十分鐘來說,對每個Section似乎是不太夠,其實只要再加個10分鐘,我想大部分的講者可以講得更從容,QA時間的互動也會更加精采,不過我猜這次熱烈的程度,應該可以預期明年會辦的更好,說不定有機會進軍國際會議廳,成為台灣開源/自由軟體界的典範。

#因為大鳥的建議 改一下標題 for SEO

2009年7月26日 星期日

這就是共和

前陣子因為討論雲林國光石化園區的關係,我提出了一個議題「什麼叫共和?」基本上只要是公民就會對政治就會有所關心,所以對於這個已經實行了那麼久的政治制度其實也不陌生。雖然得到的答覆蠻多的,可是錯誤的答覆卻不少,所以就讓我們來討論一下這個影響著現代人類社會非常深遠的政治制度吧。由於小弟是在現代社會出生的,而且我年紀並不大(??),在懂事的時候台灣社會已經離開獨裁統治(其實獨裁的情狀在"誓死"捍衛共和的傳統共和國也會發生),並步入了比較正式的民主共和時代,在這邊我並不打算對台灣政治作深入探討,只是想說明在現在出生的人對共和這兩個字並沒有比較特殊的感情,當然也不會有什麼需要誓死捍衛共和的情操。
台灣的教育對理工科是比較偏重的,自然組學生上高中之後基本上是不看歷史的,所以對最出名的古老共和國"羅馬共和國"了解並不多,僅知道有元老院這東西,而且後來就轉成帝制,史稱大秦,更不用講會去了解裡面的政治制度了。"神鬼戰士"(對岸翻譯成角鬥士)這部電影,讓我對羅馬的帝制和共和產生興趣,"恢復共和"這件事情竟然是如此的重要,讓這個我只理解中國歷史,認為一有機會大家都會想逐鹿中原,問鼎天下的人是完全想不透。後來我因為某些原因開始去研究共和國後期的歷史後,看到了一些人在網路上發表的心得說:"羅馬沒有完全共和過,也沒有完全帝制過,而在凱薩稱帝後再也沒有回到共和"(當然,這三個描述都有錯),我的心中不禁的重新思考了什麼叫共和?
共和制的英文是"Republic" ,起源於拉丁文的"Res publica"(大概的意思就是公共的事務)。而中文把英文的Republic翻譯成共和的原因則是因為在"竹書紀年"當中,周厲王因國人暴動逃離鎬京到到宣王即位這段期間稱為共和(行天子事,號曰『共和元年』,這部份史記周本紀記載的是周公、召公兩相行政),所以後來日本西化的時候,就引用這個典故,把Republic稱為共和。 就我們所知,農業的起源在西亞,人類因為農業的剩餘糧食,就可以供養一些不需要從事生產的技能專家、統治者和軍隊,統治者接而利用資源進行更大的水力建設來供養更多人口,部落和國家由此產生。上古時期的國家以君主制為主,但已經有一些國家採取共和制(如許多希臘城邦),可是真正把共和制發揚光大的卻是羅馬共和國。
羅馬是個非常神奇的城邦共和國,任何一個能維持霸權的國家一定有許多勢均力敵的對手,羅馬就是在這樣的環境下從一個小小的城邦成為一個環繞地中海的大國(他們稱為我們的內海),在這我就不贅述羅馬的歷史,來探討一下羅馬的政治制度。羅馬人自從革命趕走國王之後,就開始了漫長的共和制度,基本上羅馬走的是寡頭共和這個路線,而且政治上是非常保守的。羅馬最高的行政官叫執政官(Consul),以現在的國家來說相當於總統,為了避免獨裁者的出現,執政官有"兩名"(其中一個要來自平民),並且只有一年的任期。不過一向遵循共和傳統的羅馬在敵人兵臨城下時為了避免緊急危難,可由執政官選出一個人來獨裁,稱為獨裁官(Dictator)。會有這樣的職位存在,可以說明其實羅馬人很明確的知道"獨裁"是比較有效率的。(值得一提的是著名的獨裁官凱薩,跟一般人眼中專政獨裁的形象不同,凱薩其實並沒有濫用這個職位,第一次當獨裁官僅十一天而且也沒有福氣利用到終身獨裁官這個職位)
大家都知道共和國的理想是很遠大的,目的就是要建立一個屬於人民,不屬於個人的國家。但兩千年前的古老共和國告訴我們,縱使今天有了全民參政權,走的是民主共和,但是仔細琢磨後會發現其實今天的政治生態跟兩千多年前其實是相去不遠的,在羅馬共和時期的政客都會喊愛羅馬、愛人民,但大家也都知道他們骨子裡愛的就是錢,愛的是自己。所謂的共和制政府其實只代表著政經高層的意志,並不是大多數平民的意志,任何政客都可以利用施以小惠的方式來換取權利,平民也殊不知這些小惠也是從人民身上搜刮得來的,人民為了過生活,就只好繼續用選票選出吸自己血的政客,其實這就是共和。這一點在媒體如此發達,民智如此開化的今天也無法避免的,也就是說,即使的是2009年的今天,活在共和制底下的人民如果沒有一定的政經關係其實是監督不了政府的。什麼是共和?說穿了就是政經高層的利益。

2009年6月7日 星期日

原諒我就是這樣的字串

每個嵌入式系統工程師都想要慣C,可是我們都知道想慣C而不出包也是有難度的,原因是出在於C語言本身存在了許多陷阱,使得就算慣C把所以隱密的資料都砍掉了,貿然去修電腦還是有危險性的。字串,在電腦科學中就是一連串的符號,由於字串跟人類的生活很接近,程式又是用來解決人的問題,所以工程師們在寫程式的時候,也常常面對字串。每種語言對字串的實作方式都不太一樣,即使在同一個語言,每個函式庫也會試圖提供了不一樣實作的字串處理函式庫,這些函式庫大部分都很基本,所以是讓一些初學程式的人練功的好地方。而想慣C的工程師要面對的,就是一個叫c string (或稱為c-style string) 這樣的字串。

然而,對慣C的人來說 "C-String" 不只是無帶內褲,而是一個美麗與哀愁。對C語言來說,字串的表達方式 ─ 也就是所謂的 c string (C++族群稱之為 c-style string ) ,其實就只是一個以'\0' (null character) 當結尾 (null terminated) 的字元(或是寬字元)陣列,所以 c string 又被稱為是ASCIIZnull-terminated string。然而,由於C語言的陣列(當然不包含C99的變動大小陣列)大小必須是固定的,這個特性加上0結尾就造成了一定的麻煩。對C的初學者來說,少算了0結尾或忽略忘記了0結尾就會變成了一個還蠻常見的錯誤,也誤了不少青春,更不用說固定大小的緩衝區容易造成安全漏洞

為了使用上方便,大部分的語言會提供字串定字(string literals, C99, 6.4.5) ,讓工程師可以在程式碼內表示一些字串。由於C語言是個系統程式語言,所以大家對這些東西會身在何處是有高度興趣的。而 string literals 到底會身在何處,是跟編譯器的實作有關的。C語言規格中定義了他們會被規定分配在"static storage"當中(C99 spec, 6.4.5),並且說明了如果程式嘗試修改string literals的內容,將會造成未定義行為。以GCC的 ELF Target 來說,是將string literals分配在read-only data section中(當然包含了0結尾)。由於C語言提供了一些方便的語法甜頭來初始化陣列,這使得 char* p=”hello world”和char p[] = “hello world” 寫法差不多,可是私底下卻是大不同。

以指標的寫法 char *p 來說,代表的是p將會是指向static storage的一個指標。這樣的寫法是會有一些小問題的,因為嘗試修改string literals的內容,將會造成未定義行為,而這樣的寫法編譯器並不會對存取p的元素提出警告。比較值得注意的是陣列的寫法,依規定 string literals 是必須放在 "static storage"中的,而 char p[] 的語意則表示要把資料分配在Stack內,所以這會造成編譯器隱性的產生執行期把string literals從static storage複製到stack中的代碼,雖然字串本身不是放在Stack內,但char p[]卻是分配在Stack內,這也造成return p是未定義行為。

光是表示字串有時候是不太夠的,所以必須要在字串上做一些運算,而C語言的規格書中定義了標準函式庫必須提供一些字串處理的函式 ( 這部分定義在C99 spec, 7.21 String handling <string.h>中),來幫助工程師避免製造重複的輪子。接下來問題來了,這些字串處理函式的原型大部分是使用char *或void * 的型別來當參數,那這些參數到底能不能接受null pointer呢?如果不能,那函式要負責檢查嗎?null pointer算是一個字串嗎 ?對一個null pointer使用這些函式(如strlen)會是怎樣的結果?規格書中有定義這些東西嗎?

答案是 string handling function *不能接受* null pointer當參數,因為絕大部分的狀況null pointer並不是一個字串(可以思考一下為什麼),所以對null pointer使用strlen絕大部分是會造成未定義行為的,而大部分的實作也不會在函式庫內做null pointer檢查(有些函式庫的實作會使用編譯器的延伸語法來提出警告),所以代表工程師是要把null pointer檢查的責任一肩扛下的。大部分的人會把這部分的現象推給編譯器實作的差異,不過到GCC的論壇問這個問題,得到可能會是"由於GCC沒有提供標準函式庫所以這跟GCC無關(笑)"的答案。然而,規格書就像法律條文,有時候是需要解釋的,個人認為其實C99規格書隱晦的提到了這個問題,所以以上的現象都是合理的。原因出在於C99 Spec 7.21.1的第一點中提到了:

Various methods are used for determining the lengths of the arrays, but in all cases a char * or void * argument points to the initial (lowest addressed) character of the
array. If an array is accessed beyond the end of an object, the behavior is undefined.

null pointer顯然在*絕大部分*的狀況都不符合這個規定,既然不符合規定,函式庫的實作顯然也不需要浪費心力去做檢查。更不用說想要在一些物件導向的字串函式庫中使用 string物件的null pointer 來做字串運算,我想這一定是瘋了(題外話,以大部分物件導向語言使用字串的情形,null時要做的事情其實是跟empty時是差不多的,所以.NET的字串函式庫後來提供了IsNullorEmpty的類別方法,讓工程師不用每次檢查Empty之前還要check 是不是null )。當工程師想慣C而被這些字串的問題所擾,仰天長嘯問 c string 為什麼 要用這個方法來實作時,可能的情形就是 c string 使用了 戴佩妮(Penny) 新歌的梗來回答說 :"原諒我就是這樣的字串"

2009年5月11日 星期一

淺談C++例外處理 (前篇)

例外處理是一種程式語言的機制,他是用來偵測和處理一些執行時期的錯誤或異常狀況(Runtime error/exception) ,這也意味著如果程式語言想內建這個機制的話,就必須要有執行環境的支援,直覺上也代表著那段程式碼將會跑得比較慢。然而,在某大部份的狀況,執行時期的異常安全(exception safety)是非常重要的,付出那些代價相對來說是非常值得的。對一般的使用者而言,出現Segmentation fault或跳出一個記憶體無法存取的視窗都不太能被接受了,更不用說那些需要高度安全的程式,就好比汽車的剎車系統或核電廠的控制程式,出現執行時期的異常是需要付出慘痛的代價的。

例外處理機制"內建"在程式語言當中最早是從IBM的PL/I( I是羅馬數字的1 , 1964 Appeared, 1976 ANSI, 1979 ISO) 開始的,後來也有一些物件導向的語言開始跟進,在某些作業系統中也有API來讓某些語言(比如說慣C最喜歡的C)能有例外處理的支援,也有某些語言使用了一些語法甜頭來加強他的功能性(如C#的using block),甚至在新興的腳本語言中都可以看到他們的蹤跡。然而,程式語言內建的例外處理機制其實還蠻多人討厭的,慣C的人也覺得這個機制不太直覺。猶有甚者,認為例外處理機制根本就是無形的goto語法,不僅讓工程師們永遠不知道他們是從哪裡開始變換程式流程的,而且他們覺得使用例外處理機制並不會讓問題變得更簡單,更不用說會破壞程式的結構性了。

程式語言的例外處理機制之所以會引發很多的爭議是在於某些例外處理的情況是可以靠check return code來早期發現的,慣C的人(應該)也已經很習慣使用這種例外處理的機制了(不過有時候會夾帶很多macro),而程式語言所提供的例外處理機制要一般化且具有通透性,在黑箱作業的結果就是很多狀況是會破壞程式的控制流程,畢竟程式如何從例外發生點跑到catch block的細節已由編譯器代為處理了。加上為了能在很多特殊的狀況中都能使用,所以他們的實現會比較困難,而且效能也不是相當好。不過程式語言的例外處理是靠執行環境來支援的,這意味著只有他們可以成為最後一道防線,這是任何編譯時期的機制無法辦到的。

例外處理的語法在程式語言當中算是複雜的,也是比較少被使用的,乃至於少數工程師不是很有辦法完全駕馭,即使是有語法甜頭的支援,有時候也會有一些迷失。那讓我們回到主題來吧,C++的例外處理機制說複雜其實也蠻複雜的,其中一個複雜點在C++可以把物件放在自動變數內。自動變數的一個特性就是可以自動進行建構和解構。由於C++標準明確地定義了程式的control flow從throw(異常發生點) 到 exception handler 被執行前是必須解構區塊內所有已建構成功的自動變數。標準講的很簡單,但這其中卻隱藏了許多的陷阱,我們就來看個例子吧。

class testthrow {
public:
    testthrow() {
       test tx;
        cout<<"testthrow constructor"<<endl;
        throw;
    }
    ~testthrow(){cout<<"testthrow deconstructor"<<endl;}
};

int main() {
    try { testthrow tt; } catch(…) {}
    return 0;
}

這段程式碼大部分的人會覺得tx必須被解構(因為他已經被建構了),很可惜是不會的,即便main中使用try catch的區塊並攔截所有例外。原因出現在 rethrow 的語法當中,C++標準規定了throw保留字如沒有參數,那所表達的語意就是進行 rethrow 。然而,C++標準又規定了在沒有發生 exception 的狀況使用rethrow的話,程式是會被終結的。接下來我們來看第二個例子:

int main() {
    someclass c;
    throw ( std::runtime_error("error!!") );
}

在以上的程式碼中,假設c會建構成功的話,那c會被解構嗎?答案是不一定,基本上這段程式碼在C++的標準當中是沒有定義的。C++標準提到了自動變數要解構時機是在stack unwinding 的時候。然而,C++標準又提到,如果在程式裡沒有定義 exception handler 那無論有沒有做stack unwinding 程式都會被終結,依現行的C++編譯器 VC++ 和g++ 的 libstdc++v3 的實作,c是不會被解構的。

2009年5月1日 星期五

Signed不Signed就是問題所在

每個男人都想慣C,但是要在嵌入式系統這個領域當個慣C,其實也不是那麼容易的,君不見遠方蘋果上的銅板跟嵌入式系統一樣小,失之毫釐可是會差之千里的。Jserv前輩的我是軟體 -- 那些處理器教我的事,點出許多使用C語言進行跨平台開發時,很多狀況並不是cross compile下去就沒事了,號稱可以劈腿無數平台的慣C也是有很多平台的差異需要解決的。然而,姑且不論跨平台開發時,其實想要慣C也需要面對許多陷阱的,最簡單的陷阱像 0.1 * rate 會等於 1/10 * rate嗎? (答案是否定的),modifier的位置的差異性(最著名的是指標和const的位置),更不用說誇平台開發了。我們今天的主題就鎖定在型別轉換系統,看Signed不Signed是怎樣影響每個工程師的。

大家都知道想要慣C是必須要了解C的型態系統(tpye system)的,由於C是有形態系統的語言,所以兩個不同的型態要做運算的時候,有時候並不是說聲我們要相加囉(感謝Jserv前輩提供這個梗)就可以完成的,有必要時是必須作型態的轉換才能進行運算的。這部分的描述主要是在C99規格書(ISO/IEC 9899) 的6.3.1(Arithmetic operands)中。然而,其實規格書跟法律條文一樣都是咬文嚼字的頗複雜難懂的,我們今天就來看看其中的一些規則吧。講到變數的型別轉換,其實大家比較容易遇到的是小的型別要變大的型別,這部分在C語言中是隱性轉換,編譯器會自己把這個細節完成。基本上這個轉換會依造以下的規則來進行:

如果一個整數型態要轉換成另一個整數型態時(_Bool除外),如果新型態能表式原本的值,那值應該維持不變。

這個規則點出一個很重要的事實,就是如果一個整數型態要被放到一個比他大且能表示他的型態中,一般處理器的作法是會對變數進行Signed extend或 Zero extend,其中最常被誤會的轉換莫過於一個變數char c; 使用 (unsigned int) c;來強迫進行型態轉換,那會做Signed extend還是Zero extend?答案是都有可能,如果toolchain的char預設是使用signed type,這個運算式會做 Signed extend,如果char預設是使用unsigned type (如ARM toolchain)那會做 Zero extend。

另外一個重要的算數型別轉換規則在C99規格書(ISO/IEC 9899)的 6.3.1.8 Usual arithmetic conversions中,裡面講的是兩個不同型態的變數要做運算時,那應該轉換到哪個型態來進行運算,裡頭講的也蠻複雜的,用簡單的話來說就是以下的規則:

  1. integer conversion rank:  Rank由高到低依序是 long long > long > int > short > signed char 而 char =  signed char = unsigned char
  2. 兩個運算元都是Signed或Unsigned時,就轉成高Rank的型別
  3. 兩個運算元一個是unsigned (A)一個是signed(B)時
    • Rank(A) >= Rank(B) B轉成A的型態
    • Rank(A) < Rank(B)
      • B型態如果能表示A的值,A轉成B的型態
      • B型態如果不能表示A的值,B轉成A的型態

以上的規則最容易出狀況的點在於64位元處理器LP64模式的unsigned int和long ,由於32位元處理器的long是不能表示unsigned int的值的,所以會根據上述的最後一個規則把long轉型成 unsigned int來做運算,而LP64模式卻會使用倒數第二個規則,把unsigned int轉成long來進行運算,所以當unsigned int i=5566; long = -1時,兩個變數在32位元和64位元LP64中,兩個人的大小關係就會產生了奇妙的變化了。

 

最後我們來談談一個型別系統的除錯工具,由於C語言當中埋藏了許多隱性轉換,這部分是由編譯器來完成的,所以我們可以利用編譯器的除錯資訊來看看編譯器到底幫了我們做些甚麼轉換。GCC可以使用-dr這個參數來dump RTL(GCC的中間表式法),雖然那個檔案對很多人來說是天書,但也並非讓人無法捉摸,以下便是一個簡單的範例。透過RTL我們可以知道,在x86中char是signed type,而c轉成p時會有一個隱性轉換而且是使用sign extend來進行轉換。

C Code:

main()
{
                char c =0xea;
                unsigned int p = c;
}

RTL 片段

;; c = -22
(insn 8 6 0 (set (mem/c/i:QI (plus:SI (reg/f:SI 54 virtual-stack-vars)
                (const_int -1 [0xffffffffffffffff])) [0 c+0 S1 A8])
        (const_int -22 [0xffffffffffffffea])) -1 (nil)
    (nil))

;; p = (unsigned int) c
(insn 10 8 11 (set (reg:SI 60)
        (sign_extend:SI (mem/c/i:QI (plus:SI (reg/f:SI 54 virtual-stack-vars)
                    (const_int -1 [0xffffffffffffffff])) [0 c+0 S1 A8]))) -1 (nil)
    (nil))

2009年4月30日 星期四

啄木鳥理論

以前在求學的時候,對人類學還蠻感興趣的(其實人類學還蠻有趣的,號稱是最具科學性的人文學,以及最具人文性的科學),而人類學之中有一部分就是在探討人類的演化,儘管到現在還充滿了爭議。講到演化,大家的腦海中都能浮現起物競天擇(原文是Natural selection,筆者習慣使用自然選擇這個翻譯)和適者生存(原文是Survival of the fittest,嚴格來說是最適者生存,不是來自物種原始,也不算是個科學描述)這兩個詞。然而,很多人對演化是有很大的誤會,其實鼎鼎大名的物種原始在討論到天擇之前,先討論了人擇這個名詞,當然不是講人定勝天,而是人擇很重要,影響了人類的演化史和今天整個世界,想當然耳,也影響到了資訊世界的演化。

蒼茫大地,誰主沉浮?今天的世界為什麼會是這個樣子呢?其實大家都知道,這個大環境就是裁判。但有些人(希望是少數)總是會拿出種族主義來說明自己的人種天生就很優秀。但了解人類演化史的人會知道,所謂的"文明的基礎,不是人,而是動植物" (Haldane, 1892-1964)。環境對人類的發展速度是有絕對的關係的,而人類的選擇對人類本身和被選擇的事物的未來發展也是有絕對的關係的。現在把焦點移回到我們資訊領域,在這個蒼茫的電腦發展史上,人的選擇對電腦技術的存亡也是有關鍵性的,而被選擇的技術對人類(公司)的發展也是有關鍵性的,一個簡單的選擇,造成的影響可能非常遠大,例子太多了,族繁不及備載。

以史為鏡,可以知興替,用演化的角度來看資訊世界的變化,為的也是了解這個娑婆世界的沉浮。早期的電腦科學家,不論是Alan Turing 還是John von Neumann還是Konrad Zuse…等都是二戰左右的人物,也就是說電腦技術一開始是用來打戰的,所以電腦的發展一直受到軍事發展的影響,其中通訊這個領域影響較深遠,君不見tcp/ip,world wide web,3G其實都是從軍事科技演化來的。茫茫的技術,誰主沉浮?基本上大家普遍認為,技術是會收斂的。在演化生物學中有一個名詞叫趨同演化,可以用來說明這一點。但是,趨同演化這個講法到底正不正確呢?黑格爾不是說過,凡存在的必有理由,那些非主流的呢?

我們就來提提啄木鳥這個神奇的生物吧,啄木的能力對鳥類而言是非常有優勢的神兵利器,能在活木裡找到食物吃,是一件非常重要的事,因為活木比枯木多太多了,活木樹洞的保暖禦敵能力也比枯木好很多,所以啄木鳥在演化上是很成功的,在世界上分佈的範圍相當的廣。然而,要有啄木這個能力,必須要有許多配套條件,這些條件在某些鳥類身上也有一些,但只有啄木鳥擁有所有的配套條件,因此能使用這個神兵利器。電腦技術亦然,好的技術的確是邁向成功的條件,但要享受成功仍然有"許多" 環境因素和"許多"技術結合而成,更需要的是努力不懈的堅持。然而,如果一直害怕失敗就會一事無成,0xlab這一群有理想的年輕人,他們告訴了我們這一點,我們可以拭目以待,看他們怎麼改變台灣軟體業,改變這個世界。

2009年4月26日 星期日

產業革命與編譯技術

農牧業的興起在人類的歷史中是一個非常巨大的里程碑,不少進步史觀的歷史學家都認為,如果沒有農業,我們的生活將會是萬古如長夜,不僅過著茹毛飲血日子,像"阿宅"(或許阿宅這個名詞也不會出現了)如我,每天為了三餐的溫飽而煩憂,根本無法宅在家裡甚麼都不會,更不用說有時間在電腦(??)面前敲敲鍵盤,寫寫心得了。然而,很顯然的,這個進步史觀是有問題的(就顯而易見的就是我們阿宅們用了更不符合投資報酬率的工作時間來換取溫飽了),這算是一個無心插柳柳橙汁的結果,不過這不是我想討論的重點,所以就帶過了。

很多人會問,現今的世界為什麼是現在這個樣子?中古世紀的時候,西歐是個文化科技的輸入國,而現在看似社會保守,不講究科技發展的阿拉伯國家,當時可是科技的輸出國。甚麼事情改變了這個世界?這個問題不算難,很多人都會回答是工業革命,事實上"近因"就是如此,遠因很長,就不多談(對於這個問題,槍砲病菌與鋼鐵是個不錯的專書)。附帶一提的,這個世界的產業革命分成兩大類,一個稱英國式產業革命,一個稱為德國式產業革命,其他國家的產業革命模式基本上是逃不了這兩種模式的範圍的。(宅灣?他是走德國式產業革命的路線)

當一個國家想要發展工業時,其中有一個很重要的指標項目叫基礎建設,如果沒有這些基礎建設,這個社會將無法支撐起一個所謂的"現代化國家"的工業。在這個時期,鋼鐵被稱為工業之母,因為沒有這些鋼鐵,各式各樣的基礎建設就無法完成,一個國家生產鋼鐵的數量,當時就是這個國家工業實力的基礎數據標準(當然在21世紀的今天,鋼鐵仍然還是很重要,但鋼鐵產量一定不是等同於工業實力了)。當然,鋼鐵很重要,但只有鋼鐵是沒啥用的,如果沒有辦法賣出去或製成再製品(這些再製品的價值會比鋼鐵好很多),這些東西其實也無用武之地。

話說回來,現在是21世紀,我們都叫他資訊時代,那資訊時代的基礎建設是甚麼?有些的硬體跟鋼鐵一樣,沒有軟體把他弄成再製品是賣不了多少錢的(可以引伸為,如果沒有軟體跑在intel cpu上,這個硬體其實也賣不了多少錢),理所當然的,"系統軟體"就成為了資訊時代的新基礎建設,這些基礎建設中,其中一個很重要的元素叫"編譯器技術"。沒有編譯器,人們就會被迫使用不親近人類的低階語言,軟體的產能自然會降低,軟體工程的書做過研究,語言不是銀彈,但使用低階語言來開發軟體,開發的效率降低是明顯的。

個人認為台灣編譯器技術的研究和能量算是貧乏的,也間接造成計算機結構的研究受其影響。然而,現在是一個充滿編譯器的世界,身為一個工程師,每次聽到編譯器技術,覺得深不可測(或是腦海忽然浮現起自動機的回憶)而輕言放棄,實著可惜。就個人經驗,編譯器的基礎入門門檻的確稍高,但是其實也不會高攀不起。前輩Jserv寫了一些很好的文章"窮得只剩下 Compiler -- 淺談編譯技術的革命"來說明編譯器技術,並為編譯器技術做了一個深入淺出的剖析,就前輩所言 "編譯技術其實也可以很有趣",在這個充滿編譯器時代,就讓我們 Just Compile It !

2009年4月22日 星期三

海星計畫

大約在公元2000左右,台灣開始推廣「矽導計畫」,並在許多著名的國立大學研究所加收名額,試圖提升研發能量、培育更多產業人才來貫徹這個計畫。除此之外,行政院經濟部還在那些著名國立大學研究所執行了叫一個明星級矽智財(Star IP)的學界科專計畫。這個計畫很多大專院校執行,也不少計畫是跨校合作的,計畫內容涵蓋範圍非常廣,上到應用軟體(如H264),開發工具,作業系統,下到硬體RTL的實現,Cell Library,就連大量生產需要的技術(DFM)也包含在內。以處理器這個項目來說,就涵蓋了MCU,MPU到DSP都有學校在進行研究。

很幸運的,個人因為某種機緣,於2006年左右,受業於某執行矽導計畫的學府中,並在學界科專計畫中從事系統軟體的實現。敝校所執行的計畫是實作一個低功耗的數位訊號處理器核心的計畫(這個計畫是和隔壁學校一起執行的)。雖然資訊工程背景的學生由於必修課的關係,必然對系統軟體有所認識(?),但實作系統軟體又是另外一回事,也因為這個計畫,讓我和系統軟體結下了不解之緣,直到今日,我還是沒辦法擺脫(?)他們,套一句前輩講的話,系統軟體很有趣,資訊工程背景的學生應該去了解他們。

敝校的訊號處理器核心原本沒有名字,大家都叫他Star IP,由於執行Star IP計畫的學校有如繁星(?),所以後來執行計畫的教授就把他取個蠻宅的名字叫作starfish(海星)。海星是個採用TSMC .18製程的低功率數位訊號處理器,速度在180Mhz左右(使用SS的Cell Library),每百萬赫茲功率消耗僅0.11mW(這當然有很多需要消音的地方),multi issue的方式是採用64+32+32的方式進行的,通常是一個ALU運算配上一對Load/Store 眼尖的人馬上就可以看出他和某DSP之間的關係。關於starfish的畢業論文很多,想了解的人不妨去圖書館查一下。

個人對海灣合作委員會(GCC的google 翻譯 = =) 的認知,在參與這個計畫之前,基本上只限於使用,而且並不常使用,那時對自由軟體也沒啥概念,只道是個可以動的東西(?)。對一個處理器來說(當然包含數位訊號處理器),一些系統軟體的基礎建設很重要,沒有這些基礎建設,在好的硬體也無用武之地。在台灣這種充滿中小型企業的環境,由於資源不足(?),用很大,用不用錢(這句話在最近頗流行,可惜自由軟體的free是自由,並不是不用錢)的自由軟體便得到了一個很好的切入點,雖然不夠好,但workable,那就夠了。

在自由軟體的世界中,他們想要寫一個自由的作業系統,但不久後他們便發現沒有自由的基礎建設,這個作業系統就只是泡影,所以一切的基礎建設就得自己來過。 GNU Toolchain 就是扮演了這個角色,它基本上提供了一個還不錯的基礎建設框架,所以我們的系統軟體就在GNU Toolchain上架設起來了。有了這些基礎建設,應用程式工程師就能當起神槍手慣C,(省略了很多很多步驟後)在千鈞一髮的時候,沒有經過長官同意就能讓應用程式在海星上面執行了,王子和公主就從此過著幸福快樂的日子了(?)。

 

後話: 由於海星和某DSP之間有淵源,所以基本上海星的GNU Toolchain是從那邊branch出來的(大約在他們論壇放出原始碼,還在beta的時候)。個人在前半研究生涯是從事Binutils 和 Simulator的開發,後半生涯是從事compiler的研究和開發。另外,值得一提的是newlib,沒有這個新圖書館,王子和公主也不會幸福的 (笑)

2009年4月21日 星期二

現量是殘酷的

資訊科學學海茫茫,領域更是五花八門,典論論文開宗明義就說道了:「文人相輕,自古而然。」這種"相輕"當然不只發生在文人身上,是有很大的機會發生在每個人身上的。由於人是群居的動物,隨著農業的發展,人類的社會是相當複雜的,只要是人跟人之間有所互動,就會有相輕的問題。自古以來,人跟人之間的互相歧視,從來是不缺乏理由的,但在今天的社會中,如果隨意的就扣下自然選擇(比較多人知道的翻譯是天擇)這個大帽子,這會被算成是一種種族歧視,在現在這個社會,絕大部分的狀況還是無法公開的被接受的,至少不敢大聲地談論這個問題。

資訊領域也是一個相當複雜的社會,當然也存在這種問題。尤有甚者,認為文章和藝術之美是非常主觀的,但資訊世界是不一樣的,充滿了現實和數字而殘酷的,因此更加深了這種奇妙的對立關係。XX比較好還是YY比較好的論點和問題就會"油然而生"。然而,資訊領域是一門科學,很多東西是有證據而且是可以被量化的,如果只是淪為意氣之爭,那並沒有太大的意義。讓我們來把自然選擇這個大帽子扣下去,就會知道其實好的技術並不等於能活下來,例子太多了,不繁盛舉,這東西說不定您我桌上都有。(題外話,自然選擇本身是最佳化的問題,也有人模擬自然選擇寫出最佳化的演算法)

文章乃經國之大業,不朽之盛事,技術文章亦然,在網路上寫文章要背負的十字架(最近很流行,好像比背背包還流行XD)其實也蠻重大的,然而小弟略懂人類學,也殷切的期許不要讓自己成為一個種族歧視者。然而在資訊領域,小弟傾向不抱持任何偏見,凡是有需求的領域,如能力所及,應該給予支持和尊重才對。不過"限量是殘酷的",對一個資訊領域的專家來說,最大的問題在於時間是有限的,就如同甘道夫所說的,最算給他們三百輩子,還是會覺得時間不夠用,時間有限,要學的東西無窮,要有所取捨才對,魚與熊掌,如何取捨?大問哉。

新的開始

打開這個新的網誌,其實我心中算是五味雜陳的,究竟有多久沒寫網誌了呢?對我個人來說,算起來也快兩年了。由於為了混一張文憑,忙碌的科專計畫和研究生活讓我無法再繼續更新網誌,由於舊網誌的內容跟我從事的研究相去甚遠,舊的網誌從2007年12月後就再也沒更新了,直到前陣子有長輩問小弟說有多久沒更新網誌了,我才恍然大悟,雖然小弟寫的東西都是一些雜亂無章的殘篇斷簡,偶而還是會有路人停下一瞥的。

在為學時,常常有師長稱讚小弟文筆不錯,但由於我的吋管之跡猶如行雲流水,故不曾被提名參加比賽,是否為孤芳自賞,抑或只是客套話,小弟便不得而知了。然而,最近對我來說,心情是挺複雜的,前輩的熱血給小弟很大的啟發,就像王羲之的蘭亭集序裡提到的:夫人之相與,俯仰一世,或取諸懷抱,晤言一室之內;或因寄所托,放浪形骸之外。雖取舍萬殊,靜躁不同,當其欣於所遇,暫得於己,快然自足,曾不知老之將至矣。

莊子說過:人生猶如白駒過隙,一下而已,以至於美好的時光就只能比過隙更為短暫很多了,就像蘭亭後半所提及的:及其所之既倦,情隨事遷,感慨系之矣。向之所欣,俯仰之間已為陳跡,猶不能不以之興懷;況修短隨化,終期於盡。對於前輩敢夢逐夢的熱忱,小弟給予最大的祝福,對我來說,天下沒有不散的筵席,但是筵席會有再開的時候。雖然前輩總是自謙說踏出一步就算成功了,但我還是由衷的祝福他們鵬程萬里,得到真正的成功。

「想改變未來,最好的時刻就是現在」,希望這個新的網誌能代表著全新的開始,以生理學的角度來看,由於新陳代謝的關係,其實每個人每一天都是一個全新的人。然而,許多人不知道的是,其實人這種生物(只是另一種大型哺乳類?)用了極多的能量幫自己作新陳代謝,這個與生俱來的生物特性給了我們很大的啟發,或許我們生來就是為了日新月異、不斷求新,人生不能停止學習,就在現在,踏出了第一步,也期望能持續到最後一步。