2012-04-12 9 views
4

Herb Sutterの説得力のある講義Not your father's C++に触発されて、私はMicrosoftのVisual Studio 2010を使ってC++の最新バージョンをもう一度見直すことにしました。私がC++ 11がよく知られている上方funarg問題をどのように解決したか聞いていなかったので、 "安全"です。私が知ることから、C++ 11はこの問題を解決するために何もしないので、結果的に「安全」ではありません。C++でlambdaを使用した非決定的な破損11

ローカル変数は、関数が返された後も存在しなくなるスタックフレームに割り当てられ、割り当てられたメモリへのダングリングポインタを返すため、ローカル変数への参照を返したくない非決定的なデータ破損を引き起こします。 CおよびC++コンパイラはこれを認識し、ローカルへの参照またはポインタを返そうとすると警告します。たとえば、このプログラム:

int &bar() { 
    int n=0; 
    return n; 
} 

は警告を発するようにVisual Studioの2010年が発生します。

warning C4172: returning address of local variable or temporary 

しかし、C++ 11でラムダは参照することにより、ローカル変数をキャプチャしていることを返すが容易になりますその結果、等価なダングリングポインタが生成されます。ローカル変数nをキャプチャし、それを返すラムダ関数を返す、次の機能fooを考えてみましょう:

#include <functional> 

std::function<int()> foo(int n) { 
    return [&](){return n;}; 
} 

この無害に見える機能は、メモリ安全ではないと破損したデータのソースです。一つの場所にラムダを取得するには、この関数を呼び出した後、ラムダを呼び出すと、別の場所でその戻り値を印刷することは私のために、この出力を与える:

1825836376 

また、Visual Studioの2010年には警​​告を与えません。

これは本当に深刻なデザイン上の欠陥のようです。最も単純なリファクタリングでさえ、ラムダクロススタックフレームを作成し、非決定的なデータ破損を静かに導入することができます。しかし、この問題に関する貴重な情報はほとんどありません(例えば、StackOverflow上の "upwards funarg"とC++はヒットしません)。人々はこれを知っていますか?誰かが解決策に取り組んでいるのか、回避策を記述していますか?

+2

警告の欠如が唯一である:それは本当にあなたがC++

ビーイングは、あなたのラムダ例では、簡単な変更は、あなたのラムダの例では、完全に安全なものになる、と言わを使用するシナリオに適合しません私がここに見る問題ですが、それはまさに言語の設計上の欠陥ではありません。 –

+1

私はこれに対する解決策はありませんが、C/C++を使って自分自身を本当に傷つける別の方法だと思います - "大きな力をもって、大きな責任を負う" D – Carsten

+1

@CarstenKönig、OPのおかげで、 :) –

答えて

3

これはラムダに固有の問題ではありません。あなたが生涯に(そしてあなたは少なくとも1つの事例に気付いたことがあります)悪いことをすることができます。 C++ 11はC++ 03と比べていくつかの点で安全かもしれませんが、C++ではメモリの安全性を重視していません。

そのC++ が安全であることを望んでいませんが、私はいつもの哲学は通常、安全ガードを追加するの邪魔になる「あなたが使用していないもののために払っていない」と言うだろうと言うことではありません(すべての無効なプログラムの診断を発行できないような停止問題のようなものは考慮しません)。上向きfunarg問題を解決できる場合、は他のすべてのケースのパフォーマンスに影響しない場合、標準委員会は興味があります。 (意味があるとは限りませんが、それは面白いと思います。ハード問題)

これまでのところ、著者の知恵などはこれまでのところ知っていたようですラムダ式(例えば、[&, foo, bar])のための参照によるcatch-allキャプチャの使用を一般的には控え、一般的な参照によるキャプチャには注意が必要です。ラムダ式のキャプチャリストをC++の別の場所と考えることができます。ここでは、生涯に注意する必要があります。別の見方は、ラムダ式をファンクタのオブジェクトリテラル記法(実際にそのように指定されている)として考えることです。あなたは既に寿命に関してクラス型を設計する際に注意する必要があります:それは明らかに「不正なコードを書くことになると、この点のラムダ式で

struct foo { 
    explicit foo(T& t) 
     : ref(t) 
    {} 

    T& ref; 
}; 

foo make_foo() 
{ 
    T t; 
    // Bad 
    return foo { t }; 
    // Not altogether different from 
    // return [&t] {}; 
} 

は、現状を変更しない、と彼らは継承しますすべての既存の警告。

+1

もう一つの大きな違いは、C++ 11は環境変数の取得を自動化しないため、変数を間違って取得して悪化しないように明示する必要があるということです。私はそれがさらに危険なように見える自動化されたものだと思っていました。ありがとう! –

2

あなたは単純にメモリの扱いに気づかないようにしようとすると、どんな複雑さのC++プロジェクトでも簡単には作業できません。この種のパラダイムをより多く目的とした何百もの言語があります。 C++にガベージコレクションがない理由があります。

#include <functional> 

std::function<int()> foo(int n) { 
    return [=](){return n;}; //now n is copied by value 
} 
+1

"メモリ操作を幸せに気にかけないようにしようとすると、複雑なC++プロジェクトで単純に作業することはできません"。私は完全に同意しますが、これは現代のC++が「安全」であるというHerbの主張に惑わされてしまいます。これには何も安全なものはありません。 –

+0

言語はありませんが、それは完全に安全です。十分な知識があれば、内部構造を使用するか、サードパーティのネイティブライブラリを使用することによって、あらゆる言語を壊すことができます。 さらに、ローカル変数へのポインタを返します。参照を返すのと同じくらい危険ですが、コンパイラの警告が一切発生しないと思います... 新しいC++は、以前のバージョンよりも安全なコードを簡単に書くことができるという点では安全です。たとえば、shared_ptrを動的に割り当てられたstd :: functionのインスタンスに戻すことができ、すべてが魅力的に機能します。 – Spook

関連する問題