UTF-8 與 BOM

Unicode 簡介

Unicode(中文翻成「萬國碼」或「統一碼」)是一種文字編碼方式。主要的目的,是希望能提供一套統一的標準,以數字的方式表示全世界人類使用的文字,方便利用電腦、網路儲存及傳輸文字資訊。詳細的歷史沿革,網路上可以找到很多文章,在此不再贅述。

關於碼點 Code Point

每一個被 Unicode 規範定義的文字,都有一個獨一無二的數值,我們稱之為「碼點 (Code Point,亦翻作「代碼點」)」。通常我們會用 U+xxxxxxxx 代表 16 進位的數字)這樣的格式來表示碼點。

Continue reading  

「感謝日本」布條背後的故事

DSC_7498e

這次日本馬拉松式看球之旅,最重要的計畫,就是這張「感謝日本」的布條。

開始籌劃旅行是過年前的事。過年期間台灣發生了不幸的事情,接下來看到日本人們以「報恩」的態度支援救災、募款,內心真的相當感動。台灣在 2011 年對日本伸出援手,日本人一直到今天都還在感謝台灣。這次的感謝計畫,我是懷著「如果不表達感謝之意,就太失禮了」這樣的心情進行的。

Continue reading  

[C++11] Ranged For Loop

在以前,若是我們要針對 STL container 中的所有物件做事情,最「方便」的方法是使用 iterator 搭配傳統的 for-loop:

class Entry;
list<Entry> my_list;

void check_entries(void)
{
    for ( list::iterator<Entry> itor = my_list.begin(); itor != my_list.end(); ++itor)
    {
        Entry& e = *itor; // iterator 本身的操作類似 pointer
        // do something...
    }
}

Continue reading  

C++ bool 所占的記憶體空間與資料對齊 (data alignment)

前言

這篇文章的內容,緣起於「程式人雜誌」討論區的這個討論串。這個討論串到最後已經失焦了,不過有幾個重點是值得提出來討論的,於是順手整理出幾個重點。

bool 所占的記憶體空間

C++ 內建的標準的型別中,有一個叫 bool 的型別,用來儲存邏輯運算的 true(真)與 false(假)。由於這種型別的變數就只會有 truefalse 這兩種可能的值,因此,若是我們要儲存這樣的變數的話,理論上來說其實只要 1 個 bit 就夠用了。

然而在實作上,由於不是所有的 CPU 都能夠很有效率地處理單一 bit 的運算,因此通常編譯器會用其他更有效率方式的處理。

C++ 語言標準並沒有硬性規定 bool 要用多大的記憶體空間實作,把這件事留給編譯器自行決定。以 Visual C++ 來說,Visual C++ 4.2 是利用 typedefbool 定義成 int,因此 1 個 bool 變數會占用 4 個 byte 的空間;但在 Visual C++ 5.0 以後的版本,bool 則實作為占用 1 個 byte 的內建型別。在目前 x86 版的 GCC 中,bool 占用的空間也是 1 個 byte;但依據 CPU 架構的不同,可能會有不同的做法。

想要測試系統/編譯器如何實作,可以試著編譯並執行下面這個小程式:

#include <iostream>
using namespace std;
int main(void)
{
    cout << Size of bool =  << sizeof(bool) << endl;
    return 0;
}

資料對齊 (Data Alignment)

首先我們先來看一個小程式:(以下程式均在 x86-64 版的 Ubuntu Linux 上,使用 GCC 編譯執行)

// File: data_align_1.C
#include <iostream>
using namespace std;

struct STRUCT_A {
    bool b;
    int  i;
};

int main(void)
{
    STRUCT_A myVar;
    cout << Size of myVar.b =  << sizeof(myVar.b) << endl;
    cout << Size of myVar.i =  << sizeof(myVar.i) << endl;
    cout << Size of myVar   =  << sizeof(myVar) << endl;
    return 0;
}

編譯與執行結果如下:

