今天下午在寫程式的時候被一個奇怪的問題給卡住,我寫的程式去呼叫 CreateProcess() 但一直跳出下面的錯誤訊息:
原來問題出在我使用 Windows 的 CreateProcess() 這個 api 的方法錯誤,先來偷看一下 MSDN 的函式原型:
BOOL WINAPI CreateProcess( __in_opt LPCTSTR lpApplicationName, __inout_opt LPTSTR lpCommandLine, __in_opt LPSECURITY_ATTRIBUTES lpProcessAttributes, __in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes, __in BOOL bInheritHandles, __in DWORD dwCreationFlags, __in_opt LPVOID lpEnvironment, __in_opt LPCTSTR lpCurrentDirectory, __in LPSTARTUPINFO lpStartupInfo, __out LPPROCESS_INFORMATION lpProcessInformation );
其實問題的癥結很簡單,就出在第二個參數身上,為什麼 lpCommandLine 的型別是 LPTSTR 而不是 LPCTSTR 呢?理由很簡單,因為系統會去更改這個參數,所以 MSDN 也用了 __inout_opt 來修飾這個參數,因此我們不能傳一個 read only 的記憶體區塊到這個參數來。引用一下 MSDN 的說明:
The Unicode version of this function, CreateProcessW, can modify the contents of this string. Therefore, this parameter cannot be a pointer to read-only memory (such as a const variable or a literal string). If this parameter is a constant string, the function may cause an access violation.
有趣吧?!只有 Unicode 版本的 CreateProcess 會修改這個參數,所以下面的程式碼可以 work:
wchar_t cmd[ 100 ] = L"notepad D:\\mt.txt"; CreateProcessW( NULL, cmd, NULL, NULL, false, 0, NULL, NULL, &si, &pi ); CreateProcessA( NULL, "notepad D:\\mt.txt", NULL, NULL, false, 0, NULL, NULL, &si, &pi );
但下面的程式碼是不能 work:
CreateProcessW( NULL, L"notepad D:\\mt.txt", NULL, NULL, false, 0, NULL, NULL, &si, &pi );
是不是有點不 consistent 呢?
此外,眼尖的人可能會發現為什麼一個 LPTSTR (即TCHAR*) 型別可以接受一個型別為 const TCHAR array 呢?C++ standard 2.13.4 不是這樣說的嗎?
A string literal is a sequence of characters (as defined in 2.13.2) surrounded by double quotes, optionally beginning with the letter L, as in "..."or L"...". A string literal that does not begin with L is an ordinary string literal, also referred to as a narrow string literal. An ordinary string literal has type “array of n const char” and static storage duration (3.7), where n is the size of the string as defined below, and is initialized with the given characters. A string literal that begins with L, such as L"asdf", is a wide string literal. A wide string literal has type “array of n const wchar_t” and has static storage duration, where n is the size of the string as defined below, and is initialized with the given characters.
怎麼 VC++ 連個 warning 都不給呢?這是因為 C++ 為了相容於 C 所做出的讓步,來看一下 4.2 Array-to-pointer conversion 的描述:
A string literal (2.13.4) that is not a wide string literal can be converted to an rvalue of type “pointer to char”; a wide string literal can be converted to an rvalue of type “pointer to wchar_t”. In either case, the result is a pointer to the first element of the array. This conversion is considered only when there is an explicit appropriate pointer target type, and not when there is a general need to convert from an lvalue to an rvalue.
因此,比較好的習慣是:總是用 const char/wchar_t* 去指向一塊 literal string。Scott Meyer 不就說了嗎?
Use const whenever possible
: )
#include <iostream> #include <typeinfo> using namespace std; void foo( char* msg ) { cout << "[foo( char* msg )] " << msg << endl; } void foo( const char* msg ) { cout << "[foo( const char* msg )] " << msg << endl; } template<typename T> void printType( T* x ) { cout << "type of T: " << typeid( T ).name() << endl; } void badCall() { throw "Exception"; } int main() { foo( "Hello World" ); printType( "Hello World" ); try { badCall(); } catch ( const char* msg ) { cerr << "[const char* msg] " << msg << endl; } catch ( char* msg ) { cerr << "[char* msg] " << msg << endl; } return 0; }
我可沒說上面的 code 可以順利 compile 唷~