2009年07月24日

C++ ではキャストでアドレスが変わる場合があります

このごろは組み込みソフトウェア開発でも C++ が使われるようになってきました。C から C++ に移行する際、おそらく誰もが一度は引っかかるのではないかという C++ の挙動について書きます。

C++ では、多重継承した class(struct) のアドレスを扱う際には注意する必要があります。
#include <stdio.h>

struct A {
    int a;
};

struct B {
    int b;
};

struct C : public A, public B {
};

int main()
{
    C *c = new C();
    B *b = c;
    A *a = c;

    printf("C = %p\n", static_cast<void*>(c));
    printf("B = %p\n", static_cast<void*>(b));
    printf("A = %p\n", static_cast<void*>(a));
    return 0;
}
このプログラムの出力結果は、C と A のアドレスは等しいのですが、B だけ異なります。これは、C++ が多重継承を実装するために、キャストのたびにアドレスをずらすからです。class C (厳密には struct ですが、デフォルトの公開属性が異なるだけで、実質同じです) の実体は、class A の実体と class B の実体を並べたようなレイアウトでメモリ上に配置されます。そのため class B として参照する際には、class A のメンバーと vtbl のサイズの合計ぶんだけアドレスがずれる(そのようなコードをコンパイラが生成する)わけです。

そのため C の感覚で、うっかり void* や intptr_t などにオブジェクトのアドレスを格納したりすると、おかしな挙動に悩まされる場合があります。

Embedded C++ ではそもそも多重継承が存在しませんし、コーディング規約などで禁止されている場合も多いと思われますので、組み込みプログラミングにおいてはあまり意識する必要が無いことかもしれませんが… はまるとやっかいな場合もあるかもしれません。

追記 : @ssrisk さんにコメントをいただきました。

http://twitter.com/sscrisk/status/2812305646
http://twitter.com/sscrisk/status/2813260721

確かに手元の S&G の p.426 に、「p変換 引数は void * 型でなければならない」と書いてあります。コードを修正しました。static_cast<type>(expr) は、安全なキャストが可能な場合のみキャストが成功し、駄目な場合はコンパイルエラーになるので安心です。

そもそも C++ で printf を使うなという話でありますが、ある意味これは C の void * を要求する関数に class へのポインタを渡した場合のサンプルでもありますね。(言い訳 汗)

トラックバックURL

コメントする

名前
 
  絵文字
 
 
記事検索
最新コメント
「最新トラックバック」は提供を終了しました。
アクセスカウンター
  • 今日:
  • 昨日:
  • 累計:

QRコード
QRコード