带位域的打包工具(c++)

Packing bools with bit field (C++)

本文关键字:工具 c++ 包工具 位域      更新时间:2023-10-16

我正在尝试使用c++与Ada代码进行接口,所以我使用位字段定义一个结构体,以便所有数据在两种语言中都位于同一位置。下面的内容并不是我正在做的,而是概述了问题。下面也是VS2008中的一个控制台应用程序,但这不是特别相关。

using namespace System;
int main() {
    int array1[2] = {0, 0};
    int *array2 = new int[2]();
    array2[0] = 0;
    array2[1] = 0;
    
    #pragma pack(1)
    struct testStruct {
        // Word 0 (desired)
        unsigned a : 8;
        unsigned b : 1;
        bool c : 1;
        unsigned d : 21;
        bool e : 1;
        // Word 1 (desired)
        int f : 32;
        // Words 2-3 (desired)
        int g[2]; //Cannot assign bit field but takes 64 bits in my compiler
    };
    testStruct test;
    Console::WriteLine("size of char: {0:D}", sizeof(char) * 8);
    Console::WriteLine("size of short: {0:D}", sizeof(short) * 8);
    Console::WriteLine("size of int: {0:D}", sizeof(int) * 8);
    Console::WriteLine("size of unsigned: {0:D}", sizeof(unsigned) * 8);
    Console::WriteLine("size of long: {0:D}", sizeof(long) * 8);
    Console::WriteLine("size of long long: {0:D}", sizeof(long long) * 8);
    Console::WriteLine("size of bool: {0:D}", sizeof(bool) * 8);
    Console::WriteLine("size of int[2]: {0:D}", sizeof(array1) * 8);
    Console::WriteLine("size of int*: {0:D}", sizeof(array2) * 8);
    Console::WriteLine("size of testStruct: {0:D}", sizeof(testStruct) * 8);
    Console::WriteLine("size of test: {0:D}", sizeof(test) * 8);
    Console::ReadKey(true);
    delete[] array2;
    return 0;
}

(如果不清楚,在实际程序中,基本思想是程序从与Ada代码通信的东西中获得void*,并将其强制转换为testStruct*以访问数据)

去掉#pragma pack(1)后,输出为:

size of char: 8
size of short: 16
size of int: 32
size of unsigned: 32
size of long: 32
size of long long: 64
size of bool: 8
size of int[2]: 64
size of int*: 32
size of testStruct: 224
size of test: 224
显然4个字(索引0-3)应该是448 = 32*4 = 128位,而不是224。其他输出行用于帮助确认VS2008编译器下类型的大小。

#pragma pack(1)未注释的情况下,这个数字(在输出的最后两行)减少为176,它仍然大于128。

<;Word 0&;

注意:a&b, c, d, e, f,打包成不同的单词将是5,+2对于数组= 7个单词,乘以32位= 224,我们将#pragma pack(1)注释掉。如果c和e(两个bool)各自占用8位,而不是32位,我们得到176,这是#pragma pack(1)未注释后得到的数字。似乎#pragma pack(1)只允许bool本身被打包成单个字节,而不是单词,但根本不允许带有unsigned int的bool。

所以我的问题,用一句话来说:有没有一种方法可以强制编译器将一个through e打包成一个单词?与此相关的是这个问题:c++位域与bool打包,但这并没有回答我的问题;它只是指出了我试图强迫离开的行为。

如果真的没有办法做到这一点,有没有人有什么变通的办法?我很困惑,因为:

  1. 我被要求避免改变我正在复制的结构体格式(没有重新排序)。
  2. 我不想将bool类型更改为unsigned int类型,因为这可能会导致不断将其重新转换为bool类型的问题,并且可能会意外地使用错误版本的重载函数,更不用说使代码对稍后阅读它的其他人更加晦涩了。
  3. 我不想将它们声明为私有unsigned int,然后使其成为公共访问器或其他东西,因为项目中所有其他结构体的所有其他成员都是在没有()的情况下直接访问的,所以它看起来有点粗糙和迟钝,并且几乎需要智能感知或试错来记住哪些需要(),哪些不需要。
  4. 我想避免为数据转换创建另一个结构类型(例如为testStruct创建一个构造函数,该构造函数接受单个testStructImport类型的对象),因为实际的结构非常长,有很多位字段指定的变量。

我建议您创建一个没有任何位打包的"正常"结构。为成员使用默认的POD类型。

创建接口函数,用于从缓冲区(uint8_t)加载"正常"字段,并存储到缓冲区。

这将允许您在程序的同一方法中使用数据成员。钻头的装箱和拆包将由接口函数处理。位旋转应该使用位与和位或函数,而不是依赖于结构中的位域表示法。这将允许您调整位偏移,并在编译器之间更易于移植。

这就是我如何设计我的协议类。而且我也不用担心位域的定位,字节序之类的问题。

还可以使用块I/O来读写缓冲区

试着这样包装:

    #pragma pack( push, 1 )
struct testStruct {
    // Word 0 (desired)
    unsigned a : 8;
    unsigned b : 1;
    unsigned c : 1;
    unsigned d : 21;
    unsigned e : 1;
    // Word 1 (desired)
    unsigned f : 32;
    // Words 2-3 (desired)
    unsigned g[2]; //Cannot assign bit field but takes 64 bits in my compiler
};
#pragma pack(pop)

如果不使用访问器或接口层,就没有简单、优雅的方法。不幸的是,没有什么像#pragma一样的东西可以解决这个问题。我最终只是将bool s转换为unsigned int,并将变量从例如f重命名为f_flagf_bool,以鼓励正确使用并明确变量包含的内容。它比Thomas的解决方案更省力,但显然没有那么健壮,并且仍然绕过了任何简单方法的一些主要缺点。

在我发布这个问题多年之后,用户@WaltK在链接的相关问题中添加了这条评论:

"如果你想对位域的布局有更多的控制内存中的结构,考虑使用这个位场设施,作为库头文件实现。"