我的Objective-C项目中有一些直接的C代码。在我正在使用的一个API中,我可以使用一个带有32位整数参数的函数注册回调:
void Callback(Packet* packet, int32_t port);
我希望能够发送回调两个16位端口而不是一个32位端口。当然,我可以使用按位运算,但我更喜欢更明确的东西。
这是我目前使用union的解决方案:
typedef struct {
int16_t port1;
int16_t port2;
} MultiPortStruct;
typedef union {
int32_t port;
MultiPortStruct portStruct;
} MultiPortAdaptor;
发件人:
void registerCallback(int16_t port1, int16_t port2) {
MultiPortAdaptor adaptor;
adaptor.portStruct.port1 = port1;
adaptor.portStruct.port2 = port2;
int32_t port = adaptor.port
RegisterAPICallback(&myCallback, port);
}
在回调中:
void myCallback(Packet* packet, int32_t port) {
MultiPortAdaptor adaptor;
adaptor.port = port;
int16_t port1 = adaptor.portStruct.port1;
int16_t port2 = adaptor.portStruct.port2;
// do stuff
}
这种方法是正确的还是存在问题? (例如:我应该将适配器联合清零吗?可以访问同一联盟的不同成员吗?)有更简单的方法吗?
更新
好的,我确信:即使这样做是可能的,也许这不是一个好主意。我决定只使用一组函数为我做按位操作:
int32_t SubPortsToPort(int16_t port1, int16_t port2)
int16_t PortToSubPort1(int32_t port)
int16_t PortToSubPort2(int32_t port)
所以现在我所要做的就是:
void registerCallback(int16_t port1, int16_t port2) {
int32_t port = SubPortsToPort(port1, port2);
RegisterAPICallback(&myCallback, port);
}
在回调中:
void myCallback(Packet* packet, int32_t port) {
int16_t port1 = PortToSubPort1(port);
int16_t port2 = PortToSubPort2(port);
// do stuff
}
减少代码,减少后顾之忧!
答案 0 :(得分:3)
首先可以存储到一个成员并从同一个union
提供的另一个成员读取只读取你写的字节 - 其他任何人都有未指定的值。
您正在做的问题是编译器可能会在字段之间引入填充,这会破坏您精心安排的重叠。使用当前的编译器和cpu架构你可能会没问题,但没有必要把它留给机会,因为有很多方法来修复字段的对齐。
最简单但依赖于编译器的方法是使用packed
属性:
typedef struct __attribute((packed))
{
int16_t port1;
int16_t port2;
} MultiPortStruct;
这只是指示编译器不要添加任何填充,因此即使在使用2字节值的4字节对齐的体系结构上,上面也总是32位。
Clang,GCC& amp; LLVM(可能不是所有版本?) - 编译器会告诉你它是否没有。如果您使用的是不支持该属性(或等效属性)的编译器,那么您可以考虑使用C语言_Alignof
和_Alignas
功能。
HTH
答案 1 :(得分:2)
使用"结构进行联合技巧"你必须小心,因为结构成员可以填充:
结构对象中可能有未命名的填充,但不在其中 开始。
ISO / IEC 9899:TC3,6.7.2.1 - 13
因此,......
typedef struct {
int16_t port1;
int16_t port2;
} MultiPortStruct;
...可以有一个布局......
typedef struct {
int32_t port1;
int32_t port2;
} MultiPortStruct;
......如果实施喜欢那样。
所以联盟中的叠加层不起作用,即使这种情况不太可能。
与bitfields类似:
typedef struct {
int port1:16;
int port2:16;
} MultiPortStruct;
这是因为编译器必须将以下位域放在同一个单元中,如果它适合,但可以选择单位。
实现可以分配足够大的任何可寻址存储单元来保存位字段。如果剩余足够的空间,则紧跟在结构中另一个位字段之后的位字段将被打包到同一单元的相邻位中。
ibidem,6.7.2.1 - 10
因此,理论上可行的是,一个实现选择int16作为分配单元并在字段之间放置填充,即使这没有意义也不太可能。
正如评论中所提到的,位域没有任何问题。
答案 2 :(得分:2)
正如其他人所说,struct
可能有填充(虽然不太可能)。另一种至少没有内部填充的方法是
typedef struct {
int16_t port[2];
} MultiPortStruct;
数组从不具有内部填充,struct
的第一个字段始终位于其开头。从理论上讲,这仍然可以在最后填充,所以你应该检查类似
_Static_assert(sizeof(MultiPortStruct)==2*sizeof(int16_t));