首页 > 其他分享 >C语言:结构体,联合体和枚举(2)

C语言:结构体,联合体和枚举(2)

时间:2024-03-24 11:58:57浏览次数:18  
标签:char int 联合体 C语言 位段 枚举 内存 对齐 bit

  一:结构体

1.结构体对齐规则:

规则4: 如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍

例子4:

//结构体嵌套结构体

struct S3
{
	double d;//		8		8		8
	char c;	//		1		8		1
	int i;	//		4		8		4
};

int main()
{
	struct S4
	{
		char c1;	 //1		8		1
		struct S3 s3;//
		double d; 	 //8		8		8
	};

	printf("%zd\n", sizeof(struct S4));
	return 0;
}

解析:c1大小为1个字节,vs默认对齐数为8,所以c1的对齐数为1,占用一个字节大小;

由规则4(规则4: 如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍)可知:结构体S4中嵌套定义了一个结构体S3并且定义了结构体变量s3 ,s3包括double d,char c,int i三个成员,所以s3的对齐数为该结构体中成员的最大对齐数的整数倍,s3会跳过1,2,3,4,5,6,7的内存空间开始存放在第8个偏移量的位置,共占用16个字节大小;

d大小为8个字节,vs默认对齐数为8,所以d的对齐数为8,共占用8个字节大小;

此时结构体S4的占用内存大小为32,32是包括嵌套结构体成员对齐数中所以结构体S4的最大对齐数8的整数倍,所以32就是结构体S4的大小。(结构体S3成员的对齐数分别是8 ,1, 4;结构体S4成员的对齐数分别为1,  8;所以成员的最大对齐数为8)

如下图:

2.为什么存在内存对齐

1). 平台原因(移植原因):
    不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。


(2). 性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对齐成8的倍数,那么就可以用⼀个内存操作来读或者写值了。否则,我们可能需要执行两次内存访问,因为对象可能被分放在两个8字节内存块中。


总体来说:结构体的内存对齐是拿空间来换取时间的做法。
 

那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:
让占用空间小的成员尽量集中在一起。

例子:

int main()
{
	struct S1
	{
		char c1;//1		8		1
		int i;	//4		8		4
		char c2;//1		8		1
	};
	struct S2
	{
		char c1;//1		8		1
		char c2;//1		8		1
		int i;	//4		8		4
	};

	printf("%zd\n", sizeof(struct S1));
	printf("%zd\n", sizeof(struct S2));
}


 

S1 和 S2 类型的成员⼀模⼀样,但是 S1 和 S2 所占空间的大小有了一些区别

S1如图:

S2如图:

3.修改默认对齐数 

#pragma 这个预处理指令,可以改变编译器的默认对齐数。
例子:

//vs默认对齐数8的情况
struct S
{
	char c1;//1		8		1
	int i;	//4		8		4
	char c2;//1		8		1
};
int main()
{
	printf("%zd\n", sizeof(struct S));
}

其中:#pragma pack(1) 的意思是设置默认对齐数为1,#pragma pack()的意思是取消设置的对齐数,还远为默认

//修改对齐数为1
#pragma pack(1)
struct S
{
	char c1;//1		1		1
	int i;	//4		1		1
	char c2;//1		1		1
};
#pragma pack()
int main()
{
	printf("%zd\n", sizeof(struct S));
	return 0;
}

 

如下图:

4.结构体实现位段  
        1). 什么是位段

位段的声明和结构是类似的,有两个不同:
1. 位段的成员必须是 int、unsigned int 或signed int ,在C99中位段成员的类型也可以
选择其他类型。
2. 位段的成员名后边有⼀个冒号和⼀个数字


 S就是位段

struct  S
{
	int _a ;//4个字节---32个bit位
	int _b ;//4个字节---32个bit位
	int _c ;//4个字节---32个bit位
	int _d ;//4个字节---32个bit位
};

