【问题标题】:Passing a 32-bit struct into an 32-bit integer function argument将 32 位结构传递给 32 位整数函数参数
【发布时间】:2015-08-09 03:28:29
【问题描述】:

我的 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
}

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

【问题讨论】:

  • 请选择语言标签。联合别名在 C++ 中是不允许的,但在 C 中是这样的。
  • 仅使用按位运算,您的收益大约要好 10 亿倍。如果这让你感觉更好,你可以将它隐藏在一个内联函数后面。
  • 技术上,我使用的是 Objective-C,所以我不确定哪个更合适。
  • 那又是另一种语言,使用标签objective-c
  • 然后发布您的实际 Objective C 代码?

标签: objective-c c struct bit-manipulation unions


【解决方案1】:

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

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

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

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

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

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

HTH

【讨论】:

    【解决方案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;
    

    ...如果实现喜欢这样。

    所以union中的overlay是行不通的,即使这样也不太可能。

    与位域类似:

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

    这是因为编译器必须将以下位域放入同一个单元中,如果它适合,但可以选择单元。

    实现可以分配任何大到足以容纳位域的可寻址存储单元。如果有足够的空间,结构中紧跟在另一个位域之后的位域应该被打包到同一单元的相邻位中。

    同上,6.7.2.1 – 10

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

    正如评论中提到的,位域没有错。

    【讨论】:

    • 位域是实现定义的;编译器在布局上有很多选择。我希望第一个 MultiPortStruct 没有填充,但您可以添加一个静态断言来验证这一点。
    • 1.我已经说过 biltfields 具有实现定义的行为。 2. 断言会测试问题,但不能解决问题。
    【解决方案3】:

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

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

    数组永远不会有内部填充,struct 的第一个字段总是在它的开头。现在理论上这仍然可能在最后有填充,所以你应该检查类似

    _Static_assert(sizeof(MultiPortStruct)==2*sizeof(int16_t));
    

    【讨论】:

    • 这是最强大的想法+1。但它仍然不能解决整数布局(字节序)。我真的认为位操作是解决这个问题的最好方法。
    • 那么为什么不把数组放在联合中呢?完全避免结构也避免了填充。
    猜你喜欢
    • 2013-09-18
    • 1970-01-01
    • 2011-06-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-14
    • 1970-01-01
    • 2019-01-23
    相关资源
    最近更新 更多