8
#include <setjmp.h>
#include <ベクター>

int main(int argc, char**) {
 std::vector<int> foo(argc);
 jmp_buf 環境;
 if (setjmp(env)) return 1;
}

上記のコードを GCC 4.4.1、g++ test.cc -Wextra -O1 でコンパイルすると、次の紛らわしい警告が表示されます。

/usr/include/c++/4.4/bits/stl_vector.h: 関数 'int main(int, char**)' 内:
/usr/include/c++/4.4/bits/stl_vector.h:1035: 警告: 変数 '__first' が 'longjmp' または 'vfork' によって破壊される可能性があります

stl_vector.h の 1035 行目は、foo の構築中に呼び出した vector(n, value) コンストラクターによって使用されるヘルパー関数にあります。コンパイラが引数の値を把握できる場合 (数値リテラルなど)、警告は消えます。このテスト ケースでは、コンパイラがその値を判断できないため、argc を使用します。

警告は、setjmp ランディング ポイントの後に実際に発生するようにコンパイラがベクトル構築を最適化するためである可能性があると思います (これは、コンストラクター引数が関数のパラメーターに依存する場合のようです)。

できればsetjmp部分を別の関数に分割することなく、問題を回避するにはどうすればよいですか?

エラー処理に setjmp を使用する必要がある多数の C ライブラリに行き詰まっているため、setjmp を使用しないという選択肢はありません。

4

4 に答える 4

23

setjmp を呼び出すスタック フレーム内の非揮発性、非静的ローカル変数は、longjmp の呼び出しによって上書きされる可能性があるという規則があります。これに対処する最も簡単な方法は、setjmp を呼び出すフレームに、関心のあるそのような変数が含まれていないことを確認することです。これは通常、setjmp を単独で関数に入れ、setjmp を呼び出さない別の関数で宣言されているものへの参照を渡すことで実行できます。

#include <setjmp.h>
#include <vector>

int wrap_libcall(std::vector<int> &foo)
{
  jmp_buf env;
  // no other local vars
  if (setjmp(env)) return 1;
  // do stuff with your library that might call longjmp
  return 0;
}

int main(int argc, char**) { 
  std::vector<int> foo(argc);
  return wrap_libcall(foo);  
}

また、このコンテキストでは、クロバリングとは、setjmp が呼び出されたときの値にリセットすることを意味するだけであることに注意してください。したがって、ローカルの変更後に longjmp を呼び出すことができなくても問題ありません。

編集

setjmp の C99 仕様からの正確な引用は次のとおりです。

longjmp 関数が呼び出された時点で、すべてのアクセス可能なオブジェクトには値があり、抽象マシンの他のすべてのコンポーネントには状態があります。 volatile 修飾された型を持たず、setjmp 呼び出しと longjmp 呼び出しの間で変更されたマクロは不確定です。

于 2010-01-20T23:25:44.220 に答える
5

これは無視すべき警告ではありません。longjmp() と C++ オブジェクトは互いにうまく機能しません。問題は、コンパイラが foo オブジェクトのデストラクタ呼び出しを自動的に発行することです。longjmp() はデストラクタ呼び出しをバイパスできます。

C++ 例外もスタック フレームをアンワインドしますが、ローカル オブジェクトのデストラクタが呼び出されることを保証します。longjmp() からそのような保証はありません。longjmp() がバイトになるかどうかを調べるには、longjmp() によって早期に終了する可能性がある各関数のローカル変数を注意深く分析する必要があります。それは簡単ではありません。

于 2010-01-08T05:26:01.960 に答える
2

エラー メッセージの行番号 1035 からわかるように、コード スニペットは実際の問題コードを大幅に簡素化しています。あなたは行き​​過ぎました。「最初」をどのように使用しているかについての手がかりはありません。問題は、実際のコードでもコンパイラがそれを理解できないことです。「setjmp」からゼロ以外が返された後の「first」の値は、あなたが思っているものとは異なるかもしれません。これは、'setjmp' への最初の呼び出し (ゼロ リターン) の前後で値を変更したためです。変数がレジスタに格納された場合、値はメモリに格納された場合とは異なる可能性があります。したがって、コンパイラは警告を出すことで保守的です。

