2016-11-24 79 views
4

首先,我知道調用空指針的方法是未定義的行爲。我也知道,因爲這不應該發生,編譯器可以(並且確實)假設this始終爲非空。安全地檢查'this`是否爲空

但是在實際的代碼中,你有時會意外地做它。通常,它沒有不良影響,但當然this在方法中爲null,並且可能會崩潰。

作爲一種調試手段,本着碰撞早期的精神,我把assert(this != 0)放在一個方法中,我偶爾在空前的指針上調用過空指針。它似乎工作,但鐺與抱怨:

warning: 'this' pointer cannot be null in well-defined C++ code; comparison may be 
     assumed to always evaluate to true [-Wtautological-undefined-compare] 
    assert (this ! = 0); 
      ^~~~  ~ 

我不知道最好的(至少不正確的)的方法是檢測this是空的。一個簡單的比較可以優化出來。

  • 我可以在this上做一些指針運算來試圖欺騙編譯器,或者強制它把指針當作一個整數。
  • 我可以使用memcmp
  • 也許有編譯器特定的擴展來說「不要優化這個表達式」?

另外一個值得關注的是,在繼承的情況下,「空」這個指針實際上可能是類似0x00000004,所以這將是很好也處理這種情況。我對Clang,MSVC或GCC的解決方案感興趣。

+2

「輔助調試,並在碰撞早期的精神,」你應該現在支持GCC和鐺各種'-fsanitize'選項編譯。你應該在你的'assert'之後得到你想要的結果,而不需要修改你的代碼。這並不回答你的實際問題,但我懷疑你的實際問題沒有答案。 – hvd

+1

C++中檢查指針是否爲空的標準方法是myptr == nullptr;我不知道這是否與NULL或0不同,但無論如何這是一個常見的檢查,就像在動態轉換後查看它是否成功之後,所以我沒有看到編譯器應該優化它,因爲它是一個非常重要的檢查。 – Zebrafish

+1

@TitoneMaurice當編譯器可以在編譯時證明比較永遠不會是真的時,編譯器可以優化與'nullptr'的比較。這正是編譯器真正做的。 – hvd

回答

0

也許如果你嘗試assert(this != nullptr)但我認爲也會抱怨。你可能想嘗試類型轉換this然後檢查值:

int* ptr = static_cast<int*>(this); 

然後用assert檢查。

+2

'static_cast'到'int *'不應該編譯,'reinterpret_cast'到'int *'不處理OP的有效關注,即使原始指針是結果也不是'nullptr'。 – hvd

6

在gcc中,你可以用-fsanitize=null構建。 Clang也應該有這個選項。

man gcc

 -fsanitize=null 
      This option enables pointer checking. Particularly, the application built with this option turned on will issue an error message when it tries to dereference a NULL pointer, or if a 
      reference (possibly an rvalue reference) is bound to a NULL pointer, or if a method is invoked on an object pointed by a NULL pointer. 

這是一個測試程序:

[ ~]$ cat 40783056.cpp 
struct A { 
    void f() {} 
}; 

int main() { 
    A* a = nullptr; 
    a->f(); 
} 
[ ~]$ g++ -fsanitize=null 40783056.cpp 
[ ~]$ 
[ ~]$ ./a.out 
40783056.cpp:7:7: runtime error: member call on null pointer of type 'struct A' 
+0

謝謝,該功能看起來不錯,我會檢查出來!然而,我試圖避免顯式空值檢查的開銷 - 主要是通過確保我沒有null的地方,我不指望它。例如。通過讓我的函數不會返回null。另外,在我真實的代碼中,我可能想要做的不僅僅是中止......例如打印一個堆棧跟蹤或記錄一些東西。 – jdm

+0

例如,您只能在單元測試中使用這些檢查。他們也不應該花費太多的開銷。據我所知,谷歌在生產中使用其中的一些。 – ks1322

+0

@jdm我看到你在談論叮噹。也許你應該檢查http://clang.llvm.org/docs/AddressSanitizer.html我認爲它可以給堆棧的痕跡,即使我沒有設法讓它在我的電腦上工作。對我來說,這個功能看起來像一個開發功能,一旦你想要執行perf,你就可以禁用它,並且啓用優化,就像你將NDEBUG(並斷言) – tforgione

1

你應該調用成員函數前檢查指針,之後吧。

Class *object = ...; 
...; 
if (object) { 
    object->... 
} 

