Re: [心得] 空指標常數(null pointer constant)

作者: tinlans ( )   2016-04-28 14:19:03
※ 引述《wtchen (沒有存在感的人)》之銘言:
: 以下是板工的小小心得,準備補充13誡用。
: 若是有不對的地方,請各位鞭小力點 /(_ _)\
: (板工很懶得上色,以後會放一份在板工新blog)
你已經懂得查資料和規格書了,所以這次回文就不提規格書。
我把回文重點放在其它地方。
: ### 在C的情況(注意:C++不適用,C++的場合請看下一節) ###
: 根據C11 standard 6.3.2.3 裡對空指標常數 (null pointer constant)的定義:
: An integer constant expression with the value 0, or such an expression cast
: to type void *, is called a null pointer constant.
: 也就是說,(void*)0 是空指標常數(null pointer constant),
: 而(int *)0 是型別為整數指標的空指標(null pointer),這兩者是不一樣的。
: 比較明顯的分別是,空指標常數可以被設成任何型別指標的左值,
: 而空指標只能被設成同樣型別指標的左值。
: 以下是一個比較清楚的例子:
: int *a = 0; // 沒問題,空指標常數
: int *b = (int*) 0; // 沒問題,左值是同型別的空指標
: int *c = (void*) 0; // C沒問題,左值是空指標常數,不過C++會掛
: int *d = (long*) 0; // 爆了,左值跟右值不同型態
上面這行在 C 不會爆炸,這也是 C 之所以被 C++ 爸爸討厭的原因之一。
C 本身對這種不相容指標是相當寬容的,只會給你 warning 而已。
clang 3.4.1:
warning: incompatible pointer types initializing 'int *' with an expression
of type 'long *'
gcc 5.1.0:
warning: initialization from incompatible pointer type
這種警告訊息就算不打開 -Wall 都一樣會跳出來,目的是提醒這寫法可能有問題。
在開發階段使用 -Wall -Werror 將警告訊息全部修掉,是軟體工程師該有的態度。
因為 -Werror 會將 warning 變成 error,因此每個 warning 都逃不掉。
只是因為 warning 這東西很看 compiler 跟版本,所以 release 出去會拿掉 -Werror。
畢竟有些 compiler 會實驗一些比較新的警告功能,然後就會產生誤報。
有些使用者會使用更嚴格的 compiler,會產生 warning 的條件更多,所以不能留著。
但是在開發期間,這個選項還是該被打開,這是避免程式出錯的最基本原則之一。
另外,左值 (lvalue) 和右值 (rvalue) 是有既定成俗的中英對照。
你這邊想表達的是 LHS 跟 RHS,使用左值和右值是不恰當的。
: 這邊有另一個例子:
: typedef void (*func)(void); // func f = void (*f)();
: func a = 0; // 沒事,a是空指標常數
: func b = (void *)0; // C 沒問題,C++會掛
: func c = (void *)(void *)0; // C跟C++都看不懂
: ### 在C++的情況 ###
: 根據C++14 standard 4.10 裡對空指標常數 (null pointer constant)的定義:
: A null pointer constant is an integer literal (2.13.2) with value zero
: **or**(以後為C++11後特有) a prvalue of type std::nullptr_t.
: 意思是說,它可以是0或 nullptr,所以以下的情況:
: int x = NULL; // C++ 可能沒問題(視版本而定),不過會造成誤解
可能沒問題就是有問題的,你應該明確地叫大家不要這樣寫。
就像你貼的規格書定義,它在不同的 compiler 有不同的可能性。
因此這行丟到 gcc 和 clang 去開 C++11 mode 就會爆炸了。
clang 3.4.1:
error: cannot initialize a variable of type 'int' with an rvalue of
type 'nullptr_t'
gcc 5.1.0:
error: cannot convert 'std::nullptr_t' to 'int' in initialization
我在用 C++11 模式編譯數百個 open source 專案的過程裡,這算是常踩到的錯誤之一。
所以我對這種寫法超級不爽,原本不該寫在 C++ 的東西就不該出現,沒有寬容的餘地。
實際上這在 C++11 以前也是不好的 code。
因為就算不用 C++11 mode,即使不開 -Wall 也會有警告訊息。
clang 3.4.1:
warning: implicit conversion of NULL constant to 'int'
gcc 5.1.0:
warning: converting to non-pointer type 'int' from NULL
: int* ptr = (void*)0; // C 沒問題,C++會掛
: int* ptr = nullptr; // C++11以後的正統用法,
: 也就是上述C++14 standard裡的空指標常數
: **int x = NULL** 為啥會在C++裡造成誤解?
: 因為C++有C沒有的函數重載(function overloading),舉例來說
: void DoSomething(int x);
: void DoSomething(char* x);
: - NULL = 0: DoSomething(NULL)會呼叫void DoSomething(int x),即
: DoSomething(0)。
: - NULL=nullptr: 會呼叫void DoSomething(char* x),即DoSomething(nullptr)。
: 結論就是,C++11以後還是儘量用nullptr取代NULL吧!
事實上在 C++11 以前,C++ 的爸爸就很不喜歡使用 NULL:
http://www.stroustrup.com/bs_faq2.html#null
C++ 的爸爸很早之前就叫大家直接在 C++ 寫 0 代表空指標,而不是寫成 NULL。
因此用 0 取代 NULL 這件事是要對從 C 轉到 C++11 以前的人說的。
用 nullptr 取代 0 則是要對習慣舊 C++ 標準要轉到 C++11 以後標準的人說的。
這個拆成兩部分來說明會比較好。
: ### 參考資料 ###
: - [comp.lang.c FAQ list · Question 5.2 ](http://c-faq.com/null/null2.html)
: - [Is (int *)0 a null pointer?]
: (http://stackoverflow.com/questions/21386995/is-int-0-a-null-pointer)
: - [Why are NULL pointers defined differently in C and C++?]
: (http://stackoverflow.com/questions/7016861/
: why-are-null-pointers-defined-differently-in-c-and-c)
作者: james732 (好人超)   2016-04-28 14:34:00
作者: Frozenmouse (*冰之鼠*)   2016-04-28 15:08:00
受教 <(_ _)>
作者: mabinogi805 (焚離)   2016-04-28 15:14:00
受教了!<(_ _)>
作者: BlazarArc (Midnight Sun)   2016-04-28 15:15:00
作者: wtchen (沒有存在感的人)   2016-04-28 15:42:00
感謝,受教了!<(_ _)>已經有修改版了,請繼續鞭 /(_ _)\
作者: Caesar08 (Caesar)   2016-04-29 10:09:00

Links booklink

Contact Us: admin [ a t ] ucptt.com