//位段式结构体(位代表二进制位)
struct  S
{
	int _a : 2;//只占2个bit位
	int _b : 5;//只占5个bit位
	int _c : 10;//只占10个bit位
	int _d : 30;//只占30个bit位
};

int main()
{
	printf("%zd\n", sizeof(struct  S));
	return 0;
}

位段的大小: 

解析:结构体成员_a变量冒号后面加上一个数字代表该结构体成员是位段 ,_a后面的数字2代表着其会在内存中占用2个bit位;由于_a为int类型,所以内存会一次性开辟4个字节(32个bit位)大小;结构体成员_b变量冒号后面加上一个数字代表该结构体成员是位段 ,_b后面的数字5代表着其会在内存中占用5个bit位;结构体成员_c变量冒号后面加上一个数字代表该结构体成员是位段 ,_c后面的数字10代表着其会在内存中占用10个bit位;但是由于此时d会占用30个bit位此时一个int类型的剩余内存只有15个bit位不足d继续存放在这个int内存中,根据vs默认编译器当内存不足时会浪费掉剩余的空间重新开辟新的内存空间,此时会浪费剩余的15个空间重新开辟1个int类型的内存空间用来存放d的内存;结构体成员_d变量冒号后面加上一个数字代表该结构体成员是位段 ,_d后面的数字30代表着其会在内存中占用30个bit位;因此共开辟了(4 + 4 = 8)8个字节内存的空间,位段的大小就是8个字节。

提醒:位段在进行内存分配时会产生很多分歧,例如内存是从左向右存放还是从右向左存放,当内存不足于存放下一个位段时是浪费掉剩余的内存还是重新开辟内存空间不同的编译器是不同的。

在vs编译器上内存是从右向左存放,内存不足于存放下一个位段时会浪费掉剩余的内存。

 

 

struct S
{
	char a : 3;
	char b : 4;
	char c : 5;
	char d : 4;
};

int main()
{
	struct S s = { 0 };
	s.a = 10;//10的二进制:0000  1010
	s.b = 12;//12的二进制:0000  1100
	s.c = 3; //3的二进制: 0000  0011
	s.d = 4; //4的二进制: 0000  0100
	printf("%zd\n", sizeof(struct S));
	return 0;
}

解析: :结构体成员a变量冒号后面加上一个数字代表该结构体成员是位段 ,a后面的数字3代表着其会在内存中占用3个bit位;由于a为char类型,所以内存会一次性开辟1个字节(8个bit位)大小;此时a从左向右占用内存3个bit位大小,因为我们为a初始化为10,10的二进制表示为:

0000  1010,因此会将010存放在内存中;

b后面的数字4代表着其会在内存中占用4个bit位,此时存放好a的内存后内存还剩余5个bit位足以存放b 4个bit位大小的内存,所以b继续存放在这个char类型开辟的空间中;因为我们为b初始化为12,12的二进制表示为:0000  1100,因此会将1100存放在内存中;

存放好b后此时内存空间只剩余1个bit位大小不足以存放c的内存,所以这1个bit位会被浪费,重新开辟1个字节(8个bit位)大小空间用来存放c,c后面的数字5代表着其会在内存中占5个bit位,因为我们为c初始化为3,3的二进制表示为:0000  0011,因此会将0  0011存放在内存中;

存放好c后此时内存空间只剩余2个bit位大小不足以存放d的内存,所以这2个bit位会被浪费,重新开辟1个字节(8个bit位)大小空间用来存放d,d后面的数字4代表着其会在内存中占4个bit位,因为我们为d初始化为4,4的二进制表示为:0000  0100,因此会将0100存放在内存中;

 所以我们共向内存申请开辟了3个字节大小内存,所以位段大小为3个字节。

 

2).位段的内存分配 

1. 位段的成员可以是 int,unsigned int , signed int 或者是 char 等类型
2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的⽅式来开辟的。
3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
 

