c语言中结构体以及结构体中的对齐

结构体

结构体类型的定义

定义格式:

struct StructName {
    // 成员变量
    int member1;
    float member2;
    char member3;
}; 

声明结构体类型变量

定义结构体类型时声明

struct Point {
    int x;
    int y;
} p1, p2;  // 声明两个 Point 类型的变量 p1 和 p2

单独声明变量

struct Point p1;  // 声明一个 Point 类型的变量 p1
struct Point p2;  // 声明另一个 Point 类型的变量 p2

c语言中,每次声明结构体变量时都需要使用 struct 关键字,这是因为 C 语言的类型命名空间和标签命名空间是分开的。

c++中,struct Point 定义了一个类型 Point可以直接用Point来声明变量,类型命名空间和标签命名空间是合并的。

那么如何让c语言像c++一样,不用struct关键字就可以声明变量呢?

答案如下:通过使用 typedef,可以为结构体类型创建一个别名,从而简化变量声明:

typedef struct {

     int x;

     int y;

} Point; 

结构体变量的初始化赋值

1.使用初始化列表

struct Point p1 = {10, 20};

这样赋值做起来简单,但是一般都是需要对结构体所有成员变量进行赋值,当我们仅想对部分成员进行初始化赋值,这种方法就不太适用了。

还有就是后期临时在结构体类型中增加了成员变量,那么原先定义的变量的初始化列表赋值可能就类型不匹配而报错。当然这种临时改变结构体类型几乎不会发生。

2.逐个成员赋值

p1.x = 10; p1.y = 20;

.运算符是用来访问结构体变量的成员变量。

结构体中特殊的运算符->

跟普通变量别无二致,可以定义指向结构体的指针,也可以定义结构体数组。

struct node  n = {100, 200};
struct node *p = &n;

// 以下语句都是等价的
printf("%d\n",   n.a);
printf("%d\n", (*p).a);
printf("%d\n",  p->a);  // 箭头 -> 是结构体指针的成员引用符

可能就是觉得解引用再利用.来访问成员变量有点麻烦,所以便有了利用->直接通过指针来访问结构体变量的成员这种方式。 

结构体中的对齐

结构体对齐方式

规则总述

结构体的对齐(alignment)确实遵循一定的规则。这些规则确保结构体中的成员变量在内存中是对齐的,以便硬件可以高效地访问它们。结构体内存对齐的规则:

1.第一个成员在结构体变量为0的地址处

2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址
对齐数=编译器默认的对齐数与该成员大小的较小值

3.结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍

4.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍数,外层结构体的总大小就是所有成员最大对齐数(含嵌套结构体的对齐数)的整数倍
                        
原文链接:https://blog.csdn.net/Dreaming_TI/article/details/127495657

下面详细解释结构体中成员变量的对齐整体结构体的对齐原则

结构体成员变量的对齐

结构体成员的对齐取决于编译器默认的对齐数与该成员数据类型大小较小值例如:

假定编译器默认对齐数为8字节(64位系统通常默认对齐数为8,linux没有默认对齐数)

  • char 类型通常对齐到1字节。
  • short 类型通常对齐到2字节。
  • intfloat 类型通常对齐到4字节。
  • double 类型通常对齐到8字节。

整体结构体的对齐

结构体的整体对齐数通常取决于其成员中对齐数最大的那个,因为成员的对齐数与编译器默认的对齐数有关,所以结构体的整体对齐数也与编译器默认的对齐数有关

struct MyStruct {
    char a;    // 对齐到1字节
    int b;     // 对齐到4字节
    double c;  // 对齐到8字节
};

在这个结构体中,double c 需要8字节对齐,编译器也是8字节,因此整个结构体的对齐方式通常是8字节。

成员间的填充

为了确保每个成员都按照其对齐要求对齐,编译器可能会在成员之间插入填充字节。

同时为了保证最后的整个结构体符合整体的对齐,也可能会在末尾插入填充字节。

结构体对齐示例

下面的例子中规定编译器默认对齐数为8字节

例子1:

结构变量的对齐:

>由规则1 “第一个成员在结构体变量为0的地址处”,故c的位置在0地址处(橙色部分),又因为是char类型,只占第一个字节
>由规则2,该成员变量为int类型,大小为4个字节,比vs默认对齐数8要小,故i对齐到对齐数4的整数倍即下标为4的位置(绿色部分),并向下占用4个字节
>同理,d为double类型,大小与默认对齐数相同,故从i的结束位置向下找8的整数倍数,并占用8个字节
>蓝色部分即为结构体内存对齐遵循规则浪费的空间

整体结构体的对齐:

该结构体的变量最大对齐数为8,环境也为8,故结构体对齐数为8,上面正好占用16,为最大对齐数的整数倍

