C++ 更新动态库后强制转换基类指针遇到的坑

这两天被一个 bug 折磨得不轻,发新版本更新了 caffe-output 动态库,结果发现某一 layer 中一个参数读出来是随机数,模型预测结果总是不对。最后一点一点找 diff,发现是发布时打包的 caffe 动态库有错误,新版本 caffe 基类 layer 增加了这个参数作为成员变量,而老版本 caffe 动态库中没有这个成员变量。

直接看下面的例子吧=_=_

假设下面是老版本的库实现(注意老版本没有定义 b 这个成员):

// a.h

#ifndef _TEST_CAST_A_H
#define _TEST_CAST_A_H

class A {
public:
  A();
  virtual ~A() {}

  int a;
  // int b;  // 老版本没有定义 b 这个成员
  int c;
};

class B : public A {
public:
  B();
  ~B() {}
};

#endif
// a.cpp

#include "a.h"

A::A() {

}

B::B() {
  a = 1;
  // b = 2;
  c = 3;
}

而新版本基类新定义了 b 这个成员:

// a.h

#ifndef _TEST_CAST_A_H
#define _TEST_CAST_A_H

class A {
public:
  A();
  virtual ~A() {}

  int a;
  int b;
  int c;
};

class B : public A {
public:
  B();
  ~B() {}
};

#endif
// a.cpp

#include "a.h"

A::A() {

}

B::B() {
  a = 1;
  b = 2;
  c = 3;
}

我们再写一个 main.cpp 来测试一下:

// main.cpp

#include "a.h"

#include <iostream>

int main() {
  A *ptr_a = NULL;
  B *ptr_b = NULL;

  ptr_b = new B();

  ptr_a = static_cast<B*>(ptr_b);
  std::cout << ptr_a->a << std::endl;
  std::cout << ptr_a->b << std::endl;
  std::cout << ptr_a->c << std::endl;

  delete ptr_b;

  return 0;
}

完整起见,来个 CMakeLists.txt

project(test_cast)

add_library(a SHARED a.cpp)

add_executable(main main.cpp)

target_link_libraries(main a)

好了,我们先用老版本编译生成 liba.so,然后再用新版本生成可执行程序 main,接着用老版本的 liba.so 替换新版本的 liba.so,输出结果为:

1
3
-65534

可以看到,由于老版本 A 中按顺序定义了 ac,新版本的成员 b 的地址实际上是老版本的 c,而老版本没有第三个成员,因此出现了错误的结果。

错误并不复杂,不过耗费了大半周的时间,真的是

The devil is in the detail.