6

私のコードでは、データ メンバーとして格納し、必要に応じて元のポインターにvoid*型キャストする必要があるとします。classその信頼性をテストするために、テスト プログラム(linux ubuntu 4.4.1 g++ -04 -Wall)を作成しましたが、その動作を見てショックを受けました。

struct A
{
  int i;
  static int c;
  A () : i(c++) { cout<<"A() : i("<<i<<")\n"; }
};
int A::c;

int main ()
{
  void *p = new A[3];  // good behavior for A* p = new A[3];
  cout<<"p->i = "<<((A*)p)->i<<endl;
  ((A*&)p)++;
  cout<<"p->i = "<<((A*)p)->i<<endl;
  ((A*&)p)++;
  cout<<"p->i = "<<((A*)p)->i<<endl;
}

これは単なるテスト プログラムです。私の場合、実際には、ポインターを として保存void*してから、実際のポインターにキャストすることが必須です (の助けを借りてtemplate)。ですから、その部分は気にしないでください。上記のコードの出力は、

p->i = 0
p->i = 0 // ?? why not 1
p->i = 1

ただし、をに変更するvoid* p;と、期待される動作A* p;が得られます。なぜ ?

別の質問です。(A*&)それ以外の場合は使用できませんoperator ++。しかし、それは警告も出します。型がパニングされたポインターを逆参照すると、strict-aliasing rules が壊れます。warning を克服する適切な方法はありますか?

4

3 に答える 3

11

コンパイラが警告するように、厳密なエイリアシング規則に違反しています。これは正式には、結果が未定義であることを意味します。

インクリメントに関数テンプレートを使用することで、厳密なエイリアシング違反を排除できます。

template<typename T>
void advance_pointer_as(void*& p, int n = 1) {
    T* p_a(static_cast<T*>(p));
    p_a += n;
    p = p_a;
}

この関数テンプレートを使用すると、次の の定義によりmain()、Ideone コンパイラで期待される結果が得られます (警告は発行されません)。

int main()
{
    void* p = new A[3];
    std::cout << "p->i = " << static_cast<A*>(p)->i << std::endl;
    advance_pointer_as<A>(p);
    std::cout << "p->i = " << static_cast<A*>(p)->i << std::endl;
    advance_pointer_as<A>(p);
    std::cout << "p->i = " << static_cast<A*>(p)->i << std::endl;
}
于 2011-06-11T04:43:15.740 に答える
6

あなたはすでに正しい答えを受け取っています。これは厳密なエイリアシング ルールに違反しているため、コードの予期しない動作が発生します。あなたの質問のタイトルは、「元のクラスへのポインタのキャストバック」に言及していることに注意してください。実際には、あなたのコードは何かをキャストすることとは何の関係もありません。コードは、ポインターが占有する生のメモリ コンテンツをポインターとして再解釈します。これは「キャストバック」ではありません。これが再解釈です。遠く離れても同じことではありません。void *A *

違いを説明する良い方法は、 andintfloatexample を使用することです。floatとして宣言および初期化された値

float f = 2.0;

cab型にキャスト (明示的または暗黙的に変換)int

int i = (int) f;

期待される結果で

assert(i == 2);

これは確かにキャスト (変換) です。

または、同じ値を値floatとして再解釈することもできますint

int i = (int &) f;

ただし、この場合、 の値はiまったく意味がなく、通常は予測できません。これらの例から、変換とメモリの再解釈の違いが簡単にわかると思います。

再解釈は、まさにコードで行っていることです。この(A *&) p式は、ポインタが占有する生メモリvoid *pを type のポインタとして再解釈したものに他なりませんA *。言語は、これら 2 つのポインター型の表現が同じであり、サイズも同じであることを保証しません。したがって、コードから予測可能な動作を期待することは、上記の(int &) f式が に評価されることを期待するようなもの2です。

ポインターを実際に「キャストバック」する適切な方法は、 ではなくvoid *を行うことです。の結果は確かに元のポインター値であり、ポインター演算によって安全に操作できます。元の値を左辺値として取得する唯一の適切な方法は、追加の変数を使用することです(A *) p(A *&) p(A *) p

A *pa = (A *) p;
...
pa++;
...

(A *&) pまた、キャストで試みたように、「その場で」左辺値を作成する合法的な方法はありません。コードの動作はその例です。

于 2011-06-11T05:38:31.353 に答える
2

他の人がコメントしているように、あなたのコードはうまくいくように見えます。一度だけ (C++ での 17 年以上のコーディングで) 私は、あなたの場合のように、コードと動作をまっすぐに見ているところに出くわしました。デバッガーでコードを実行し、逆アセンブリ ウィンドウを開きました。ちょうど 1 つの命令が欠落していたため、VS2003 コンパイラのバグとしてしか説明できないものを見つけました。関数の先頭 (エラーから 30 行程度) でローカル変数を再配置するだけで、コンパイラは正しい命令を元に戻します。

ポインターを進める限り、次のようにして進めることができます。

p = (char*)p + sizeof( A );

VS2003 から VS2010 までは、それについて不満を言うことはありません。g++ についてはわかりません

于 2011-06-11T04:50:46.183 に答える