發表文章

目前顯示的是 2013的文章

在 Ubuntu 下用 gdb debug CPython

用法和其它 library 稍微不同, 安裝 python-dbg 後, 是提供另一個含有 debug symbol 的 python 執行檔 python2.7-dbg, 而不是 gdb 自己另外載入 python 的 debug symbol。 可從 dpkg -L python-dbg 的結果得知這事:$ dpkg -L python2.7-dbg | grep bin/python /usr/lib/debug/usr/bin/python2.7-gdb.py /usr/lib/debug/usr/bin/python2.7 /usr/bin/python2.7-dbg /usr/bin/python2.7-dbg-config /usr/lib/debug/usr/bin/python2.7-dbg-gdb.py $ nm /usr/bin/python2.7 nm: /usr/bin/python2.7: no symbols $ nm /usr/bin/python2.7-dbg | head U ASN1_INTEGER_get@@OPENSSL_1.0.0 U ASN1_STRING_data@@OPENSSL_1.0.0 U ASN1_STRING_length@@OPENSSL_1.0.0 U ASN1_STRING_to_UTF8@@OPENSSL_1.0.0 U ASN1_TIME_print@@OPENSSL_1.0.0 U ASN1_item_d2i@@OPENSSL_1.0.0 00000000008857e0 d AST_type 0000000000967588 b Add_singleton 00000000009675e8 b Add_type 0000000000967560 b And_singleton 實際執行的結果:$ gdb --args python2.7-dbg -c 'import time; time.sleep(10)' GNU gdb (Ubuntu/Linaro 7.4-2012…

了解 2D Graphics Library 的基本知識

有很多套不同的 2D Graphics Library, 像是 Linux 下的 Cairo, OS X 和 iOS 下的 Quartz 2D 還有跨平台的 Skia, 但它們的概念應該差不多, 若看不懂其中一套, 可以參考別套的文件再回頭理解 (特別是 iOS 的文件最齊)。這篇記錄看了那些文件協助我了解 2D Graphics Library 的基本知識。 Cairo TutorialCairo Tutorial 說明 Cairo 的架構, 配合圖示和簡短的範例程式, 相當實用。 Cairo 的架構分成: source, mask, destination, context。可以想像 destination 是一塊畫布 (pixel buffer), 可能對應到 bitmap、螢幕、PDF 或印表機。使用 Cairo API 設定好 source (如顏色、漸層模式 (gradien) 或另一個畫布) 和 mask, 再決定繪圖的動作, 就會將 source 經過 mask 的結果複製到 destination。 context 管理目前設定的各種屬性, 若有用過 OpenGL 或其它繪圖函式庫, 應該不會對 context 的概念感到陌生。繪製的動作會參考 context 內設定的屬性。 整體來說, 使用 2D Graphics Library 就是先產生一個 pixel buffer (Cairo 稱為 cairo_surface_t), 然後產生一個包含 pixel buffer 的 context (Cairo 稱為 cairo_t)。接著設定 context 屬性、source、mask, 然後決定繪圖動作 (如 fill、stroke)。 PathCairo Tutorial 有介紹 path 是什麼。Cocoa with Love: 5 ways to draw a 2D shape with a hole in CoreGraphics 提到如何用 path 畫出不同的填色效果。用同一個例子的不同作法說明為何需要 non-zero winding ruleeven-odd rule。 Blending參考 Porter/Duff Compositing and Blend Modes 了解各種 blend mode 的計算方式和圖例…

在 Chrome DevTools 修改 CSS 並同步回檔案裡

Chrome DevTools 最近的新功能, 參考 Chrome DevTools Revolutions 2013 - HTML5 Rocks, 作法如下: DevTools: settings -> workspace, 點選 add folder 加入開發用的目錄。允許 Chrome 讀寫該目錄下的檔案。 開啟網址 (本機或網路上的), 在 DevTools 的 source panel 找到要同步存檔的檔案, 按右鍵選 Map to Network Resource。 重新載入該頁。 注意: 即使直接開啟 workspace 目錄內的檔案, 仍需要執行 "Map to Network Resource" 該步, 沒作這步讓我卡了一陣子 ...。 只能同步存外部 CSS 檔, html 本體無法同步存檔, 仍得自己按 Ctrl+S。 成功後改網頁就快多啦, 在 html 內寫好骨架, 幫主要元件加上 class, 之後就在 DevTools 內選 tag, 直接在 class 內加 CSS rules, 調整參數。調整的當下就會存回檔案了, 真是超爽的啦。 備註: 依官方的說明, 也可以同步 Sass。

Mobile Safari 和 Chrome on Android 上的 1 pixel 間距

參考 CSS Tip: How to Prevent Div Seam Lines from Appearing in Apple's Mobile Safari | Oddo Design, 解法很簡單: position: relative; top: 1px; 似乎是 webkit 的 bug, 搜 "mobile safari 1 pixel gap" 可看到很多討論。 position: relative 真好用, 不只可以用來疊底, 加裝飾, 也可以 workaround 瀏覽器的 bug。

網頁顯示順序的規則: stacking context

瀏覽器在繪出網頁內容時, 並不是單純在 CSS 的 z-index 排序。而是依 stacking context。 有兩篇很棒的說明文章:The stacking context - Web developer guide | MDN: 一圖勝千文, 說明什麼是 stacking context。What You May Not Know About the Z-Index Property – Tuts+ Tutorials: 進一步說明 stacking order。由此得知為何文字 (嵌在 inline box 裡) 一定顯示在 div (block level box) 之上。這篇似乎不是用標準定義的詞彙, 不過比較容易理解。

g++ 最佳化 switch-case 的一個小例子

最近看到一段程式如下: bool isOkay(Type type) { switch (type) { case A: case C: case D: case E: case G: case I: case K: case L: case Y: return false; default: ; } return true; } 原本想說 switch-case 和一串 if 沒差太多, 這樣寫應該比 set 慢。畢竟從演算法的角度來看, 前者是線式比對, 複雜度和 type 特例的數量成正比, 而 set 保證是 O(1)。 後來經 command 提醒, 想說搞不好 compiler 有做最佳化, 還是來看產生的組語好了。結果 compiler 真的有做最佳化, 從組語來看, switch 不見得會比較慢。觀察用的程式見這裡, 用 g++ -O2 產生的組語見這裡。 結論是: 寫成「一串 if」, 結果的確是一串 cmp 和 je, 複雜度如原本預期。 但是 switch 讓 compiler 知道比較同一個變數, compiler 有機會做最佳化, 變成少數幾個計算和比較。 使用 set 有額外呼叫函式的成本, 對於簡單計算的例子, 是不可忽略的額外成本。 使用 -O2 後, set 的版本有些複雜, 沒辦法藉由「大概看看+腦補推測」搞懂。實測的結果如下:$ g++ -O2 benchmark_switch_cases_check.cpp -DSWITCH; time ./a.out; 1700000000 real 0m6.398s user 0m6.396s sys 0m0.000s $ g++ -O2 benchmark_switch_cases_check.cpp -DALOTIF; time ./a.out 1700000000 real 0m8.392s user 0m8.389s sys 0m0.000s $ g++ -O2 benchmark_switch_cases_check.cpp -DIF; time ./a.out 1700000000 real 0m8.4…

拆解使用純 CSS 做的碼表

難得看到一個簡單的純 CSS 卻含控制功能的網頁 Stopwatch in CSS, 分析了一下怎麼做到的, 才發覺 CSS 有許多神奇的語法: 顯示1. 時分秒的內容一個由 00 ~ 59 共60個文字數字組成的長方形, 用 overflow:hidden + line height 只顯示第一列; 毫秒則是 00 ~ 99 2. 文字內容用 content 產生, 不用圖片的好處是可以用 CSS 做出更多微調 http://www.w3schools.com/cssref/pr_gen_content.asp 3. 設定 position: relative, 然後用 animation 控制 top offset。 各區塊用不同長度循環播放, 比方說秒是以 60s 為單位, 將60列文字用 60 步逐步往上捲出畫面 http://www.w3schools.com/css/css3_animations.asp控制1. 用 selector ":target" 改變 CSS 的屬性, 藉此控制 animation-play-state http://www.w3schools.com/cssref/sel_target.asp#start:target 對到running, 其它 (包含預設) 對到 paused 在 CSS 中要做出點擊控制效果的關鍵可能就是 :target。由 URL 的 anchor 記錄狀態, 不同狀態提供不同 CSS 屬性, 就能切換不同畫面了。 2. stop 則是連結, 直接重新載入同一頁面

gdb "backtrace full" 不會停的可能原因

當程式有狀況的時候, 可以用下面的指令取得所有 thread 的 backtrace 含帶區域變數的資訊:gdb --batch --quiet -ex 'thread apply all backtrace full' -p PID 但是以前遇過幾次下完指令後不會結束的狀況, 不知道確切原因為何。最近剛好遇到一個容易重製的情境, 總算找到一種可能的原因。答案是: 有區域變數使用 reference 指向 dangle pointer, 於是 gdb 讀不出區域變數的資訊, 變成輸出到這個 reference 的時候, 持續輸出一堆 "Cannot access memory ..." 而不會結束。 若區域變數是指標, gdb 只會輸出指標的值。但 reference 等同於一般變數 (不知「非指標非參考」的正式名稱是什麼), gdb 會輸出整個變數 (物件) 的值, 於是 reference 指向 dangle pointer 就成了糟糕的組合。 我遇到的情況 dangle pointer 剛好含有 vector, 可能因此找不到 vector 的結束點而不會停止。Btw, 原本想說試看看用 python gdb 看看能不能自己寫個 backtrace 不會卡住, 可惜用 python gdb 一樣會卡住, 沒有丟出 exception。

為什麼無法轉型 char ** 為 const char ** ?

參見 Why am I getting an error converting a Foo** --> Foo const**?, C++ FAQ。這裡摘錄文中附的例子。 class Foo { public: void modify(); // make some modification to the this object }; int main() { const Foo x; Foo* p; Foo const** q = &p; // q now points to p; this is (fortunately!) an error *q = &x; // p now points to x p->modify(); // Ouch: modifies a const Foo!! ... } C/C++ 的轉型實在是一個我總是以為我搞清楚了, 但其實我從來沒搞清楚的東西 ...。想必過沒多久又會忘了原因吧。

LICEcap: 錄制桌機操作輸出 GIF 的工具

原本想找錄制 terminal 操作輸出成 GIF 檔的工具, 一直找不到。結果找到一個更完整更好用的工具: LICEcap, 可以錄 Windows 或 Mac OS X 的操作。安裝和使用都超簡單的。下載執行就會用了。

gj: 閱讀 C/C++ 原始碼的好幫手

兩年前剛開始接觸大型 C++ 專案時, 研究了一下尋找 symbol 相關位置的工具。最後依自己的需求, 做了一個小工具 gj。經歷了近兩年的使用和改進, gj 已成為我工作環境裡不可或缺的一塊了。 gj 的特點是: 建立索引和找尋 symbol 的時間極短, 沾 ID Utils 的光, 不費任何力氣就有這樣的成果。 盡可能不要漏掉可能的相關檔案, 並提供互動過濾的機制, 方便找出目標。 和 vim 整合一起使用。這裡有 gj demo 的畫面。 雖說目前已可應付我自己的九成需求, 還是有不少可改進的地方, 只是用到的情境較少就懶得補了。大家若有興趣使用, 歡迎提供建議。愈多人有需求, 愈有動力來改。或是用過 gj 後, 覺得有其它和 vim 整合得好工具, 也歡迎推薦。

縮小 VirtualBox VDI 格式占用硬碟的空間

先說作法, 再提冗長的碎碎念雜談。 作法參考 How to shrink a dynamically-expanding guest virtualbox image | dantwining.co.uk 簡化的作法如下: 首先要在 Guest VM 將沒用到的磁區填為 0, 這樣 Host VM 才知道那些空間沒有被用到: $ sudo aptitude install zerofree $ sudo reboot 開機時按下 left shift -> 選取 recovery mode -> 選取 Drop to root shell prompt $ mount -n -o remount,ro -t ext3 /dev/sda1 / $ zerofree -v /dev/sda1 $ shutdown -h now 奇怪的是, zerofree 填到 7x% 的時候, 整台電腦會當掉出現藍白畫面。 再來在 Host VM 壓縮 VM (VirtualBox 4.x 後支援): $ VBoxManage modifyhd my.vdi –compact 結果出現 VBOX_E_FILE_ERROR, 參考 VirtualBox Solved VBOX_E_FILE_ERROR 0x80BB0004 » deltalounge 得知可能是 VDI 上面有壞軌。於是再參考 The How-To Geek Guide to Using Check Disk in Windows 7 or Vista 用 Windows 內建的圖型介面要求 Windows 7 在下次開機時使用 chkdsk 掃壞軌。這回明確找出 VDI 檔內有壞軌, 幸好沒有遺失資料。 解決壞軌問題後, zerofree 可以正常處理完整個 partition, VBoxManage modifyhd 也正常運作了。 碎碎念雜談最近工作用的筆電三不五時就當一下, 有時是 VM 當掉 (Ubuntu), 有時是 Host OS 當掉 (Win 7), 直到某次重開機出現硬碟掃描, 掃完後說壞了八個 sector, 才確定應該是 SSD 快掛了, 只好來備份資料。 為了方便備份 Ubuntu VM, 想說買個 64GB 的隨身碟來用, 看了Tom's Hardware 針對…

screen 和 vim 切換以及移動分頁的方法

在 screen 或 vim 內開太多分頁後, 常常會忘記先前在那個分頁做什麼, 雖然可以設快速鍵在分頁間切換, 還是不太方便。實驗一陣子後, 適度地重新排列分頁, 可以加快不少操作速度。備忘一下兩者的用法。 screen在 $HOME/.screenrc 裡加上快速鍵 F7/F8 替代 ctrl+a p/ctrl+a n: bindkey -k k7 prev bindkey -k k8 next 另外, 移動分頁的方法是 ctrl+a :number N, 其中 N 表示要移往的位置。 vim在 $HOME/.vimrc 裡加上快速鍵 nmap <leader>h gT nmap <leader>l gt nmap t <C-w> 前兩項對應到切換 vim tab page, 預設 leader key 是 \, 不過我習慣用 e, 比較好按。參考 vim :help mapleader 了解 leader key 更多訊息。第三個方便在 vim window 間切換 (如 tl 表示切到右側 window), 可以少按 ctrl。 另外, 移動分頁的方法是 :tabm N, 其中 N 表示要移往的位置。

gdata-javascript-client 不能用了

今早使用 ego-post 時才發現, 原來 gdata-javascript-client 在 2012/04/20 就 deprecated 了, 不過到上上週以前還可以用。之後要改用 google-api-javascript-client - Google APIs Client Library for JavaScript, 需要先申請 Blogger API Key 才可以用, 剛才申請完後, 說五個工作天內會送認證信給我, 在那之前只好手動複製貼上到 Blogger 了。

在Windows 7 找出己安裝的 product key

若先前忘了備忘 product key, 重裝 Windows 的時候就麻煩了, 幸好經 Wens 提醒, 有軟體可以讀出 product key, 不見得適用所有軟體, 至少 可以找出Windows 7 和 Office 的樣子。 相關資料:How To Find Your Windows 7 Product Key: 說明原理和可用的工具。Belarc - System Management For The Internet Age. Software license management, IT asset management, IT security audits and more.: 挑了其中一個來用, 一用就見效。這軟體有讀出其它電腦硬體和區網資訊, 滿有趣的。

對 C++ 的觀感

寫了快兩年的 C++, 覺得 C++ 是學習成本高昂, 投資報酬率卻很低的語言。剛好看到 Less is exponentially more 有感, 在 G+ 寫了一些感想。提及經年累月的 C++ 經驗也會變為機會成本, 阻礙程式設計師轉向其它語言。不過在 Go 有龐大的 C++ library 和產品替代品以前, 也不容易選定用 Go 開發有效率需求的程式。實在是兩難啊。下面取 G+ 留言的其中一段說明為什麼我覺得學 C++ 投資報酬率很低。 舉一個大家可能都會遇到的例子: 執行時多型。 在 Java 和 Python 裡, 說明執行時多型只要一句話就結束了, 就.....看那物件指到誰, 就會執行它的 method。 C++ 的話, 要明白兩件事: 是否有使用指標呼叫? 等等, 還有參考的效果等同指標, 也要留意是否有用參考 ( 雖然很「理所當然」, 多了一個細節要留意 ) 呼叫的 method 是否有宣告 virtual? 如果你在目標類別的 method 沒看到 virtual, 記得還要順便檢查它的全部父類別是否有宣告該 method 為 virtual, 因為只要父類別有宣告 virtual, 子類別可以省略 virtual 的宣告 再來要明白 pure virtual 以及漏實作 virtual 函式造成的不易理解的連結錯誤訊息。到目前為止還只是基本語法, 還沒到實戰注意事項。 實戰注意事項包含: 若類別有可能被繼承, 記得宣告 destructor 為 virtual private virtual 的使用時機 不能在 constructor 和 destructor 裡呼叫 virtual method 可能還有其它點, 目前有需要學到的就這幾點。明白為什麼後會覺得很「直覺」, 但是每當有必要用到時, 就得分神查一下這是在做什麼或是為什麼這樣可以運作。我明白 virtual 的出發點是 C++ 的核心精神 zero cost, 只是在看到這麼多衍生出來瑣碎的注意事項後, 我滿懷疑執行期的 zero cost 是否 >> 開發者的時間成本, 特別是獲得的好處和付出的代價是如此地不成比例。

是否能讓 C++ template 的標頭檔只含宣告不含實作?

參考《C++ Templates - The Complete Guide》 ch 6.1 ~ 6.3, 答案是: 可以。 過去一直覺得自訂 template 的時候, 將宣告和定義 (實作) 同時放在標頭檔裡最保險, 但不確定是否能將兩者拆開放到不同檔案。拆開的明顯好處是不會因為修改 template 的實作, 而需要重新編譯 include 此標頭檔的檔案。在經歷過改一行標頭檔要重新編譯十分鐘後, 我愈來愈在意這件事了。 在說明如何折開 template 的宣告和實作之前, 得先明白編譯使用到 template 程式碼過程發生了什麼事。實際上有兩個步驟需要留意: 讀入 template 宣告, 檢查 caller 是否有正確使用目標函式、類別。instantiate (實例化) 特定參數的 template。 比方說定義 std::map<std::string, int> scores 的時候, 除了需要 map 的宣告了解 scores 是否有正確使用 map 的介面外, 還需要 map 的定義 (實作)才知道如何 instantiate std::map<std::string, int>。實例化時會檢查參數 std::string 和 int 是否支援 map 預期的介面。附帶一提, 《Effective C++》稱 template 為「隱式介面 + 編譯期多型」, 而 virtual 是「顯示介面 + 執行期多型」, 很精闢的描述。 回到原本的議題, template 標頭檔是否能只含宣告? 可以, 只要之後有辦法 instantiate 用到的 template 即可。假設 <map> 裡只有 std::map 的宣告, 就需要在某個 cpp 檔裡面 include map 的定義, 然後明確地告訴 compiler 你要 instantiate std::map<std::string, int>。 以下以自訂函式說明: t.h#ifndef T_H #define T_H template <typename T> void foo(T& t); #endif // T_H t.cpp#include "t.h" #include <…

python 快速開發: 使用 ipython 撰寫 python 程式

一但習慣某個東西的好處後, 久了就會忘了它的重要性。今天重操舊業寫了久違的 python, 覺得能用 python 寫程式已很爽了, 還能用 ipython 寫 python, 更是爽上加爽!特此記錄一下, 分享給還未試過的人。 ipython 是 python 的互動式 shell, 功能相當強大, 而且預設設定就相當好用。如今 ipython 已發展到令人難以想像的地步, 以下只是基本的使用情境。 試用別人的模組$ ipython Python 2.7.3 (default, Aug 1 2012, 05:14:39) Type "copyright", "credits" or "license" for more information. IPython 0.13.1 -- An enhanced Interactive Python. ? -> Introduction and overview of IPython's features. %quickref -> Quick reference. help -> Python's own help system. object? -> Details about 'object', use 'object??' for extra details. In [1]: import os In [2]: os.pa<TAB> os.pardir os.pathconf os.pathsep os.path os.pathconf_names In [2]: os.path.join? Type: function String Form:<function join at 0x7f67f9c09ed8> File: /usr/lib/python2.7/posixpath.py Definition: os.path.join(a, *p) Docstring: Join two or more pathname components,…

C++ 的 C style cast 不等於 static_cast

即使看了幾次《Effective C++》item 27 "Minimize casting", 還是不明白 C style cast 有什麼重大問題。只覺得新的 cast 比較明確安全, 但寫起來很囉唆, 又是角括號又是括號 (愛惜手指, 請從少按 shift 做起)。一直以為 C style cast 等同於 static_cast, 這樣的話, 在使用 static_cast 的場合, 就偷懶寫成 C style cast 吧。 今天看到 Ken 寫的 On C-Style Cast in C++, 才發覺情況和我想得不同。於是寫個小程式驗證一下:$ cat b.cpp -n 1 class A {}; 2 class B : public A {}; 3 class C {}; 4 5 int main(void) { 6 A* a = new A(); 7 B* b = (B*)a; 8 C* c = (C*)a; 9 B* b2 = static_cast<B*>(a); 10 C* c2 = static_cast<C*>(a); 11 return 0; 12 } $ g++ b.cpp -o b b.cpp: In function ‘int main()’: b.cpp:10:28: error: invalid static_cast from type ‘A*’ to type ‘C*’ 果真 C style cast 可以隨意轉換, static_cast 限制較為嚴格一些, 但是 pointer-to-base 仍可透過 static_cast 轉成 pointer-to-derived (即使轉型後會有問題)。若需要更安全的作法, 可以用 dynamic_cast, 看轉完的值是否為空指標, 可以知道是否能安全轉過去。代價是會增加執行時間, 詳細說明見《Effective C++》item 27 "Minimize casting"。關於轉型最重要的觀念, 大概就是盡量別轉型吧。

C++ 指標轉型後位置可能會改變

今天踩到一個和 C++ 指標轉型相關的 bug, 幸好以前看《深度探索 C++ 物件模型》時有個模糊的印象, 知道物件指標轉型後, 位置可能會變, 發現這問題後很快就想通了。 先寫個小程式實驗一下:#include <iostream> struct A { int x; }; struct B { int y; }; struct C : public A, B { int z; }; int main() { C *c = new C(); A *a = c; B *b = c; void* v = c; std::cout << a << " " << b << " " << c << " " << v << std::endl; return 0; } 範例輸出:0xbd0010 0xbd0014 0xbd0010 0xbd0010 由此可得知 a、c、v 位置一樣, 但 b 不同。 若用指標相等做邏輯判斷, 要留意轉型帶來的影響, 特別是中間有轉型成 void* 的時候, 可能傳了同樣的物件, 卻因型別不同造成誤判。 以下是一個示意的例子:class Foo final { public: void Save(const Data& data, void* source); ... private: ... FooCallback *m_callback; } void Foo::Save(const Data& data, void* source) { ... if (m_callback && source != m_callback) { m_callback->OnSave(data); } } 有不同的來源會呼叫 Foo::Save, 此時 Foo 會通知事先註冊的 callback, 但要避開 callback object 主動呼叫 Foo::Save 的情況。 乍看之下有檢查 source != m_callback 即可安心。但若 callback o…

以安裝 ccache 為例, 說明如何使用系統工具除錯

ccache 是什麼?ccache 藉由暫存編譯過的 object 檔, 可以減少不必要的重新編譯時間。 用法很簡單:# 安裝 $ sudo aptitude install ccache # 啟用 ccache $ export PATH="/usr/lib/ccache:$PATH" $ gcc ... # 這時會用到 /usr/lib/ccache/gcc 以我測試的例子來說, 從頭重新編譯一次是半小時左右, 裝了 ccache 後變成一分半。 依 ccache 官網所言, ccache 有可能重編譯不必要的程式, 但不會用到不對的暫存檔, 這也是使用這類工具時最重要的保證。 不過這篇的重點不在 ccache 的用法, 而是如何「知道如何安裝和使用它」。以前的文章或多或少有提到下文用的工具, 這篇比較有系統地用一個完整的例子使用它們。 已知 ccache 替換 gcc 等編譯工具, 檢查編譯條件和檔案沒變時, 直接取得暫存檔作為編譯結果。 粗略地掃過 ccache manual, 得知是用 symbolic link 替換 gcc 為 ccache。 截至目前為止, 我們已得知 ccache 的核心運作原理, 接下來可以動手試看看。 安裝和使用過程1. 首先確認是否能透過系統套件安裝 ccache:$ aptitude search ccache i ccache - Compiler cache for fast recompilation of C/C++ code p ccache:i386 - Compiler cache for fast recompilation of C/C++ code 2. 再來看套件訊息, 確定沒搞錯套件, 還有看看版號是否夠新: $ aptitude show ccache Package: ccache State: installed Automatically installed: n…

用 gdb 略過部份程式碼

尋找「誰是兇手」的時候, 以往的作法是註解掉部份程式、重新編譯、重新執行、觀察結果是否有變化。然後反覆前述動作直到找到兇手為止。編譯的時間略長的時候, 這個過程頗為痛苦。 幸好 gdb 有跳掉部份程式碼的方法。其中一個是用 return 結束目前所在的函式, 返回上一層 frame。另一個作法是先設中斷點, 再用 jump 跳到中斷點。 用 return 的小小缺點是: gdb 每次都會問你 yes/no, 用 jump 則無此困擾。可以用 define 寫個簡單的函式, 設暫存中斷點, 再用 jump 跳到中斷點, 藉此忽略中間的程式碼。 作法如下: # skip the next N line statements define j if $argc == 1 && $arg0 > 0 tbreak +$arg0 jump +$arg0 end end 之後就可以用 j N 跳掉接下來的 N 行程式。

有繼承的情況下, C++ method 存取到誰的 member field?

Effective C++ item 27 "盡量少做轉型" 提到下列轉型看起來像對的, 實際是錯的:class SpecialWindow : public Window { public: virtual void onReSize() { static_cast<Window>(*this).onResize(); ... } } 對這點感到很納悶, 於是寫個小程式實驗看看。結果發覺自己沒看清楚, 書上舉例是轉型 Window, 不是轉型 Window*, 所以 Window::onResize() 作用到新產生的物件身上, 結果不同於呼叫 Window::onResize()。 既然已經寫了小程式做實驗, 順便記在這裡供日後備忘。 要點如下: 類別 C 的 method 會存取自己的 member field x, 若 C 本身沒有這個 member field, 會往父類別 A 找。 承上, A 和 C 的 method 存取到的 x 是同一個 x, 也就是 A::x。 若類別 B 宣告和父類別 A 同名稱的 member field x, 則類別 B 和父類別 A 各自擁有一份不同位置的 member field x。 承上, A 的 method 會存取 A::x; B 的 method 存取 B::x。呼叫到誰的 method, 就知道改到誰的 x。 題外話, 允許子類別覆寫或重覆宣告同名 method 或 member field 是個糟糕的主意, 再加上可以轉型, 而且轉型後結果還會不一樣, 真是火上加油啊.......。了解 C++ 愈多, 愈覺得要從實作層面才能理解它的語法。 程式碼範例輸出$ ./a b.set_x(3) 3 0 copy constructor A is called 3 0 3 30 3 30 c.set_x(3) 3 3 3 3 3 30 3 30 method address &A::set_x 0x4006c8 &B::set_x 0x400710 &C::set_x 0x4007da member field address &b.x 0x7fffb4ef43a4 &a…

C++: 使用 private virtual 區分實作介面的 method

問題描述Java 有語法 interface 明確定義 class 之間的接口, 但是 C++ 沒有, 只能透過「習以為常」的慣例表示, 也就是: class I 宣告一組 public pure virtual function, 表示 I 是一個 "interface" class A 希望實作 I, 於是透過繼承的方式實作 I 需要用 I* 的 class, 取得實作 I 的物件 (也就是 A 的物件), 存成 I* 當 class A 需要實作多組介面, 或是自己也有一些 public method 供別人使用時, 看 class A 的宣告會不易找出那些是 A 的 public API, 比方說以下的例子: class I { public: virtual void foo() = 0; virtual void bar() = 0; }; class A : public I { public: void handle(); virtual void process(); // I's methods. virtual void foo(); virtual void bar(); }; 一眼看去有四個 method, 實際上可能只有 handle() 和 process() 才是使用 A 的人需要關心的。當 A 同時實作 (繼承) 多組介面就不易閱讀了。 解法若宣告實作介面的 method 為 private, 像下面這樣: class A : public I { public: void handle(); virtual void process(); private: // I's methods. virtual void foo(); virtual void bar(); }; 有幾點好處: 降低這些 method 可存取的範圍, 避免被誤用 縮小閱讀程式的範圍 間接暗示它們是作為 I 的介面使用, 若 A 自己的程式碼都沒用到它們的話, 會更明確地表明此點。特別適合用於 callback 的介面。宣告 private virtual 的基本精神和宣告 member field …

用 doxygen 產生 class hierarchy diagram

最近需要讀比較複雜 C++ 程式, 常常看到一堆 class 有個祖宗十八代, 要找出眾多子代的 class 或是某個 method 到底是那一代祖先實作的, 有些麻煩。 原本想自己玩看看 clang, 用 clang 分析原始碼產生 graph, 再套 graphviz 畫出來。轉念一想, 這麼常見的需求, 應該有人做好了。況且, 自己處理單檔可能不會麻煩, 但若多個檔案需要不同 include path (-I) 時, 到也有些頭痛。 上網查一下, 發覺 stackoverflow 有人推薦用 doxygen, 一試就靈, 最棒的是 doxygen 不需另外讀編譯 C/C++ 的設定檔, 它直接讀取指定的目錄。先跑 doxygen -g myconfig 產生設定檔 myconfig, 再修改 myconfig: INPUT RECURSIVE = YES 最後執行 doxygen myconfig 即可。 產生的網頁裡可看到各個 class 的祖譜, 非常方便。我猜 doxygen 可能是直接做文字分析, 不是用 compiler 下手。 實測的結果也的確有問題, Ubuntu 12.04 的 doxygen 有點舊, class 有用到 C++11 的 keyword final 時, final 反而會被當成 class name。幸好在 doxygen 1.8.2 版已解決, 先裝 doxygen 舊版, 再自己抓原始碼編譯, 用 checkinstall 封裝成 package 即可解掉這問題。 用 checkinstall 封裝的好處是, 可從 dpkg -L PKG_NAME 看裝的內容, 日後有問題也可用 dpkg -r PKG_NAME 刪除。Btw, doxygen 參數一堆, 有閒再來研究看看有什麼好用的東西。2013-09-10 更新若遇到 macro 造成 doxygen 分析錯誤, 可試著要求 doxygen 展開特定的 macro。作法是修改下列的設定:ENABLE_PREPROCESSING = YESMACRO_EXPANSION = YESEXPAND_ONLY_PREDEF = YESPREDEFINED = ABC=xyzEXPAND_AS_DEFINED …

用 python gdb 客製化 backtrace 的結果 (2)

之前寫的指令 bt 加上參數 -s, 使用 -s 時, 會一併記錄 backtrace 每個 frame 附近的原始碼。 以下是一個範例輸出: (gdb) bt -s #0 A::hello at b.cpp:8 | class A | { | public: | void hello(int n) | { ->| std::cout << n << std::endl; | } | | void foo(int n) | { #1 A::bar at b.cpp:18 | bar(n + 1); | } | | void bar(int n) | { ->| hello(n + 10); | } | }; | | int main(void) { #2 A::foo at b.cpp:13 | std::cout << n << std::endl; | } | | void foo(int n) | { ->| bar(n + 1); | } | | void bar(int n) | { #3 main at b.cpp:24 | } | }; | | int main(void) { | A a; ->| a.foo(5); | return 0; | } 修改後的 python script 如下: 作法大致和 《用 python gdb 客製化 backtrace 的結果》一樣, 只是改取檔案的 fullname, 再自己讀出相關的原始碼。這部份用 python 實作, 相當地輕鬆寫意。

用 macro 組合變動式參數, 強化 log 函式

程式除錯時常需要加些 log 印出變數內容, 通常會希望附帶印出所在的函式。每次要自己重打一次函式名稱太麻煩了, 可以利用 C 的 macro 避免重覆的動作。 先來看個範例程式: 以及輸出結果:$ g++ a.cpp -o a; ./a result: 3 $ g++ -DDEBUG_PRINT a.cpp -o a; ./a int Calculator::add(int, int) this=0x7ffff03c1d7f a = 2, b = 1 result: 3 有 #define DEBUG_PRINT 的才會真的執行 DebugPrintf, 除了兼顧原本 printf 有的變動式參數外, 順便自動補上函式名稱和目前物件的指標; 反之則完全不會執行到, 不會增加額外負擔。 有需要的話, 可以稍微修改 DebugPrintf, 如同 《trace C/C++ function call 的方法》 記錄檔名和行數。 關於巨集這行:#define DebugPrintf(format, args...) fprintf(stderr, "%s this=%p " format, __PRETTY_FUNCTION__, this, ##args) 用到的語法包含: C 的 "abc" "def" 會組成 "abcdef" args... 比 __VA_ARGS__ 易於閱讀 只有一個參數的時候, __VA_ARGS__ 會自動移掉前一個 "," 可以參照 Variadic Macros - The C Preprocessor 了解細節。

用 python gdb 客製化 backtrace 的結果

需求想要了解模組之間函式呼叫的關係時, 與其一層層比對多個類別之間的呼叫關係, 不如直接在最後一個呼叫函式放中斷點, 直接顯示 backtrace。但是當函式裡有太多參數或 template 時, backtrace 的 frame 訊息會變得很長, 不易閱讀。我的目的只是找出呼叫的函式名稱、檔名和行數, 函式帶的參數反而是困擾。 作法一: 用 gdb.execute() 一個簡單的作法是截取 gdb 的輸出, 然後解析文字去掉不要的部份: Btw, 上面的作法還順便幫行首的標號上色。 但是, 使用 cgdb 時會無法運作, 理由是 cgdb 使用 GDB MI, gdb.execute('backtrace') 的結果不是原本看到的格式, 難以解析。 作法二: 用 gdb.Frame() API 只好改用中規中矩的方式逐一讀取 frame, 取出需要的資訊: 將上面的 script 存到 /path/to/gdb/scripts/backtrace.py, 接著在 $HOME/.gdbinit 裡加入以下設定:python sys.path.insert(0, '/path/to/gdb/scripts') import backtrace end 之後就能用 bt 顯示精簡後的 backtrace 了, 也方便手動複製貼上到筆記裡。以下是一個輸出例子: (gdb) bt # 0 A::hello at a.cpp:8 # 1 A::bar at a.cpp:13 # 2 A::foo at a.cpp:18 # 3 main at a.cpp:25 Btw, 若是需求比較簡單, 可以試看看 Print Settings, 有些選項可以改變 backtrace 顯示的訊息。 參考資料:The Cliffs of Inanity › 2. Writing a new gdb commandPruning backtrace output with gdb script - Stack OverflowFrames In Python - Debugging with GDBSymbols In Python - Debugging with GDBSymbol Tables In Python - Debuggi…

vim 快速開啟目錄下的檔案

用過 XCode 的 "shift+command+o" 快速開啟檔案後, 一直很想在 vim 內用同樣的功能。按個快速鍵會跳出一個輸入框, 接著只要打部份字串, 就會濾出有可能的檔案。 看到 DKcmd 推薦用 ctrlp, 就來試用一下。試用後感覺相當不錯, 用法如下: 在 command mode 打 ctrl+p 打檔名 選定檔案, 按 ctrl+t 開在新分頁; 按 ctrl+v 開在目前分頁的新 window; 按 enter 開在目前分頁ctrlp 除介面好用外, 另一大優點是使用 pure vim script, 理論上有 vim 的平台都能用 ctrlp。不過最近用一陣子後發覺一些小問題, 微調之後才會更好用。 在 $HOME/.vimrc 中加上這幾行: let g:ctrlp_clear_cache_on_exit = 0 " 離開 vim 後不要清 cache let g:ctrlp_max_files = 1000000 " 加大 cache 索引的檔案數, 否則會漏找檔案 let g:ctrlp_user_command = 'find %s -type f' " 使用 find 加速建索引的速度備註: 無用的實驗觀察之前沒讀完官網, 漏看了參數 g:ctrlp_user_command, 只好自己手動建索引加速, 下面的心得就當紀念吧。 另外 cache 預設存放在 $HOME/.cache/ctrlp 下, 若嫌 ctrlp 建 cache 太慢, 可以自己手動建:$ cd /path/to/target $ index=$(echo `pwd` | sed 's#/#%#g' | sed 's#$#.txt#') $ find . | cut -b 3- | LC_ALL=C sort > $HOME/.cache/ctrlp/$index 自己建會快超多, 不過會比較不乾淨一點, ctrlp 似乎有濾掉不能開的檔案, 像是目錄或圖檔之類的。

gdb 顯示 STL container 的方法

問題描述如下列的程式碼:std::vector<int> numbers; for (int i = 0; i < 4; i++) { numbers.push_back(i * 3); } std::map<std::string, std::string> contacts; contacts["john"] = "0987-654321"; contacts["mary"] = ""; 在 gdb 中不易閱讀 numbers 和 contacts 的顯示結果:(gdb) p numbers $1 = {<std::_Vector_base<int, std::allocator<int> >> = {_M_impl = {<std::allocator<int>> = {<__gnu_cxx::new_allocator<int>> = {<No data fields>}, <No data fields>}, _M_start = 0x6060 10, _M_finish = 0x606020, _M_end_of_storage = 0x606020}}, <No data fields>} (gdb) p contacts $2 = {_M_t = {_M_impl = {<std::allocator<std::_Rb_tree_node<std::pair<std::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::basic_string<char, std::char_ traits<char>, std::allocator<char> > > > >> = {<__gnu_cxx::new_allocator<std::_Rb_tree_node<std::pair…

trace C/C++ function call 的方法

目的 了解特定模組或類別的使用方式。找出執行時間過長的函式方法一: 使用 gcc 參數 -finstrument-functions參考資料:Code Gen Options - Using the GNU Compiler Collection (GCC)Jserv's blog: GCC 函式追蹤功能 優點: 自動含蓋所有函式。 缺點: 可能增加太多執行負擔, 雖然可以配合 -finstrument-functions-exclude-file-list 去除不想觀察的函式, 對於大專案來說有些不方便。方法二: 自己寫 logger使用 C/C++ 或 gcc 提供的語法, 自己寫 logger 沒有想像中的麻煩: 可用 gcc 的 __PRETTY_FUNCTION__ 或標準的 __func__ 取得函式名稱 用 __LINE__ 取得行數 用 __FILE__ 取得檔名 於是可定個巨集:#define LOG_IT() MyFunctionLogger logger(__FILE__, __LINE__, __PRETTY_FUNCTION__) 然後用 vim 的全域取代::% s/^{/&\r LOG_IT();\r\r/c 這會逐個詢問是否取代下列字串:void foo() { // blah blah 為void foo() { LOG_IT(); // blah blah Btw, 由此可知, 函式的左大括號放在行首比放在行尾來得方便。 MyFunctionLogger 只是個 wrapper, 目的是利用 RAII 記錄進入和離開函式, 這樣即使函式中間有任何 return, 都不會漏記離開函式:class MyFunctionLogger { public: MyFunctionLogger(const char *file, int line, const char *func) : m_file(file), m_line(line), m_func(func) { MyLogger::Instance()->EnterFunctionCall(m_file, m_line, m_func); } ~MyFunctionLogger() { …

Linux 寫 code 確認某個 process 仍活著

shell script 直接用 ps PID | grep ... 檢查, 無須多說。寫 C/C++ 程式的話, 基於同樣的想法, 可以用 stat("/proc/PID/stat") 檢查檔案是否存在, 但這作法有一些不確定性, 有可能目標 process 掛了, 之後有新的 process 用了同樣的 pid, 結果誤判目標還活著。 若自己有權限送 signal 給目標 process 的話 (如 child 或 parent process), 可以用 kill 來檢查:bool IsAlive(pid_t pid) { return !(kill(pid, SIGCONT) == -1 && errno == ESRCH); } 送個無關緊要的 signal 給目標 process (若它真的有機會進入 STOP 狀態的話, 得挑別的 signal), 再檢查錯誤值判斷目標是否還活著。覺得用 kill 檢查的小技巧還滿有趣的, 備忘一下。2014-03-28 更新實際用了以後發現一個不方便的副作用。使用 gdb 連上 process 後, gdb 會在 process 收到 signal 時中斷。若採用送 signal 定期偵測 process 是否活著的話, gdb 會三不五時地中斷, 不方便除錯。 還是用 stat("/proc/PID/stat") 比較方便一些。或是觀察的 process 是 child process 的話, 可用 waitpid + WNOHANG 判斷。

在 iOS 刪除 App 後不會刪除 keychain 內的資料

不過理論上 App 不能存取別其它 App 的 keychain 資料, 所以這勉強不算安全漏洞吧。若希望使用者重新安裝 App 時, 不會用到之前存在 keychain 的資料, 可在安裝後第一次啟動時先刪除 keychain 資料:- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { //Clear keychain on first run in case of reinstallation if (![[NSUserDefaults standardUserDefaults] objectForKey:@"FirstRun"]) { // Delete values from keychain here NSArray *secItemClasses = @[(__bridge id)kSecClassGenericPassword, (__bridge id)kSecClassInternetPassword, (__bridge id)kSecClassCertificate, (__bridge id)kSecClassKey, (__bridge id)kSecClassIdentity]; for (id secItemClass in secItemClasses) { NSDictionary *spec = @{(__bridge id)kSecClass: (__bridge id)secItemClass}; SecItemDelete((__bridge CFDictionaryRef)spec); } [[NSUserDefaults standardUserDe…

VirtualBox 使用 Bridged Adapter 的方法

打從開始用 VirtualBox 到現在, 一直沒有成功地用過 Bridged Adapter, 所幸 cmd 很自然地使用 Bridged Adapter, 比對一下成功的設置方式, 總算明白我搞錯的地方。 Bridged Adapter 的設定幾乎和 NAT 一樣簡單, 在 guest OS 關機狀態下, 從 VM 的 Settings -> Network -> Attached to 選擇 Bridged Adapter 即可。若 host OS 是透過 DHCP 取得上網 IP (比方說家裡有裝 IP 分享器), 那 guest OS 無須設定, 開機後也會從 DHCP 取得 IP 上網。 若 host OS 是用 PPPoE 連上網路, guest OS 也需要設定 PPPoE 才能連上網。可以想成 guest OS 和 host OS 是連上同一個 LAN 的另一台機器。Btw, 可以參考這裡了解 Ubuntu 設定 PPPoE 的方法, 作法也很簡單。

shell script: 用 eval 執行內含單引號的變數

某些情況下需要在指令裡加入單引號, 但是會有問題。下面是一個簡化後沒意義的例子:cmd="ls '/etc'" echo "> Run $cmd" $cmd 執行結果如下:> Run ls '/etc' ls: cannot access '/etc': No such file or directory 但是實際在 bash 上打入 ls '/etc' 卻沒問題。從這裡得知, 解法是用 eval 執行:cmd="ls '/etc'" echo "> Run $cmd" eval $cmd 另外記得除錯時可以配合 set -x 顯示 bash 實際執行的指令, 或是用 bash -x 執行 script。

加速 gdb 載入 symbol 時間

gdb 7.4 以後支援 index, 可以加速載入 gdb symbol 的時間, 參考 Index Files - Debugging with GDB 的說明, 寫了個 script 接受一個 binary 檔, 會作以下的事: 用 gdb 載入 binary 的 symbol, 並存下 symbol index 到暫存檔 加入 symbol index 到原 binary symbol 太多時, 用 gdb index 可大幅減少載入時間, 不過每次重編又要重新產生 index 檔, 不知有沒有更好的方法, 可以在 linking 時直接在 binary 內加入 symbol index, 並且不會增加太多 linking 時間。2013-07-19 更新 依 Viller Hsiao 留言裡的建議, 試了 gold 的 --gdb-index。這功能還滿新的, Ubuntu 12.04 的 gold 沒這選項, 不過 12.10 的 gold 有。可以用 objdump -h BINARY | grep index 確認 link 時加上 --gdb-index 確實有效。 2013-07-23 更新 用 gold 編譯加上 --gdb-index 後踩到雷, 在目前的專案裡 100% 會在執行 backtrace 後卡住不動, 接著一直狂吃記憶體, 漲到 4G 以後顯示gdb virtual memory exhausted can't allocate 4064 bytes, 然後和使用者說 gdb 掛了, 你願意結束這個 debug session 嗎?交叉比對了下述三種情況: 用 gold link (不含 --gdb-index)用 gold link (不含 --gdb-index), 事後補 gdb index用 gold link + --gdb-index 結果只有第三種會中獎, 所以現在改回用第二種方法了。

Objective-C 的 scope 以及 property

今天要用又忘了, 寫下來備忘一下。 scopeinstance variable 有 scope, method 似乎沒有 scope。畢竟是 dynamic typing, method 加上 scope 也滿奇怪的。 instance variable 的 scope: 分為 @public, @protected, @private 和 @package (看不懂 @package, 要用到再研究, 我猜是指 scope 在「函式庫」內) @interface 預設 scope 是 @protected @implementation 預設 scope 是 @privateproperty宣告 @property Klass myObject 後, 可用 @synthesize 讓 compiler 產生基本的 getter 和 setter 實作。 或是自己在 @implementation 內實作-(void) setMyObject:(Klass*)object { } -(Klass*) myObject { return ...; } 個人不太喜歡 @property 的設計, 要找 property 相關存取程式時, 有點不方便。以 @property myObject 來說, 要先確定是否有用 synthesize, 若有的話, 要同時看 myObject、setMyObject 以及 synthesize 的 instance variable (如 _myObject), 才能確定沒有其它存取到這個屬性的地方。

MacPorts 基本操作

之前問朋友在 Mac OS X 下用那套工具, 結果強者們清一色地回答用 MacPorts, 不管它好不好用, 總之強者們意見如此一致, 跟著試就是了。 最近用一陣子後覺得還算順手, 記得依這裡的說明設 bash-completion, 關鍵的一步是要用 macports 裡的新版 bash 才能搭上 bash-completion, 但是 iTerm2 預設用系統的 bash, 要改一下 iTerm2 的偏好, 改用 macports 裝的 bash 才行。 目前只到下列幾個指令, man ports 裡都有: port search PATTERN port install PKG port info PKG port content PKG 美中不足的是, macports 沒有像 apt-file 這種從檔名反查套件的功能。不然, 有考慮日後改用 Macbook Pro 開發, 可兼顧日常使用應用程式, 以及用 terminal 開發的需求。

Optimizing the Critical Rendering Path 閱讀筆記

Optimizing the Critical Rendering Path - Velocity SC 2013 這篇滿有趣的, 看完順便筆記一下。 問題描述 使用者等超過一秒就會不耐煩 3G / 4G 網路 latency 很高以 3G 的情況來說, 假設 RTT 是 200ms, 去掉必須的 DNS lookup、TCP handshake、載入 html 檔 (用 http), 就只剩下 400ms 可以 render 網頁。 https 需要額外 1 ~ 2 個 RTT, 情況更糟 載入網頁時, 雖然 html 可以邊載入邊顯示, 但是需要載入外部 CSS 檔時會延後顯示網頁。CSS 需要載入完整個檔案才可使用, 無法邊載入邊用 外部 javascript 檔也會阻擋整個網頁顯示 ( ps. 網頁加速的基本知識可以參閱 Best Practices for Speeding Up Your Web Site, 還有用 Chrome Developer Tool 內建的功能檢查 ) 解法 要下參數避免 TCP slow start, 降低 RTT 一個 roundtrip 大概可下載 14KB 資料 (應該是以 MTU 1500 Bytes 來估吧), 盡量讓第一份 html 只含 14KB 以下的資料, 之後再補載入剩下內容 避免在第一次顯示時用到外部 CSS 檔, 有 Google 有提供模組讓 apache2 或 nginx "inline CSS" 避免在第一次顯示時用到外部 JS 檔

使用 iOS notification 的好處

先前在《iOS delegation vs. notification》提到我偏好 delegation, 這篇稍微修正一下對兩者的觀點。 開發 iOS 和 Android 以前我只有寫 Web 的經驗, 沒有寫圖形應用程式的經驗 (硬漢工程師就是要用 CLI 啊!!)。之前只覺得開發 Web 很容易上手, 不過跨瀏覽器很煩。最近多了一些 iOS 開發經驗, 才體會到為什麼 Web 這麼好寫, 個人認為的原因有二: 透過 AJAX 做 model 的操作, 預設就有成功或失敗的 callbackUI 明確知道目前 model 的狀態, 要不要反應是另一回事, 至少預設就有能力針對非同步操作提供 UI 回饋。 DOM 的架構很容易存取到所有 UI 元件。一來 DOM 是全域變數, 二來有 id、class 等屬性方便索引到目標 UI 元件, 任何時間任何一行程式碼都可以一行取得目標 UI。 換句話說, 寫網頁可以輕鬆得知 model 狀態變化, 得知變化時還可以輕鬆地改變任何相關的 UI 元件。 反觀寫圖形應用程式時, 離上述兩點相當地遙遠, 寫起來綁手綁腳的。或許是出於類似的需求, iOS 有提供 NSNotificationCenter 協助 observe 和 dispatch event。 於是, 透過 NSNotificationCenter 這個全域變數, 我們可以讓 model 透過 NSNotificationCenter 告知狀態變化, 或是寫一個 model delegate 做這件事。讓有需求的 UI 元件透過 NSNotificationCenter 觀察事件變化。 如此一來, 減輕不少實作負擔。不過如同 When to use Delegation, Notification, or Observation in iOS 所言, notification 過於彈性會造成一些問題。個人認為配合一些規範, 像是使用 header 檔定義 notification name 和 user info 的 key, 不要直接用 string, 應該是不錯的選擇。 備註: 為簡化起見, 上述關於網頁的連結是以 jQuery 為例。 漸漸覺得全域變數不是那麼糟的東西, 需要考量它的優缺點來使用, 而不是預設就想盡辦法避免全域變數。UIView …

GUI model 的介面設計心得

最近寫了稍微複雜一點的 iOS 介面, 多了一些心得。先前已寫過《非同步程式心得》包含 GUI 和 IO 的體會, 這篇則著重在 model 介面的設計。 如同《從需求出發理解背後技術的思考脈胳》的體會, 思考技術議題時不仿從需求切入, 比較容易掌握到必要項目, 需要在設計上做取捨時也有個依據。 先來看兩個生活中的例子: 電梯外側和內側有螢幕顯示電梯目前所在的樓層。 紅綠燈的秒數倒數。 這兩個例子裡, 不會因為多了螢幕顯示樓層或秒數而影響結果發生的時間, 但是會讓使用者明白現在發生什麼事, 比較不會失去耐心。相信體驗過沒提供樓層的電梯或沒秒數的紅綠燈的人, 應該更能體會沒有告知狀態變化的感覺。 回到程式開發的世界裡, model 時常會有非同步的操作, 比方說寫一個線上文書編輯器, 介面可以是 Web 或是 App UI, 但資料存在線上伺服器裡。 存檔的必要介面如下:void Document::Save(); void DocumentDelegate::OnSaveResult(bool ok); caller 產生一個 document, 將自己傳入作為 DocumentDelegate。呼叫 Save() 之後, 會觸發 callback OnSaveResult() 告知存檔是否成功。 乍看之下這樣的介面已滿足所有需求了, 使用者要求存檔, 介面也會回應存檔成功或失敗。但是, 若 Save() 和 OnSaveResult() 之間間隔過久, 使用者不知道剛才的操作是成功或失敗, 或是他沒有按到存檔鈕 (使用觸控面版特別容易有這樣的疑問), 甚至會懷疑應用程式是否當掉了。 或許我們會考慮在呼叫 Document::Save() 之後, 順便在 UI 上顯示訊息, 讓使用者明白已經進行 Save() 的操作。單就這例子來看沒有問題, 但是當 model 操作比較複雜時就有問題了。比方說, 後來支援了自動存檔, 有可能使用者沒有按「存檔」, 但 document 定期會執行 Save(), 介面無從反應現在是否正在存檔中。 反過來說, 若是介面改為:void Document::Save(); void DocumentDelegate::OnSaveResult(bool ok); void DocumentDelegate::OnSav…

iOS UIView 除錯

Refernece: Visual View Debugging | Cocoanetics 這篇實在是太實用了, 備忘一下文中提到的技巧。 取得 view 資訊NSLog(@"%@", view); 直接輸出 UIView 即可, 不用自己笨笨的輸出 frame 之類的資訊。 輸出 view 的巢狀結構NSLog(@"%@", [view recursiveDescription]); [UIView recursiveDescription] 是 private method, 不過 Objective-C 有 dynamic typing 的特性, 真的要呼叫的話, 系統也不會擋你, 只是編譯時有個 warning 而已。這功能相當實用, 可以確保相關的 view 是否有加進去, 了解 sub views 之間的順序 (後面的 view 會蓋掉前面的 view)。 在多個關鍵處輸出 recursive description 後, 才發覺我在某處不小心刪掉某個 view 沒加回去。若沒有這項功能, 執行後只知道沒出現這個 view, 到底是放錯位置、沒放進去或被覆蓋掉, 完全是一無所知啊。 畫出 view 的外框view.layer.borderColor = [UIColor redColor].CGColor; view.layer.borderWidth = 1.0; 使用上述程式的前置動作為: linking 加入 QuartzCore.framework 程式加上 #import <QuartzCore/QuartzCore.h>2014-07-23 更新[UIView recursiveDescription] 會顯示本身整層的結構, 若想看更上層的結構, 要往上存取 superview。但在 lldb 中打 po view.superview.superview 會失敗, 原因是 view.superview 傳回 id, 而 id 沒有型別, 無法直接呼叫 superview。所以要寫成 po ((UIView*)view.superview).superview。一但取得目標 UIView 的位置後 (例如 0x12345678), 再來就可以用 po [(UIView*)0x12…

High Performance Browser Networking ch5, ch6 心得

Chapter 5. Introduction to Wireless Networkshttp://chimera.labs.oreilly.com/books/1230000000545/ch05.html 影響傳輸頻寬的項目: C=BW×log2(1+S/N) C is the channel capacity, and is measured in bits per second BW is the available bandwidth, and is measured in hertz S is signal and N is noise, and are measured in watts 訊號的強弱就像聲音一樣: 若環境音很吵, 很難聽清楚對方說的話。 離愈遠愈不清楚。 頻段受法律影響, 不是想用那一段就可以用那一段。Chapter 6. Wi-Fihttp://chimera.labs.oreilly.com/books/1230000000545/ch06.html 802.11 (Wi-Fi) 基於 802.3 (Ethernet) 的修改而來的 Ethernet 的作法是 "先聽一聽, 確定沒人說話之後再說話"。若有人同時想說話, 各自隨機休息一段時間, 再重試一次。 Wi-Fi 的作法和 Ethernet 相似, 只是受到無線通訊硬體的限制, 送資料的時候無法偵測是否有人在說話, 所以要由接受者回應 ACK 得知是否傳送成功。 這個基於 Ethernet 而來的通訊協定只適用於低負擔的無線環境, 反之會很慘。 Wi-Fi 有重新傳輸 packet 的機制, TCP packet lost 比例通常不會因 physical layer 使用 Ehternet 或 Wi-Fi 而有差別, 但是使用 Wi-Fi 時很可能有較高且不同的延遲時間。 Wi-Fi 有分 2.4GHz 和 5GHz 兩個不同頻段區間。各區間有數個 channel, 若使用者使用不同 channel, 傳輸的訊號就不會打架, 這是提升傳輸品質的最好方法。這裡有 802.11b 不同 channel 對應到不同頻段的示意圖。使用 2.4GHz 的 Wi-Fi 協定時, 選用 1、6、11 最適當, 可減少和其它 channel 相衝。 5GHz 有比較多…

XCode 新增子專案編譯 static library 的方法

這種事沒記下來立即就忘了, 我是用 XCode 4.6。 新增子專案 在 Project Navigator 點專案 A 點左下角的 '+', 選擇加新專案, 選擇使用 static shared library, 完成剩下的步驟 點專案 A: Build Phases -> Target Dependencies -> 加入專案 B 的 static shared library 點專案 A: Build Phases -> Link Binary with Libraries: 加入專案 B。若專案 A 之上有專案 M, 這步要加在專案 M雜七雜八問題 找不到 header: 在 Build Settings 搜尋 header 會找到 Header Search Paths 和 User Header Search Paths, 記得填入正確的值。可以對一下編譯的原始訊息了解設下去轉換成什麼東西。這裡的路徑是相對於專案位置的。 連結失敗, 找不到 static shared library 內的 symbol: 確定 Link Binary with Libraries 裡面有加到這個 library, 還有有在正確的 project 內設 Link Binary with Libraries。 連結失敗, static shared library 內有 undefined symbol: 注意在 Link Binary with Libraries 裡和其它 libraries 的順序。同樣地, 對一下編譯的原始訊息會比較清楚情況。 static shared library 一直是紅字: google 一下有人說疑是 XCode bug, 在實體裝置上編譯過後, 就沒有紅字了。我試的結果確實如此。 找不到 C++ STL 的 symbol: 錯誤訊息像這樣: std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::__init(char const*, unsigned long)", referenced from:檢查 Build Settings -&…

hg-svn 速度極慢的原因

簡短說明使用 hg-svn 時, 底層需要操作 svn 的 python 模組。hg-svn 有封裝兩組不同的實作, 在 Ubuntu 下的套件分別是 python-subvertpy 和 python-subversion。記者要裝 python-subvertpy, 個人試用的感覺, 前者比後者快了百倍以上吧。 冗長苦水心得每次用 hg-svn 更新一堆檔案時 (大概數十個左右), 不管是 push 還是 pull 都相當地久, 有時等上一小時都不會好, 但用 strace 看, 持續有些動靜, 但是不知道在做什麼。 原本以為這是 hg 和 svn 溝通的極限, 今天問一下 cmd 平時用 git-svn 速度如何, 結果 cmd 說沒特別感到慢過, 當場試了一下, 果真迅速無比。差點想跳糟到 git-svn 陣營, 更何況安裝容易, sudo aptitude install git-svn 就結束了。 回家後想想不太甘心, 就用老方法在 hg-svn 原始碼內塞 print 找看看瓶頸卡在那裡 (慢到這種程度不太可能是 CPU bound, 不適合用 perf 之類的工具找)。 雖然有找到瓶頸, 但是找得過程中發覺 hg-svn 使用兩個不同的模組作為 svn 的接口, 一個是我原本用 svnwrap/svn_swig_wrapper.py, 對應到 Ubuntu 的套件為 python-subversion。另一個是 svnwrap/subvertpy_wrapper.py, 對應到的套件為 python-subvertpy, 而且 hg-svn 優先使用 python-subvertpy。 接著查 python-subvertpy:$ aptitude show python-subvertpy ... Description: Alternative Python bindings for Subversion Python bindings for the Subversion version control system. The aim of these bindings is to be fast, complete and to provide an API that feels native to Python programm…

計算 Linux 記憶體使用量

雖然直覺上會使用 top 或 free 觀看結果, 但是 top 或 free 顯示的資訊包含 kernel 使用的 cache 量, 不是「所有 process 使用的量」。因此, 我習慣看 htop 顯示的值。 經 Viller Hsiao 提醒, 才知道原來 htop 顯示的記憶體用量和 free 第二行的數值一樣, 都是讀 /proc/meminfo, 然後得出實際使用量 = MemTotal - MemFree - Buffers - Cached。若需要寫 script 記錄系統的 memory peak, 可用 free 的輸出, 畢竟 htop 沒有 batch mode 可用。以下是一個 free 顯示的例子:$ free total used free shared buffers cached Mem: 11418888 3247916 8170972 0 1274988 693072 -/+ buffers/cache: 1279856 10139032 Swap: 0 0 0 我之前一直誤會第二行的意思, 原來它的意思是「考慮第一行顯示的 buffers 和 cached 後, 重新計算 used 和 free 的值」。Viller Hsiao 翻了一下原始碼證實這件事。我自己也試著練習翻閱原始碼, 增加翻原始碼查資料的能力。 以下是查詢過程的一些記錄。 free找 free 原始碼的過程比較麻煩一點: $ apt-file search /usr/bin/free | grep "free$" procps: /usr/bin/free $ aptitude show procps ... Description: /proc file system utilities This package provides command line and full screen utilities for browsing procfs, a "pseudo" file system dynamically …

除錯小技巧: 在程式中直接中斷及偵測是否被 gdb 監控中

在程式中直接中斷依《Binary Hack》 #92 所言, 在 gdb 監控 process 的情況, 使用下列程式碼會進入中斷:raise(SIGTRAP); 對 x86 環境, 下列作法也許會更好, 少了一層 function call__asm__ __volatile__("int3"); Scott 有個 debugbreak 有處理 x86 以外的情況, 有需要時再來參考看看。 偵測是否被 gdb 監控中比起在程式中直接中斷, 我比較需要知道目前程式是否被 gdb 監控 (attach) 中。以下是兩個實用的情境: 原本已有 log 錯誤訊息, 在 log 極為嚴重錯誤訊息 (如 severity = FATAL) 時, 檢查看看是否有 gdb 在監控, 有的話不如順便進入中斷, 提前察看錯誤點。 寫 multi-thread 時, 有時會有特定 thread 負責特定的事, 像是 UI 或 IO。有時會監控這個 thread 是否正常運作。或是有網路連線時, 有時會想監控另一端連線是否仍然正常運作。其中一種監控 thread 或網路連線的方式是週期性察看有無更新資料, 超過一定時間沒更新, 就中止程序或關掉連線重連。在使用 gdb 除錯時, 有可能在中斷點待太久而觸發前述安全防護機制, 反而無法長時間除錯。這時自動關閉防護措施會比較方便。《Binary Hack》 #92 的作法利用設 gdb 會覆寫 SIGTRAP 的 signal handler 做檢查, 這個作法可以跨比較多平台, 但可用時機太窄。我偏好 Chromium 的作法, 直接讀 /proc/PID/status, 由 "TracerPid" 的值得知目前是否有 process 控控自己。程式執行中的任何時間都可用這方法得知正確的結果, 不過只適用於 Linux 和 Android。以下是參考 Chromium 實作稍作修改的版本:

High Performance Browser Networking ch4 Transport Layer Security (TLS) 筆記

原文: http://chimera.labs.oreilly.com/books/1230000000545/ch04.html TLS 1.0 相當於 SSL 3.0, 兩者沒有顯著的差異。由於 SSL 屬於 Netscape 的協定, IETF 另定了 TLS。現在我們用的 "SSL" 其實是指 TLS TLS 屬於 session layer 的協定, 在 application (例如 HTTP) 之下, transport (例如 TCP) 之上 TLS 三個要點: Encryption: 加密資料, 其他人無法竊聽 Authentication: 認證對方身份, 傳錯人的話, 加密也沒用了 Integrity: 確保資料沒有被修改。中間人無法得知資料內容, 但有可能竄改內容。多了 checksum 可以避免使用被改過的資料TLS handshake: 連 TCP handshake 在內要三次 round trip time (RTT)。若一次 RTT 要個 170ms, 這表示 0.5s 後才會開始傳第一筆資料。 TLS 多加的兩個 RTT 是: -> ClientHello <- ServerHello, Certificate, ServerHelloDone -> ClientKeyExChange, ChangeCipherSpec, Finished <- ChangeCipherSpec, FinishedTLS 的主要缺點是較高的 latency, CPU、記憶體和傳輸資料量到是沒差 (後述)。 TLS 主要的計算量在用 public/private key 加解密 symmetric key。整體的 CPU、記憶體和網路負擔都很小。 依 2010 Google 使用 Gmail 的發言: "... we had to deploy no additional machines and no special hardware. On our production frontend machines, SSL/TLS accounts for less than 1% of the CPU load, less than 10 KB of memory per connection …

vim-protodef: 從 C++ header 產生實作樣板的好幫手

C++ 寫一陣時間後, 覺得每次在標頭檔寫完宣告又要在實作檔裡重打一次類似的函式名稱, 有點麻煩。原本想說自己寫個 vim script 處理這件事, 轉念一想, 應該有人做過了。結果找到別人滿完整的實作: derekwyatt/vim-protodef 有影片有真相, 這裡有 demo: http://vimeo.com/4448265 安裝方式: 讀一下文件的 Dependencies 和 Setup 即可 作法滿聰明的, 用 ctags 讀標頭檔產生索引, 然後用 perl script 清理 ctags 產生的結果, 最後用 vim script 搜尋目標函式是否已出現在實作檔裡, 沒有的話就輸出到實作檔裡。日後有需要寫比較複雜的 vim script 時, 可以回頭來參考語法。

linux thread 與 signal

雖然是基礎知識, 一陣子沒用大概會忘記, 備忘一下重點: 在 multi-thread 情況下, signal() 的行為是未定義, 必須用 sigaction() 註冊 signal 用 kill 送 signal 給 process 後, process 內任何一個 thread 都有可能執行 signal handler。若不希望特定的 thread 收到 signal, 可用 pthread_sigmask() 改變特定 thread 的行為, 或是用 pthread_kill() 送出 signal 給指定的 thread SIGSEGV 會送給發生 segmentation fault 的 thread。所以有 core dump 的時候, gdb 可以正確找出那個 thread 造成 segfault 參考資料:http://www.linuxprogrammingblog.com/all-about-linux-signals?page=11 相關 man pages

和 Ubuntu 安裝套件相關的知識

先散亂地記在這邊, 慢慢補完: 1. 用 apt-get cache FILE 從檔案反查套件名稱 2. http://packages.ubuntu.com/ 找詢是否有這套件, 那一版開始有這套件 (Distribution 選 "any") 3. 套件分類 ( 參考資料 ) main: Free software, officially supported by Canonical universe: Free software, NOT supported by Canonical restricted: Non-free software officially supported by Canonical multiverse: Non-free software NOT supported by Canonical X 版本 (如 X = precise) 的 X-updates 表示 stable release update, 會有比較新一點的套件。預設 /etc/apt/sources.list 裡應該已有 X/X-updates 和 main/universe/restricted/multiverse 組合的八種來源。 4. 用 apt-cache policy PKG 了解安裝的優先順序 例如:$ apt-cache policy sqlite3 sqlite3: Installed: 3.7.9-2ubuntu1.1 Candidate: 3.7.9-2ubuntu1.1 Version table: *** 3.7.9-2ubuntu1.1 0 500 http://free.nchc.org.tw/ubuntu/ precise-updates/main amd64 Packages 100 /var/lib/dpkg/status 3.7.9-2ubuntu1 0 500 http://free.nchc.org.tw/ubuntu/ precise/main amd64 Packages

使用 vim script 自動開啟 C/C++ 程式的標頭檔或程式碼

先附上完整 script:" Open .h if it's a cpp file, and vice versa. function! OpenComplementFile() let f = expand('%') " (1) let suffix = matchstr(f, '\.\a\+$') let pattern = suffix . "$" if suffix == '.h' let suffixes = ['.cpp', '.cc', '.mm', '.m', '.h'] for suf in suffixes let target = substitute(f, pattern, suf, '') " (2) if filereadable(target) break endif endfor elseif suffix == '.cpp' || suffix == '.cc' || suffix == '.m' || suffix == '.mm' let target = substitute(f, pattern, '.h', '') if !filereadable(target) let tmp = target let target = substitute(tmp, '\v(.+)\..+', 'public/\1.h', '') " (3) if !filereadable(target) let target = substitute(tmp, '\v(.+)/(.+)\.(.+)', '\1/public/\2.h', '') endif …

使用 --start-group 和 --end-group 解決 circular dependencies

只是想備忘這篇: GCC: --start-group and --end-group command line options - Stack Overflow 關鍵的 gcc 指令:-Wl,--start-group -lmy_lib -lyour_lib -lhis_lib -Wl,--end-group -ltheir_lib gcc man page 說明 -Wl,X 傳遞參數 X 給 linker ld man page 說明 --start-group archives --end-group 這個作法的好處是不用理會 static library 在參數裡的順序 (預設是假設 A 依賴 B 時, A 要放在 B 前面), 壞處是會加長 linking 時間。 Btw, 遇到和 linking 相關的問題時, 記得要同時從 compiler 和 linker 的角度看問題。

用 mutt 作為 command line 寄信程式

預設的 mail 不方便夾帶附加檔, 備忘一下用 mutt 做這事的指令:mutt -s TITLE EMAIL -a ATTACHMENT < CONTENT

python BaseHTTPServer 速度緩慢的原因

在用 Bottle 的開發模式時, 發覺有時候速度會異常的慢。等到受不了以後, 按下 ctrl+c 看到以下的 backtrace:Exception happened during processing of request from ('xxx.xxx.xxx.xxx', 37515) Traceback (most recent call last): File "/usr/lib/python2.7/SocketServer.py", line 284, in _handle_request_noblock self.process_request(request, client_address) File "/usr/lib/python2.7/SocketServer.py", line 310, in process_request self.finish_request(request, client_address) File "/usr/lib/python2.7/SocketServer.py", line 323, in finish_request self.RequestHandlerClass(request, client_address, self) File "/usr/lib/python2.7/SocketServer.py", line 638, in __init__ self.handle() File "/usr/lib/python2.7/wsgiref/simple_server.py", line 121, in handle self.rfile, self.wfile, self.get_stderr(), self.get_environ() File "/usr/lib/python2.7/wsgiref/simple_server.py", line 85, in get_environ host = self.address_string() File "/usr/lib/python2.7/BaseHTTPServer.py", line 49…

C++ 執行後掛在 __cxa_pure_virtual

今天遇到一個神奇的 bug, 程式掛掉後, 從 backtrace 看到其中一個函式名稱為 __cxa_pure_virtual, 從字面上的意思來看, 我呼叫了一個 pure virtual function, 所以程式掛了。但是 compiler 應該能抓到在 C++ 內呼叫 pure virtual function (Btw, Objective C++ 則否), 沒道理在 runtime 遇到這情況。 google 了一陣子, 看到這篇說有可能是呼叫 dangling pointer 的 virtual function, "Pure Virtual Function Called": An Explanation 則解釋各種可能的觸發原因。最後抓到兇手, 確實是呼叫 dangling pointer 的 virtual function。 依文章的解釋, destructor 會改變 virtual method table pointer 指向的位置, 最後會指向 abstract base class 的 virtual table, 裡面的 virtual function 則指向某個表示 pure virtual function 的指標 (gcc 用__cxa_pure_virtual)。所以, 若刪除指標後剛好記憶體內容保持原樣, 執行後就會呼叫到 __cxa_pure_virtual。 依管理記憶體的方式, 刪除指標時可能會保留原狀, 也可能填入特殊值故意觸發 crash。呼叫 dangling 可能會有不同結果。

在 linux 和 iOS 上抓 memory leak 和 heap profiling 心得

問題描述首先要定義一下什麼是「memory leak」。它的正式定義是指「程式弄丟了未釋放空間的位置」, 之後無法使用這塊空間也無法釋放它。另一方面, 程式沒有弄丟位置, 但是配置的空間愈用愈大, 或是用完後沒有歸還, 導致記憶體不足。若要應付第一種情況, 可用 memory leak 作為關鍵字找相關討論; 第二種情況要用 memory profiler 或 heap profiler 了解記憶體用去那裡, ( 感謝 Scott 告知 )。 就我自己少量的經驗來說, 第二種比較棘手, 第一種通常是粗心, 找到後補上 free/delete 即可。但第二種可能和程式配置和運用記憶體的策略有關。我遇到的問題都是第二種。若程式有針對不同區塊自己管記憶體的話, 比較方便日後植入統計程式找出瓶頸。順道一提, 如同 TDD 的精神, 在設計程式架構時就考慮測試, 會讓程式更易測試、維護和除錯。在設計架構時就考慮除錯, 也會讓程式更易維護和除錯。 在 Linux 上找 memory leak 和 profile heap在 Linux 上試了兩套工具 mtracevalgrind。mtrace 滿簡單的, 可用來抓 C 的 memory leak, 但不適用於 C++, 原因是 mtrace 在 malloc/free 動手腳, 可以找出那一行呼叫 malloc 後沒呼叫 free。但是 C++ 的情況是 libstdc++ 呼叫 malloc, 而我們有興趣的是誰呼叫了 new。 後來改試 valgrind 效果還不錯。valgrind 是多個除錯工具的集合體, 我試了兩個工具: memcheck: 抓 memory leak 和記憶體讀寫錯誤。 massif: profile heap 的用量, 可看到不同時間 heap 由各函式配置的比例。使用 valgrind 的代價是程式會慢個 20 倍左右。但是用工具協助檢測問題, 99% 的情況會比自己憑空猜想來得準。還是有些耐性使用 valgrind 會比較快找到問題源頭。memcheck$ valgrind --tool=memcheck --leak-check=full PROG PROG_ARG ... 跑完後會輸出結果在 console。建議先有些耐心看完官網文件。對 memcheck 的報表有疑問的話…

比 soft link 更好用的 mount --bind

當目錄 a 所在的 partition 沒空間但又得放東西在 a 下面時, 通常會另建 partition, 然後 soft link 新 partition 下某個目錄過來, 比方說從新 partition 用 soft link 指向 a/b。 這個作法有個缺點, 若 a/b/ 下的程式想透過 ../ 存取 a 目錄下的東西, 比方說用 ../c 取得 a/c。在 soft link 的情況會找不到 c, 因為 b 其實在另一個 partition, b/.. 並不是 a。 這時可用 mount --bind 解, 作法很簡單, 就直接抄 man mount 的內容過來備忘:Since Linux 2.4.0 it is possible to remount part of the file hierarchy somewhere else. The call is mount --bind olddir newdir or shortoption mount -B olddir newdir or fstab entry is: /olddir /newdir none bind

和 C 前置處理器相關的小技巧

在 gdb 內查詢 macro 需要在編譯時加上 -g3, info macro 等指令才會有用。 參考資料: http://sourceware.org/gdb/onlinedocs/gdb/Macros.html 列出 include 的內容$ gcc -E a.c 編譯錯誤指出缺少某個 symbol 時, 可用這招確定到底有沒有引入目標的宣告。有時 #ifdef 太多, 看原始碼不易確認這件事。 列出 define 的內容 比方說若想知道 errno 111 代表什麼意思, 可這麼做:
$ echo "#include <errno.h>" | gcc -dM -E - | grep " 111$" #define ECONNREFUSED 111 再來 man errno 查詢 ECONNREFUSED 即可得到答案。
另外, 可用以下的方法得知系統預先 define 的東西
gcc -dM -E - < /dev/null 參考資料: http://stackoverflow.com/questions/2224334/gcc-dump-preprocessor-defines

python memory leak

由於 CPython 使用 reference counting 管理記憶體, reference count 到 0 時會立即釋放記憶體, 遇到 memory leak 時, 至少可以用 del 稍微自救一番。 import sys a = [] print sys.getrefcount(a) # 2 b = a print sys.getrefcount(a) # 3 del b print sys.getrefcount(a) # 2 如上所示, 可在程式中用 sys.getrefcount() 檢查 referece count 是否符合預期, 盡量在不用以後立即呼叫 del, 當 reference count 變為 0 時, 會提前釋放記憶體。 注意 python 的 while、for 沒有形成新的 scope, 有可能因此誤判一些物件的生命週期:for i in range(10): pass print i # 9, 並非 NameError 備註附上 CPython 2.7.3 Include/object.h 內關於減 reference 相關的程式:769 #define Py_DECREF(op) \ 770 do { \ 771 if (_Py_DEC_REFTOTAL _Py_REF_DEBUG_COMMA \ 772 --((PyObject*)(op))->ob_refcnt != 0) \ 773 _Py_CHECK_REFCNT(op) \ 774 else \ 775 _Py_Dealloc((PyObject *)(op)); \ 776 } while (0) 可看出在 reference count 為 0 時, 確實會呼叫 d…

Core Data lightweight migration

上份工作需要階段性處理大量資料, 當時有不少 database schema 與 data migration 的經驗, 雖然我不怎麼喜歡 Django 的 ORM, 不過配合 South 做 migration 到是滿方便的。 最近用 Core Data 不幸地在發佈測試程式後發覺需要增加欄位, 查了一下作法, 所幸 Core Data 有針對簡單的情況提供自動化 migration, 相當輕鬆, 簡記用法如下:Core Data Model Versioning and Data Migration Programming Guide: Model File Format and Versions: 學會增加 model version 和選擇目前使用 model version 的方法Core Data Model Versioning and Data Migration Programming Guide: Lightweight Migration: 學會在程式裡加上參數, 要求 Core Data 作 lightweight migration

說明瀏覽器運作相關的文章

備忘一些不錯的概念文章:WebKit for Developers - Paul Irish: 說明各家使用 WebKit 的瀏覽器到底共用的 WebKit 是什麼。另有附一份 slide 說明 "How WebKit Works", 不過 slide 講的內容就很少了。How browsers work, 2011 年的文章, 說明瀏覽器運作的每個步驟

iOS delegation vs. notification

初始使用 iOS 的 NSNotificationCenter 覺得頗不錯的。UI 物件需要從一個遙遠的物件裡取得資料改變的狀態, 當初沒考慮好這個需求, 不太方便傳遞資料。這時用 notification 解套就容易多了。但是隱約覺得 notification 的用法不太妙, 查到這篇 When to use Delegation, Notification, or Observation in iOS 分析得很清楚, 摘要重點如下。
使用 notification 的缺點 (或說特色) 是:
notification 是非同步呼叫, 發送 notification 和接受 notification 在不同的 runloop iteration 裡, 不易除錯。 notification 的發送和註冊採用字串做為 key, 無法在編譯時期找到錯誤。執行時才會察覺「好像沒呼叫到」 另一方面, delegation 透過定義使用端的介面 (iOS 術語為 protocol), 運作的特點為:
透過函式呼叫通知事件, 方便除錯 可在編譯時期確保提供服務者和用戶端有正確串在一起 除通知發生事件外, 可讓客戶提供實作供提供服務者使用 原文有提及更多項目, 不過對我來說最要緊的是「是否容易除錯」。從這個角度來看, 使用 delegation 遠比 notification 安全。所以可以的話, 盡量避免使用 notification 較好。

2013-03-03 更新

經 Sam 指正, 發覺 notification 預設是同步呼叫, 在 backtrace 裡可找到發出點和接收點。這樣就沒那麼難除錯了。重看一遍該篇文章, 作者提的除錯困難, 可能是指用太多 notification 混在一起的情況。

兩者的主要差別應該是介面的強度。介面規範強, 編譯時期自然抓到較多錯誤, 寫起來也比較麻煩一些。其它的小細節如「少了 observer 訊息、一對多 (多對一) 架構」等, 則是設計介面或使用時可補救的。

GUI 架構的考量

( 和 Scott 聊過後, 決定改寫《非同步程式新手心得: 為什麼要用單一 thread + task queue 的方式設計架構》的前半段。比較完整地描述 GUI 架構的入門心得。 ) GUI 需要低延遲的反應速度, 隨時能接收使用者輸入和並提供畫面回饋。這表示要在不互相阻檔的前提下應付三種工作: 更新畫面 偵測使用者輸入 處理使用者輸入 甚至第四種情況: 網路輸入 若是使用 multi-thread 處理上述情況 (比方說有三個 thread 分別專職處理前述三件工作), 必須留意 thread 之間有共享資料的情況。為了避免 race condition 必須使用 lock; 使用 lock 後又要擔心 dead lock。 使用 global lock 有可能成效不彰, 比方說更新畫面太久, 而沒有即時回饋使用者的輸入。另一方面, 切許多細小的 lock 處理各塊共享資料, 容易出錯造成 dead lock, 或誤以為有處理好卻仍存在 race condition。 反過來說, 若每個工作 (#1) 都切得很細, 執行時間不長, 但可能需要多個工作單元才能完成目標。這樣就能在一個 thread 內以類似 queue consumer 的方式執行全部的工作。有新的需求時, 放入工作到 queue 裡, 大致上可確保相依工作之間的執行順序 (#2)。由於全部工作都在同一 thread 執行, 自然不用擔心 dead lock 或 race condition。 於是可以看到, 像 Gtk、iOS、Android 的平台, 都將更新畫面、偵測事件和處理事件三者整合在 main thread 處理, 並且假設程式以 single thread 的方式執行 (#3)。 我不清楚其它平台的情況, iOS 有提供 NSURLRequest 應付常見的網路需求。讀寫資料和事件的 callback 也都在 main thread 內執行。 這麼一來, 框架開發者省事許多, 使用框架的開發者也可以安心地實作 callback。 備註#1 可想像每個工作是一個函式, 在 iOS 或 Android 上, 整合這種「工作 queue」的概念到基本框架裡, 用起來很直覺, 希望盡量避免開發者從 thread 的角度去處理事情。 #2 不行的時候只好加入狀態變數了解目前…

async 與 non-blocking IO

以前一直沒有分清楚 asynchronous 和 non-blocking 的差異, 寫久了以後才明白兩者是不同層次的東西。 asynchronous programming: 程式不是一條線走到底, 可能目前做到一個段落停下來, 「過一會兒」再繼續執行 non-blocking IO: 呼叫讀寫函式時, 不會等到完成才返回 通常要讀寫外部資料時 (一般檔案、網路、pipe 等) , 無法確定返回的時間, 這樣會阻擋程式的運作, 所以希望這個讀取 (或寫入) 不要等完成後才返回 (即「非同步」)。 若不希望此次呼叫有不確定時間的擔擱, 有兩種可能的作法: 開新 thread 或用既有的 thread 進行讀寫。總之, 停是停別人家的 thread, 和本 thread 無關 設定 file descriptor 為 O_NONBLOCK, 告訴 OS 這個 fd 為 non-blocking IO, 呼叫後要能立即返回 兩者作法的優缺點分析已超出本「隨手記」的範圍 (書本作者愛用的大絕), 需要特別注意的是, 設了 O_NONBLOCK 不表示呼叫 read/write 後就結束了。 man 2 read (或 man 2 write) 的 ERRORS 提到:EAGAIN The file descriptor fd refers to a file other than a socket and has been marked nonblocking (O_NONBLOCK), and the read would block.EAGAIN or EWOULDBLOCK The file descriptor fd refers to a socket and has been marked nonblocking (O_NONBLOCK), and the read would block. POSIX.1-2001 allows either error to be returned for this case, and does not require these constants to have the same value, so a portable application should check for both possibil…