zl程序教程

您现在的位置是:首页 >  后端

当前栏目

C语言结构体字节对齐 | 结构体与联合

C语言字节 结构 联合 对齐
2023-06-13 09:13:43 时间

结构体字节对齐

结构体的空间大小:

  • 结构体为了保证CPU的访问效率,默认采用内存对齐机制
  • 对齐标准为结构体中基础数据类型的成员最大值
  • 对齐标准和成员申明顺序有关
#include<stdio.h>
#include<string.h>

struct stu{
        int age;
        char name[15];
};

int main(){
    struct stu s1;
    printf("sizeof: d%\n",sizeof(s1));
    return 0;
    
}

1.结构体内存对齐是指

当我们创建一个结构体变量时,会向内存申请所需的空间,用来存储结构体成员的内容。我们可以将其理解为结构体成员会按照特定的规则来存储数据内容。

通过上图我发现结构体的存储不是简单的字节的累加:4+16 = 20;4+15 = 20;

2.为什么要使用字节对齐规则呢?

有关于内存的访问效率的问题,就是对于一个32位的CPU,一个周期它取出的应该是一个完整的周期,4个4个来取。

以上面案例分析:CPU可以在一个周期中直接取出age,因为age正好占4个字节,但是如果想取出name,占15个字节,cpu仍需要4个的来取,所以仍然需要取16个。

以上面图片分析,假如不采取内存对齐规则:CPU从上往下进行读取,上面的矩形代表age的4个字节,中间的椭圆代表name的15个字节,下面的矩形代其他数据占用的内存(假设占4个字节)。根据CPU读取内存的周期,我知道CPU需要读取name时要去读16个字节,也就是会读取到下面的其他数据的一个字节,但是使用时只截取前15个字节,可以正常使用,此时cpu指向下面的其他数据的那一个一个字节所在的地址处,即上面的箭头处,假如程序需要读取下面的那4个字节了,这时需要把指针往上调一个字节才能完整读取这4个字节,往上调这个动作不是这个程序该执行的动作,也就是说这使得程序的效率降低,为了满足结构体或者空间访问的效率,提出了一个概念:字节对齐。

3.结构体的对齐规则

(1)第一个成员在相比于结构体变量存储起始位置偏移量为0的地址处。

(2)从第二个成员开始,在其自身对齐数的整数倍开始存储(对齐数=编译器默认对齐数和成员字节大小的最小值,VS编译器默认对齐数为8)。

(3)结构体变量所用总空间大小是成员中最大对齐数的整数倍。

(4)当遇到嵌套结构体的情况,嵌套结构体对齐到其自身成员最大对齐数的整数倍,结构体的大小为当下成员最大对齐数的整数倍。

实例:

struct stu{
    char a;
    int b;
    char c;
}

int main(){
    struct stu s1;
    printf("sizeof: %d\n",sizeof(s1));//4(1)+ 4 + 4(1) = 12
    return 0;
    
}

分析:红色填充内存为结构体成员a,其为char类型(1个字节)且是第一个成员,由规则(1)可得如下;

橙色填充为结构体成员b,因其为int类型(4个字节)且不是第一个成员,由规则(2)可得如下;

绿色填充为结构体成员c,因其为char类型且不是第一个成员,由规则(2)(3)可得如下;

画红叉内存位置属于因对齐造成的浪费内存。

#include<stdio.h>
#include<string.h>

struct stu {
    
    char a;
    char b;
    int c;
};

int main() {
    struct stu s1;
    printf("sizeof: d%\n", sizeof(s1));//4(1 + 1) + 4 = 8
    return 0;
}

分析:

红色填充内存为结构体成员a,因其为char类型且是第一个成员,由规则(1)可得如下;

橙色填充为结构体成员b,因其为char类型且不是第一个成员,由规则(2)可得如下;

绿色填充为结构体成员c,因其为int类型且不是第一个成员,由规则(2)(3)可得如下;

画红叉内存位置属于因对齐造成的浪费内存。

struct stu{
    double a;
    char b;
    int c;
}

int main(){
    struct stu s1;
    printf("sizeof: d%\n",sizeof(s1));//16
    return 0;
    
}

分析:

红色填充内存为结构体成员a,因其为double类型且是第一个成员,由规则(1)可得如下;

橙色填充为结构体成员b,因其为char类型且不是第一个成员,由规则(2)可得如下;

绿色填充为结构体成员c,因其为int类型且不是第一个成员,由规则(2)(3)可得如下;

画红叉内存位置属于因对齐造成的浪费内存。

#include<stdio.h>
#include<string.h>

struct stu1 {

    double a;
    char b;
    int c;
}s1;

struct stu2 {

    double a;
    struct stu1 s1;
    int c;
}s2;

int main() {
    
    printf("sizeof: d%\n", sizeof(s2));//32
    return 0;
}

分析:

红色填充内存为结构体成员a,因其为double类型且是第一个成员,由规则(1)可得如下;

橙色填充为结构体成员s1,因其为嵌套结构体且不是第一个成员,大小为16,由规则(4)可得如下图;

绿色填充为结构体成员c,因其为int类型且不是第一个成员,由规则(2)(3)可得如下;

画红叉内存位置属于因对齐造成的浪费内存。

共用体与结构体

共用体

#include<stdio.h>
#include<iostream>

union stu{

    char a;
    short b;
    int c;
};

int main() {
    
    stu s1;
    printf("sizeof: %d\n",sizeof(s1));
    s1.a = '1';
    printf("sizeof: %c\n", s1.c);
    system("pause"); return 0;

}

1.共用体所占用的内存大小是共用体里面的最大数据类型所占的内存大小,上面案例int最大。

2.只给a赋值,但是输出c时输出了a的内容。

分析:内存中用左图来表示结构体,右图表示共用体。结构体的成员在内存中都有各自的空间,而共用体中共用同一块内存。

区别

共用体(联合):

使用union 关键字 共用体内存长度是内部最长的数据类型的长度。 共用体的地址和内部各成员变量的地址都是同一个地址

结构体大小:

结构体内部的成员,大小等于最后一个成员的偏移量+最后一个成员大小+末尾的填充字节数。 结构体的偏移量:某一个成员的实际地址和结构体首地址之间的距离。 结构体字节对齐:每个成员相对于结构体首地址的偏移量都得是当前成员所占内存大小的整数倍,如果不是会在成员前面加填充字节。结构体的大小是内部最宽的成员的整数倍。

共用体

#include <stdio.h>
//gcc让不同类型的变量共享内存地址 ,同一时间只有一个成员有效
union data{ 
    int a;
    char b;
    int c;
};

int main(){
    union data data_1 = {1};//初始化时只填写一个值。(同一时间只有一个成员有效)
    data_1.b = 'c';
    data_1.a = 10;//后赋值的才有效。前面的赋值被覆盖
    //打印地址,发现指向同一个地址
    printf("%p\n%p\n%p\n",&data_1.a,&data_1.a,&data_1.a);
    return 0;
}

PS:

结构体内的数组的两种表示方式:指针(常量区),数组(栈)

结构体中的数组可以进行赋值操作