【问题标题】:C variable width bitfieldC 可变宽度位域
【发布时间】:2014-04-18 19:09:24
【问题描述】:

我在这里问的问题不多,如果我的“提问技巧”有点生疏,请见谅。如下:


我环顾四周,似乎找不到解决办法。

这个想法是程序打印出一个包含任意位大小的数字的每个可能值的十进制(有符号和无符号)、十六进制和二进制表示形式的表格。它接受来自 argv 的位大小,示例输出应该是(关注二进制表示):

$ ./test 2
00
01
10
11
$ ./test 4
0000
0001
0010
0011
0100
0101
0110
0111
1000
1001
1010
1011
1100
1101
1110
1111

如果我使用预处理器宏,这可以正常工作,但这意味着我每次想要更改位大小时都必须重新编译。如果不是一个小小的警告,Argv 将解决我所有的问题。那就是编译器不喜欢我正在尝试做的事情,即使它看起来完全合乎逻辑。

我想要做的是:

int bit_size = atoi(argv[1]);
struct { unsigned int a : bit_size; } test;

但是编译器给了我一个错误,非常严厉地告诉我我不能这样做(“位域不是整数常量”)。

好的...所以我尝试改用const int

const int bit_size = atoi(argv[1]);
struct { unsigned int a : bit_size; } test;

我得到了同样的错误。

请注意:GCC 想要我做的是:

struct { unsigned int a : 8; } test;

而且它可以正常工作。但我需要能够改变这一点。

我很困惑。

请注意,我不希望或不需要位域的宽度来更改程序中间,这是我假设 GCC 试图阻止我这样做的原因。这实际上是一次性操作。

另外请注意,我并没有尝试对像 this question (以及许多其他人喜欢的)这样的普通变量执行此操作。

This question 也与我要完成的工作无关。


另外,如果有帮助,这是一个运行无错误时显示的示例(位域宽度 = 4):

$ ./test 4
$ cat table.md
Table for a/an 4-bit integer: 

| Unsigned | Signed | Hex | Binary | Sign Bit |
|:--------:|:------:|:---:|:------:|:--------:|
|    0     |   0    |  0  |  0000  |    0     |
|    1     |   1    |  1  |  0001  |    0     |
|    2     |   2    |  2  |  0010  |    0     |
|    3     |   3    |  3  |  0011  |    0     |
|    4     |   4    |  4  |  0100  |    0     |
|    5     |   5    |  5  |  0101  |    0     |
|    6     |   6    |  6  |  0110  |    0     |
|    7     |   7    |  7  |  0111  |    0     |
|    8     |   -8   |  8  |  1000  |    1     |
|    9     |   -7   |  9  |  1001  |    1     |
|    10    |   -6   |  A  |  1010  |    1     |
|    11    |   -5   |  B  |  1011  |    1     |
|    12    |   -4   |  C  |  1100  |    1     |
|    13    |   -3   |  D  |  1101  |    1     |
|    14    |   -2   |  E  |  1110  |    1     |
|    15    |   -1   |  F  |  1111  |    1     |

【问题讨论】:

  • 你想要一个宽度可变的位域,但 C 没有这个。哦,const 限定对象不是 C 中的常量,它只是一个只读对象。
  • 只需使用完整的无符号整数并将代码限制为特定宽度。
  • @ouah 我不知道“常量”和“只读”之间存在区别
  • 啊哈,所以基本上,位域不是解决方案。我使用的唯一原因是有符号整数,但我只是注意到有符号负数中的逻辑模式(符号位 - 数字的其余部分 = 负数的绝对值)。稍后我可能会发布我自己的答案。
  • @Étienne 啊,所以这就是二进制补码是什么。

标签: c gcc struct


【解决方案1】:

您不能在 C 中在运行时定义位域的大小。但是您不需要位域来打印二进制值,只需编写一个函数以二进制格式打印数字,例如一个在这里: Is there a printf converter to print in binary format?

然后编写一个简单的循环来打印你的数字:

//get n
for (int i = 0; i < n; i++) {
    print_binary(i);
}

编辑: 回答您关于打印在 C 中没有本机类型的二进制补码编码的负数的问题(如 int8_t、int16_t、int32_t ..),就像您发现在 2 的补码中的 N 位上编码的有符号字一样,对于负数您可以使用等式的数字:

Xnegative = 2^N - Xpositive

//get n
for (uint32_t Xpos = 0; Xpos < (1<<n); Xpos++) {
    if (Xpos > 1<<(n-1))
        printf("%d\n", -(1 << n) + Xpos);
    else
        printf("%u\n", Xpos);
}

【讨论】:

  • 注意:问题不在于二进制数本身。在我看来,使位域成为必需的真正问题是,我不确定如何打印已签名的整数(请参阅我添加到问题中的示例运行)。例如,我如何告诉它 8 是 -8 而 9 是 -7?我想我可以在数学上到达那里。 (例如 1010 => 1000 - 0010 => 8 - 2 => 6 => -6)
  • 无论如何,谢谢你的直接回答,现在这个问题已经为未来有同样问题的人解决了!
  • "我怎么知道 8 是 -8 而 9 是 -7?"无论如何,你不能在没有上下文的情况下这样做。
【解决方案2】:

所以我想通了。