最后输出结果为:16

原文链接:https://blog.csdn.net/Dreaming_TI/article/details/127495657

 例子2:(含嵌套结构体)

结构变量的对齐:

>同例①一样,c1的位置从S2的起始位置0处开始
>由例①知,struct S1 s1的大小为16,大于默认对齐数,则对齐数为较小值8,故从对齐数的整数倍下标为8的地址处开始,并向下占用16个字节
>d1的大小是8个字节,与默认对齐数一致,故向下取8的整数倍,而24恰好为8的整数倍,则从24开始向下占用8个字节
>蓝色部分即为结构体内存对齐遵循规则浪费的空间

 整体结构体的对齐:

嵌套了结构体(S1),且嵌套的结构体(S1)对齐到自身成员内最大对齐数8的位置,而结构体(S2)的总大小为32,恰好为S1的整数倍

最后输出结果为:32

原文链接:https://blog.csdn.net/Dreaming_TI/article/details/127495657

例子3:(最后不满足整体规则,要补为整数倍) 

struct MyStruct {
    char a;    // 对齐到1字节
    int b;     // 对齐到4字节
    double c;  // 对齐到8字节
    int d;     // 对齐到4字节
};
int main() {
    struct MyStruct a;
    printf("%d",sizeof(a));

    return 0;
}

偏移量:
0     :a (1 byte)
1-3  :填充字节 (3 bytes)
4-7  : b (4 bytes)
8-15 :c (8 bytes)
16-19 :d (4 bytes)
 19-23:填充字节(4 bytes)因为不满足整体是8的整数倍

输出:24

如果这里改为char d,那么将变为

16 :d (1 bytes)

17-23:填充字节(4 bytes)因为不满足整体是8的整数倍

输出:24

合理排序结构体成员来减少总占用空间

当使用结构体的时候可以适当的调整结构体成员的排序,因为不同的排序可能导致结构体的总大小有所差异,正确的排序可以占用更少的大小

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

struct S2
{
	char c1;
	char c2;
	int i;
}s2;

void main()
{
	printf("%d\n", sizeof(s1));//12
	printf("%d\n", sizeof(s2));//8
}

自定义对齐

#pragma pack (n)是一种预处理指令,用于告诉编译器如何对齐结构体的成员。它可以指定成员的对齐方式为特定字节大小的倍数。

其中 n 是所需的对齐字节数。编译器会调整结构体成员的对齐方式,使其每个成员都按照 n 的倍数进行对齐。

例如,指定 #pragma pack(1) 将强制所有成员按照1字节对齐:

#pragma pack(1)

struct MyStruct {
    char a;    // 1字节对齐
    int b;     // 1字节对齐
    double c;  // 1字节对齐
};

有时可以通过编译器指令或特定的语言扩展来指定结构体或其成员的对齐方式。例如,在GCC编译器中,可以使用 __attribute__((aligned(n))) 来指定对齐。第二种方式在linux下才能用

struct MyStruct {
    char a;
    int b;
    double c;
} __attribute__((aligned(16)));  // 强制结构体对齐到16字节

总结来说,结构体中每个变量的对齐遵循其数据类型的对齐要求,而整个结构体的对齐遵循其成员中最大对齐要求的成员。外观上,结构体的内存布局会通过插入填充字节来确保所有成员变量都正确对齐。

为什么要结构体对齐

1.内存对齐的硬件要求

许多计算机体系结构(如x86、ARM等)要求某些数据类型必须位于特定的内存地址上,否则会导致访问效率低下甚至错误。例如,大多数平台要求整数类型(如 intlong)必须在4字节或8字节的地址上对齐,而双精度浮点数(double)通常需要在8字节或更大的地址上对齐。如果数据没有按照硬件要求对齐,可能会导致处理器需要额外的时间来访问数据,或者在某些架构上甚至会导致异常。

2. 访问效率和性能优化

正确的结构体对齐可以确保结构体的每个成员都位于其所需的地址上,从而减少内存访问时间和处理器的数据加载时间。这对于需要频繁访问结构体成员或大量处理结构体实例的程序特别重要,可以显著提升程序的执行效率和性能。

3. 数据传输的要求

在某些应用场景中,如网络数据包的传输或存储设备的数据处理,数据的对齐方式直接影响数据的传输效率和解析速度。通过结构体对齐,可以保证数据在传输过程中的正确性和高效性。

4. 平台兼容性和移植性

结构体对齐可以帮助确保程序在不同的硬件平台上具有相同的行为和性能表现。尤其是在开发跨平台应用程序时,合理地利用结构体对齐可以减少因平台差异而引入的错误和性能问题。

  • 16
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值