std::string 轉換成 char*

** 這篇的程式沒有實際編譯執行過,不保證沒有錯誤 **

很多時候我們得在 C++ 使用 C 語言寫的函式庫,
這時候 std::string 有幾種不同的方法來轉為 char*:

最安全也最常見的用法,是呼叫 std::string.c_str()。
這個函式會回傳一個標準的 char array,但是是唯讀的。

如果我們要讓 char array 可以寫入呢?
那就要先將字串複製到可寫入的 buffer:

變得麻煩了,對吧?

有沒有什麼方法可以直接更新到 string 而不用複製來複製去的呢?
std::string.data() 可以用來做這件事。
它回傳的是 string 內部用來儲存字串的記憶體區塊,所以修改值的話就會反應到 string 去。
不過舊的 C++ 標準並沒有強迫內部的記憶體區塊是連續的,所以這是有一點風險的。
另外,內部的記憶體也不保證會有 NULL 結尾,使用時一定要搭配長度的限制。

但風險也沒有那麼大,Herb Sutter 指出他目前所知的 std::string 實作,都是連續且 NULL 結尾的[1]。
所以用起來還算可以放心。

另外還有一個方式,就是使用陣列運算子。
因為 std::string[] 會回傳單一字元的參考,若對此參考取指標,就可以得到一個能用來修改內容的指標。
然而若對空字串取第一個字元 (empty_string[0]) 的話,不保證能夠得到 ‘\0’。
因此還是建議使用 std::string.data()。

在參考資料[2]裡面有很詳細的解說。基本上 C++11 解決了很多這類不確定的混亂情況,若是允許的話還是用新的標準編譯吧。

參考資料:
[1] http://herbsutter.com/2008/04/07/cringe-not-vectors-are-guaranteed-to-be-contiguous/
[2] http://stackoverflow.com/questions/347949/how-to-convert-a-stdstring-to-const-char-or-char

Design Pattern — Data Access Object

DAO(Data Access Object) 應該是最基礎且最廣泛運用的一個設計模式了。其適用的情境為:

  • 程式需要利用別的媒介來儲存或取得資料
  • 程式需要能移植到其它儲存媒介
  • 各個儲存媒介之間使用的存取方式不同
  • 希望程式不會受到儲存媒介的變換而修改

DAO 通常看到的範例都會與另外一個物件搭配使用,稱之為 Data Transfer Object 。但是這兩者之間並不一定要綁在一起用。DAO 的重點在提供一個統一的資料存取方式,而回傳的資料可以用 DTO 表示,但也可以用其它方式(例如陣列)來實作。這邊以一個會員系統為例,如何以 DAO 模式來提供會員相關資料的存取。

首先需要設計一個 Interface 來提供資料的存取,這邊為了簡化程式數量,還是按照常見作法搭配 DTO 來使用:

有了這個 Interface 之後,我們就可以實作兩種 DAO 分別對應到記憶體與資料庫這兩種不同的儲存媒介:

因為偷懶的關係所以這兩個 DAO 長的根本一模一樣。但是實際應用的時候 MemoryUserDAO 應該只會帶有一些陣列操作之類的程式碼,而 MySQLUserDAO 裡面可能會有 PDO 及 SQL 語法,或是利用 ORM 來存取資料。

使用的時候我們就可以不用管 DAO 實際對應的媒介,只需要呼叫其界面提供的方法就可以了:

另外,當需要支援更多種類的儲存媒介,你可以很容易的實作更多的 DAO 來處理。而且完全不用修改現有的程式碼。這同時也提供了程式良好的可測性,我們只需要實作一個 DAO 來控制回傳的資料,就能測試各種資料組合的單元測試了。

總結一下優點:

  • 將特定媒介的 API 使用方式隔離在 DAO 內,易於維護
  • 易於新增或抽換儲存媒介
  • 降低程式的複雜度
  • 提昇可測性

參考資料:
http://www.oracle.com/technetwork/java/dataaccessobject-138824.html
http://www.adam-bien.com/roller/abien/entry/value_object_vs_data_transfer
http://martinfowler.com/eaaCatalog/dataTransferObject.html

用 PHP 做檔案下載功能時遇到的記憶體問題

一般來說,為了避免使用者上傳了什麼奇奇怪怪的檔案被執行,會將上傳的檔案放在網頁根目錄以外的地方,再利用 PHP 將檔案內容輸出給 Client。

最簡單的方法是用 readfile 這個函式直接將檔案內容輸出,然而如果允許上傳的檔案比較大的話, readfile 會一次將檔案讀到記憶體內,造成記憶體用量超出上限的問題。

