C语言结构体全面详解:从声明到内存对齐与位段

前言

在C语言编程中,结构体是一种非常重要的复合数据类型,它允许我们将多个不同类型的数据组合成一个单一的类型。本文将全面深入地讲解C语言结构体的各个方面,包括声明、初始化、访问、内存对齐、传参以及位段的使用。

1. 结构体类型的声明

结构体是一种用户自定义的数据类型,用于将多个不同类型的变量组合成一个单一的类型。结构体的每个成员可以是不同的数据类型。

1.1 结构体的声明

示例:描述一个学生

1.2 结构体变量的创建

struct Stu为我们所创建的结构体类型,不是变量,类型里包含各种成员变量

s为我们按照结构体类型所创建的变量,随后按照成员变量进行初始化

1.3 结构体成员的引用

创建了结构体变量,我们就应该引用其中的成员变量

其中可以使用.和->操作符

结构体变量名称.成员名称就可以引用成员变量

如果我们创建结构体指针,那么就可以利用结构体指针->成员变量进行访问

1.4 结构体的特殊声明

在声明结构体时,可以省略标签(tag),称为“匿名结构体”。但注意:匿名结构体只能使用一次。

上⾯的两个结构在声明的时候省略掉了结构体标签(tag)

由于没有tag,那么就无法利用指针设置结构体的指针

因为编译器会将两个匿名结构体视为不同的类型,因此 p = &x; 是非法的。

1.5 结构体的自引用

我们之前在函数中可以自己调用自己,那么结构体能不能自己包含自己?

仔细分析,其实是不⾏的,因为⼀个结构体中再包含⼀个同类型的结构体变量,这样结构体变量的⼤ ⼩就会⽆穷的⼤,是不合理的。如果用sizeof求结构体类型的大小,又是多少?可以想象是无穷大

正确的⾃引⽤⽅式:

2. 结构体内存对齐

如果我们利用sizeof求结构体的大小来看看

直观上看,char为1个字节,int和float为4个字节,那么加起来应该为9个字节,但显示的却是12个字节

说明结构体大小不能简单地把各个大小都加起来,因为结构体内部存在结构体内存对齐

2. 1对齐规则

  1. 第一个成员在偏移量为0的地址。

  2. 其他成员对齐到 对齐数 的整数倍地址。

    • 对齐数 = min(编译器默认对齐数, 成员大小)

    • VS默认对齐数为8,Linux中gcc无默认对齐数。

  3. 结构体总大小为最大对齐数的整数倍。

  4. 嵌套结构体对齐到其内部最大对齐数的整数倍。

在上述案例中,char的大小为1,vs对齐数为8,取较小值则为1,依次类推int和float对齐数都为4

char为第一个变量,放在与结构体首地址偏移量为0的位置

int对齐数为4,得放在偏移量为其对齐数整数倍的地方,也就是4。那么就说明1-3的位置被浪费了

float对齐数为4,可以直接放在对齐数为8的位置

然后普总大小是最大对齐数的整数倍,也就是4的倍数

这样一算大小就为12了

2.2 为什么存在内存对⻬?

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

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

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

2.3 修改默认对齐数

使用 #pragma pack(n) 修改对齐数:

3.结构体位段

3.1 什么是位段?

位段是一种特殊的结构体成员,用于指定成员占用的比特位数。

在某些情况下,我们使用一个变量只需要赋值为0或1,或者其他较小的值

如果我们使用int类型,占了32个比特位,但实际上若存储1或0只需要1个比特位,就会造成极大的空间浪费

因此存在结构体位段,来指定结构体成员的大小

在上述案例中,a被指定只需要2个比特位,b只需要5个,以此类推。

3.2 位段的内存分配

1. 位段的成员可以是 int unsigned int

2. 位段的空间上是按照需要以4个字节( signed int 或者是 char 等类型 int )或者1个字节( char )的⽅式来开辟的。

3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使⽤位段。

在位段存储中,无非是几种变量挤在一块空间中

这种有两种存储方式,一种是正着存,一种是倒着存,那么到底是哪种?

这种标准尚未定义,取决于编译器

那么如果一块空间容不下下一个变量,这个变量是会继续利用剩余的空间,然后再开辟一块空间;还是把全部的数据都存储在另一块空间中?

如果通过调试内存,会发现是第二种

因此结构体的大小为3个字节

3.3 位段的跨平台问题

1. int 位段被当成有符号数还是⽆符号数是不确定的。

2. 位段中最⼤位的数⽬不能确定。(16位机器最⼤16,32位机器最⼤32,写成27,在16位机器会 出问题。

3. 位段中的成员在内存中从左向右分配,还是从右向左分配,标准尚未定义。

4. 当⼀个结构包含两个位段,第⼆个位段成员⽐较⼤,⽆法容纳于第⼀个位段剩余的位时,是舍弃 剩余的位还是利⽤,这是不确定的

总结:

跟结构相⽐,位段可以达到同样的效果,并且可以很好的节省空间,但是有跨平台的问题存在。

3.4 位段注意事项

位段的⼏个成员共有同⼀个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位 置处是没有地址的。内存中每个字节分配⼀个地址,⼀个字节内部的bit位是没有地址的。 所以不能对位段的成员使⽤&操作符,这样就不能使⽤scanf直接给位段的成员输⼊值,只能是先输⼊ 放在⼀个变量中,然后赋值给位段的成员

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值