C中位字段运算符的意外行为

时间:2014-01-16 14:45:10

标签: c

void main()
{
    struct bitfield
    {
        unsigned a:5;
        unsigned c:5;
        unsigned b:6;
    }bit;
    char *p;
    struct bitfield *ptr,bit1={1,3,3};
    p=&bit1;
    p++;
    printf("%d",*p);
}



Explanation:
  • a = 1的二进制值是00001(在5位中)
  • b = 3的二进制值是00011(在5位中)
  • c = 3的二进制值是000011(在6位中)

    我的问题是:在记忆中它将如何表示?

当我编译它给出输出12时,我无法弄清楚为什么会发生这种情况:在我看来,让我们说内存表示将采用以下格式:

00001 000011 00011
        |        |
       501       500 (Let Say starting address)

如果我在这里错了,请纠正我。

3 个答案:

答案 0 :(得分:1)

实际表示如下:

000011 00011 00001
     b     c     a

当对齐为字节时:

00001100 01100001
       |        |
       p+1      p 

地址(p + 1)为0001100,给出12。

答案 1 :(得分:1)

C标准没有完全指定如何将位字段打包成字节。细节取决于每个C实现。

来自C 2011 6.7.2.1:

  

11实现可以分配足够大的任何可寻址存储单元来保存位字段。如果剩余足够的空间,则紧跟在结构中的另一个位字段之后的位字段将被打包到相同单元的相邻位中。如果剩余的空间不足,则是否将不适合的位域放入下一个单元或重叠相邻单元是实现定义的。单元内的位域分配顺序(高阶到低阶或低阶到高阶)是实现定义的。可寻址存储单元的对齐未指定。

答案 2 :(得分:1)

来自C11标准(6.7.2.1):

  

单位内的位域分配顺序(高阶到低阶或低阶到高阶)是实现定义的。可寻址存储单元的对齐未指定。

我知道GCC和类似unix的系统上的其他编译器按照主机字节顺序对位字段进行排序,这可以从我操作系统源的IP头的定义中得到证明:

struct ip {
#if _BYTE_ORDER == _LITTLE_ENDIAN
        u_int     ip_hl:4,              /* header length */
                  ip_v:4;               /* version */
#endif
#if _BYTE_ORDER == _BIG_ENDIAN
        u_int     ip_v:4,               /* version */
                  ip_hl:4;              /* header length */
#endif

其他编译器也可能这样做。由于您最有可能使用小端机器,因此您的位域将从您期望的位置向后移动(除了已经向后的单词)。很可能它在内存中看起来像这样(注意你问题中结构中字段的顺序是“a,c,b”,而不是“a,b,c”,只是为了让这更加混乱):< / p>

01100001 00001100
|        |
byte 0   byte 1
|  |     |     |
x  a     b     c

因此,所有三个位字段都可以填充在int中。填充是自动添加的,它位于所有位域的开头,它放在字节2和3处。然后b从字节1的最低位开始。c在字节1中开始两个,但我们只能得到它的两个位,c的两个最高位是0,然后c继续在字节0(上图中的x),然后你有a

请注意,图片的两个字节的地址最低,左侧的位增长到右边(这在文献中非常标准,你的图片在一个方向上有位,在另一个方向上有字节,这使得更令人困惑的是,尤其是添加字段“a,c,b”的奇怪排序。

我没有上述任何意义,运行此程序,然后阅读字节顺序:

#include <stdio.h>
int
main(int argc, char **argv)
{
        unsigned int i = 0x01020304;
        unsigned char *p;
        p = (unsigned char *)&i;
        printf("0x%x 0x%x 0x%x 0x%x\n", (unsigned int)p[0], (unsigned int)p[1], (unsigned int)p[2], (unsigned int)p[3]);
        return 0;
}

然后当你理解了little-endian对int中字节顺序的作用时,将你的位字段映射到其上,但是字段向后。然后它可能开始变得有意义了(我已经这么做多年了,它仍然让人困惑)。

另一个示例显示位字段是如何向后两次,一次是因为编译器决定将它们向后放在little-endian机器上,然后再次因为int的字节顺序:

#include <stdio.h>
int
main(int argc, char **argv)
{
        struct bf {
                unsigned a:4,b:4,c:4,d:4,e:4,f:4,g:4,h:4;
        } bf = { 1, 2, 3, 4, 5, 6, 7, 8 };
        unsigned int *i;
        unsigned char *p;
        p = (unsigned char *)&bf;
        i = (unsigned int *)&bf;
        printf("0x%x 0x%x 0x%x 0x%x\n", (unsigned int)p[0], (unsigned int)p[1], (unsigned int)p[2], (unsigned int)p[3]);
        printf("0x%x\n", *i);
        return 0;
}