cclo@kennethlo-ubuntu:~/projects$ g++ data_align_1.C
cclo@kennethlo-ubuntu:~/projects$ ./a.out
Size of myVar.b = 1
Size of myVar.i = 4
Size of myVar   = 8
cclo@kennethlo-ubuntu:~/projects$

「等一下!明明 STRUCT_A 中的兩個資料成員 bi 的體積分別是 1 和 4 個 bytes,為什麼合起來變成 STRUCT_A 之後,就變成 8 個 bytes 了呢?」 是啊!為什麼呢?我們來把 myVar.bmyVar.i 的記憶體位址印出來看看好了:

// File: data_align_2.C
#include <iostream>
using namespace std;

struct STRUCT_A {
   bool b;
   int  i;
};

int main(void)
{
   STRUCT_A myVar;

   cout << Size of myVar.b =  << sizeof(myVar.b) << endl;
   cout << Size of myVar.i =  << sizeof(myVar.i) << endl;
   cout << Size of myVar   =  << sizeof(myVar) << endl;

   // 印出 myVar 中所有成員的記憶體位址
   cout << endl;
   cout << Addr of myVar.b =  << &myVar.b << endl;
   cout << Addr of myVar.i =  << &myVar.i << endl;
   return 0;
}

執行結果如下:

cclo@kennethlo-ubuntu:~/projects$ ./a.out
Size of myVar.b = 1
Size of myVar.i = 4
Size of myVar   = 8
Addr of myVar.b = 0x7fff41fe3e00
Addr of myVar.i = 0x7fff41fe3e04
cclo@kennethlo-ubuntu:~/projects$

你可以試著多跑幾次,雖然每次跑出來的記憶體位址可能不一樣,但都會符合兩件事實:

  1. myVar.i 的位址一定是 4 的倍數,而且 myVar 的位址(也就是 myVar.b 的位址)也是 4 的倍數。

  2. 雖然 myVar.b 的體積只有 1 byte,但 myVar.i 的位址卻是在「myVar.b 的位址 + 4」,而非「myVar.b 的位址 + 1」。

這其實是作業系統和編譯器幫你做的安排。那為什麼它們要這樣做呢?這和 CPU 的結構設計有關係。

現代大部份的 CPU 在讀取記憶體內容的時候,並不是 1 個 byte 1 個 byte 讀取的,而是依照其資料線的寬度,一次讀取數個 byte(目前一般來說是 4 個 byte,也就是 32 個 bit)的資料。而且通常讀取位址的起始位置也有限制,例如一次要讀 4 個 byte,那麼開始的位址就必須是 4 的倍數。這個單位我們一般稱為 chunk

假設我們有個這樣的組語指令:(下面的組語是參考 GNU Assembler 的語法編出來的,實際 CPU 可能沒有 MOVL 這樣的指令,僅供參考)

# 從絕對位址 0x98760003 開始,搬 4 個 byte 的資料到 R9 暫存器
MOVL         $0x98760003, %r8
MOVL         (%r8), %r9

在大部份 RISC 系列的 CPU(如 ARM、PowerPC 等)上並不支援「跨 chunk」存取記憶體。若是你要求 CPU 執行上面的指令的話,CPU 可能會因為無法執行而產生例外狀況 (exception)。至於 x86 系列的 CPU,雖然可以執行,但一般來說執行效率會比較差。

在不支援「跨 chunk 存取記憶體」的 CPU 上,若是硬要做的話,必須要讀取前後的 2 個 chunk 的內容,然後再湊成我們想要的結果:

# 先將 0x98760000 這個 chunk 搬進來,取出 0x98760003 開始的 1 個 byte。
# 假設這顆 CPU 為 little-endian,則此 byte 為最低的那個 byte。
MOVL         $0x98760000, %r8
MOVL         (%r8), %r10
ANDL         $0x000000FF, %r10  # 留下最後的 1 個 byte

# 然後再讀進 0x98760004 那個 chunk,取出 0x98760004~0x98760006 這 3 個
# byte,往高位元 shift 8 個 bit。
MOVL         $0x98760004, %r8
MOVL         (%r8), %r9
SALL         $0x08, %r9         # R9 往左 shift 8 bits

