C语言中的位域填充

C语言中的位域填充,c,C,继续我在C中的实验,我想看看位字段是如何放置在内存中的。我在英特尔64位计算机上工作。以下是我的代码: #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> int main(int argc, char**argv){ struct box_props { unsigned int opaque

继续我在C中的实验,我想看看位字段是如何放置在内存中的。我在英特尔64位计算机上工作。以下是我的代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
int main(int argc, char**argv){
       struct box_props
       {
         unsigned int opaque       : 1;
         unsigned int fill_color   : 3;
         unsigned int              : 4; 
         unsigned int show_border  : 1;
         unsigned int border_color : 3;
         unsigned int border_style : 2;
         unsigned int              : 2; 
       };

       struct box_props s;
       memset(&s, 0, 32);
       s.opaque = 1;
       s.fill_color = 7;
       s.show_border = 1;
       s.border_color = 7;
       s.border_style = 3;

       int i;
       printf("sizeof box_porps: %d sizeof unsigned int: %d\n", sizeof(struct box_props), sizeof(unsigned int));
       char *ptr = (char *)&s;
       for (i=0; i < sizeof(struct box_props); i++){
          printf("%x = %x\n", ptr + i, *(ptr + i));
       }

       return 0;
这里有一个问题:为什么
struct box_props
的大小是
4
——它不能是
2
字节吗?在这种情况下如何填充?我对它有点困惑


所有答案的提前Thx

在这种情况下,尽管总需求仅为2字节(1+3+4+1+3+2+2),但所使用的数据类型(
unsigned int
)的大小为4字节。因此,分配的内存也是4字节。如果只需要分配2个字节,请使用
unsigned short
作为数据类型,然后再次运行该程序

位字段在内存中的位置不仅取决于编译器决定如何分配结构中的各个字段,还取决于您运行的机器的endian属性。让我们一个接一个地拿着。编译器中字段的分配可以通过指定字段的大小(如@DDD)来控制,但也可以通过另一种机制来控制。您可以告诉编译器打包您的结构,或者让它更适合编译器为您正在编译的机器体系结构进行优化的方式。使用
packed
指定打包。因此,如果将结构指定为:

struct __attribute__ ((__packed__)) box_props {
    ...
} 
您可能会在内存中看到不同的布局。请注意,通过检查结构组件,您不会看到布局有所不同-内存中的布局可能会改变。当与其他设备(如在特定位置需要特定位的IO设备)通信时,打包结构至关重要

位场结构的第二个问题是它们的布局依赖于端序。结构在内存中的布局(或与此相关的任何数据)取决于您是否在计算机上运行。某些系统(例如,嵌入式PowerPC系统是双端的)

通常,位字段使移植代码变得非常困难,因为您正在对内存中的数据布局进行优化


希望这有帮助

来自ISO C标准:

一个实现可以分配任何足够大的可寻址存储单元来容纳一个位- 领域(及以后)在结构或联合体的末端可能有未命名的填充物


因此,不需要总是为结构选择尽可能小的内存块。由于32位字可能是编译器的本机大小,这就是它所选择的。

出于某种原因,我不太明白,C标准的实现者决定,指定数字类型和位字段应该分配足够的空间来容纳该数字类型,除非前一个字段是位字段,已从相同类型中分配,剩余空间足以处理下一个字段

对于您的特定示例,在具有16位无符号短字符的计算机上,您应该将位字段中的声明更改为无符号短字符。碰巧,unsigned char也可以工作,并产生相同的结果,但情况并非总是如此。如果最佳压缩位字段跨越字符边界而不是短边界,那么将位字段声明为
无符号字符
将需要填充以避免这种跨越

尽管一些处理器在为跨越存储单元边界的位字段生成代码时不会遇到问题,但目前的C标准将禁止以这种方式打包它们(同样,出于我不太明白的原因)。例如,在具有典型8/16/32/64位数据类型的计算机上,编译器不能允许程序员指定包含八个三字节字段的三字节结构,因为字段必须跨越字节边界。我可以理解该规范不要求编译器处理跨越边界的字段,也不要求以某种特定方式布置位字段(如果可以指定某个特定位字段应该使用某个位置的位4-7,我认为它们会非常有用),但目前的标准似乎给出了两个世界中最坏的一面

在任何情况下,有效使用位字段的唯一方法是找出存储单元边界的位置,并适当地选择位字段的类型

PS——值得注意的是,尽管我记得编译器过去不允许对包含位字段的结构进行
volatile
声明(因为编写位字段时的操作序列可能没有很好的定义),但在新规则下,语义可以很好地定义(我不知道规范是否确实需要它们)。例如,假设:

typedef struct {
  uint64_t b0:8,b1:8,b2:8,b3:8, b4:8,b5:8,b6:8,b7:8;
  uint64_t b8:8,b9:8,bA:8,bB:8, bC:8,bD:8,bE:8,bF:8;
} FOO;
extern volatile FOO bar;
语句
bar.b3=123
将从
读取前64位,然后用更新的值写入
的前64位。如果
bar
不是易失性的,编译器可能会用简单的8位写入来替换该序列,但
bar
可能类似于硬件寄存器,只能以32位或64位块写入

如果我有druthers,则可以使用以下内容定义位字段:

typedef struct {
  uint32_t {
    baudRate:13=0, dataFormat:3,
    enableRxStartInt: 1=28, enableRxDoneInt: 1, enableTxReadyInt: 1, enableTxEmptyInt: 1;};
  };
} UART_CONTROL;

指示波特率是从第0位(LSB)开始的13位,数据格式是从波特率之后开始的3位,enableRxStartInt是第28位,等等。这样的语法允许以可移植的方式编写多种类型的数据打包和解包,并允许以与编译器无关的方式进行许多I/O寄存器操作(虽然这样的代码显然是特定于硬件的)。

我认为这是因为您使用的是int类型,如果您想要大小为2,请尝试使用short int int。尝试发送反馈
memset(&s,0,32)
Ouch!你要求
memset
将32个字节归零,但只有四个字节。你只是运气不好,没有破坏堆栈的某些重要部分,导致程序严重崩溃。”C标准的实现者决定指定数字类型
typedef struct {
  uint32_t {
    baudRate:13=0, dataFormat:3,
    enableRxStartInt: 1=28, enableRxDoneInt: 1, enableTxReadyInt: 1, enableTxEmptyInt: 1;};
  };
} UART_CONTROL;