C中的结构相似性
struct similarity in C
考虑下面的两个 struct
:
struct A {
double x[3];
double y[3];
int z[3];
struct A *a;
int b;
struct A *c;
unsigned d[10];
};
struct B {
double x[3];
double y[3];
int z[3];
};
请注意 struct B
是 struct A
的严格子集。现在,我想将成员 .x
、.y
和 .z
从 struct A
的实例复制到 struct B
的实例。我的问题是:根据标准,这样做是否有效:
struct A s_a = ...;
struct B s_b;
memcpy(&s_b, &s_a, sizeof s_b);
即是否保证成员的填充按出现顺序相同,这样我就可以 "partially" memcpy
struct A
到 struct B
?
如何使用 struct B
作为 struct A
的 anonymous struct 成员?但是,这需要 -fms-extensions
用于 gcc(顾名思义,VC 应该有类似的扩展名):
struct B {
double x[3];
double y[3];
int z[3];
};
struct A {
struct B;
struct A *a;
int b;
struct A *c;
unsigned d[10];
};
这允许使用 struct A
中的字段,例如:
struct A as;
as.x[2] = as.y[0];
等这保证了相同的布局(标准不允许在结构的开头进行填充,因此内部结构保证从与外部相同的地址开始)并且 struct A
与 struct B
兼容。
还有:
struct A as;
struct B bs;
memcpy(&as, &bs, sizeof(bs));
不保证 struct A
的布局与 struct B
的布局相同。
但是,当且仅当他们都是工会成员时:
union X
{
struct A a;
struct B b;
};
then it is guaranteed公共初始序列具有相同的布局。
我从未听说过任何编译器会在检测到结构是联合的成员时以不同的方式布置结构,所以在实践中你应该是安全的!
我认为标准不会禁止实现在 s_a
中包含比 s_b
多得多的填充,以至于前者实际上更大,即使其成员是 [=12] 的子集=]的。这样的行为会很奇怪,我想不出编译器为什么会这样做,但我认为这不会被禁止。
如果复制的字节数是sizeof s_a
和sizeof s_b
中的较小者,那么memcpy
操作将保证复制所有公共字段,但不一定保持 s_b
后面的字段不受干扰。在典型的机器上,如果声明是:
struct A { uint32_t x; char y; };
struct B { uint32_t x; char y,p; uint16_t q; };
第一个结构将包含五个字节的数据和三个字节的填充,而第二个将包含八个字节的数据,没有填充。如您的代码所示,使用 memcpy
会将 s_a
中的填充复制到 s_b
中的数据。
如果您需要在不影响结构平衡的情况下复制初始结构成员,您应该计算最后一个感兴趣成员的添加偏移量和大小,并将其用作要复制的字节数。在我上面给出的示例中,y
的偏移量为 4,大小为 1,因此 memcpy
将忽略在 [=23= 中用作填充的结构部分] 但可能会在 B
.
中保存数据
考虑下面的两个 struct
:
struct A {
double x[3];
double y[3];
int z[3];
struct A *a;
int b;
struct A *c;
unsigned d[10];
};
struct B {
double x[3];
double y[3];
int z[3];
};
请注意 struct B
是 struct A
的严格子集。现在,我想将成员 .x
、.y
和 .z
从 struct A
的实例复制到 struct B
的实例。我的问题是:根据标准,这样做是否有效:
struct A s_a = ...;
struct B s_b;
memcpy(&s_b, &s_a, sizeof s_b);
即是否保证成员的填充按出现顺序相同,这样我就可以 "partially" memcpy
struct A
到 struct B
?
如何使用 struct B
作为 struct A
的 anonymous struct 成员?但是,这需要 -fms-extensions
用于 gcc(顾名思义,VC 应该有类似的扩展名):
struct B {
double x[3];
double y[3];
int z[3];
};
struct A {
struct B;
struct A *a;
int b;
struct A *c;
unsigned d[10];
};
这允许使用 struct A
中的字段,例如:
struct A as;
as.x[2] = as.y[0];
等这保证了相同的布局(标准不允许在结构的开头进行填充,因此内部结构保证从与外部相同的地址开始)并且 struct A
与 struct B
兼容。
还有:
struct A as;
struct B bs;
memcpy(&as, &bs, sizeof(bs));
不保证 struct A
的布局与 struct B
的布局相同。
但是,当且仅当他们都是工会成员时:
union X
{
struct A a;
struct B b;
};
then it is guaranteed公共初始序列具有相同的布局。
我从未听说过任何编译器会在检测到结构是联合的成员时以不同的方式布置结构,所以在实践中你应该是安全的!
我认为标准不会禁止实现在 s_a
中包含比 s_b
多得多的填充,以至于前者实际上更大,即使其成员是 [=12] 的子集=]的。这样的行为会很奇怪,我想不出编译器为什么会这样做,但我认为这不会被禁止。
如果复制的字节数是sizeof s_a
和sizeof s_b
中的较小者,那么memcpy
操作将保证复制所有公共字段,但不一定保持 s_b
后面的字段不受干扰。在典型的机器上,如果声明是:
struct A { uint32_t x; char y; };
struct B { uint32_t x; char y,p; uint16_t q; };
第一个结构将包含五个字节的数据和三个字节的填充,而第二个将包含八个字节的数据,没有填充。如您的代码所示,使用 memcpy
会将 s_a
中的填充复制到 s_b
中的数据。
如果您需要在不影响结构平衡的情况下复制初始结构成员,您应该计算最后一个感兴趣成员的添加偏移量和大小,并将其用作要复制的字节数。在我上面给出的示例中,y
的偏移量为 4,大小为 1,因此 memcpy
将忽略在 [=23= 中用作填充的结构部分] 但可能会在 B
.