# 最後再把兩次取得的結果組合成我們想要的內容
ORL          %r9, %r10

原本只需要存取記憶體 1 次,現在必須要存取 2 次,還需要加上額外的運算,可想而知的是速度一定會變慢。

因此,為了克服這個問題,作業系統在配置記憶體的時候會以 chunk 為單位;而編譯器在安排資料結構時,也會避免產生必須跨 chunk 存取的狀況。我們上面的例子,myVar.i 被安排在 0x7fff41fe3e04 而不是 0x7fff41fe3e01,就是這個原因。這樣的行為,我們就稱之為資料對齊 (Data Alignment)

「編譯器一定要這麼雞婆嗎?我就是不想要浪費記憶體空間,就算執行的速度再慢也沒有關係!」 如果你真的有需要的話,沒問題。一般有做資料對齊的編譯器都會提供關掉這個功能的做法。以 Visual C++ 和 GCC 來說,你可以在「不想做資料對齊」的資料結構前後加上兩行 #pragma 指令:

// File: data_align_3.C
#include <iostream>

using namespace std;

#pragma pack(push,1)   /* 加入這一行 */
struct STRUCT_A {
    bool b;
    int  i;
};
#pragma pack(pop)  /* 還有這一行 */

int main(void)
{
    STRUCT_A myVar;
    cout << Size of myVar.b =  << sizeof(myVar.b) << endl;
    cout << Size of myVar.i =  << sizeof(myVar.i) << endl;
    cout << Size of myVar   =  << sizeof(myVar) << endl;

    // 印出 myVar 中所有成員的記憶體位置
    cout << endl;
    cout << Addr of myVar.b =  << &myVar.b << endl;
    cout << Addr of myVar.i =  << &myVar.i << endl;
    return 0;
}

(每一套編譯器的做法可能不太一樣,不過 Visual C++ 和 GCC 在這件事情上剛好有共識。)

輸出的結果如下:

cclo@kennethlo-ubuntu:~/projects$ ./a.out
Size of myVar.b = 1
Size of myVar.i = 4
Size of myVar   = 5
Addr of myVar.b = 0x7fff41fe3e00
Addr of myVar.i = 0x7fff41fe3e01
cclo@kennethlo-ubuntu:~/projects$

嗯,看樣子 myVar.i 的確乖乖地貼在 myVar.b 後面了。很好。

延伸思考

  1. 「強制關閉資料對齊功能」所產生出來的資料結構,和一般情況(開啟資料對齊)所產生出來的資料結構,編譯器會產生出不同的機械語言來處理嗎?我們可以要求編譯器產生組語程式,然後再觀察組語程式的內容;或者可以直接從除錯器的反組譯視窗觀察。

  2. 8 位元的 CPU(例如 8051)、編譯器(例如 Keil C51)會做資料對齊嗎?

參考資料

Swapping Nibbles in Keil C51

今天寫程式的時候,有個地方需要 swap 某個 byte 的兩個 nibbles。其實 8051 原本就有一個 instruction 專門做這件事。若是直接寫組語的話,只要 3 行就搞定了:

    MOV     A,tmp       ; 先將原本的值搬進 accumulator
    SWAP    A           ; swap nibbles
    MOV     tmp,A       ; 先將原本的值搬進 accumulator

問題我是用 C 在寫 code 的,所以我就開始研究是不是有辦法讓 Keil C 幫我產生這樣的 code。

首先,我們用標準的 C 的作法來做:

    tmp = (tmp >> 4) | (tmp << 4);

Keil C 產生出來的組語程式如下:

    MOV     A,tmp
    SWAP    A
    ANL     A,#0F0H
    MOV     R7,A
    MOV     A,tmp
    SWAP    A
    ANL     A,#0FH
    ORL     A,R7
    MOV     tmp,A

