Linux 系统编程 —— C结构体之位域(位段)

2024-06-03 22:18

本文主要是介绍Linux 系统编程 —— C结构体之位域(位段),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

        有些信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位。例如在存放一个开关量时,只有0和1 两种状态, 用一位二进位即可。为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为“位域”或“位段”。所谓“位域”是把一个字节中的二进位划分为几个不同的区域, 并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。 这样就可以把几个不同的对象用一个字节的二进制位域来表示。


一、位域的定义和位域变量的说明

位域定义与结构定义相仿,其形式为: 

struct 位域结构名 
{位域列表};

其中位域列表的形式为:

类型说明符 位域名:位域长度

位域变量的说明与结构变量说明的方式相同。 可采用先定义后说明,同时定义说明或者直接说明这三种方式。例如:

struct bs
{int a:8;int b:2;int c:6;
}data;

说明data为bs变量,共占两个字节。其中位域a占8位,位域b占2位,位域c占6位。对于位域的定义尚有以下几点说明:


1. 一个位域必须存储在同一个字节中,不能跨两个字节。

如一个字节所剩空间不够存放另一位域时,应从下一单元起存放该位域。也可以有意使某位域从下一单元开始。例如:

struct bs
{unsigned a:4unsigned b:5 /*从下一单元开始存放*/unsigned c:4
}

2. 由于位域不允许跨两个字节,因此位域的长度不能大于一个字节的长度。

3. 位域可以无位域名,这时它只用来作填充或调整位置。无名的位域是不能使用的。例如:

struct k
{int a:1int :2 /*无位域名,该2位不能使用*/int b:3int c:2
}; 

举个例子:

普通结构体内存示例:

struct
{double   a;//8 8 8  0-7 char     b;//1 8 1     8    8-11浪费int      c;//4 8 4    12-15int      d;//4 8 4    16-19   20-24浪费
}S;

解析:(vs下默认对齐数为8,linux下为4,以下环境为vs)

1.结构体中第一个成员a放在0偏移处,a是double类型,占8个字节,对齐数为8,从0偏移处开始往后放,0-7.

2.b占1个字节,对齐数为1(b自身大小为1,默认为8,较小值为1,即对齐数为1),8是1的倍数,所以从8偏移处开始放,8.

3.c占4个字节,对齐数为4,9-11浪费,从12开始放c,12-15.

4.d占4个字节,对齐数为4,16是4的倍数,从16开始放,16-19.

5.0-19是20个字节,最大对齐数为8,8的倍数最小的为24,20-24浪费.

6.因此,该结构体的大小为24.

 

位段大小的计算,及计算机的存储方式:

struct 
{int  a : 2;int  b : 10;int  c : 5;int  d : 20;
}S;

注意:

(1)位段成员的类型必须指定为unsigned或int类型。

(2)一个位段必须存储在同一存储单元中,不能跨两个单元。如果第一个单元空间不能容纳下一个位段,则该空间不用,而从下一个单元起存放该位段。

(3)可以定义无名位段。

(4)从图中可以看出位段是如何存储的,a、b、c放在一个存储单元,为4个字节,剩下的空间放不下d,放在下一个存储单元中,占4个字节,共占8个字节。

二、位域的使用

#include <iostream>
#include <memory.h>
using namespace std;
struct A
{int a:5;int b:3;
};
int main(void)
{char str[100] = "0134324324afsadfsdlfjlsdjfl";struct A d;memcpy(&d, str, sizeof(A));cout << d.a << endl;cout << d.b << endl;return 0;
}

在32位x86机器上输出:

$ ./langxun.exe
-16
1

解析:在默认情况下,为了方便对结构体内元素的访问和管理,当结构体内的元素长度都小于处理器的位数的时候,便以结构体里面最长的元素为对其单位,即结构体的长度一定是最长的数据元素的整数倍;如果有结构体内存长度大于处理器位数的元素,那么就以处理器的位数为对齐单元。由于是32位处理器,而且结构体中a和b元素类型均为int(也是4个字节),所以结构体的A占用内存为4个字节。

上例程序中定义了位域结构A,两个个位域为a(占用5位),b(占用3位),所以a和b总共占用了结构A一个字节(低位的一个字节)。

当程序运行到14行时,d内存分配情况:

 高位 00110100 00110011   00110001    00110000 低位'4'       '3'       '1'          '0'  其中d.a和d.b占用d低位一个字节(00110000),d.a : 10000, d.b : 001

 d.a内存中二进制表示为10000,由于d.a为有符号的整型变量,输出时要对符号位进行扩展,所以结果为-16(二进制为11111111111111111111111111110000)

 d.b内存中二进制表示为001,由于d.b为有符号的整型变量,输出时要对符号位进行扩展,所以结果为1(二进制为00000000000000000000000000000001)

 三、位域的对齐

  如果结构体中含有位域(bit-field),那么VC中准则是:

  1) 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止;

  2) 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;

  3) 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式(不同位域字段存放在不同的位域类型字节中),Dev-C++和GCC都采取压缩方式;

  系统会先为结构体成员按照对齐方式分配空间和填塞(padding),然后对变量进行位域操作。

四、位域与数据类型uint8_t uint16_t uint32_t uint64_t size_t ssize_t

参考:https://blog.csdn.net/lzx_bupt/article/details/7066577

