栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

结构体与其对齐规则

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

结构体与其对齐规则

结构体

定义格式

//基本定义格式
struct  结构体类型名
{
	成员1类型  成员1名;
	成员2类型  成员2名;
	成员3类型  成员3名;
	....
	成员n类型  成员n名;
};

例如:
struct Student_info
{
	char Name[64];
	char ClassID[7];
	char Sex;
	char Age;
};

//如何使用自定义的结构体类型去定义变量:
格式:
struct 结构体类型名  结构体变量名;
struct Student_info a; //定义一个结构体变量a


//定义结构体类型的同时定义变量
struct  结构体类型名
{
	成员1类型  成员1名;
	成员2类型  成员2名;
	成员3类型  成员3名;
	....
	成员n类型  成员n名;
}变量名1,变量名2....,变量名n;//定义了若干个结构体变量

例如:
struct Student_info
{
	char Name[64];
	char ClassID[7];
	char Sex;
	char Age;
}a,b,c,d,e;


//定义结构体类型的同时定义变量,但是是一次性的,以后就不能再创建再结构体变量了。
struct  
{
	成员1类型  成员1名;
	成员2类型  成员2名;
	成员3类型  成员3名;
	....
	成员n类型  成员n名;
}变量名1,变量名2....,变量名n;//定义了若干个结构体变量

例如:
struct //匿名结构体
{
	char Name[64];
	char ClassID[7];
	char Sex;
	char Age;
}a,b,c,d,e;//该结构在这个语句后不能再定义新的结构体变量了,因为没名字。


//定义结构体类型的同时,为其取个类型别名
-----------------------------------------------------------------------------
C关键词: typedef   为C数据类型取别名
取别名格式:
typedef  原名  别名;

例如:
typedef  unsigned int  u32;

typedef给函数指针类型取别名
typedef  void  (*p)(int ,int) ;//给函数指针类型vod (*)(int,int) 取了个别名p(p是类型)
可以使用原版方法:
void (*p1)(int,int);
使用别名:
p p1;//定义一个函数指针变量p1
-----------------------------------------------------------------------------
对结构体定义并取别名
typedef struct  结构体类型名
{
	成员1类型  成员1名;
	成员2类型  成员2名;
	成员3类型  成员3名;
	....
	成员n类型  成员n名;
}结构体类型别名;

例如:
typedef  struct Student_info
{
	char Name[64];
	char ClassID[7];
	char Sex;
	char Age;
}STD,*pSTD;//结构体类型别名 STD ,同时为结构体指针类型取了别名pSTD

struct Student_info a;//定义一个结构体变量a
STD  a;//使用别名定义一个结构体变量a

struct Student_info *p;//定义一个结构体指针变量p
pSTD  p;//使用别名定义了一个结构体指针变量p
注意:以上定义结构体类型,使用了typedef后可以不给结构体类型名,但之后定义变量或指针只能使用别名!!!
例如:
typedef  struct 
{
	char Name[64];
	char ClassID[7];
	char Sex;
	char Age;
}STD,*pSTD;//结构体类型别名 STD ,同时为结构体指针类型取了别名pSTD
之后想要创建该结构体的变量就只能使用STD或pSTD了。

结构体定义时的初始化问题

typedef struct  Bank_UserInfo
{
	char name[32];
	char Sex;
	char PhoneNum[12];//电话
	int Age;
	char CardID[19];//银行卡号
	float  money;//存款
}BUI,*pBUI;

//全部初始化:按顺序填充初值
BUI a={"kkk",0,"18707694830",18,"xxxxx",99.8};

//部分初始化:
BUI a={"kkk",0};//后面未初始化默认为0 和数组很像;必须从头按顺序开始,不能从中间开始

//选择初始化--初始化部分成员:
BUI a={.name="kkk",.Age=18,.money=99.8};

结构体的自引用

struct Node
{
    int a;
    char b;
    struct Node data;//这种写法是错误的
};
//正确写法
struct Node
{
    int a;
    char b;
    struct Node *data;
};
-----------------------------------------------------------
typedef struct Node
{
    int a;
    char b;
    Node *data;//这种写法是错误的,因为是先执行成员列表再执行到命名的Node,所有这里编译器是不知道Node是什么
}Node;
//正确写法:
typedef struct Node
{
    int a;
    char b;
    struct Node *data;
}Node;

结构体的成员类别

  1. 基本数据类型

  2. 数组

  3. 结构体

  4. 指针

  5. 枚举

  6. 共同体

  7. 其它结构体(不能是自己的结构体变量,可以是自己的结构体指针)

结构体类型大小

typedef struct  Bank_UserInfo
{
	char name[32];//默认就是32byte
	char Sex;
	char PhoneNum[12];//电话
	int Age;
	char CardID[19];//银行卡号
	float  money;//存款
}BUI,*pBUI;