這樣翻出來的組語程式雖然看起來很長,不過我們還是可以看出 Keil C 厲害的地方:雖然我們寫的程式碼是「向左/向右 shift 4 個 bits」,但翻出來的並沒有「連續 shift 4 次」這樣的過程(8051 的 shift 一次只能 shift 1 個 bit,所以要 shift 多個 bits 就得用迴圈來跑),而是很聰明地利用 SWAP,分別取得 high/low nibble,再重新組合成 完成的 byte。

這樣的結果讓我蠻興奮的,因為這表示 Keil C 會去看 bit operation 的位數,適時地使用處理 nibble 的指令。也就是說,我有機會找到一種寫法,讓 Keil C 翻出直接 SWAP 的組語程式。

但接下來我就卡住了,因為我找不到更好的寫法去表達 “swap nibble” 這樣的想法。

後來,我在 Keil 的文件中找到一系列 intrinsic routines。很不幸地,裡面還是沒有 swap;但卻_crol_()_cror_() 這樣的 rotate 函式。於是我試著這樣寫:

    tmp = _crol_(tmp,4);

結果翻出來的組語如下:

    MOV     R7,tmp
    MOV     R0,#04H
    MOV     A,R7
    INC     R0
    SJMP    ?C0081
?C0080:
    RL      A
?C0081:
    DJNZ    R0,?C0080
    MOV     tmp,A

結果令人失望:變得更糟了!沒有直接用 SWAP 就算了,還利用迴圈的方式來處理。看樣子這個方法也行不通。

剩下一個方法:寫 inline assembly。但 Keil C 的 inline assembly 實在很難用:只要用了這個功能,該 .C 檔就不能直接編譯成 .OBJ 檔,而必須先產生出組語程式,再另外去組譯這個檔案。很麻煩。站在專案維護的角度來看,代價太高了,所以我放棄這樣的做法。

到頭來,我還是採用一開始的那種做法,畢竟在可維護性、執行速度、code size 等各方面,都算是平衡的做法。只是不能用到 8051 提供的功能,心中還是有些遺憾….

PS: 在 這個討論串中,也有人提到用 intrisic routine 的做法,而且說 Keil C 會幫忙做最佳化;不過我實驗的結果並不是這樣。

偶爾的素食

過去的這五天,在公司吃了五天不那麼嚴格(鍋邊素、就算不小心遇到肉也會把它吃完)的素食。

同事問我為什麼,我也很難講出個所以然來。
所以最後我稱這個行動為「個人式的反美肉」。(笑)

其實我的出發點不只是美牛,而是所有不正常產生的肉品。
仔細去思考的話,會覺得這些肉的確不應該去吃——
不管從道德、健康、環保的角度想,都是這樣。

但身為一個長期的雜食者,我得承認我戒不掉肉。
偶爾吃吃素大概就是我(目前)的極限了。
所以這星期過去後,我還是會繼續吃肉;
但也許之後還是會不定時地找個一週來吃素吧!

也歡迎大家一起來參與「沒有壓力的偶爾吃素行動」。
行動準則:

  1. 時間沒有壓力:隨便挑一週來執行,或是一週裡挑一天來執行,或是只有遇到素食來執行…. 都可以。但建議有個規則,這樣自己會比較有成就感、比較能達到執行的目的。
  2. 行動沒有壓力:鍋邊素即可。就算是有放肉絲的青菜也可以挾(當然儘可能少挾肉絲),和豬肉一起滷的豆干也可以吃。牛奶和雞蛋可以視自己的目標決定要不要吃。
  3. 不要浪費食物:盤子上的東西請全部吃完。(你會發現:因為沒有骨頭,可以吃得很乾淨)
  4. 還是要注意營養均衡:其實我指的是蛋白質。吃肉的時候蛋白質不太可能缺乏,但吃素時就要提醒自己要去攝取植物性蛋白質。(所以其實我這種喜歡黃豆製品的人應該比較適合吃素 XDDD)

