9

私のプログラムは、動的呼び出しの状況でデータまたはオブジェクトを転送するために void* を利用する必要があるため、プリミティブ型であっても任意の型のデータを参照できます。しかし、最近、複数の基本クラスを持つクラスの場合にこれらの void* をダウンキャストするプロセスが失敗し、メモリアドレスが正しいように見えても、これらのダウンキャストされたポインターでメソッドを呼び出した後にプログラムがクラッシュすることさえあることを発見しました。「vtable」へのアクセス中にクラッシュが発生します。

そこで、小さなテスト ケースを作成しました。環境は Mac OS X の gcc 4.2 です。

class Shape {
public:
    virtual int w() = 0;
    virtual int h() = 0;
};

class Square : public Shape {
public:
    int l;
    int w() {return l;}
    int h() {return l;}
};

class Decorated {
public:
    int padding;
    int w() {return 2*padding;}
    int h() {return 2*padding;}
};

class DecoratedSquare : public Square, public Decorated {
public:
    int w() {return Square::w() + Decorated::w();}
    int h() {return Square::h() + Decorated::h();}
};


#include <iostream>

template <class T> T shape_cast(void *vp) {
//    return dynamic_cast<T>(vp);   // not possible, no pointer to class type
//    return static_cast<T>(vp);
//    return T(vp);
//    return (T)vp;
    return reinterpret_cast<T>(vp);
}

int main(int argc, char *argv[]) {
    DecoratedSquare *ds = new DecoratedSquare;
    ds->l = 20;
    ds->padding = 5;
    void *dsvp = ds;

    std::cout << "Decorated (direct)" << ds->w() << "," << ds->h() << std::endl;

    std::cout << "Shape " << shape_cast<Shape*>(dsvp)->w() << "," << shape_cast<Shape*>(dsvp)->h() << std::endl;
    std::cout << "Square " << shape_cast<Square*>(dsvp)->w() << "," << shape_cast<Square*>(dsvp)->h() << std::endl;
    std::cout << "Decorated (per void*) " << shape_cast<Decorated*>(dsvp)->w() << "," << shape_cast<Decorated*>(dsvp)->h() << std::endl;
    std::cout << "DecoratedSquare " << shape_cast<DecoratedSquare*>(dsvp)->w() << "," << shape_cast<DecoratedSquare*>(dsvp)->h() << std::endl;
}

次の出力が生成されます。

Decorated (direct)30,30
Shape 30,30
Square 30,30
Decorated (per void*) 73952,73952
DecoratedSquare 30,30

ご覧のとおり、「Decorated (per void*)」の結果は完全に間違っています。また、最初の行のように 30,30 にする必要があります。

shape_cast() でどのキャスト方法を使用しても、装飾されたパーツに対して常に同じ予期しない結果が得られます。これらの void * には完全に問題があります。

私のC++の理解から、これは実際に機能するはずです。これを void* で動作させる機会はありますか? これは gcc のバグでしょうか?

ありがとう

4

3 に答える 3

14

reinterpret_cast10 回繰り返します。ポインターに対して安全にできる唯一のことはreinterpret_cast、元の同じポインター型に戻すことです。同じことが への変換にも当てはまりvoid*ます。元の型に戻す必要があります。

したがって、 を にキャストした場合は、にキャストDecoratedSquare*void*直す必要がありますDecoratedSquare*。違うDecorated*、違うSquare*、違うShape*。それらのいくつかはあなたのマシンで動作するかもしれませんが、これは幸運と実装固有の動作の組み合わせです. 通常は単一継承で動作します。動作を停止する方法でオブジェクト ポインターを実装する明らかな理由はないためですが、これは保証されておらず、多重継承では一般に動作しません。

あなたのコードは、void* を介して「プリミティブ型を含む任意の型」に​​アクセスすると言います。これには何の問題もありません。おそらく、データを受け取る人は誰でも、それを としてではDecoratedSquare*なく として扱うことを知っているでしょうint*

それを受け取った人がそれを のような基本クラスとして扱うことしか知らない場合Decorated*、それを に変換する人は誰でも、最初にそれを基本クラスに変換し、次に に変換するvoid*必要があります。static_castvoid*

void *decorated_vp = static_cast<Decorated*>(ds);

にキャストdecorated_vpバックするとDecorated*、必要な の結果が得られますstatic_cast<Decorated*>(ds)

于 2010-03-04T13:37:08.867 に答える
9

これはコンパイラのバグでreinterpret_castはありません。オブジェクトは、DecoratedSquare次のようにメモリに配置されます。

Square
Decorated
DecoratedSquare specific stuff

this へのポインターを に変換するvoid*と、このデータの開始アドレスが得られますが、そこにどのような型があるかはわかりません。reinterpret_cast<Decorated*>そのアドレスを取得し、そこにあるものをDecorated- として解釈しますが、実際のメモリの内容はSquare. これは間違っているため、未定義の動作が発生します。

reinterpret_cast正しい動的タイプ (つまりDecoratedSquare) に変換してから基本クラスに変換すると、正しい結果が得られるはずです。

于 2010-03-04T13:23:10.420 に答える
2

多重継承が存在する場合の static_cast または dynamic_cast は、正しいアドレスを指定するようにポインターをオフセットすることにより、ポインターの表現を変更する場合があります。static_cast は、静的型付け情報を考慮して正しいオフセットを決定します。dynamic_cast は、動的タイプをチェックすることによってそれを行います。void* をスローすると、すべての静的型付け情報と動的型付け情報を取得する可能性が失われるため、使用している reinterpret_cast はオフセットが null であると想定し、何度か失敗します。

于 2010-03-04T13:16:37.143 に答える