sizeof(BUI) = 76;//32+1+12+(3)+4+19+(1)+4 = 76-->括号括起来的是为了对齐而补上的,76刚好为4(成员中最大的字节)的倍数

对齐的意义:

字节对齐,增加CPU读取数据的速度!!!

为什么要对齐?

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

  2. 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

    总体来说:

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

    那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:

    让占用空间小的成员尽量集中在一起。

基本对齐规则

规则1 :结构体(struct)的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存放在offset为该数据成员大小的整数倍的地方(比如int在32位机为4字节,则要从4的整数倍地址开始存储)。

规则2:如果一个结构体B里嵌套另一个结构体A,则结构体A应从offset为A内部最大成员的整数倍的地方开始存储。(struct B里存有struct A,A里有char,int,double等成员,那A应该从8的整数倍开始存储。),结构体A中的成员的对齐规则仍满足原则1、原则3。

结构体A所占的大小为该结构体成员内部最大元素的整数倍,不足补齐。不是直接将结构体A的成员直接移动到结构体B中

规则3:结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐.

结构体内含有数组,看成是连续若干个同类型数据处理即可

强制对齐规则

//结构体后面加:
__attribute((aligned (n))) //n:1 2 4 8
按照n字节对齐,如果结构体成员有大于n字节成员,使用默认基本规则,否则按照规定的对齐规则

    
__attribute__ ((packed))
取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐,没有所谓的对齐(相当于以1字节对齐)

例如:
struct  abc
{
	char a;//1+空隙7Byte
	double b;//8byte
	char c;//1+空隙3Byte
	int d;//4byte
	char e;
}__attribute((aligned (4))) ;//32

struct  abc
{
	char a;//1
	char b;//1
	char c;
}__attribute((aligned (4))) ;//4

struct  abc
{
	char a;//1
	char b;//1
	char c;
};//3

struct  abc
{
	char a;//1
	double b;//8
	char c;//1
	int d;//4
	char e;//1
}__attribute__ ((packed));//15

//使用伪指令
#pragma pack(n)
C编译器将按照n个字节对齐--强制。
例如:
#pragma pack(1)
struct  abc
{
	char a;//1
	double b;//8
	char c;//1
	int d;//4
	char e;//1
};//15

//使用伪指令
#pragma pack ()
取消自定义字节对齐方式。
例如:
#pragma pack(1)
#pragma pack ()
struct  abc
{
	char a;//1
	double b;//8
	char c;//1
	int d;//4
	char e;//1
};//32

结构体的成员访问

结构体变量
	点访问
例如:
struct  abc
{
	char a;
	double b;
	char c;
	int d;
	char e;
};
struct abc  arg;

arg.a = 1;
arg.b = 1.2;
arg.d = 100;


结构体指针:
	箭头访问  ->
前提是你的结构体指针必须是有指向一个有效的结构体空间
struct  abc  arg;
struct abc *p = malloc(sizeof(struct abc));
p->a = 1;
p->b = 1.2;
p->d = 100;


结构体嵌套访问:
struct  abc
{
	char a;//1+7
	double b;//8
	char c;//1+3
	int d;//4
	char e;//1+7
};//32
struct def
{
	char a;//1
	char b[10];//10+5(为后面的32字节补齐)
	struct abc e;//32
	char f;//1+7
};//56

struct def   arg;
arg.e.a = 10;//多层嵌套 结构体变量用点.逐层访问

结构体传参

struct abc
{
	int a;
    char b;
}
struct abc data = {1,'a'};
//结构体传参
void print(struct abc data)
{
    printf("%dn",data.a);
}
//结构体地址传参
void print2(struct abc *data)
{
    printf("%dn",data->a);
}
int main()
{
    print(data);
    print2(&data);
    return 0;
}
//上面两种传参形式,哪种更好?
一般都是首选print2函数;因为函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。如果传递的结构体对象是很大的,参数压栈的系统开销就会很大,导致性能下降。

位域结构体

//有时候我们使用数据类型来存储一些数据,但这些数据只需要占用该变量空间中很少的一部分,剩下的空间就浪费了(无法再被使用);使用位域,就可以让数据只占用该空间中的某几个位,剩下的位可以分配给其它数据使用
struct 位域结构名
{
    类型说明符 位域名1:位域长度;
    类型说明符 位域名2:位域长度;
    ...
};
//只能用于整数类型,char、int...;不能用于float、double

例如:
struct abc
{
	unsigned char  a:1;//0
	unsigned char  b:3;//1~3 
    char           d:3;//4~6
};//占用空间大小为1

struct abc
{
	unsigned char  a:1;//0
	unsigned char  b:3;//1~3 
    char           d:3;//4~6
    char		   e:2;//0~1
};//占用空间大小为2

