Lamigo與我有猿 – 我們的猿份

(原文張貼於 PTT https://www.ptt.cc/bbs/Monkeys/M.1571754227.A.BF7.html

2019 年 10 月 17 日,台中洲際棒球場。我跟著數千名的十號隊友們,一起拋下了藍色的彩帶。終於,我們親眼見證了 Lamigo 王朝的誕生。手上拿著印著「劉玠廷」的毛巾,聽著領隊講話,一向被老婆笑說沒有眼腺的我,眼眶也忍不住濕了。

劉家經營這支球隊 16 年了,而我也當了 15 年的熊/猿迷。我想,是時候整理一下這十多年來的回憶了。


Continue reading  

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


(取自九把刀的部落格)