在共享库中添加成员变量到class,不会破坏二进制兼容性吗?
Add member variable to class in the shared library, will not break binary compatibility?
我本来想找个例子来表达一下我对二进制兼容性的理解,结果搞砸了。我想通过在class的开头或中间添加成员来改变class的成员在DLL中的布局,并期望无法正确访问该变量
或者访问变量会生成crash.However,一切顺利。我发现,无论我如何将成员变量添加到class的任何位置,都没有崩溃,也没有破坏二进制兼容性。我的代码如下:
//untitled1_global.h
#include <QtCore/qglobal.h>
#if defined(UNTITLED1_LIBRARY)
# define UNTITLED1_EXPORT Q_DECL_EXPORT
#else
# define UNTITLED1_EXPORT Q_DECL_IMPORT
#endif
//base.h
class UNTITLED1_EXPORT Base
{
public:
Base();
double getA();
double getB();
private:
int arr[100]; //Add later to update the DLL
double a;
double b;
};
//derived.h
#include "dpbase.h"
class UNTITLED1_EXPORT Derived : public Base
{
public:
Derived();
void setC(double d);
double getC();
private:
char arrCh[100]; //Add later to update the DLL
double c;
};
下面是客户端代码,base.h
、derived.h
包含的和DLL里的不一样,一个有注释,一个没有。 DLL.I中的实现和声明是分开的,试图直接访问变量和通过函数访问变量(如main.cpp
开头的注解)。
//main.cpp
#include "dpbase.h"
#include "dpbase2.h"
#include <QDebug>
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Base base;
qDebug() << base.getA();
qDebug() << base.getB();
Derived base2;
base2.setC(50);
qDebug() << base2.getC();
return a.exec();
}
其中,classBase
、Derived
是从dll导出的。
无论我如何将成员变量添加到 Base
或 Derived
任何地方,都没有崩溃,也没有破坏二进制兼容性。
我正在使用 qt.There 是
在这里,但对我没有帮助。
此外,我删除了DLL中class的所有成员var,我仍然通过链接DLL在客户端中使用不存在的变量,赋值,获取它......似乎足够space保留在动态库中被客户端重新定义,即使没有成员变量也是defined.So奇怪!
我的问题是,为什么改变DLL中class成员的布局,不会破坏二进制兼容性?删除DLL中class的所有成员var,但为什么调用者可以还在使用 .h 文件中的成员吗?
首先你要了解成员是如何访问的。
我们来
class UNTITLED1_EXPORT Base
{
public:
Base();
double getA();
double getB();
private:
double a;
double b;
};
访问 a
就像访问 *(this + 0)
一样。
当你访问 b
就像做 *(this + 8)
(假设 double
是 8 个字节)。
然后当你改变你时 class 像这样:
class UNTITLED1_EXPORT Base
{
public:
Base();
double getA();
double getB();
private:
int arr[100];
double a;
double b;
};
当您访问 a
时,它将执行 *(this + 400)
,当您访问 b
时,它将执行 *(this + 408)
。
现在这可能是也可能不是问题。如果您仅通过 getA()
和 getB()
访问 a
和 b
并且它们是在您的 DLL 的 .cpp 中定义的,那么您将更新 getter 的定义同时更新 class.
您可以通过内嵌 getter 的定义来创建一些奇怪的行为:
class UNTITLED1_EXPORT Base
{
public:
Base();
double getA() { return a; }
double getB();
private:
double a;
double b;
};
在这种情况下,您的 .exe 可能 有自己的 getA()
和 getB()
副本。
这意味着在您更新 DLL 并添加 int arr[400]
后,aven 您的 .exe 仍将尝试访问现在被 arr
.
占用的 *(this + 0)
这是未定义的行为,但它不会使您的程序崩溃,因为您正在访问分配的内存。
如果你反其道而行之:
- 用
arr
编译你的 exe
- 删除
arr
- 构建 DLL
- 运行.exe
那么你更容易发生崩溃。因为 exe 将尝试访问 this + 400
而 Base
只有 16 个字节。但由于多种原因,它仍然不能保证崩溃。例如 this + 400
可能有效。但更重要的是,它取决于您从何处为 Base
分配内存。
如果您在 .exe 中执行 new Base
,那么即使您更改了 DLL,它也会分配 416 个字节。但是,如果您在 DLL 中执行 new Base
,它将只分配 16 个字节。
这是一个例子。
这是 .exe 的 header:
class Base
{
public:
Base();
double getA() const { return a; }
double getB() const { return b; }
static Base *create();
void print();
private:
double a;
double b;
};
DLLheader
class基地
{
public:
根据();
双 getA() const { return a; }
双 getB() 常量;
静态基础*创建();
无效打印();
私人的:
诠释[400]
双a;
双b;
};
DLL代码:
Base::Base()
{
for (int i = 0 ; i < 100 ; ++i) {
arr[i] = i;
}
a = 3.14;
b = 1.42;
}
double Base::getB() const { return b; }
Base *Base::create()
{
return new Base();
}
void Base::print()
{
std::cout << a << std::endl;
std::cout << b << std::endl;
}
而我的exe中的代码是:
Base *b = Base::create();
std::cout << b->getA() << std::endl;
std::cout << b->getB() << std::endl;
b->print();
通常我应该期望得到输出:
3.14
1.42
3.14
1.42
但实际上我有:
2.122e-314
1.42
3.14
1.42
原因是对于第一行(2.122e-314
),exe在地址this + 0
寻找a
,但是由于这个内存现在被[=35=占用了] 就像我们会做的那样:std::cout << *((double *)arr)
。
b
的值不受影响,因为 b
始终从 DLL 中读取,因为 getB()
未内联。
现在,如果我将 Base *b = Base::create();
更改为 Base *b = new B();
,程序就会崩溃,因为 new
是从 .exe 完成的,它只会分配 16 个字节,而 Base()
会跨 416 个字节访问其所有成员变量。
备注
为了这个答案的目的,我做了指针运算,假设增量总是 1。实际上它不是。
所以当我写this + 8
时,在C++中要理解为reinterpret_cast<double *>(reinterpret_cast<char *>(this) + 8)
。
我本来想找个例子来表达一下我对二进制兼容性的理解,结果搞砸了。我想通过在class的开头或中间添加成员来改变class的成员在DLL中的布局,并期望无法正确访问该变量 或者访问变量会生成crash.However,一切顺利。我发现,无论我如何将成员变量添加到class的任何位置,都没有崩溃,也没有破坏二进制兼容性。我的代码如下:
//untitled1_global.h
#include <QtCore/qglobal.h>
#if defined(UNTITLED1_LIBRARY)
# define UNTITLED1_EXPORT Q_DECL_EXPORT
#else
# define UNTITLED1_EXPORT Q_DECL_IMPORT
#endif
//base.h
class UNTITLED1_EXPORT Base
{
public:
Base();
double getA();
double getB();
private:
int arr[100]; //Add later to update the DLL
double a;
double b;
};
//derived.h
#include "dpbase.h"
class UNTITLED1_EXPORT Derived : public Base
{
public:
Derived();
void setC(double d);
double getC();
private:
char arrCh[100]; //Add later to update the DLL
double c;
};
下面是客户端代码,base.h
、derived.h
包含的和DLL里的不一样,一个有注释,一个没有。 DLL.I中的实现和声明是分开的,试图直接访问变量和通过函数访问变量(如main.cpp
开头的注解)。
//main.cpp
#include "dpbase.h"
#include "dpbase2.h"
#include <QDebug>
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Base base;
qDebug() << base.getA();
qDebug() << base.getB();
Derived base2;
base2.setC(50);
qDebug() << base2.getC();
return a.exec();
}
其中,classBase
、Derived
是从dll导出的。
无论我如何将成员变量添加到 Base
或 Derived
任何地方,都没有崩溃,也没有破坏二进制兼容性。
我正在使用 qt.There 是
此外,我删除了DLL中class的所有成员var,我仍然通过链接DLL在客户端中使用不存在的变量,赋值,获取它......似乎足够space保留在动态库中被客户端重新定义,即使没有成员变量也是defined.So奇怪!
我的问题是,为什么改变DLL中class成员的布局,不会破坏二进制兼容性?删除DLL中class的所有成员var,但为什么调用者可以还在使用 .h 文件中的成员吗?
首先你要了解成员是如何访问的。
我们来
class UNTITLED1_EXPORT Base
{
public:
Base();
double getA();
double getB();
private:
double a;
double b;
};
访问 a
就像访问 *(this + 0)
一样。
当你访问 b
就像做 *(this + 8)
(假设 double
是 8 个字节)。
然后当你改变你时 class 像这样:
class UNTITLED1_EXPORT Base
{
public:
Base();
double getA();
double getB();
private:
int arr[100];
double a;
double b;
};
当您访问 a
时,它将执行 *(this + 400)
,当您访问 b
时,它将执行 *(this + 408)
。
现在这可能是也可能不是问题。如果您仅通过 getA()
和 getB()
访问 a
和 b
并且它们是在您的 DLL 的 .cpp 中定义的,那么您将更新 getter 的定义同时更新 class.
您可以通过内嵌 getter 的定义来创建一些奇怪的行为:
class UNTITLED1_EXPORT Base
{
public:
Base();
double getA() { return a; }
double getB();
private:
double a;
double b;
};
在这种情况下,您的 .exe 可能 有自己的 getA()
和 getB()
副本。
这意味着在您更新 DLL 并添加 int arr[400]
后,aven 您的 .exe 仍将尝试访问现在被 arr
.
*(this + 0)
这是未定义的行为,但它不会使您的程序崩溃,因为您正在访问分配的内存。
如果你反其道而行之:
- 用
arr
编译你的 exe
- 删除
arr
- 构建 DLL
- 运行.exe
那么你更容易发生崩溃。因为 exe 将尝试访问 this + 400
而 Base
只有 16 个字节。但由于多种原因,它仍然不能保证崩溃。例如 this + 400
可能有效。但更重要的是,它取决于您从何处为 Base
分配内存。
如果您在 .exe 中执行 new Base
,那么即使您更改了 DLL,它也会分配 416 个字节。但是,如果您在 DLL 中执行 new Base
,它将只分配 16 个字节。
这是一个例子。
这是 .exe 的 header:
class Base
{
public:
Base();
double getA() const { return a; }
double getB() const { return b; }
static Base *create();
void print();
private:
double a;
double b;
};
DLLheader
class基地 { public: 根据(); 双 getA() const { return a; } 双 getB() 常量; 静态基础*创建(); 无效打印(); 私人的: 诠释[400] 双a; 双b; };
DLL代码:
Base::Base()
{
for (int i = 0 ; i < 100 ; ++i) {
arr[i] = i;
}
a = 3.14;
b = 1.42;
}
double Base::getB() const { return b; }
Base *Base::create()
{
return new Base();
}
void Base::print()
{
std::cout << a << std::endl;
std::cout << b << std::endl;
}
而我的exe中的代码是:
Base *b = Base::create();
std::cout << b->getA() << std::endl;
std::cout << b->getB() << std::endl;
b->print();
通常我应该期望得到输出:
3.14
1.42
3.14
1.42
但实际上我有:
2.122e-314
1.42
3.14
1.42
原因是对于第一行(2.122e-314
),exe在地址this + 0
寻找a
,但是由于这个内存现在被[=35=占用了] 就像我们会做的那样:std::cout << *((double *)arr)
。
b
的值不受影响,因为 b
始终从 DLL 中读取,因为 getB()
未内联。
现在,如果我将 Base *b = Base::create();
更改为 Base *b = new B();
,程序就会崩溃,因为 new
是从 .exe 完成的,它只会分配 16 个字节,而 Base()
会跨 416 个字节访问其所有成员变量。
备注
为了这个答案的目的,我做了指针运算,假设增量总是 1。实际上它不是。
所以当我写this + 8
时,在C++中要理解为reinterpret_cast<double *>(reinterpret_cast<char *>(this) + 8)
。