0
点赞
收藏
分享

微信扫一扫

结构体大小的计算【C语言】

梅梅的时光 2022-04-05 阅读 81
c语言c++

一、struct 特点和注意事项

 1.结构体定义

struct stu1
{
	int num;
	char name[20];
	double score;
}s1;

struct stu2
{
	int num;
	char name[20];
	double score;
}s2;

PS:这里定义了一个结构体 stu1,并实例化了一个对象s1。但是注意,这里虽然stu1和stu2里面定义的变量是一样的,但是它们还是两个不同的结构体。

结构体最后有一个分号,要注意不要忘记写 

2. 匿名结构体

struct
{
	int num;
	char name[20];
	double score;
}s2;

        定义结构体的时候,可以不用定义结构体类型名,此种写法叫做匿名结构体用此结构体实例化的s2,使用只能使用一次

3.typedef类型重命名

        一般自定义的数据类型,都可以使用typedef来重命名。

typedef struct Node //类型重命名
{
    int i;
    char c;
}Node,*pNode;

        这里把struct Node重命名为了Node,把struct Node* (指针类型)重命名为了*pNode,以后进行结构体变量的声明的时候

        就可以简化写法,struct Node N,这种定义的结构体变量的写法简化为,Node N。指针声明,struct Node* pN,简化为,pNode pN

PS:在c++的语法里面,不用typedef也可以简化写法。 匿名对象不能重命名

4.结构体嵌套和变量初始化

struct Book
{
	int num;
	char name[20];
	char id[12];
};
struct Stu
{
	struct Book HG;
	struct Stu* next;
}HG={{1001,"zhaoshuang","ZS001"},NULL};

        会发现这里把Book嵌套进了Stu里面。这种写法如果不是很明白可以这样理解,一个学生Stu在图书馆里借了一本书,因为是图书馆的书,书也有名字和编号。

二、结构体大小的计算方法

1.一般结构

struct S
{
    char c1;
    int i;
    char c2;
}s1

sizeof(s1)

         这里用sizeof(s1),计算出结构体大小为12字节,发现如果按照直接1+4+1=6,来算比这个多出6个字节。

        这是因为结构体在内存中的存储,是按照一定规则来排列的。

        首先c1要放在在一个偏移为0处的地址,放好后到下一个偏移1,然后判断此地址的偏移是否是要存放的数据对齐数的整数倍。

        这里1不是i (4字节)的整数倍,会被自动补齐到偏移4处,而4是4的整数倍, 然后从偏移4处往后放4个字节,到达偏移8,8是1的整数倍,放置变量c2,到达偏移9处。

        最后结构体的大小必须是最大对齐数的整数倍,这里最大对齐数是4,而已经存了9个字节,但是9不是4的整数倍所以这里补齐到11偏移处,整体大小为12字节,12是4的整数倍,所以这里12就是结构体大。

PS:这里蓝色的区域就是补齐后浪费的区域。

        计算结构体大小时候,不一定是按照结构体中的变量自身的大小来做对齐数,对齐数是变量自身对齐数和默认对齐数的较小值

        如果默认对齐数是4,那么一个double类型的数据也要按照4字节来对齐。 

PS:vs中默认对齐数的大小是8,Linux系统中没有默认对齐数,对齐数就是变量本身的大小

2.注意事项

offsetof -- 一个宏,计算变量相较于起始地址的偏移量

struct s1
{
    char c1;
    int i;
    char c2;
};
printf("%d\n",offsetof(struct s1,c1));    
printf("%d\n",offsetof(struct s1,i));
printf("%d\n",offsetof(struct s1,c2));

可以看到跟我们实际计算是一样的。

#pramga pack(4);

         使用这行代码就可以改变编译器的默认对齐数。括号里面是几对齐数就是几。这里要注意设置默认对齐数的时候,是为了让某些数据跟好的对齐,所以不要把对齐数改为1和其他奇数,或者0,变为1就是没有对齐数,顺序排列,0是错误的写法。

3.结构体嵌套的大小计算

