std :: stringパラメーターに渡された0から最良に保護する方法は?


20

何か気がかりなことに気づきました。std::stringaをパラメーターとして受け入れるメソッドを作成するたびに、未定義の動作に自分自身を開放しました。

たとえば、これ...

void myMethod(const std::string& s) { 
    /* Do something with s. */
}

...このように呼び出すことができます...

char* s = 0;
myMethod(s);

...そしてそれを防ぐために私ができることは何もありません(私は知っています)。

だから私の質問は次のとおりです。誰かがこれからどのように身を守るのでしょうか?

頭に浮かぶ唯一のアプローチは、次のstd::stringように、パラメータとしてを受け入れるメソッドの2つのバージョンを常に記述することです。

void myMethod(const std::string& s) {
    /* Do something. */
}

void myMethod(char* s) {
    if (s == 0) {
        throw std::exception("Null passed.");
    } else {
        myMethod(string(s));
    }
}

これは一般的なおよび/または許容可能なソリューションですか?

編集: 私はパラメータとしてではconst std::string& sなく受け入れるべきだと指摘する人もいstd::string sます。同意する。投稿を変更しました。しかし、それが答えを変えるとは思わない。


1
漏れやすい抽象化のために万歳!私はC ++開発者ではありませんが、文字列オブジェクトのc_strプロパティを確認できなかった理由はありますか?
メイソンウィーラー

6
それは発信者が本当にせいですので、ただ文字に0を代入すると、*コンストラクタは、未定義の動作です
ラチェットフリークを

4
@ratchetfreakそれchar* s = 0が未定義であることは知りませんでした。私はそれを私の人生で少なくとも数百回見ました(通常はの形でchar* s = NULL)。それをバックアップするための参照はありますか?
ジョンフィッツパトリック

3
私はstd:string::string(char*)コンストラクターに向けて
ラチェットフリーク

2
あなたの解決策は素晴らしいと思いますが、何もしないことを考慮すべきです。:-)メソッドは文字列を非常に明確に取ります。有効なアクションを呼び出すときにnullポインターを渡すことはありません。たとえば、ログファイルで報告されるよりも優れています。 コンパイル時にこれを防ぐ方法があれば、それを行うべきです。そうでなければ、そのままにしておきます。私見では。(ところで、あなたはconst std::string&そのパラメーターをとることができなかったと確信していますか?)
グリムオピナー

回答:


21

自分を守るべきではないと思います。呼び出し側の動作は未定義です。あなたではなく、発信者がを呼び出しますがstd::string::string(nullptr)、これは許可されていません。コンパイラはそれをコンパイルさせますが、他の未定義の動作も同様にコンパイルさせます。

同じ方法で「null参照」を取得します。

int* p = nullptr;
f(*p);
void f(int& x) { x = 0; /* bang! */ }

NULLポインターを逆参照するのは、UBを作成し、その責任を負います。

さらに、未定義の動作が発生した後は、オプティマイザが未定義の動作が発生したことはないと想定する完全な権利があるため、自分自身を保護することはできませんc_str()


同じことを言っている美しく書かれたコメントがあるので、あなたは正しいに違いありません。;-)
グリムオピナー

2
ここでのフレーズは、マキャベリではなく、マーフィーに対する保護です。精通した悪意のあるプログラマーは、十分な努力をすれば、不正なオブジェクトを送信してヌル参照を作成する方法を見つけることができますが、それは彼ら自身のことです。期待できる最善の方法は、偶発的なエラーを防ぐことです。この場合、誰かが誤ってchar * s = 0を渡すことはまれです。整形式の文字列を要求する関数に。
ヤングジョン

2

以下のコードは、の明示的なパスに対してコンパイルエラーを0発生させ、char*with値に対してランタイムエラーを発生させます0

通常はこれを行う必要があることを意味するわけではありませんが、呼び出し側エラーからの保護が正当化される場合があることは間違いありません。

struct Test
{
    template<class T> void myMethod(T s);
};

template<> inline void Test::myMethod(const std::string& s)
{
    std::cout << "Cool " << std::endl;
}

template<> inline void Test::myMethod(const char* s)
{
    if (s != 0)
        myMethod(std::string(s));
    else
    {
        throw "Bad bad bad";
    }
}

template<class T> inline void Test::myMethod(T  s)
{
    myMethod(std::string(s));
    const bool ok = !std::is_same<T,int>::value;
    static_assert(ok, "oops");
}

int main()
{
    Test t;
    std::string s ("a");
    t.myMethod("b");
    const char* c = "c";
    t.myMethod(c);
    const char* d = 0;
    t.myMethod(d); // run time exception
    t.myMethod(0); // Compile failure
}

1

また、数年前にこの問題に遭遇しましたが、実際に非常に恐ろしいことがわかりました。これはnullptr、値0のintを渡すか、誤ってintを渡すことで発生する可能性があります。これは本当にばかげています。

std::string s(1); // compile error
std::string s(0); // runtime error

しかし、最終的にはこれは数回しか私を悩ませませんでした。そして、コードをテストするときに、すぐにクラッシュを引き起こしました。したがって、それを修正するために一晩のセッションは必要ありません。

関数をオーバーロードするのconst char*は良い考えだと思います。

void foo(std::string s)
{
    // work
}

void foo(const char* s) // use const char* rather than char* (binds to string literals)
{
    assert(s); // I would use assert or std::abort in case of nullptr . 
    foo(std::string(s));
}

もっといい解決策が可能だったらいいのにと思います。ただし、ありません。


