我试图转换一个结构来访问它的第一个成员,但是结构的第一个成员的值正在被更改/弄乱。
代码如下:
typedef struct ObjectBase
{
int integer1;
int integer2;
}ObjectBase;
typedef struct ObjectExtended
{
ObjectBase* baseObj;
char* string;
}ObjectExtended;
int main(int argc,char** argv)
{
void* voidObject = malloc(sizeof(ObjectExtended));
ObjectExtended* objExtended = voidObject;
objExtended->string = "TEST_OBJECT";
objExtended->baseObj = malloc(sizeof(ObjectBase));
objExtended->baseObj->integer1 = 10;
objExtended->baseObj->integer2 = 11;
printf("Extended Object:\n");
printf("\tString: %s\n",objExtended->string);
printf("\tInt1: %i\n",objExtended->baseObj->integer1);
printf("\tInt2: %i\n",objExtended->baseObj->integer2);
ObjectBase* objBase = voidObject;
printf("Base Object:\n");
printf("\tInt1: %i\n",objBase->integer1);
printf("\tInt2: %i\n",objBase->integer2);
free(objExtended->baseObj);
free(objExtended);
return 0;
}
输出如下:
Extended Object:
String: TEST_OBJECT
Int1: 10
Int2: 11
Base Object:
Int1: 166544
Int2: 6
166544&6是从哪里来的?
最佳答案
您使用的是“虚拟继承”,因此ObjectExtended
不包含ObjectBase
作为其第一个成员,而是指向一个成员的指针。当您将ObjectExtended *
转换为ObjectBase *
时,您开始将该指针的高位字节和低位字节解释为ObjectBase
的整数(假设sizeof(int *) == 2 * sizeof(int)
)。
要解决这个问题,可以使用“非虚拟继承”,即声明
typedef struct ObjectExtended
{
ObjectBase baseObj; /* not a pointer */
char* string;
} ObjectExtended;
不管怎样,这可能是最好的设计,或者改变你的铸造逻辑:
#define UPCAST(EXTPTR) ((EXTPTR)->baseObj)
与C++中的
ObjectExtended
不同(在编写代码时,您最终会考虑到),在C中抛出指针只需指示编译器重新解释指针指向的任何内存位置作为新类型的对象。如果dynamic_cast
的第一个成员是基对象,这就没问题,因为基对象和扩展对象都从同一个地址开始但是在原始代码中,您需要的不仅仅是对指针的目的地的重新解释。基本对象的地址不是扩展对象的地址,而是不同的地址。也就是说,它存储在struct
指针中您可以看到,它们的地址不能与您使用两个不同的baseObj
调用创建的地址相同。为了使事情更清楚,下面是对象的内存布局:
+----------------+<--+ + +
| integer1 | | | sizeof (int) | sizeof
+----------------+ | + | (ObjectBase)
| integer2 | | | sizeof (int) |
+----------------+ | + +
|
+----------------+ | + +
| baseObj |---+ | sizeof (ObjectBase *) | sizeof
| | | | (ObjectExtended)
+----------------+ + |
| string |---+ | sizeof (char *) |
| | | | |
+----------------+ | + +
V
somewhere else ...
这两个物体之间的间隙当然不能按比例缩小。两个
malloc
调用可以按任意顺序返回任何地址*如果
malloc
被声明为上述内容(“非虚拟”),您将得到以下布局:+----------------+ + + +
| integer1 | | sizeof (int) | | sizeof
+----------------+ + | | (ObjectBase)
| integer2 | | sizeof (int) | |
+----------------+ + | +
| string |---+ | sizeof (char *) | sizeof
| | | | | (ObjectExtended)
+----------------+ | + +
V
somewhere else ...
在这两幅图中,编译器可能已经向对象添加了额外的填充字节,以使它们在内存中按照目标体系结构的要求对齐。
*这与使用C++的内置支持继承的语言形成对比,其中使用虚拟继承不会导致子对象的动态内存分配。相反,C++编译器将计算两个内存需求的总和,然后保留足够的堆栈空间来容纳整个对象,或者在自由存储中分配所需的内存量(如果使用操作符
ObjectExtended
创建对象)。您可以在C中使用new
完成相同的操作,然后自己计算内存布局。但是你必须更加小心不要违反对齐规则。