struct s3
{
    double d;
    char c;
    int i;
}; //结构体大小为 16
struct s4
{
    char c1;
    struct s3 s3;
    double d;
};

        先把c1存在偏移为0的地址处,到偏移1。

        然后存放结构体s3的时候, s3要对齐到自己的最大对齐数的整数倍,自己最大对齐数是double类型的变量d,对齐数为8,1不是8的倍数,补齐到偏移8处,再加上s3的大小16,到了偏移24处。

        24是8的倍数,所以加上8到了31偏移处,结构体整体大小为32,这里最大对齐数包括嵌套的结构体的最大对齐数,这里s3跟s4都最大的对齐

890数都是8,32是8的整数倍,所以结构体s4大小为32字节

 

4.巧算

PS:这里偏移处要存一个字节,所以这里才到偏移31处,而不是32处,但是整体存的字节是32个字节

        那么你自己计算的时候就不用一定要按照计算机的规定来,这里我们自己计算的时候就可以把结构体想成是从偏移1处直接放置的。

struct s3
{
    char c;
    double d;
    int i;
};

         那么直接加到几就是几,这里c在偏移1处就是1,1补齐到8,8再加8就是16,16再加4就是20,20不是8的倍数补齐到24。那么24就是结构体的大小。

        当然如果还不是特别理解结构体的话,还是老老实实计算吧。

三、结构体为什么需要对齐

        那么为什么结构体要浪费空间进行数据对齐呢,直接一个挨着一个排不是节约空间吗?

        其实在x32位系统中,计算机访问内存一次4个字节。假设一段内存存了a(4字节)、b(3字节)、c(4字节)三个变量,一个顺序排列,一个对齐排列。

        

        根据图示我们会发现,这里要访问c变量的话,要进行两次访问。要是访问b变量的话,则会访问到一部分c的数据。看似节省了空间但是降低了效率

         

        而这里对齐访问的话,访问c值需要一次就好,访问b不会访问到多余的数据。看似浪费了空间,但是却提升了效率

        所以结构体的数据对齐是空间换时间的方法。


拓展: 

位段:

        位段是用来节约空间的一直写法,不考虑对齐。

struct A
{
	int a:2;		//:冒号后面的意思是此变量,能用的字节数
    int b:5;
    int c:10;
};//位段没有对齐,其目的就是为了节省空间

EXMPLE: 

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

struct S s={0};
s.a=10;
s.b=12;
s.c=3;
s.d=4;

        这里结构体首先申请一个字节存储a,a使用3个比特位,b使用4个比特位。到c的时候,存不下了,再申请一个字节,c使用5个比特位,剩下的不够存d,那么再申请一个空间来存储d,d使用4个比特位。

        

        然后把数据存储进去,a=10,10 = 二进制 1010 ,但是只能a存储三个比特位,把前三个比特位101存了进去。b=12,12 = 二进制 1100,b有4个比特位可以全部存进去。c=3,3 = 二进制 011,也可以全部存入,d=4,4 = 二进制 0100,也可以存入,所以这里3个字节的在内存中的存储为。

         在内存中就是 0x62 03 04

联合体:

        关键字union,也是一种自定义的数据类型,特点是联合体变量成员共用一块空间。联合体大小,最小都是最大变量的大小。

        要注意和结构体不一样,结构体是每一个变量都有一个独立的空间 

union Un
{
	char c;
    int i;
}
Un u;
sizeof(u);//u大小为4,跟struct不同

        这里计算的大小是4字节。但是注意联合体也是存再数据对齐的。

union Un
{
	char arr[5]; //5字节
	int i;	//4字节
};

        这里arr数组大小是5个字节,但是对齐数还是char类型的大小1,i 的对齐数是4,因为共用一块空间且大小为最大变量的大小,那么联合体大小就是5。    

        但是因为存在对齐的情况,5不是4的最大对齐数4的整数倍,所以补齐到8,那么这里8就是联合体的大小。

利用联合体特性判断机器大小端:

int checl_sys()
{
    union D
    {
        char c;
        int i;
    }u;
    u.i=1;
    return u.c
}
//如果 返回的是0,则是大端
//如果 返回的是1,则是小端

         因为这里i和c共用空间,如果是小端存储 01 00 00 00,读取char就会读取到相同空间中i的首地址存储的数据,那么如果是1就证明机器是小端存储数据。

举报

相关推荐

0 条评论