其實我不是什麼注重養生的人,也不算什麼環保主義者。
甚至以前我也是為了追求美食、享樂,不顧一切的人。
但有了孩子以後,我覺得看某些事情的角度,慢慢在改變。
如果孩子們只能吃垃圾食物(或說是「飼料」)長大,那不是很可憐嗎?
如果我們一些小小的舉動,能讓世界(稍微地)變好一點點,那不是很好嗎?

「勿以惡小而為之、勿以善小而不為。」
這不是國中生的作文,而是我們的生活。

[悼] 菅野ひろゆき(洋之)過世了….

今年真是不平靜。
許多內外的(其中包含好幾位我相當尊敬或喜愛的)名人相繼傳出過世或罹癌的消息。

今天一早在噗浪上看到菅野ひろゆき(筆名:剣乃ゆきひろ)過世的消息,更是令我一下子說不出話來。

我很少在 blog 中談遊戲的事情,真的有在看我 blog 的人大概沒幾個人認識他。但他創作出來的《この世の果てで恋を唄う少女YU-NO(譯:在世界的盡頭唱著愛的少女~YU-NO)》,不管是在當時、還是用現在的角度來看,都是件前無古人、後無來者的偉大作品。即使是菅野本人後來的作品,也很難再創造如 YU-NO 般的成績。

強者我朋友 lordmi 寫了一篇部落格文記念他。暫且讓小弟仿造此格式,貼上當年在聖誕夜玩了十數個小時 YU-NO 後的感想(玩完後得了重感冒… orz)。願菅野桑能安息。

警告:下文充滿御宅之氣!具有強烈高貴氣息、無法忍受宅氣者,請勿閱讀

作者  closer (買っちゃダメだ!)                            看板  Saturn
標題  初玩 YU-NO...
時間  バハムート (Fri Dec 26 09:14:50 1997)
───────────────────────────────────────
前天晚上,有一個穿著夾克,背著書包,戴著眼鏡的聖誕老人來到我房
間,拿給了我最恐怖的聖誕禮物 -- YU-NO。這件事,註定了我在這個
世界中的接下來一天多裡,瘋狂地玩 YU-NO 的命運。
在拿到 YU-NO 之前,我一直在陪我們有氣質的小女王格蘭迪莉耶魯冒
險、撿寶物、煮飯。我以為沒有什麼遊戲可以讓我離開她了;但是我錯
了!從 YU-NO 的 Disk A 放進我的 Saturn 的那一刻起,我的世界就
分裂了....從此,我一頭栽入 YU-NO 的平行世界裡....
YU-NO 的故事實在是太精采了!從序章到目前我玩完一次亞由美篇,絕
少冷場。每一個發生的事情都給我莫大的衝擊(即使我在玩之前已經稍
微了解故事的情節),讓我捨不得把手中的手把放下來。近兩天裡面,
我已經玩了約 15 個小時(不包括我嘗試錯誤的時間),幾乎是扣掉吃
飯睡覺上網和偶爾陪陪小女王的時間,我全部都丟到 YU-NO 裡去了...
能想出 A.D.M.S. 的人,一定是個變態....^^; 這個系統,看似一般的
存檔系統,可是硬是和別人不一樣,能夠完全將 YU-NO 的特性發揮出
來。沒有 A.D.M.S.,YU-NO 一定少了不少樂趣。
畫面和音樂就不說了。語音方面真的是太棒了!不愧是堅強聲優陣容的
成績!尤其是亞由美(目前為止)....不愧是井上大姐....好感動....
主角的語音也配得不錯,大家不要急著把它關起來....^^;
今天早上五點多終於把亞由美的結局玩出來。唉!只可惜目前還不能和
她過著幸福快樂的日子,因為還有其他的世界要主角來挽救....過了後
稍微看了一下攻略,發現還有很多分歧點還沒玩出來....唉!努力還不
夠啊!加油加油!
--
沐浴在陽光下的亞由美好可愛啊....
--
等等!主角是高中生,約 17 歲;亞由美比主角大十歲,約 27 歲.....
--
....いやよ!!我不要變成 obacom!!

