在 C/C++ 裡 debug 的時候,__FILE__ 和 __LINE__ 很好用,可以幫我們將錯誤發生的地點指出來。可是,因為 __FILE__ 對應的型別是 const char* (__LINE__ 對應的型別是 size_t),在使用 wide character I/O 的時候,便不能用。就算像 L"some string" 一樣,前面加一個 L 也不行,因為這樣子就變成了 L__FILE__,是另外一個 symbol 了。空一格也不行,因為 L"some string" 的 L 必須與引號直接連接。 還好,使用 ## 這個 preprocessor operator 可以解決這個問題:
#define WIDEN_(x) L##x#define WIDEN(x) WIDEN_(x)#define __WFILE__ WIDEN(__FILE__)
這樣子我們就可以依據 output 的環境,選擇 __FILE__ 或 __WFILE__ 來使用了:
void fout(ostream& os){ [...]
抱歉,在標題裡用了注音符號了,沒辦法,這個台語不曉得怎麼用國語表達。
貓部大概是無意間逛到了我的 Blog,留了個言,提到:
而事實上, 花很多心思去榨出那5%, 10%的效能, 花的時間力氣可能遠超過說服老闆用好一點的hardware/software, 這是很悲哀的. 有時不如花這些時間去跟老闆social一下, 對前途還比較有幫助, 哈….工作這麼多年有感.
之前也有類似的經驗。因為我們的辨識引擎還不夠快,所以資深同事打算用 SIMD 靠硬體架構拼效能。我很不以為然,不是因為這樣就會與硬體相依,而是因為在現在的 CPU 架構裡,pipeline、prediction 一大堆,cache 機制、大小也各有巧妙,我才不相信自己搞可以搞的好。
果不其然,在效能瓶頸處改用組語寫 SIMD,結果讓程式的整體速度更慢了。所以我就推薦改用 Intel C/C++ Compiler 試試,在網頁上申請之後,可以有一個月的試用期。如我所料,要靠硬體拼效能,手算是比不上 compiler 算的。換 compiler 帶來的,是 10% 的效能增進,執行速度比 MSVC++6 編譯出來的執行檔,還要少了 10% 的執行時間。
Intel C/C++ Compiler 才一、兩萬台幣,可以永久使用,加買 Support Services Renewal 的話,期限內還可以免費升級。比起 case-by-case 讓工程師花昂貴的時間ㄍㄧㄣ效能,還不一定能夠成功,要來得划算多了。
今天搞了一個下午,結論就是這個教訓:小心繼承 iostream 的邊際效應 (side effect)。白話一點,就是不要在與 I/O 不相干的地方偷懶對 C++ iostream 使用繼承。
去年年中寫了一個簡單的小 database wrapper 程式庫,一樣是因為不方便使用四處可見的既有 library,所以得自己搞。裡面有一個 class 叫做 Statement,我希望它的用法就跟 cout 一樣,因為數字在 SQL 指令裡,一樣是以字串的形式呈現,另外,這樣也有機會可以依據傳入的型別的不同,自動決定要不要加 SQL 引號,以及要不要 escape 掉插入內容的引號。既然用法就像 cout 一樣,那就乾脆繼承自 ::std::ostream,然後一堆程式都不必寫了。
結果今天在用 Statement 的時候,要把一個 time_t 的值插入 SQL 裡,因為應該也必須,我有在程式一開始的時候設定好 locale 為正體中文,結果要插入的 1423912421 就變成了 "1,423,912,421",多了三個逗號,產生了錯誤的 SQL 指令。很明顯地,因為偷懶繼承 ::std::ostream 的關係,現在必須承受所帶來的 side effect 的苦果。
更糟糕的是,C++ 的 locale/iostream,大概是除了 STL 之外,另外一個 compiler vendor 很容易沒有做到 standard [...]
在 Standard C 裡頭,僅規定了我們可以利用 setlocale() 函式,來查詢或設定 C run-time 所用的,全域且唯一的 locale。然而,standard C 並沒有規定,傳進 setlocale() 的第二個參數,locale id,的格式與規定,而是交由各平台 (OS) 自訂。
所以問題來了,假若我們需要在程式裡 hard code 這個 locale id,以便傳給 setlocale(),那就會產生 portability 的問題。
解決辦法可以是,我們預先準備一張表格,訂定一套虛擬的 locale id 表格,在 compile-time 或 run-time 轉換成目標平台所用的 locale id。在 compile-time 我們可以利用 pre-processor 的 #define 來編寫這個表格,在 run-time 則建立 structure,linear-search 查表即可[1]。這樣子,我們就可以把 portability 問題,侷限在這個表格裡,且一併可達到 code-reuse 的目標。
因此,問題就轉換成了,我們怎樣建立與維護這個表格?這個表格不必太完整,但至少會用到的平台與 locale,都應該在裡面有資料。
今天就是碰到這個麻煩的問題。原本我僅為這個表,建立了正體中文,Windows、Linux/FreeBSD 的資料,因為大約也只會用到這麼多。更因為平常根本就是在正體中文裡頭跑,setlocale() 時直接就傳長度為零的字串 [...]
有時候,為了效率,不得不用比較糟糕的程式寫法,好比說,全域變數 (global variable) 的使用。不過,真的是這樣子嗎?多使用全域變數,真的可以讓程式的執行效率變高嗎?
一般來說,CPU 會有多種定址模式 (addressing mode),可以給 array、pointer 用,難道這些定址模式,沒有增進 array、pointer 使用效率的功能嗎?當效率至上的時候,如果使用全域變數,可以增進效能很多的時候,當然我們可以考慮犧牲維護性 (mantainability)、可讀性 (readability);但如果效能只有增進一點點,那全域變數的使用,就會變成值得商榷思考,慎重考慮的選項了。
所以,我們最好做個實驗,看看到底效能的差異有多少。測試程式如下:
#include <stdio.h>#include <stdlib.h>
#define INDIRECT_ACCESS 1
#define VAR_NUM (1024 * 32)#define LOOP_NUM1 (1024)#define LOOP_NUM2 (1024)
int global_var[VAR_NUM];
struct struct_var_t{ int v[VAR_NUM];};
struct struct_var_t* struct_var;
#if INDIRECT_ACCESS# define ACCESS_VAR struct_var->v#else# define ACCESS_VAR global_var#endif
int main(){ int i; int [...]
最近在讀《Imperfect C++ / Practical Solutions for Real-Life Programming》這本書,是 Matthew Wilson 寫的,有常在讀 C/C++ Users Journal 的人,應該對他不陌生。不過,開始上班後,自己可以用來讀書的大塊時間變少了,所以到現在才剛把第一章唸完。
第一章主要是在講 Design by Contract,在 C++ 裡,其所需之相關技術,最重要的大概就屬 static assertion 與 type traits 了。要實作這兩種技術,最好的 reference design 當屬 boost 裡的 static_assert 與 type_traits 這兩個 libraries。
由於不方便直接採用 boost,故必須自己實作一個來用,而我一直到了最近幾天,才真的開始動手。遲到最近的主要原因是因為,type traits 真的很難寫,必須對各個 compiler 的特性與行為,有深入地瞭解才行。有興趣的人,可以去稍微 trace 一下 boost 是怎麼實作 type_traits,便可知其難度。
這本書的第一章裡,最讓我覺得值得一提的是,最後面的最後一段話:
It's worth noting that both the invalid index [...]
剛同事跑來問我 wstring 的問題,一看,哈!碰過了。問題是這樣子的,在 GCC 2.9x 上想要使用 wstring 卻無法使用,compile 時會出 error。原因有二:
第一個原因是,至少在 FreeBSD 4 以及我同事用的某一版 Linux 上,GCC 2.9x 沒有為我們預先 typedef std::basic_string<wchar_t> 為 std::wstring,因此無 wstring 可用。不過,我們可以自行 typedef 如下以解決這個問題:
#if (defined(__FreeBSD__) && defined(__GNUC__) && (__GNUC__ == 2))
# include <string>
namespace std {
typedef std::basic_string<wchar_t> wstring;
[...]
基本上,我是用 FreeBSD 的,而 FreeBSD 本就已經有內建一個 indent(1)。不過可惜的是,正如其 man-page 的 BUGS 這節所述:"The indent utility has even more switches than ls(1)." 其複雜簡潔的設定,使其設定檔 ~/.indent.pro 如天書一般難解。因此,衝著 gindent 的 ~/.indent.pro 可以使用 C/C++/Java 註解的特性,我選擇改用 gindent。
Indent 的設定,正代表著設定者對於 C/C++ 程式的排版風格的喜好。為了調整 gindent 的設定檔至我所喜好的模樣,當年亦花了不少時間作實驗。看了 s88 兄的《gnu’s indent》後,我亦有感而發,拿出我的 ~/.indent.pro 來陷醜:
// ---------------------------------------------------------------------// gindent options// ---------------------------------------------------------------------
// ---------------------------------------------------------------------// -bad, --blank-lines-after-declarations// Force blank lines after the declarations.// See BLANK LINES.-bad
// [...]
我都是這樣子 declare structure 的,當然,這樣子的 structure 必須要是單純的struct (ie. POD),不能有 member function 之類 members 存在:
#ifndef __cplusplus
typedef struct LinkList LinkList;
#endif /* __cplusplus */
struct LinkList
{
/* ... */
LinkList* next;
};
這樣一來,我們就可以像 C++ 一樣直接使用 type name 來 declare instances。幾個重點:
其實 C++ 也可以 struct Foo 或是 Foo 混著用來 declare instances,不過這樣總是難看了些,所以用上面那招,統一使用 C++ 的方法,包括在 structure 裡面的 member 在內。
在 C/C++ [...]
從這邊輾轉看到這邊。 然後我就到 GCC 官方網站看 ChangeLog。
比較令我感興趣的有:
GCC now generates location lists by default when compiling with debug info and optimization. (disabled by -fno-var-tracking)
#pragma pack() semantics have been brought closer to those used by other compilers. This also applies to C++.
ELF visibility attributes can now be applied to a class type, so that it affects every member [...]