"Called C++ object pointer is null" 警告が表示される - Xcode4
SPECIAL
"Called C++ object pointer is null" 警告が表示される
Xcode 4.6 で C++ コードを混在させたプログラムを作成していたら、ビルド時に次の警告が表示されることがありました。
Called C++ object pointer is null
この警告が表示されたのは、次のようなコードを記載していたときでした。
CEzValue CEzUtilities::valueWithField(const CEzField& field)
{
if (field.isNameEqualTo("NAME"))
{
:
:
ここの isNameEqualTo 関数を呼び出すところで field に対して、先ほどの "Called C++ object pointer is null" が表示されました。
これと同じように、参照を受け取ってそれが持つ関数を呼び出すコードは他にもいくつもあったのですけど、それらではこの警告が表示されることはありませんでした。
警告の原因と発生タイミング
調べてみると LLVM の場合はプロトタイプ宣言で __attribute__((nonnull)) を付ければ良いという情報もありましたけど、参照型の引数にこれを付けたところで、次の警告メッセージに変わるだけでした。
Nonnull attribute only applies to pointer arguments
いまひとつ直接的な原因が判らずいろいろ試してみたところ、この "Called C++ object pointer is null" という警告は文字通り、参照に nullptr が渡される可能性があることを知らせる警告だったようでした。
この警告が表示されるのは、実際に参照型の引数に nullptr が渡されてくる可能性が出てきたときになるようです。
しかもこれが表示されるのは、呼び出し元ではなくて呼び出し先の、その引数で受け取ったインスタンスに対してドット演算子やアロー演算子を使ったそのタイミングで、その行に対して警告メッセージが表示されるところが少し厄介です。
そのため、この警告が表示されたときには、呼び出し元を順に辿って nullptr が渡されるかもしれない個所を見つけ、適切な対応を取る必要があります。
今回の自分の場合は、二つ前の呼び出し元で nullptr が混入する可能性がありました。
CEzValue CEzUtilities::inclementedValue()
{
// ここでポインタ型を、関数の戻り値から受け取っています。
CEzField* currentField = this->currentField();
// それを値型にして関数の引数に渡します。(ここで nullptr の可能性が発生)
return this->inclementedValue(*currentField);
}
CEzValue CEzUtilities::inclementedValue(const CEzField& field)
{
// 引数の field は、呼び出し元で nullptr が混入している可能性があります。
CEzValue value = this->valueWithField(field);
return ++value;
}
CEzValue CEzUtilities::valueWithField(const CEzField& field)
{
// このタイミングで初めて "Called C++ object pointer is null" が報告されます。
if (field.isNameEqualTo("NAME"))
{
:
:
このようにポインタ変数を、参照型を受け取る引数に渡すようなことが引き金になる様子でした。
"Called C++ object pointer is null" 警告を解消する
"Called C++ object pointer is null" 警告の原因は、その変数に nullptr が設定されている可能性があるということなので、解消するにはその可能性を潰してあげれば良いことになります。
nullptr である可能性を潰すということは、その変数に nullptr が代入されていないかを判定すれば大丈夫です。
たとえば今回の例で言うと、根本の呼び出し元で対応するのであれば、currentField 関数で受け取ったポインターが nullptr かどうかで処理を分岐してあげます。
CEzValue CEzUtilities::inclementedValue()
{
// ここでポインタ型を、関数の戻り値から受け取っています。
CEzField* currentField = this->currentField();
// それが nullptr だった場合は inclementedValue 関数を呼ばないようにします。
if (currentField != nullptr)
{
return this->inclementedValue(*currentField);
}
else
{
return CEzValue();
}
}
これで nullptr が、警告の出た valueWithField 関数に渡される道が絶たれるので、LLVM は警告を出さなくなりました。
しかし非ランタイムで nullptr の可能性を検出できるなんて、そう考えると LLVM ってかなり賢いとも言えますね。ちゃんと警告に応えて行けばプログラムの品質も高まりそうです。
最終的に呼び出される関数で nullptr が渡されてきたかを判定しても大丈夫です。
CEzValue CEzUtilities::valueWithField(const CEzField& field)
{
// このタイミングで、参照が nullptr を指していないかを判定する方法もあります。
if (&field != nullptr && field.isNameEqualTo("NAME"))
{
:
:
こうすることでも "Called C++ object pointer is null" 警告を解消できます。
こちらの方が、どんな場面から field の値が渡されてもこの 1 箇所で対処ができるので手間は少ないのですけど、nullptr が渡ってくるのが例外的であるなら、呼び出し元で対応するのがきっと適切です。
それに、このように参照が示すアドレスを取得して nullptr かどうかを判定するのって普通の書き方なのかどうか判りません。
nullptr かどうかを判定するのなら、せいぜいそれを参照型に渡す直前までに済ませておくのが自然なような気もします。