被九把刀勾起的青春…

最近,著了魔似地,瘋狂地在吸收《那些年,我們一起追的女孩》的相關訊息。

網路上的小說看不過癮,直接在網路書店買一本;到九把刀的部落格,把所有電影相關的文章看完;到 PTT 找別人看完電影後的心得;就連聽著主題曲,都能讓我感覺眼眶濕潤。

我應該不算是九把刀的書迷,但他的小說有一股莫名的魔力。當初看《樓下的房客》,越看越覺得噁心,卻停不下來,一口氣看到凌晨兩、三點,把它看完後,才帶著一顆亂七八糟的腦袋爬上床睡覺。

不過《那些年》的情況並不是只有「寫得很好」這樣而已;而是讓我整個人都陷入青春的回憶之中。

不是柯景騰的青春,而是我自己的青春。

「怎麼會這樣啊?」我問著我自己。

也許是九把刀的年紀和我差不多,書中描述的場景、事件都和我的人生經歷重疊;也許是我年輕時的個性也和九把刀一樣自命不凡、充滿自信(不過我的勇氣是遠不如他的);也許是我也像九把刀一樣,有一群可以不帶心防、廝混打屁的朋友……

也許,是我們心中都有一位女神,在她面前總是會不知所措,做些不合時宜的可笑舉動,只為了見到她的一顰一笑……

我遠比九把刀膽小,不像九把刀一樣鬼點子那麼多。他在他的女主角心中永遠占有一個特別的角落,而我,也許,永遠只是個不討人喜歡的小丑。

反正,故事中的許多地方,都讓人情不自禁地把自己的青春投射其中,進而產生共鳴。我想不只是我,很多人應該也有一樣的感受(小說可以印到兩百多刷、累計賣出超過 30 萬本,不是沒有理由的)。同時這也能說明,為什麼男生對這個故事的感受比較深(Well, 至少在我們家是這樣 🙂 )。

電影是一定會去看的啦!不奢望它能幫我「找回青春」什麼的,能讓我有一點點的感動,那就夠了。

 

 

喔當然,有很漂亮的女主角也是很重要的啦! XDDDDD


(取自九把刀的部落格)

[歌詞] 俄羅斯輪盤

[Update] 2011/08/07: MV 換成 official 版的。

我被這首歌洗腦了。 XDDDD

Tizzy Bac rocks!!


俄羅斯輪盤

主唱:Tizzy Bac
作曲:Tizzy Bac
填詞:陳惠婷

I don’t wanna be ok 所以離我遠一點
反正我也受夠這世界
早就知道不應該 偏要痛了才學乖
我們為何沒人能例外

以為真心擁有過 常常都只是搞錯
賭上真實的自我 自己造孽自己受

如果忘了你是誰 也許比較好一點
美夢清醒的瞬間 只剩下 我的心碎 和你的狼狽

會痛 沒什麼 這點苦我還能夠
只是我現在不明所以
落單 沒什麼 好過找錯人寄託
只是我現在有點兒Lonely
有一點Lonely

I don’t wanna be ok 不能就只是ok
我曾相信這世上有絕對
美好往事多可愛 壞掉的就隨風散
可我要怎麼給自己交代

以為真心擁有過 常常都只是搞錯
賭上真實的自我 自己造孽自己受

如果可以都不算 也不曾有你的陪伴
是否就能夠避開 這尷尬的殘酷毀滅 也不用妥協
畢竟錯加錯 不會變成對

賤事 我很多 這點苦不算什麼
只是我現在不明所以
落單 沒什麼 好過找錯人寄託
只是我現在覺得Lonely
會不會來不及?
真的好Lonely

愛過 沒有錯 我為人人沒什麼
只是我現在覺得不公平( hey hey hey !)
滯銷 沒什麼 好過送錯人寄託
只是我現在覺得Lonely

不屬於的時候 該放手的就放手
只是我現在 只是我現在 只是我現在
只想耍賴

I don’t wanna be ok 所以離我遠一點
反正我也受夠這世界!