C语言中好像没有这种数据类型,但是在实际应用的过程中,发现许多人的代码中都存在这种表示方式。其实uintX-t就是通过typedef定义的,利用预编译和typedef可提高效率也方便代码移植。总结如下:

    typedef unsigned char   uint8_t;     //无符号8位数

    typedef signed   char   int8_t;      //有符号8位数

    typedef unsigned int    uint16_t;    //无符号16位数

    typedef signed   int    int16_t;     //有符号16位数

    typedef unsigned long   uint32_t;    //无符号32位数

    typedef signed   long   int32_t;     //有符号32位数

    typedef float           float32;     //单精度浮点数

    typedef double          float64;     //双精度浮点数

一般来说整形对应的*_t类型为:
    uint8_t为1字节    

    uint16_t为2字节  

    uint32_t为4字节    

    uint64_t为8字节    

 

uint8_t / uint16_t / uint32_t /uint64_t 是什么数据类型

这些数据类型是 C99 中定义的,具体定义在:/usr/include/stdint.h    ISO C99: 7.18 Integer types <stdint.h>
 

#ifndef __int8_t_defined
# define __int8_t_defined
typedef signed char             int8_t; 
typedef short int               int16_t;
typedef int                     int32_t;
# if __WORDSIZE == 64
typedef long int                int64_t;
# else
__extension__
typedef long long int           int64_t;
# endif
#endif/* Unsigned.  */
typedef unsigned char           uint8_t;
typedef unsigned short int      uint16_t;
#ifndef __uint32_t_defined
typedef unsigned int            uint32_t;
# define __uint32_t_defined
#endif
#if __WORDSIZE == 64
typedef unsigned long int       uint64_t;
#else
__extension__
typedef unsigned long long int  uint64_t;
#endif

格式化输出:

unit64_t     %llu   

unit32_t     %u

unit16_t    %hu

unit8_t    %d

举例:

#include<stdio.h>
#include<stdint.h>struct A
{uint8_t a:1;uint8_t b:4;
};
int main()
{struct A myA;printf("A size is %d\n",sizeof(myA));myA.a=1;myA.b=13;printf("myA.a is %c\n",myA.a);printf("myA.a is %d\n",myA.a);printf("myA.b is %c\n",myA.b);printf("myA.b is %d\n",myA.b);printf("end\n");return 0;
}

32位系统输出结果:

[root@localhost tmp]# ./a.out 
A size is 1
myA.a is 
myA.a is 1
myA.b is 
myA.b is 13
end

 

这篇关于Linux 系统编程 —— C结构体之位域(位段)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1028259

相关文章

Linux命令之firewalld的用法

《Linux命令之firewalld的用法》:本文主要介绍Linux命令之firewalld的用法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux命令之firewalld1、程序包2、启动firewalld3、配置文件4、firewalld规则定义的九大

Linux之计划任务和调度命令at/cron详解

《Linux之计划任务和调度命令at/cron详解》:本文主要介绍Linux之计划任务和调度命令at/cron的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux计划任务和调度命令at/cron一、计划任务二、命令{at}介绍三、命令语法及功能 :at

Linux下如何使用C++获取硬件信息

《Linux下如何使用C++获取硬件信息》这篇文章主要为大家详细介绍了如何使用C++实现获取CPU,主板,磁盘,BIOS信息等硬件信息,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下... 目录方法获取CPU信息:读取"/proc/cpuinfo"文件获取磁盘信息:读取"/proc/diskstats"文

Linux内核参数配置与验证详细指南

《Linux内核参数配置与验证详细指南》在Linux系统运维和性能优化中,内核参数(sysctl)的配置至关重要,本文主要来聊聊如何配置与验证这些Linux内核参数,希望对大家有一定的帮助... 目录1. 引言2. 内核参数的作用3. 如何设置内核参数3.1 临时设置(重启失效)3.2 永久设置(重启仍生效

kali linux 无法登录root的问题及解决方法

《kalilinux无法登录root的问题及解决方法》:本文主要介绍kalilinux无法登录root的问题及解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,... 目录kali linux 无法登录root1、问题描述1.1、本地登录root1.2、ssh远程登录root2、

shell编程之函数与数组的使用详解

《shell编程之函数与数组的使用详解》:本文主要介绍shell编程之函数与数组的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录shell函数函数的用法俩个数求和系统资源监控并报警函数函数变量的作用范围函数的参数递归函数shell数组获取数组的长度读取某下的

Linux ls命令操作详解

《Linuxls命令操作详解》通过ls命令,我们可以查看指定目录下的文件和子目录,并结合不同的选项获取详细的文件信息,如权限、大小、修改时间等,:本文主要介绍Linuxls命令详解,需要的朋友可... 目录1. 命令简介2. 命令的基本语法和用法2.1 语法格式2.2 使用示例2.2.1 列出当前目录下的文

利用Python快速搭建Markdown笔记发布系统

《利用Python快速搭建Markdown笔记发布系统》这篇文章主要为大家详细介绍了使用Python生态的成熟工具,在30分钟内搭建一个支持Markdown渲染、分类标签、全文搜索的私有化知识发布系统... 目录引言:为什么要自建知识博客一、技术选型:极简主义开发栈二、系统架构设计三、核心代码实现(分步解析

Linux中的计划任务(crontab)使用方式

《Linux中的计划任务(crontab)使用方式》:本文主要介绍Linux中的计划任务(crontab)使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、前言1、linux的起源与发展2、什么是计划任务(crontab)二、crontab基础1、cro

Linux换行符的使用方法详解

《Linux换行符的使用方法详解》本文介绍了Linux中常用的换行符LF及其在文件中的表示,展示了如何使用sed命令替换换行符,并列举了与换行符处理相关的Linux命令,通过代码讲解的非常详细,需要的... 目录简介检测文件中的换行符使用 cat -A 查看换行符使用 od -c 检查字符换行符格式转换将