這個不能單純將記憶體上限提高來解決,不然只要同時幾個人下載檔案就會造成伺服器記憶體用盡。我想目前最簡單的方法是利用 stream 的功能來做:

這樣子就可以了,經測試下載數百 MB 的檔案也不會直接造成 PHP 錯誤。

GDBM 讀取資料失敗

GDBM 就是 GNU dbm,是一個輕量的 key/value 資料庫。
今天在使用時,發生了開啟檔案失敗的情況,錯誤訊息:File seek error
原因是 GDBM 它在 32bit 與 64bit 的環境下,產生的檔案格式不相容。
也沒什麼好的解決辦法,只能將資料都倒出來再用對應的版本建立資料庫。
若要在 64bit 的環境下編譯成 32bit 來執行,可使用 gcc -m32 來指定編譯 32bit 版本。

使用 Perl 搜尋字串, 執行運算並取代

有時候想要對文字檔的內容做運算後再取代原本的文字,本來是想弄個小程式來處理的,寫著寫著發現根本沒必要。

一開始是這樣子寫的:

這樣子就能用類似 sed 一樣的方式處理文字,例如把秒數換算成分鐘:
script.pl ‘s#second=(\d+)#”minutes=”.int($1/60)#e’ file.txt

弄完以後看著這小程式感覺很眼熟,好像在書上看過?Perl 不是有參數可以做類似的事情嗎?

最後用命令列達成一樣的目的:
perl -p -e ‘s#second=(\d+)#”minutes=”.int($1/60)#e’ file.txt

這次印象深刻了,希望下次要用到時能想起來 xD

Android Debug 時追蹤 SDK Source Code

不管是除錯還是學習,能夠看到完整的原始碼總是比較方便。Android 本身是開放原始碼,所以可以自行下載後再 attach source code。
自 4.0 以後,Android SDK Manager 也提供了 Sources for Android SDK 的選項,可以直接勾選安裝。

ADT 安裝好後,如果你嘗試追蹤一個 Android Framework 的類別,會看到類似如下的畫面:
source not found

這時,先確認有安裝 SDK 原始碼:
install Sources for Android SDK

然後,到專案裡面的 Android SDK 按右鍵,選擇 Properties:
android sdk properties

在 External location 選擇 SDK 路徑底下的 sources/android-[你要的版本]
set sdk source folder

這時隨便挑一個類別,按右鍵選擇 Open Declaration
Open Declaration

接著應該就會顯示出這個類別的原始碼了:
display the source code

在 Debug 時,若是 Trace 進入了 SDK 原始碼,卻發生了跑錯行號的現象,那麼有兩點需要確認:

  1. 你選擇的原始碼版本有對應到目前 Debug 執行的 Android 版本,例如 Debug 的環境是 Android 4.3.1,那就要使用 android-18 的原始碼。
  2. 用來 Debug 的機器最好是官方提供的模擬器,若在實體裝置上執行,那裝置上安裝的版本不一定是官方版本。我自己在 Nexus 7 上面測試倒是都能對的上就是了…