正如其他答案所述,GCC 字面上不允许您在运行时设置位域的宽度,因为它是在编译时完成的。

所以我开始思考为什么我首先需要位域,这样我才能显示负数。

然后我研究了几个有符号整数,看看是否能找到数学模式。

我发现了这样的模式,并决定不再需要该位域。

1010 = 0 - (sign_bit (1000) - raw_number (0010)) = -6

虽然我最终在调试原始算法时意外绊倒了另一种算法,但效果也一样:

number - ( (number << 1) & (1 << number of bits) )

还有我的笔记,以便我能够理解并确保这不是侥幸:

For a negative number:

n = 10 (binary 1010)
num_bits = 4
power = (1010 << 1 => 10100) & (1 << num_bits => 10000)
  10100
& 10000
  10000
return n (10) - power (16) => -6

For a positive number:

n = 6 (binary 0110)
num_bits = 4
power = (0110 << 1 => 01100) & (1 << num_bits => 10000)
  01100
& 10000
  00000
return n (6) - power (0) => 6

以及最终的功能:

signed int get_signed(long long int n, int num_bits){
  int power = (n << 1) & (1 << num_bits);
  return n - power;
}

我在 num_bits 设置为 1、2、4、8 和 16 的情况下对其进行了测试,你瞧,它运行良好


编辑:

我的原始算法刚刚开始工作

signed int get_tc(long long int n, int num_bits){
  int sign_bit = n >> (num_bits - 1) & 1;
  sign_bit <<= (num_bits - 1);
  int raw = n ^ sign_bit;
  return -1 * (sign_bit - raw);
}

还有逻辑:

n = 10
num_bits = 4
sign_bit = 10 >> (4-1) & 1
  1010
>>3
  0001
& 0001
  0001
sign_bit <<= (4-1)
  0001
<<3
  1000
sign_bit = 8
raw = 10 ^ 8
  1010
^ 1000
  0010
raw = 2
return -1 * (8 - 2)
  8 - 2 = 6
  6 * -1 = -6
return = -6

n = 6
num_bits = 4
sign_bit = 6 >> (4-1) & 1
  0110
>>3
  0000
& 0001
  0000
sign_bit <<= (4-1)
  0000
<<3
  0000
sign_bit = 0
raw = n ^ sign_bit
  0110
^ 0000
  0110
raw = 6
return -1 * (0 - 6)
  0 - 6 = -6
  -6 * -1 = 6

【讨论】:

    【解决方案3】:

    我知道您已经想通了,但这是我打印此表的解决方案。特别是因为我更喜欢使用更简单的异或运算。这依赖于采用负数表示,例如 4 位情况下的 0x1001 为 -7,并将其符号扩展为有符号长整数,然后打印它。

    从两件事开始,您的数字(例如,4 位中的 -7,0b1001)和一个掩码,其中包含设置为 1 (0b1111) 的相关位数(例如,4)与 -1 异或您选择的签名代表。因此,对于 32 位有符号数,掩码为:

    掩码:0b1111 ^ 0b11111111111111111111111111111111 = 0b11111111111111111111111111110000

    当您将此掩码与您的号码异或时,您最终会在您选择使用的分辨率中得到该号码的符号扩展版本。

    0b1001 ^ 0b11111111111111111111111111110000 = 0b11111111111111111111111111111001

    这是带符号的 32 位表示形式的 -7,易于打印。 Sign extension 是一个简单的概念。

    #include <stdio.h>
    #include <stdlib.h>
    #include <math.h>
    
    void makeStrbits(long n,long b,char *strbits)
    {
       long i=0, m=1L<<(b-1);
       while(m){
          strbits[i++]= (n&m)?'1':'0';
          m=m>>1;
       }
    }
    
    int main(int argc, char *argv[])
    {
       long i,bit=4,mask=0;
       char *strbits = NULL;
       if (argc==2) bit = strtol( argv[1],NULL,10 );
    
       strbits = calloc(1,bit+1);
    
       for(i=0;i<bit;i++) mask |= (1L<<i);
       mask ^= -1L;
    
       printf("Table for a %ld bit integer\n", bit);
       printf("unsgn\t sign\t hex\t sbit\t bits\n" );
       for(i=0; i<pow(2,bit); i++) {
          int sign = ((i&(1L<<(bit-1))) != 0);
          makeStrbits(i,bit,strbits);
          printf("%lu\t %ld\t %0x\t %1d\t %s\n", i, sign?i^mask:i, i, sign, strbits );
       }
       free(strbits);
       return 0;
    }
    

    【讨论】:

    • 读起来很有趣,但算法确实需要处理任何数字,而不仅仅是 0..15。这就是为什么我尝试通过获取符号位来实现该算法,然后获取其余的数字,但没有符号位,然后从符号位的值中减去“原始”数字,然后乘以 -1。 1010 =&gt; -1 * (1000 - 0010) = -60101 =&gt; -1 * (0000 - 0101) = +5。虽然我在尝试计算如何获取“原始数字”时意外发现的算法也同样有效。
    • 它适用于任何数字(最多为 long 中的位数,但使用 long long 来扩展它是微不足道的)。我上面的程序将您想要在命令行上的位数作为第一个 arg,但如果您不提供 arg,则默认为 4。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-09
    • 1970-01-01
    • 1970-01-01
    • 2012-11-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多