因為在 porting 某支程式,所以用 MSDN 找 read(),想要確定該 include 哪個 header,link 哪個 library,結果 MSDN 的 read() 頁面,僅僅顯示這麼一行:

This POSIX function is deprecated beginning in Visual C++ 2005. Use the ISO C++ conformant _read instead.

這真是活見鬼了,什麼時候 ISO C++ 裡有以底線開頭的 API 了?更別提 _read() 根本不是 ISO C++/POSIX 的 function。

好吧,他說的是 conformant,所以不是在說 _read() 是 ISO C++ 的標準,而是說用 _read() 的方法呼叫 read(),才符合 ISO C++ 的一些規定。但即使如此,這個解釋,還是怎麼聽來就怎麼怪。

勉強為其解釋,我記得 Microsoft 平台有一種 calling convention 在處理 name decoration (name mangling) 時,不會在前面加底線。因為在 ANSI C/ISO C++ 裡,實際上唯一有規定的 name decoration,就是「在 function 名字前面加底線」。也許是因為用了某種 calling convention,導致最後編譯成執行碼時,沒有在前面加底線,因此為了要在 binary level 與 run-time library 對應,所以程式裡要加底線。

也就是說,按正常標準的 C/C++/POSIX 標準,read() 是沒有底線的,而是在 compile-time,因為是 C linkage 的關係,compiler 負責幫我們加底線。但是在 VC2005 裡,因為用了某種 calling convention 的關係,compiler 不會幫我們加底線,所以我們的程式,必須加底線。

總結就是:VC2005 的某種不標準作法,導致我們必須寫不標準的程式。

確實是活見鬼,Microsoft 總是喜歡搞出這樣的飛機出來。查一下 read()_read() 是怎麼在 Visual C++ 的 header 裡宣告的:

VC6 的 io.h 裡有:

_CRTIMP int __cdecl _read(int, void *, unsigned int);
_CRTIMP int __cdecl read(int, void *, unsigned int);

Visual C++ 2005 的 io.h 裡有 (太長故折行):

_CRTIMP __checkReturn \
int __cdecl _read(__in int _FileHandle, \
                  __out_bcount(_MaxCharCount) void * _DstBuf, \
                  __in unsigned int _MaxCharCount);
...
_CRT_NONSTDC_DEPRECATE(_read) _CRTIMP \
 int __cdecl read(int _FileHandle, \
                  __out_bcount(_MaxCharCount) void * _DstBuf, \
                  __in unsigned int _MaxCharCount);

都是用 __cdecl 宣告的。故再查了一下 MSDN 的這篇《Argument Passing and Naming Conventions》,發現常見的有這三種:

keyword parameter passing stack cleanup name-decoration
__cdecl right to left caller Underscore character (_) is prefixed to names, except when exporting __cdecl functions that use C linkage.
__stdcall right to left callee An underscore (_) is prefixed to the name. The name is followed by the at sign (@) followed by the number of bytes (in decimal) in the argument list. Therefore, the function declared as int func(int a, double b) is decorated as follows: _func@12.
__fastcall The first two arguments are passed by registers; others are passed right to left. callee At sign (@) is prefixed to names; an at sign followed by the number of bytes (in decimal) in the parameter list is suffixed to names.

至此,總算發現了「兇手」就是 __cdecl。對 __cdecl 的描述是這麼說的:

Underscore character (_) is prefixed to names, except when exporting __cdecl functions that use C linkage.

翻成中文就是,「在 function 名字前面加底線,除非這個 function 採用 C linkage」。因為 read() 採用 C linkage,再加上 __cdecl,在編譯時 compiler 就不會幫我們加底線。是故,「為了符合 ISO C++ 規定」[1],我們的程式裡,必須用不標準的寫法,為 Microsoft奇怪的作法買單。

這也說明了,為甚麼許多的 POSIX 函式,在 VC 裡總要在前面加底線。這種情形不是 VS2005 問世後才有,至少 VC6 時代就有一大堆。這種搞法,實在是亂來。

再罵一次,這真是活見鬼了。

2009-02-12 更新:在丸子·酱的這篇《Cross-platform Development in C/C++ (3)》裡,有提到一串討論,與此相關,P.J. Plauger 亦有參與其中:《Visual Studio and ISO C++ Names》。這一串我還在看,消化完畢後再來報告。


  1. 或者更明白地說,「為了要讓 VS2005 編譯出來的程式,能夠在任何在 binary level 只有提供 _read() 之 run-time 環境(即所有版本的 windows)上執行」。