Android Studio 越來越接近正式發行,這個時候寫 ADT 的筆記好像沒什麼價值了 (?

SQLite 的效能

在考慮要使用 SQLite 還是其它資料庫的時候看到了這篇:http://stackoverflow.com/questions/14217249/sqlite-and-concurrency
裡面提到 SQLite 的效能最高可以達到每秒五萬筆 INSERT,但會因硬碟的性能不同而有差異。而我要使用的環境別說每秒上萬筆,一秒有上百次操作就很了不起了。

於是寫了簡單的測試,看看在我機器上跑得怎麼樣:

本來想說每秒五萬筆的話,那我得弄個十萬筆才能執行超過一秒,結果沒想到這一跑就超過了半個小時還沒跑完。平均下來每秒只執行了 38 筆的操作。果然夢想與現實的差距就是這麼大嗎?

不可能吧。於是開始檢討差距這麼大的原因,第一個想到的是硬碟速度的問題,也確實在執行期間硬碟幾乎是一直忙碌的狀態。但我這已經是固態硬碟了,怎麼樣也不會這麼難看吧。而且這已經是少到不能再少的資料了,實際運用上一定會更多資料量的。

後來在這篇文章裡看到了 nosync 這個關鍵字:http://www.sqlite.org/speed.html
進而找到了這個說明:http://www.sqlite.org/pragma.html#pragma_synchronous

所以就開始了第二版的測試:

只加了一行,但這次只花了不到八秒就跑完了。算一算這樣每秒就有上萬筆的操作,雖然跟五萬筆還是差了一些,但應該是很夠用了。

ZendFramework 2 AbstractRestfulController

Zend Framework 2 有提供一個 AbstractRestfulController 用來實作 REST API。不過按照慣例,Zend Freamework 的說明文件都只是簡單帶過而已,真正的用法與規則還得去閱讀程式碼。

使用方法,就只要繼承 AbstractRestfulController 即可:

在 Router 那邊,記得不要設置 action:

之後,就會自動對應到 Controller 的 Method:

  • GET => getList()
  • GET + id => get($id)
  • POST => create($data)
  • PUT => replaceList($data)
  • PUT + id => update($id, $data)
  • DELETE => deleteList()
  • DELETE + id => delete($id)
  • HEAD => head($id)
  • OPTIONS => options()
  • PATCH => patchList($data)
  • PATCH + id => patch($id, $data)

因此,一個支援 GET 與 POST 的 Controller 大概長這樣:

其它沒有實作的部份,AbstractRestfulController 會幫你回應 405 Method Not Allowed 。

另外,identifier 的名稱,預設是用 “id”,為了可讀性,你也可以自訂 identifier,下面我們改用 “name” 作為 identifier:

相對應的 Router 也需要把 id 改為 name:

AbstractRestfulController 的用法大致上就這樣。

PHP include 的運作方式

PHP 的 include 暗藏了一些潛規則,如果沒搞懂的話,遇到問題會以為見鬼了。所以這篇主要就在研究 PHP 的 include 運作方式。

首先,我們從官方文件可以看到 include 會區分為兩種模式,一種是 include 檔案,另一種是 include 路徑。只要是絕對路徑,或是相對路徑,就是以 include 路徑來運作。這裡比較容易搞混的是相對路徑的部份,因為 PHP 只把 “.” 或 “..” 開頭的作為相對路徑看待。

因此 include “foo/bar.php” 是屬於 include 檔案,而 include “./foo/bar.php” 則是 include 路徑。

在 include 檔案時,會先從 include_path 開始搜尋,再來是搜尋此 PHP 程式所在的資料夾(the calling script’s own directory),最後是搜尋 current working directory(CWD)。

而在 include 路徑時,則只會從 CWD 去搜尋。

為了驗證,我設置了這樣的環境來作測試:

其中 lib{xx,yy,zz}.php 的內容都是一樣的:

include_priority.php 是將要執行的主程式,分別將 include_path 以及 CWD 設置到 inc_path 與 cwd 資料夾,這樣就能分辨優先順序,其內容是:

最終結果是這樣子:

由結果來看,符合文件中所描述的運作方式。

接著來驗證第二種情況,如果 a.php include b.php 且 b.php 又 include c.php 的話,那麼在排除 include_path 與 CWD 的情況下,會如何運作?
環境設置:

root.php 是主程式,內容為:

sub.php 的內容:

libxx.php 的內容與前一個實驗相同。
最後的輸出結果是:

由此可見程式所在的資料夾(非CWD)是會隨著 include 所在的檔案而變動的。換句話說,就是以這一行 include 語法所在的檔案的位置為主。

綜合以上的內容,就可以解釋下面這個看到鬼的例子了:

在 root.php 裡面 include 了 sub.php 檔案:

然後在 sub.php 裡面又分別 include libxx.php 與 tools.php 兩個檔案:

因為 include “library/libxx.php” 成功了,於是以為相對路徑是由 sub.php 所在位置來計算,因此又下了 include “../tools.php” 這樣的指令,結果死的不明不白。

其實 libxx.php 是以檔案的方式去 include 的,所以會搜尋三個地方:include_path,sub.php 所在的位置,以及 CWD。而 sub.php 所在的位置剛好就可以找到 library/libxx.php 這檔案,所以 include 成功。

在 include tools.php 時則是因為它以 “..” 開頭,所以只會從 CWD 來計算相對路徑找檔案。而此時的 CWD 是在 root.php 那邊,自然就找不到東西了。

C語言網路相關的標頭檔

從網路上找資訊, 最怕的就是找到舊的或錯誤的東西
今兒個找了個範例, 結果遇到無法編譯的問題, 就是因為用的是舊方法

問題主要出在 #include <linux/ip.h> 然後就跳出一堆錯誤:

拿這些訊息去 google 還查不出什麼來, 都是些老舊的文章
後來跑去翻自己以前寫的程式來比對, 才找到真正問題

原因是 linux/ 底下的東西本身是給 kernel 內部使用的
因此開發一般 userland 的程式不應該使用這些
較新的 linux 版本甚至把這些標頭檔也拿掉了

比較正確的作法, 是採用 netinet/ 底下的標頭檔
這是由 POSIX 所規範的標頭檔, 所以用這個開發出來也比較具有移植性
改成使用 netinet/ip.h 以後, 上面遇到的錯誤也就消失了