2
ただし、次の場合にfoo(0)コンパイルエラーが発生すると、ランタイムエラーが発生しますfoo(1)
Bryan Chen

1

メソッドのシグネチャを次のように変更するのはどうですか?

void myMethod(std::string& s) // maybe throw const in there too.

そうすれば、呼び出し元は呼び出し前に文字列を作成する必要があり、あなたが心配しているずさんなことがあなたのメソッドに到達する前に問題を引き起こし、他の人が指摘していること、それがあなたのものではなく呼び出し元のエラーであることを明らかにします。


はいconst string& s、私はそれを作るべきでした、私は実際に忘れていました。しかし、それでも、未定義の動作に対して脆弱ではありませんか?発信者はまだを渡すことができ0ますよね?
ジョンフィッツパトリック

2
非const参照を使用する場合、一時オブジェクトは許可されないため、呼び出し側は0を渡すことができなくなります。ただし、ライブラリを使用すると(一時オブジェクトは許可されないため)ずっと煩わしくなり、constの正確性をあきらめます。
ジョシュケリー

クラスに非const参照パラメーターを使用し、それを格納できるようにし、変換または一時的に渡されないようにする必要がありました。
CashCow 14年

1

オーバーロード時にintパラメータを受け取るのはどうですか?

public:
    void myMethod(const std::string& s)
    { 
        /* Do something with s. */
    }    

private:
    void myMethod(int);

オーバーロードを定義する必要さえありません。呼び出そうとするとmyMethod(0)、リンカーエラーが発生します。


1
これは0char*タイプのある問題のコードから保護しません。
ベンフォークト

0

を使用して呼び出しようとすると、最初のコードブロックのメソッドは呼び出されません(char *)0。C ++は単純に文字列を作成しようとし、例外をスローします。やってみた?

#include <cstdlib>
#include <iostream>

void myMethod(std::string s) {
    std::cout << "s=" << s << "\n";
}

int main(int argc,char **argv) {
    char *s = 0;
    myMethod(s);
    return(0);
}


$ g++ -g -o x x.cpp 
$ lldb x 
(lldb) run
Process 2137 launched: '/Users/simsong/x' (x86_64)
Process 2137 stopped
* thread #1: tid = 0x49b8, 0x00007fff99bf9812 libsystem_c.dylib`strlen + 18, queue = 'com.apple.main-thread, stop reason = EXC_BAD_ACCESS (code=1, address=0x0)
    frame #0: 0x00007fff99bf9812 libsystem_c.dylib`strlen + 18
libsystem_c.dylib`strlen + 18:
-> 0x7fff99bf9812:  pcmpeqb (%rdi), %xmm0
   0x7fff99bf9816:  pmovmskb %xmm0, %esi
   0x7fff99bf981a:  andq   $15, %rcx
   0x7fff99bf981e:  orq    $-1, %rax
(lldb) bt
* thread #1: tid = 0x49b8, 0x00007fff99bf9812 libsystem_c.dylib`strlen + 18, queue = 'com.apple.main-thread, stop reason = EXC_BAD_ACCESS (code=1, address=0x0)
    frame #0: 0x00007fff99bf9812 libsystem_c.dylib`strlen + 18
    frame #1: 0x000000010000077a x`main [inlined] std::__1::char_traits<char>::length(__s=0x0000000000000000) + 122 at string:644
    frame #2: 0x000000010000075d x`main [inlined] std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::basic_string(this=0x00007fff5fbff548, __s=0x0000000000000000) + 8 at string:1856
    frame #3: 0x0000000100000755 x`main [inlined] std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::basic_string(this=0x00007fff5fbff548, __s=0x0000000000000000) at string:1857
    frame #4: 0x0000000100000755 x`main(argc=1, argv=0x00007fff5fbff5e0) + 85 at x.cpp:10
    frame #5: 0x00007fff92ea25fd libdyld.dylib`start + 1
(lldb) 

見る?心配する必要はありません。

これをもう少し上品にキャッチしたい場合は、単に使用しないでください。そうすればchar *問題は発生しません。



2
EXC_BAD_ACCESS例外はNULLデリファレンスのように聞こえます。これは、良い日にセグメンテーション違反でプログラムをクラッシュさせます
ラチェットフリーク

@ vy32私が受け入れるコードの一部は、std::string私が呼び出し元ではない他のプロジェクトで使用されるライブラリに送られます。私は、状況を適切に処理し、プログラムをクラッシュさせずに間違った引数を渡したことを呼び出し元に通知する方法を探しています(例外がある場合があります)。(OK、当然、呼び出し元は私がスローした例外を処理しない可能性があり、プログラムはとにかくクラッシュします。)
ジョンフィッツパトリック

2
@JohnFitzpatrickあなたは、std :: stringに渡されたnullpointerから自分自身を保護することはできません。未定義の振る舞いの代わりに例外を標準のコミュニティに納得させることができなければ
ラチェットフリーク

@ratchetfreakある意味では、それが私が探していた答えだと思います。だから基本的に私は自分自身を守らなければなりません。
ジョンフィッツパトリック

0

nullポインタの可能性があるchar *(外部C APIからの戻りなど)が心配な場合、答えはstd :: string oneではなくconst char *バージョンの関数を使用することです。すなわち

void myMethod(const char* c) { 
    std::string s(c ? c : "");
    /* Do something with s. */
}

もちろん、これらの使用を許可する場合は、std :: stringバージョンも必要になります。

一般に、外部APIへの呼び出しを分離し、引数を有効な値にマーシャリングすることが最善です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.