注意:这不是C ++问题,我不能使用C ++编译器,只能使用C99。
这是有效的(可接受的,漂亮的)代码吗?
typedef struct sA{
int a;
} A;
typedef struct aB{
struct sA a;
int b;
} B;
A aaa;
B bbb;
void init(){
bbb.b=10;
bbb.a.a=20;
set((A*)&bbb);
}
void set(A* a){
aaa=*a;
}
void useLikeB(){
printf("B.b = %d", ((B*)&aaa)->b);
}
简而言之,当我需要指定的行为时,将“子类”强制转换为“超类”并重铸“超类”到“子类”是否有效?
由于
答案 0 :(得分:5)
首先,C99标准允许您将任何结构指针强制转换为指向其第一个成员的指针,另一种方式( 6.7.2.1结构和联合说明符):
13在结构对象中,非位字段成员和位域所在的单元具有按声明顺序增加的地址。指向适当转换的结构对象的指针指向其初始成员(或者如果该成员是位字段,则指向它所在的单元),反之亦然。在结构对象中可能有未命名的填充,但不是在它的开头。
换句话说,在您的代码中,您可以自由地使用:
B*
转换为A*
- 它将始终正常运行,A*
转换为B*
- 但如果它实际上没有指向B
,那么您将获得访问其他成员的随机失败,A*
的结构分配给A
- 但如果指针是从B*
转换而来,则只会分配公共成员,B
的其余成员将被忽略,B*
的结构分配给A
- 但您必须先转换指针,然后注意(3)。因此,您的示例几乎正确。但是useLikeB()
无法正常工作,因为aaa
是A
类型的结构,您指定的结构如第(4)节所述。这有两个结果:
B
成员实际上不会被复制到aaa
(如(3)所述),A
B
,而不是A
(您正在访问不存在的成员,如(2)中所述)。要以更实际的方式解释这一点,当您声明A
编译器保留保留B
所有成员所需的内存量时。 A
拥有更多成员,因此需要更多内存。由于B
是常规变量,因此无法在运行时更改其大小,因此无法保留{{1}}的其余成员。
作为一个注释,通过(1)你实际上可以指向成员而不是转换更好的指针,它将允许你访问任何成员,而不仅仅是第一个成员。但请注意,在这种情况下,相反的情况将不再起作用!
答案 1 :(得分:2)
我认为这很脏并且相对危险。你想用这个来实现什么?也不能保证aaa是B,它也可能是A.所以当有人称之为“uselikeB”时,它可能会失败。同样取决于体系结构“int a”和“指向struct a的指针”可能正确或不正确重叠,并且当您分配给“int a”然后访问“struct a”时可能会导致有趣的事情发生
答案 2 :(得分:0)
你为什么要这样做?具有
set((A*)&bbb);
比正确的
更难写set(&bbb.a);
您在此处发帖时应该避免的其他事项:
set
aaa=a
应为aaa = *a
答案 3 :(得分:0)
首先,我同意以前海报中关于这项任务安全的大多数问题。
话虽如此,如果你需要走那条路,我会添加一个间接级别和一些类型安全检查器。
static const int struct_a_id = 1;
static const int struct_b_id = 2;
struct MyStructPtr {
int type;
union {
A* ptra;
B* ptrb;
//continue if you have more types.
}
};
这个想法是你通过传递一个包含一些“类型”信息的结构来管理你的指针。您可以在描述类树的一侧构建一个类树(请注意,考虑到安全构建的限制,可以使用树来表示)并且能够回答问题以确保您正确地上下构建结构。所以你的“useLikeB”函数可以这样写。
MyStructPtr the_ptr;
void init_ptr(A* pa)
{
the_ptr.type = struct_a_id
the_ptr.ptra = pa;
}
void useLikeB(){
//This function should FAIL IF aaa CANT BE SAFELY CASTED TO B
//by checking in your type tree that the a type is below the
//a type (not necesarily a direct children).
assert( is_castable_to(the_ptr.type,struct_b_id ) );
printf("B.b = %d", the_ptr.ptrb->b);
}
我的2美分。