8

最近、C++ プログラムのエラーのほとんどが次の例のような形式になっていることがわかりました。

#include <iostream>

class Z
{
 public:
 Z(int n) : n(n) {}
 int n;
};

class Y
{
 public:
 Y(const Z& z) : z(z) {}
 const Z& z;
};

class X
{
 public:
 X(const Y& y) : y(y) {}
 Y y;
};

class Big
{
 public:
 Big()
 {
   for (int i = 0; i < 1000; ++i) { a[i] = i + 1000; }
 }
 int a[1000];
};

X get_x() { return X(Y(Z(123))); }

int main()
{
 X x = get_x();
 Big b;
 std::cout << x.y.z.n << std::endl;
}

出力: 1000

このプログラムは 123 (get_x() で設定された xyzn の値) を出力すると予想しますが、「Big b」の作成により一時的な Z が上書きされます。その結果、オブジェクト Y 内の一時的な Z への参照は、次のように上書きされます大きなb、したがって、出力は私が期待するものではありません.

オプション "-Wall" を指定して gcc 4.5 でこのプログラムをコンパイルしたところ、警告は表示されませんでした。

修正は明らかに、クラス Y のメンバー Z から参照を削除することです。ただし、多くの場合、クラス Y は私が開発していないライブラリの一部であり (最近は boost::fusion)、さらに状況ははるかに複雑です。私が与えたこの例よりも。

これには、gcc へのある種のオプション、またはそのような問題をできればコンパイル時に検出できる追加のソフトウェアがありますが、ランタイムでさえ何もないよりはましでしょうか?

ありがとう、

クリントン

4

3 に答える 3

2

私は数ヶ月前にそのようなケースを clang-dev メーリング リストに提出しましたが、当時は誰もそれに取り組む時間がありませんでした (残念ながら私もそうでした)。

ただし、Argyrios Kyrtzidis は現在この問題に取り組んでおり、この件に関する彼の最新情報は次のとおりです (30 Nov 23h04 GMT):

以前のコミットを元に戻し、 http://lists.cs.uiuc.edu/pipermail/cfe-commits/Week-of-Mon-20101129/036875.htmlでより適切に修正しました 。例えば

struct S {   int x; };

int &get_ref() {   S s;   S &s2 = s;   int &x2 = s2.x;   return x2; }

我々が得る

t3.cpp:9:10: warning: reference to stack memory associated with local variable 's' returned
  return x2;
         ^~
t3.cpp:8:8: note: binding reference variable 'x2' here
  int &x2 = s2.x;
       ^    ~~
t3.cpp:7:6: note: binding reference variable 's2' here
  S &s2 = s;
     ^    ~
1 warning generated.

前回の試みは自己ホスティング テストに失敗したため、この試みが成功することを願っています。とにかく、Argyriosがそれを調べてくれてとてもうれしいです:)

確かに、取り組むのは非常に複雑な問題であるため、まだ完全ではありません (ある意味でポインターのエイリアシングを思い起こさせます) が、それでも、これは正しい方向への大きな一歩です。

このバージョンの Clang に対してコードをテストできますか? 私は Argyrios がフィードバックを歓迎すると確信しています (それが検出されたかどうかに関係なく)。

于 2010-12-01T08:56:42.173 に答える
0

[3 番目の箇条書きを編集して役立つテクニックを示します] これは、同じ呼び出し元構文で値または参照による引数の受け渡しが言語で許可されている場合に陥るうさぎの穴です。次のオプションがあります。

  • 引数を非 const 参照に変更します。一時値は非 const 参照型と一致しません。

  • これが可能である場合は、参照を完全に削除してください。const 参照が、呼び出し元と呼び出し先の間で論理的に共有された状態を示していない場合 (そうであれば、この問題はそれほど頻繁には発生しません)、複雑な型の単純なコピーを回避するために挿入された可能性があります。最新のコンパイラには、ほとんどの場合、値渡しを参照渡しと同じくらい効率的にする高度なコピー省略最適化があります。http://cpp-next.com/archive/2009/08/want-speed-pass-by-valueを参照してください素晴らしい説明のために。一時変数を変更する可能性のある外部ライブラリ関数に値を渡す場合、コピー省略は明らかに実行されませんが、その場合は、値を const 参照として渡していないか、const を意図的にキャストしていません。 - オリジナル版の -ness。これは、コンパイラがコピーの最適化について心配できるようになり、コード内の他のエラーの原因について心配する必要がなくなるため、私が好む解決策です。

  • コンパイラが右辺値参照をサポートしている場合は、それらを使用してください。この問題が気になる関数のパラメーターの型を少なくとも編集できる場合は、次のようにラッパー メタクラスを定義できます。

template < typename T > class need_ref {

T & ref_;

公衆:

need_ref(T &&x) { /* なし */ }

need_ref(T &x) : ref_(x) { /* なし */ }

演算子 T & () { return ref_; }

};

次に、T& 型の引数を need_ref 型の引数に置き換えます。たとえば、次のように定義するとします。

クラスユーザー{

整数 &z;

公衆:

user(need_ref< int> arg) : z(arg) { /* なし */ }

};

次に、「int a = 1, b = 2; user ua(a);」という形式のコードを使用してタイプ user のオブジェクトを安全に初期化できますが、「user sum(a+b)」として初期化しようとすると、または"user five(5)" コンパイラは、need_ref() コンストラクターの最初のバージョン内で初期化されていない参照エラーを生成する必要があります。この手法は明らかにコンストラクターに限定されず、実行時のオーバーヘッドはありません。

于 2010-12-01T02:42:42.043 に答える
-1

ここでの問題はコードです

 Y(const Z& z) : z(z) {}

メンバー「z」は、仮パラメーター「z」への参照で初期化されるためです。コンストラクターが返されると、参照は無効になったオブジェクトを参照します。

多くの場合、コンパイラがそのような論理的な欠陥を検出する、または検出できるとは思いません。IMOの修正は、明らかにそのようなクラスを認識し、それらの設計に適した方法でそれらを使用することです。これは、ライブラリ ベンダーによって実際に文書化される必要があります。

ところで、可能であれば、メンバー 'Y::z' に 'Y::mz' という名前を付けることをお勧めします。「z(z)」という表現はあまり魅力的ではありません

于 2010-12-01T02:44:01.950 に答える