将 32 位结构传递给 32 位整数函数参数

Passing a 32-bit struct into an 32-bit integer function argument

我的 Objective-C 项目中有一些直接的 C 代码。在我使用的其中一个 API 中,我可以使用一个采用 32 位整数参数的函数注册一个回调:

void Callback(Packet* packet, int32_t port);

我希望能够将我的回调发送到两个 16 位端口而不是一个 32 位端口。当然,我可以只使用按位运算,但我更喜欢更明确的东西。

这是我目前使用联合的解决方案:

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
}

更少的代码,更少的烦恼!

使用"struct in a union trick"你必须小心,因为结构成员可以被填充:

There may be unnamed padding within a structure object, but not at its beginning.

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;

…如果实现喜欢那样。

因此联合中的叠加将不起作用,即使这样也不太可能。

它与位域类似:

typedef struct {
  int port1:16;
  int port2:16;
} MultiPortStruct;

这是因为编译器必须把后面的位域放到同一个单元中,如果合适,但是可以选择单元。

An implementation may allocate any addressable storage unit large enough to hold a bit- field. If enough space remains, a bit-field that immediately follows another bit-field in a structure shall be packed into adjacent bits of the same unit.

同上,6.7.2.1 – 10

因此,从理论上讲,实现选择 int16 作为分配单元并在字段之间放置填充是可能的,即使这没有任何意义,也不太可能。

如评论中所述,位域没有任何问题。

正如其他人所说,struct 可能有填充(尽管不太可能)。另一种至少没有内部填充的方法是

typedef struct {
    int16_t port[2];
} MultiPortStruct;

数组从来没有内部填充,struct 的第一个字段总是在它的开头。现在理论上这仍然可以在最后填充,所以你应该检查像

这样的东西
_Static_assert(sizeof(MultiPortStruct)==2*sizeof(int16_t));

首先,可以存储到一个成员中并从同一成员的另一个成员中读取union 提供您只读取您写入的字节 - 任何其他未指定的字节值。

您正在做的事情的问题是编译器可能在字段之间引入填充,这会破坏您精心安排的重叠。使用当前的编译器和 cpu 体系结构,您可能没问题,但没有必要碰碰运气,因为有多种方法可以修复字段的对齐方式。

最简单但依赖于编译器的方法是使用 packed 属性:

typedef struct __attribute((packed))
{
   int16_t port1;
   int16_t port2;
} MultiPortStruct;

这只是指示编译器不要添加任何填充,因此即使在对 2 字节值使用 4 字节对齐的体系结构上,上述内容也将始终为 32 位。

此属性受 Clang、GCC 和 LLVM(可能不是所有版本?)支持 - 如果不支持,编译器会告诉您。如果您使用的编译器不支持该属性(或等效属性),那么您可以考虑使用 C 语言 _Alignof_Alignas 功能。

HTH