有时我对结构进行指针运算,我不确定它是否是定义的行为。
如果我有这个结构
typedef struct SCustomData{
uint32_t a;
uint32_t b;
}CustomData;
有时对我来说,不必不断地输入结构的不同字段,而是给它一个恒定的大小并适合我需要的任何内容。
我的第一个问题是:结构的地址也是结构中第一个变量的地址是否定义了行为?
int main(){
CustomData cd = {0};
cd.a = 0xDEADBEEF;
uint32_t a = 0;
memcpy(&a,&cd,sizeof(uint32_t));
assert(a==0xDEADBEEF);
return 0;
}
这会永远成功吗?
我的下一个问题是,只要我知道每个成员的大小,我可以使用指针算法来访问结构的任何成员吗?
int main(){
CustomData cd = {0};
cd.b = 0xABCD0000;
uint16_t highshort = *(uint16_t*)((uint8_t*)&cd+sizeof(uint32_t)+sizeof(uint16_t));
assert(highshort == 0xABCD);
uint8_t highbyte = *(uint8_t*)((uint8_t*)&cd+sizeof(uint32_t)+sizeof(uint16_t)+sizeof(uint8_t));
assert(highbyte == 0xAB);
return 0;
}
除了可能看起来丑陋或糟糕的代码实践之外,至少是定义的行为吗?我应该避免吗?
如果我不使用指针转换而是使用 memcpy ,这仍然是一个坏主意吗?
int main(){
CustomData cd = {0};
cd.b = 0xABCD0000;
uint16_t highshort = 0;
memcpy(&highshort, ((uint8_t*)&cd)+sizeof(uint32_t)+sizeof(uint16_t), sizeof(uint16_t));
assert(highshort == 0xABCD);
return 0;
}
回答1
第一个问题
它实际上是在 C 标准中定义的! 6.7.2.1
部分(在本https://www.open-std.org/jtc1/sc22/WG14/www/docs/n1570.pdf中)
15 在结构对象中,非位域成员和位域所在的单元的地址按声明顺序递增。一个指向结构对象的指针,经过适当的转换,指向它的初始成员(或者如果该成员是位域,则指向它所在的单元),反之亦然。结构对象中可能有未命名的填充,但不是在其开头。
第二个问题
它不是根据标准定义的(同样是 6.7.2.1
部分):
结构或联合对象的每个非位域成员都以适合其类型的实现定义的方式对齐。
但是,大多数编译器以相同的方式对齐(至少按架构和位数)
为什么这仍然是个坏主意
可维护性 - 如果您自己使用成员,您的代码将更容易维护。它不会依赖于编译器和位数,并且不会对代码中的更改(例如更改类型或重新排序成员)敏感。
可读性 - 你的意图当然会更清晰。