位域结构体的大小和访问
大小问题:
	①连续的同种类型成员,其位域加起来不超过当前类型大小,算一个当前类型
	②如果超过了1个当前类型大小,需要跨一个当前类型大小的空间
	③位域长度不能超过当前类型
	//例如上面的题目:a、b、c变量总共占用7个位,可以全部存在char(8位)中的,如果再加上一个e,就变成了9位,超出了char的位数,所以e就不能再放在第一个char中,只能放在第2个char中。
访问问题:
	会因为宽度问题,把大的数据截断---按照位域长度操作结构体变量
例如:
struct abc arg;
arg.a = 2;//因为a只占1位,只能存放0和1,当存入2-->00000010时,会发生数据截断,只取第一位0
printf("%dn",arg.a);//0
arg.a = 3;//00000011-->截断取1
printf("%dn",arg.a);//1

//位段的内存分配
		1、位段的成员可以是int 、unsigned int、signed int或者是char(属于整形家族)类型
		2、位段的空间上是按照需要以4个字节(int)或者1个字节(char)的方式来开辟的。
		3、位段的空间很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。(因为C语言没有给出明确的规定,所以位段有些标准是按照编译器自己的理解来执行的,每个编译器的方法可能不一样,所以不支持跨平台)

//位段的跨平台问题
		1、int位段被当成有符号数还是无符号数是不确定的。
		2、位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,在16位机器会出问题。)
		3、位段的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
		4、当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
总结:
跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。

枚举

枚举的定义格式
enum 枚举类型名
{
	枚举名1,
	枚举名2,
	枚举名3,
	枚举名4,
	....
	枚举名n
};
例如:
enum week
{
	Mon,
	Tru,
	Wen
};

枚举类型定义枚举变量:
enum week  a;//定义一个枚举变量a-->相当于int a;
a = Mon;//0
a = Wen;//2
a = 100;//100
注意:枚举变量定义完成后,枚举类型里面的只是可选列表,枚举类型内部的可选列表标识符可以直接使用.
    
枚举类型的大小
sizeof(enum week) == 4;(不管枚举里有多少个成员,大小都是4)

枚举类型的本质
枚举类型的本质可以看作一个int类型,定义枚举变量时,选用那个选项,这个选项的数据就占据该枚举的空间
    
枚举类型内的可选列表参数,不给初值默认从0开始,后面的选项的值为前面选项的值+1;
如果某个选项给了初值后,该选项之前的选项仍然使用默认规则,后面的选项也是等于前面选项的值+1.
例如:
enum abc
{
	a,//0
    b,//1
    c = 6,//6
    d,//7
    e//8
};//4

枚举的优点
	我们可以使用#define定义常量,为什么非要使用枚举?枚举的优点:
	1、增加代码的可读性和可维护性
	2、和#define定义的标识符比较枚举有类型检查,更加严谨
	3、防止了命名污染(封装)
	4、便于调试(用define,在调试时常量已经被替换了,调试看到的内容和编译器实际运行的内容不一样,可能发生一些错误而不容易被发现;但是使用枚举调试看到什么运行就是什么)
	5、使用方便,一次可以定义多个常量

共用体/联合体

特性:多个成员变量共用一个内存 ,并且是互斥的

//共用体的定义格式
union  共用体名
{
	数据类型  成员1名;
	数据类型  成员2名;
	.....
};	
例如:
union  Selection
{
	char Job[32];
	float C_Grade;
};

//共用体类型大小
共用体的类型大小按里面最大的成员大小计算
sizeof(union Selection) == 32

共用体顾名思义,里面所有的成员都共用同一块空间,每个成员都是从基地址开始,你修改了任何一个共用体成员的数据,其他的成员数据都会受到影响。
例如:
union abc
{
    char a;//1
    int b;//4
    float c;//4
    long e;//8
};//8

union abc data;
data.a = 1;
printf("%dn",data.a);//1
data.b = 10;
printf("%dn",data.a);//10
printf("%dn",data.b);//10
    
//共用体成员访问与结构体一样
    共用体变量:点访问
    共用体指针:箭头访问

        
//使用共同体来判断大小端
int check()
{
   union u
   {
       char a;
       int b;
   }
   union u un;
   un.b = 1;
   return (int)un.a;
}
int main()
{
    int ret = check();
    if(ret == 1)
    {
        printf("大端n");
    }else
    {
        printf("小端n");
    }
    return 0;
}

//共同体的大小
共同体的大小就是成员里最大的那个成员的大小(需要对齐);
例如:
union abce
{
    short c[9];//18
    int i;
};//20--->最大成员是18,对齐后为20就是该结构体的大小
union abce
{
    char c[9];//9
    int i;
    double a;
};//16--->最大成员是9,需要与8对齐,对齐后为16.

>>
指针、野指针、常量指针、指针常量
C语言实现24点游戏算法
C语言实现汉诺塔

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/512678.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号