やみくもに質問に答えるには、'first' の宣言を 'volatile' で修飾することにより、警告メッセージを取り除くことができる場合があります。「first」をグローバルにすることもできます。おそらく、最適化レベル (-O フラグ) を下げることによって、コンパイラーが変数をメモリー内に保持するようにすることができます。これらは簡単な修正であり、実際にはバグを隠している可能性があります。

コードと、「first」をどのように使用しているかを実際に確認する必要があります。別のワイルドな推測をして、その変数を排除できるかもしれないと言います. その名前「first」は、「setjmp」への最初の呼び出し (ゼロ リターン) を示すために使用しているという意味でしょうか? もしそうなら、それを取り除き、ロジックを再設計してください。

実際のコードが 'setjmp' からのゼロ以外のリターンで終了する場合 (スニペットのように)、'first' の値はそのロジック パスでは重要ではありません。「setjmp」の両側で使用しないでください。

于 2010-01-08T04:19:46.427 に答える
-1

簡単な答え: -O1 フラグを削除するか、コンパイラを以前のバージョンに戻します。どちらかが私のシステムで警告を消しました。そもそも警告を表示するには、gcc4.4 をビルドして使用する必要がありました。(すごいシステムですね)

いいえ?私はそうは思わなかった。

C++ がそのオブジェクトに対して行うすべてのことと、それらがどのように解放されるかを正確に理解しているわけではありません。それでも、ベクトルサイズの「argc」の代わりに定数値が使用された場合、問題は発生しなかったというOPのコメントは、首を突き出す機会を与えてくれます。初期割り当てが定数でない場合にのみ、C++ が解放時に '__first' ポインターを使用するという推測を危険にさらします。より高いレベルの最適化では、コンパイラーはレジスターをより多く使用し、setjmp の事前割り当てと事後割り当ての間に競合があります...わかりません。意味がありません。

この警告の一般的な意味は、「自分が何をしているのか知っていますか?」です。コンパイラは、longjmp を実行し、'setjmp' からゼロ以外の戻り値を取得するときに、'__first' の値がどうなるかを知っているかどうかを知りません。問題は、(ゼロ以外の) 戻り値の後の値が保存バッファーに入れられた値なのか、それとも保存後に作成した値なのかということです。この場合、'__first' を使用していることを知らなかったので混乱します。また、このような単純なプログラムでは、'__first' への (明示的な) 変更がないためです。

コンパイラは複雑なプログラムの論理フローを分析できないため、どのプログラムに対しても試行しないようです。値を変更した可能性があります。したがって、友好的な「ヘッズアップ」を提供するだけです。コンパイラは、あなたの役に立ちたいと思っています。

コンパイラと最適化の選択に固執している場合は、プログラミングの修正があります。ベクターを割り当てる前に環境を保存してください。「setjmp」をプログラムの先頭に移動します。実際のプログラムでのベクトルの使用とエラー ロジックに応じて、これには他の変更が必要になる場合があります。

編集 1/21 -------

私の正当化 (g++-mp-4.4 -Wextra -O1 main.cpp を使用):

#include <setjmp.h>
#include <vector>
#include <iostream>

int main(int argc, char**) {
    jmp_buf env;
    int id = -1, idd = -2;

    if ((id=setjmp(env)))
        idd = 1;
    else 
        idd = 0;
    std::cout<<"Start with "<< id << " " << idd <<std::endl;
    std::vector<int> foo(argc );

    if(id != 4)
        longjmp(env, id+1);

    std::cout<<"End with "<< id << " " << idd <<std::endl;
}

警告なし。a.out 生成:

0 で始まる 0
1
で始まる 1 2
で始まる 1 3
で始まる 1 4 で始まる 1 4
で終わる 1

于 2010-01-20T22:04:24.270 に答える