标签:char,int,联合体,C语言,位段,枚举,内存,对齐,bit
From: https://blog.csdn.net/2302_80892233/article/details/136953791

相关文章

  • Linux C编程一站式学习 part2: C语言本质
    LinuxC编程一站式学习(akaedu.github.io)22.Makefile基础1.基本规则欲更新目标,必须首先更新它的所有条件;所有条件中只要有一个条件被更新了,目标也必须随之被更新。“更新”:执行一遍规则中的命令列表,命令列表中的每条命令必须以一个Tab开头对于Makefile中的每个以Tab开头......
  • C语言UNIX域套接字CS模型
    实验目标:1实现基于流的unix域套接字通信cs模型2实现基于数据报的unix域套接字通信cs模型3可以观察到CS两端的完整启动退出流程,为了实现这一目标仅进行一次通信实验心得:1使用unlink避免地址冲突清理资源2 传统udp在首次sendto时系统临时分配端口,在套接字关闭|程......
  • c语言学习路线
    学习C语言可以按照以下路线进行:基础知识:了解C语言的基本语法和特性学习C语言的数据类型、控制流和函数熟悉指针和内存管理的概念数组和字符串:学习如何操作数组和字符串掌握数组和字符串的常见操作和算法熟悉C语言中的字符处理函数结构体和指针:理解结构体的概念和用法......
  • 快速排序(C语言)
    快速排序(英语:Quicksort),又称分区交换排序,简称「快排」,是一种被广泛运用的排序算法。快速排序的工作原理是通过分治的方式来将一个数组排序。快速排序分为三个过程:将数列划分为两部分(要求保证相对大小关系);递归到两个子序列中分别进行快速排序;不用合并,因为此时数列已经完全有序......
  • # c语言程序设计——实验报告二
    实验项目名称:实验报告2数据描述实验项目类型:验证性实验日期:2024年3月21日一、实验目的1、掌握C语言数据类型,熟悉如何定义一个整型、字符型和实型的变量,以及对它们赋值的方法。2、掌握不同数据类型之间赋值的规律。3、学会使用C的有关算术运算符,以及包含这些运算符的......
  • C语言作业(二)
    1.在数组中查找某个数字#include<stdio.h>intmain(){intarr[]={1,2,3,4,5,6,8,9,10,11};intk=7;intsz=sizeof(arr)/sizeof(arr[0]);//求解数组的元素个数intleft=0;intright=sz-1;while(left<=right){......
  • C语言作业(五)
     1.逆序字符串函数//写一个函数,来逆序一个字符串的内容#include<stdio.h>#include<string.h>#include<assert.h>voidreverse(char*str){assert(str);//保证指针的有效性intlen=strlen(str);char*left=str;//left指针指向第一个字符char*......
  • 20240322,结构类型,枚举,
     一,枚举1.1常量符号化 程序中用符号表达数字,增加程序的可读性?#include<stdio.h>//能跑,但是报错不推荐将字符串转为CHAR**constintred=0;constintyellow=1;constintgreen=2;//为撒在前面?intmain(intargc,charconst*argv[]){ //两个参数,整数,字符串数组......
  • Java 枚举(超详细讲解)
    Java语言的强大之处在于它提供了多种多样的类库,从而大大提高了程序的编程效率和质量。一、枚举事先考虑到某一变量可能的取值,尽可能用自然语言忠表意清楚的单词来表示它的每一个值,用这中思路定义的类型被称为枚举类型。枚举事由一组固定的常量组成的类型。在Java中每个枚......
  • # c语言程序设计——实验报告一
    实验项目名称:实验一熟悉C语言运行环境实验项目类型:验证性实验日期:2023年3月14日一、实验目的下载安装Devc6.0程序。了解在该系统上如何进行编辑、编译、连接和运行一个C程序。通过运行简单的C程序了解C程序的特点。二、实验硬、软件环境Windows计算机、Devc6.0三、......