檢查是否this是nullptr在成員函數只能引進調用開銷,它應該永遠是調用者的責任,以確保this永遠不會nullptr或懸掛或野指針。 不檢查rhs是否與作業運算符中的lhs相同的原因。 (但應該確保自賦值可以正常工作,實現這樣的操作可能會非常棘手且容易出錯)

試圖訪問成員函數或變量時this是nullptr通常會立即崩潰您的程序,並取消引用nullptr是實際上。斷言(這個!= nullptr)當這個== nullptr也會立即終止你的程序,但提供比崩潰更多的信息,這也是依賴於ub。

第一個ub檢查方法需要較少的工作量,並提供較少的信息。前者的ub檢查方法需要更多的努力(無處不在),但確實提供了更多信息。原來一個折衷?

如果可以複製崩潰,我寧願第一個(沒有斷言),可以添加一些日誌並快速(O(lgn))找出哪一行崩潰的程序,並且事實thisnullptr 。並找出哪個呼叫有問題。

否則,assert可能會提供更多的信息,但是在這種情況下「取消引用nullptr」是否有幫助?我對此表示懷疑。

所以在我看來,正確的做法是建立一個良好的日誌記錄系統,並使其更容易重現錯誤。

+1

「不檢查rhs是否與lhs在作業操作員。「 - 呃?對於所有內置類型和幾乎所有標準庫類型,給定一個支持賦值類型的對象'x',自賦值('x = x')定義良好,並且有效地保留'x'不被修改。對於自定義類也可以合理地假定它是明確定義的。 – hvd

+0

可能存在一些誤解。通過「不檢查」,我的意思是不要把(lhs == rhs)返回lhs;在作業的第一行操作符中。但是不要讓程序崩潰,賦值運算符應該在賦值後始終確保lhs == rhs。 – felix

+1

對於自定義賦值運算符的很多(但不是大多數)實現,如果在主體開始時沒有'if(lhs == rhs)return lhs;',自賦值會導致錯誤,因爲實現常常假定類別不變式在體系中始終保持從身體的開始到結束,但是在lhs上的類不變式可能暫時不成立。如果lhs和rhs是同一個對象,那麼打破lhs上的不變量意味着它們也在rhs上被破壞。還有其他方法可以避免這種情況,但'if(lhs == rhs)'檢查也是一種有效的方法。 – hvd

1

首先,正如對這個問題的評論所指出的那樣:嘗試使用內置於編譯器中的消毒劑。

要回答您的問題:您可以將您的指針投射到uintptr_t並比較該值。請注意,此轉換爲實現定義,並不保證按預期工作。

下面的代碼沒有優化鏗鏘聲3.9,但刪除空指針檢查與GCC 7.0。 0x10已被任意選擇。

struct foo 
{ 
    void bar() { 
     if(this == nullptr) { 
     sink(__LINE__); 
     } 
     if((uintptr_t)this < 0x10) { 
     sink(__LINE__); 
     } 
     if((volatile uintptr_t)this < 0x10) { 
     sink(__LINE__); 
     } 
    } 
}; 

demo

-2

clang是正確與警告你 - 如果條件this == 0值爲true,你的程序已經走了南方,並露出不確定的行爲。所以,你期待定義的性能(assert()正確執行),當你已經知道你的程序不能預期的行爲,這是一種奇怪的。

聽起來像是掛在您嘗試削減你上面,再一起結兩端繩子......

如果你想確保你的方法是不是有(this == 0)意外調用,只需讓它virtual和它會強制轉儲核心。

+4

這會嘲笑提出問題的OP,甚至不會嘗試回答它。即使沒有發誓,這也是毫無意義的粗魯。 – hvd

+0

雖然你是正確的,這是根據標準(我知道這是不確定的行爲),在實踐中,這並不會導致着名的鼻子惡魔。相反,x86/x64上的主要編譯器(gcc,clang,msvc)具有可重複的行爲,並且顯而易見(用'this' = null調用該方法)。有很多現有的代碼依賴於這個事實,包括MFC。 – jdm

+0

@jdm即使(?)如果你告訴我「MFC使用它」 - 它仍然是錯誤的。依靠你使用的編譯器的一些實現特定的運氣並不是我稱之爲安全軟件的東西。只要你嘗試用虛擬函數來做這件事,你的代碼幾乎肯定會崩潰。 – tofro