C语言中结构体位域以及操作

位段的定义

        首先我们要知道位段的成员必须是int、unsigned int、signed int、char、unsigned char(属于整形家族)类型。有时候我们可以直接用 unsigned a来定义一个位段的成员。但是这里要注意用unsigned来直接声明变量在不同平台的不同编译器中所分配的内存大小是不同的。这里我依然是采用三个环境来验证。三个环境分别是:visual stdio  32位单片机MDK环境  8位单片机C51环境。代码如下:

	unsigned a = 0;

	printf("大小为 %d 字节\r\n", sizeof(a));

根据上述代码,在visual stdio环境中得出大小为4个字节,MDK环境中为4个字节,C51环境中为2个字节,因此我们可以得出 unsigned或者signed 直接声明变量应该是默认为int整形。因此只要根据具体平台来确定位段成员的位数,就不会出错。

位段的跨平台问题

位段虽然比起结构体可以很好的节省空间,但是在不同的平台上,分配内存的机制是有所不同的。总结来说有以下5点:

  1. int位段被当成有符号数还是无符号数是不确定的。
  2. 位段中最大位的数目不能确定(上文我们说过不同平台的int类型大小不同)
  3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。(即位段成员在内存中的排列顺序)
  4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余位时是舍弃剩余的位还是利用,这是不确定的。
  5. 当一个结构中相邻的位段是不同类型时,但是位数能够相容于上一位段成员的大小,是紧挨着分配,还是舍弃上一个位段剩余位,这是不确定的

上述5点我们这里一一解释:

  • 第一条中很好理解不同编译器会将int位段默认当成有符号还是无符号是不确定的,这里很好理解就不做验证了。
  • 第二条中上文也说明过,位段的位数大小不能超过当前平台int整形的大小,即32位平台就不能超过32位,8位平台就不能超过16位。
  • 第三条我们需要用下面的代码来验证:
typedef struct {

	int ch : 8;
	int  a : 8;
}st;

typedef union {
	st s;
	char arr[2];
}un;

int main() {

	st s = { 0 };
	un u;
	u.s.a = 0x15;
	u.s.ch = 0x11;

	printf(" %d %d\r\n", u.arr[0], u.arr[1]);

}

上述代码通过打印联合体成员arr数组,可以看到位段成员在内存中的分布,到底是从左至右分配还是从右至左分配(低地址到高地址,还是高地址到低地址),在visual stdio环境中打印如下图所示:

 可以得出是从右到左来分配的,那我们来看看相同代码8位c51环境下结果如何:

通过串口打印我们可以看到,这里两种平台下分配的顺序相反。于是我们再看看下面这段代码在不同平台下得运行结果:

typedef struct {
	char a : 4;
	char b : 4;
	char c : 8;
}st;

typedef union {
	st s;
	unsigned char arr[2];
}un;

int main() {

	un u;
	u.s.a = 1;
	u.s.b = 1;
	u.s.c = 1;
	printf("value1: %d  value2:%d \r\n", u.arr[0],u.arr[1]);
}

在VS环境下得到结果如下:

 在C51平台下我们通过串口打印的方式得到结果如下:

 这时在这段代码中两个平台的分配顺序又相同了,为了找到规律,我们继续使用代码来验证:

typedef struct {
	char a : 4;
	char b : 2;
	char c : 2;
}st;

typedef union {
	st s;
	unsigned char ch;
}un;

int main() {

	un u;
	u.s.a = 1;
	u.s.b = 1;
	u.s.c = 1;
	printf("value: %d\r\n", u.ch);
}

上述代码在VS和C51环境中的结果均为81,通过这三段代码我们可以得出以下结论来解释第三条:当结构体中的位段成员位数刚好容纳于一个int整形,整个结构体位段得大小就小于等于一个int整形,这时分配内存时,就和一个int变量的内存结构一样,根据该平台是大端存储还是小端存储来分配(例如第一段代码)。如果结构体中位段成员位数刚好容纳于一个char整形,那么就看成一个字节,先分配低位再分配高位(例如第三段代码)。如果结构体中相邻的位段成员位数不能容纳于一个整形的大小,需要重新开辟字节空间,那么分配的内存的顺序就会按照栈帧的顺序从低到高的地址分配(例如第二段代码)。

其实上述的论证中,不同平台的差异就只有大小端的存储不同,在不同平台的程序设计时,我们对位段的操作时,当位段成员刚好容纳于一个int整形时,我们要注意不同平台大小端的差异,就不会出错。

  • 第四条我们还是通过代码来验证:
    typedef struct {
    
    	int ch : 30;
    	int  a : 10;
    }st;
    
    
    int main() {
    	st s = {0};
    	
    	s.a = 0x15;
    	s.ch = 1023;
    
    }

    经过测试,在visual stdio环境中是当第二个位段成员较大,无法容于第一个成员的剩余位时,是选择丢弃的。可以通过内存监视窗口看到如下:

        接着我在MDK平台上也做了测试,通过内存窗口看到结果如下:

 可以看出和vs环境一致,那我们再在C51平台上做一下试验呢,但是这里需要修改下代码,因为c51平台的int是16位大小,代码如下:

typedef struct {

	int ch : 15;
	int  a : 10;
}st;

typedef union {

	st s;
	int arr[2];
}un;

int main(){

	un u;
	u.arr[0] = 0;
	u.arr[1] = 0;
	u.s.a = 0x15;
	u.s.ch = 1023;
	
    printf("s.a: %d  s.ch: %d\r\n", u.arr[0], u.arr[1]);

}

运行上述代码,通过串口打印结果如下:

 结果和VS环境也相同,因此不知道其余平台是否会对剩余位进行利用,这里不得而知。

  • 最后我们来推断第五条。代码如下:

    typedef struct {
    
    	int ch : 30;
    	char  a : 2;
    }st;
    
    
    int main() {
    	st s = {0};
    	
    	s.ch = 0x15;
    	s.a = 1;
        printf("结构体大小为:%d\r\n", sizeof(s));
    }
    

     在vs环境中,通过调试可以看到其内存分配:

  • 在vs环境中不同变量会重新开辟空间,不会利用上一个位段成员的剩余位,最后结构体大小通过内存对齐占用8个字节,但是在MDK环境中却不是,内存分配如下:

     

    由上图可看出,虽然位段成员类型不同,在MDK环境中,当位数能够相容,是会利用上一位段成员的剩余位的。然后结构体占用的空间为4个字节。

    由此我们可以得出不同平台的位段内存分配的不同。因此我们在用位段操作时,一定要非常谨慎。 

补充

 上图所示位段成员a下一个成员没有名字,后面位数写成0,代表位段成员a剩余位全部赋值成0,位段成员c只能从下一个字节开始,所以整个结构体占2个字节。

上图所示,上图所示位段成员a下一个成员同样没有名字,但是这里后面位数写成了4,代表这里要空出4位,整个结构体依然占一个字节。

最后说明一点位段的成员不能进行取地址操作,因为是按照位来存储,所以取不到地址

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值