2013年8月27日 星期二

C++ 指標轉型後位置可能會改變

今天踩到一個和 C++ 指標轉型相關的 bug, 幸好以前看《深度探索 C++ 物件模型》時有個模糊的印象, 知道物件指標轉型後, 位置可能會變, 發現這問題後很快就想通了。

先寫個小程式實驗一下:

#include <iostream>

struct A { int x; };
struct B { int y; };
struct C : public A, B { int z; };

int main() {
  C *c = new C();
  A *a = c;
  B *b = c;
  void* v = c;
  std::cout << a << " " << b << " " << c << " " << v << std::endl;
  return 0;
}

範例輸出:

0xbd0010 0xbd0014 0xbd0010 0xbd0010

由此可得知 a、c、v 位置一樣, 但 b 不同

若用指標相等做邏輯判斷, 要留意轉型帶來的影響, 特別是中間有轉型成 void* 的時候, 可能傳了同樣的物件, 卻因型別不同造成誤判。

以下是一個示意的例子:

class Foo final
{
public:
  void Save(const Data& data, void* source);
  ...

private:
  ...
  FooCallback *m_callback;
}

void Foo::Save(const Data& data, void* source)
{
  ...
  if (m_callback && source != m_callback) {
    m_callback->OnSave(data);
  }
}

有不同的來源會呼叫 Foo::Save, 此時 Foo 會通知事先註冊的 callback, 但要避開 callback object 主動呼叫 Foo::Save 的情況。

乍看之下有檢查 source != m_callback 即可安心。但若 callback object (即有繼承 FooCallback 的類別的物件) 呼叫 Foo::Save 時這麼寫:

Foo *foo = new Foo();
...
foo->Save(data, this);

有可能因指標型別不同, 而得到不同的位置, 導致 source != m_callback 為真。

反之, 這樣的寫法才能安全過關:

foo->Save(data, (FooCallback*)this);

C++ 的世界真是處處驚奇啊!

2 則留言:

  1. struct C 同時有 A, B 兩個部份, 而它的 B 部份又剛好放在 A 後面, 如此而已 ...


    ref.《深度探索C++ 物件模型》

    回覆刪除
  2. 《深度探索C++ 物件模型》真是好書, 就是有看過它, 遇到的時候才沒有大叫「搞什麼鬼~~~」XD

    回覆刪除

在 Fedora 下裝 id-utils

Fedora 似乎因為執行檔撞名,而沒有提供 id-utils 的套件 ,但這是使用 gj 的必要套件,只好自己編。從官網抓好 tarball ,解開來編譯 (./configure && make)就是了。 但編譯後會遇到錯誤: ./stdio.h:10...