结构体嵌套,结构体大小,内存对齐【结构体】

结构体嵌套,结构体大小,内存对齐【结构体】(34)_Code_Beginner-CSDN博客_结构体嵌套内存对齐

结构体嵌套,结构体大小,内存对齐
结构体嵌套
结构体类型的小大
内存对齐
小结
总结
结构体嵌套
我们继续说明C语言结构体的基本知识,接下来我们说明结构体嵌套,结构体嵌套的意思就是说在结构体里面使用结构体,接下来我们通过例子进行说明。

我们同样使用学生结构体但是这次我们在定义两个结构体,一个结构体保存学生的出生年月日,另一个结构体保存的是学生的信息,而学生信息里面有一项是学生的出生年月日。这样我们就实现了结构体的嵌套。
代码演示:

#include <stdio.h>

typedef struct _birth
{
    int year;
    int month;
    int day;
}Birth;

typedef struct _stu
{
    char name[1024];
    int num;
    char sex;
    float score;
    Birth brith;
}Stu;

//下面嵌套的写法和上面结构体嵌套的写法等价
/*
typedef struct _stu
{
    char name[1024];
    int num;
    char sex;
    float score;
    typedef struct _birth
    {
        int year;
        int month;
        int day;
    }Birth;
}Stu;
*/

int main()
{
    Stu s = { "wangwu",1,'f',99,{1999,9,9} };
    printf("name = %s\n",s.name);
    printf("year = %d\n", s.brith.year);
    printf("month= %d\n", s.brith.month);
    printf("day = %d\n", s.brith.day);
    return 0;
}

打印结果为:

我们可以看到在访问嵌套结构体的变量的时候,我们通过使用 . 成员运算符进行结构体再次访问:

    printf("year = %d\n", s.brith.year);
    printf("month= %d\n", s.brith.month);
    printf("day = %d\n", s.brith.day);

结构体类型的小大
接下来我们说明需要特别理解的一点就是:
类型本身是不占空间的,只有用类型定义变量的时候,变量才占用内存空间,那么结构体是我们自定义类型所以结构体本身也是不占内存的,只有在使用结构类型定义变量的时候,变量占用内存空间。

结构体类型,本身不占有内存空间,只有结构体类型生成的变量的时候才占内存空间。

我们知道结构体变量里面会有很多成员变量,结构体变量首部占用低地址,尾结构体变量尾部占用高地址。

接下来我们先定义一个结构体变量来看一下变量的大小:

#include <stdio.h>

typedef struct _staff
{
    char sex;
    int age;
}Staff;

int main()
{
    Staff s;
    printf("sizeof(Staff) = %d\n", sizeof(Staff));
    printf("sizeof(s) = %d\n", sizeof(s));
    printf("&s = %p\n", &s);
    printf("&s.sex = %p\n", &s.sex);
    printf("&s.age = %p\n", &s.age);
    return 0;
}

运行结果为:


内存对齐
结构体中每一个成员变量的地址都是可以获得的,也就是说我们可以通过取地址符号&获得每一个成员变量的地址。
上面代码再次运行打印结果为:

我们知道char类型定义的变量大小为1个字节,int类型定义的变量所占大小为4字节,但是当我们把两个变量放到同一个结构体里面的时候,结构体变量的大小8字节。
我们先根据打印结果进行图解分析:


&s代表的是整个变量地址,&s.sex代表的是结构体变量第一个成员的地址。由于sex是结构体的第一个变量,所以sex的地址和整个结构体的地址指向的是同一块内存空间。
但是上面出现的蓝色字体内存空间被跳过的问题是怎么回事呢?

接下来我们再给出一个例子:(我们在结构体成员里面加入一个short类型变量)

#include <stdio.h>
typedef struct _staff
{
    char sex;
    int age;
    short number;
}Staff;

//结构体中每一个成员变量的地址都是可以获得的
int main()
{
    Staff s;
    printf("sizeof(Staff) = %d\n",sizeof(Staff));
    printf("sizeof(s) = %d\n", sizeof(s));
    printf("&s = %p\n",&s);
    printf("&s.sex = %p\n", &s.sex);
    printf("&s.age = %p\n", &s.age);
    printf("&s.short = %p\n", &s.number);
    return 0;
}

打印结果为:

我们可以看到打印结果,short类型大小占用4个字节。为什么会出现这样的情况呢?
接下来我们再举出一个例子:

#include <stdio.h>
typedef struct _staff
{
    char sex;
    short number;
    int age;
}Staff;

//结构体中每一个成员变量的地址都是可以获得的
int main()
{
    Staff s;
    printf("sizeof(Staff) = %d\n",sizeof(Staff));
    printf("sizeof(s) = %d\n", sizeof(s));
    printf("&s = %p\n",&s);
    printf("&s.sex = %p\n", &s.sex);
    printf("&s.age = %p\n", &s.age);
    printf("&s.short = %p\n", &s.number);
    return 0;
}

打印结果为:

这个时候我们可以看到只是改变了变量定义的顺序,但是整个结构体变量的大小都变了,并且sex占用两个字节,short也是占用两个字节。整个结构体会占用8个字节,而不是之前的12个,怎么会出现这样的情况呢?

那么我们虽然直接用代码看出了不同变量之间的地址距离来推断出变量所占内存的大小,那么还存在一个问题就是当定义:

typedef struct _staff
{
    char sex;
    int age;
}Staff;

的时候,age存储的实际地址到底是和sex的存储位置紧挨着的,还是中间有跳过去的内存空间?我们上面已经通过地址来推断出来,sex占用一个空间之后跳过3个空间然后到age的地址。

如果我们在代码最前面加上:

#pragma pack(1)
代码为:

#include <stdio.h>

#pragma pack(1)
typedef struct _staff
{
    char sex;
    short number;
    int age;
    
}Staff;

//结构体中每一个成员变量的地址都是可以获得的
int main()
{
    Staff s;
    printf("sizeof(Staff) = %d\n",sizeof(Staff));
    printf("sizeof(s) = %d\n", sizeof(s));
    printf("&s = %p\n",&s);
    printf("&s.sex = %p\n", &s.sex);
    printf("&s.short = %p\n", &s.number);
    printf("&s.age = %p\n", &s.age);

    return 0;
}

打印结果为:

我们可以看到,只是加了

#pragma pack(1) 
1
结构体大小变成了7,sex所占大小又变成了一个字节,并且所有类型会根据原本变量的大小进行排列。

那么为什么不同的写法和形式,结构体里面的sex会有所占1,2,4甚至其他字节大小的情况,但是还是同一类型的变量,为什么会出现在不同的方式中所占内存大小不一样的情况呢?

接下来我们就上面的疑问进行解释来理解内存对齐。
内存对其背后有一套完整的规则来对于内存对齐进行说明。
这里需要强调在面试的时候内存对齐的也是一个比较重要的点。

我们接下来先说明内存不对齐:
一个成员变量需要多个机器周期去读的现象,称为内存不对齐。为什么要对齐
呢?
本质是牺牲空间,换取时间的方法。

以上的结论是我们根据代码的运行结果打印出来的。
那么上面定义也会有另一种存储方式:


上面我们也通过代码验证过连续存储变量的方式。
但是连续的存储会出现什么问题呢,我们接下来进行分析,我们的32位机器,如果是连续的,当我们要读int类型变量的内容,我们会发现在读取的时候会出现一个非常麻烦的过程,我们通过图解进行理解说明:

由上面我们可以看到读取到int类型的变量需要两个机器周期,需要更长的时间,虽然会更加节省空间但是读起来使用的时间太长。也就是用时间换区空间。
但是如果使用内存对齐:

中间会有三个字节被空出来,但是读取到int的时候时间更短,读取速度更快,也就是我们所说的空间换取时间。
关于时间换取空间以及空间换取时间的应用,软硬件之间相互转化的应用,在我们计算机行业是经常会用到的思想。
如果我们在读取变量的时候所用的时间大于等于两个机器周期就理解为内存不对齐。所以我们使用内存对齐来用空间换取时间,在不断的发展过程中我们更多的都会更加去用牺牲空间换取时间来提高效率。

x86(linux 默认#pragma pack(4), window 默认#pragma pack(8))。linux 最大支持 4 字节对齐。也就是说#pragma pack(1)的意思就是内存按照1个字节对齐。

结构体嵌套不会出现内存对齐:
代码演示:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>

struct Staff
{
    int num;            //4
    char name[30];        //30
    char job;            //1
    double sex;            //1
    union
    {
        int grade;        //4
        double d;        //8
    }gorp;
};

int main()
{
    struct Staff test;
    printf("sizeof(test) = %d\n", sizeof(test));
    return 0;
}

运行结果为:


结构体里面有数组,把数组分开为单独元素处理:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>

struct Staff
{
    int num;            //4
    char name[30];        //32
    char job;            //4
    double sex;            //8
};

int main()
{
    struct Staff test;
    printf("sizeof(test) = %d\n", sizeof(test));
    return 0;
}

运行结果为:


#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>

struct Staff
{
    int num;            //4
    short name[30];        //60
    char job;            //4
    double sex;            //8
};

int main()
{
    struct Staff test;
    printf("sizeof(test) = %d\n", sizeof(test));
    return 0;
}

运行结果为:


小结
用多个机器周期区读一个变量,这种现象造成的原因,就是内存不对齐,要实现内存对齐,主要的目的就是牺牲空间换取时间。**
使用方法如下:
方法:
①取 pack(n)的值(n= 1 2 4 8–),取结构体中类型最大值 m。两者取小即为外对齐大小 Y= (m<n?m:n)。
②将每一个结构体的成员大小与 Y 比较取小者为 X,作为内对齐大小。
③所谓按 X 对齐,即为地址(设起始地址为 0)能被 X 整除的地方开始存放数据。
④外部对齐原则是依据 Y 的值(Y 的最小整数倍),进行补空操作。

上面我们给出的是内存对齐的方法
上面的方法如果不理解的话,就直接使用一个已知的结构体变量进行套用总结即可,如果不熟悉可以多使用几组进练习就会理解。方法不难,读者进行自行练习。

我们再给出一种方法来理解内存对齐:
①结构体成员存放的地址要能够整除成员本身的大小。
②前面所有成员的大小相加应当是当前成员的倍数。
③结构体所有成员的大小加起来必须是最大成员的倍数。
④内存对齐可以进行调整最小值。

总结
关于内存对齐既是对于结构体的理解也是对于内存存储数据的理解,从语言到内存底层的组织方式有助于我们对于整个计算机体系理解更加深刻。
————————————————
 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
结构体嵌套中,字节对齐是一个需要注意的问题。字节对齐是为了提高内存访问的效率和处理器的性能。当结构体中包含多个成员时,编译器会根据特定的对齐规则在成员之间插入额外的字节,以确保结构体成员按照特定的字节对齐方式进行存储。 在结构体嵌套中,需要特别注意的是: 1. 结构体成员的字节对齐方式:结构体成员的字节对齐方式可能会影响嵌套结构体的对齐。一般来说,结构体成员的对齐方式与其类型有关,比如 `int` 类型一般按照 4 字节对齐,而 `double` 类型一般按照 8 字节对齐。结构体成员的对齐方式可能会导致整个结构体的字节对齐方式发生变化。 2. 结构体嵌套的字节对齐方式:当一个结构体嵌套在另一个结构体中时,编译器会尽量保持嵌套结构体的对齐方式与其在外部结构体中的位置一致。但是,如果外部结构体的字节对齐方式要求更严格,编译器可能会在嵌套结构体之间插入额外的填充字节。 为了避免字节对齐带来的问题,我们可以使用编译器提供的对齐指令或者特性来控制结构体的对齐方式,以确保结构体的成员按照预期进行布局。 在 C 语言中,可以使用 `#pragma pack` 指令来设置字节对齐方式。例如,`#pragma pack(1)` 将设置字节对齐为 1 字节,可以避免额外的填充字节。但是需要注意,使用 `#pragma pack` 指令可能会影响性能和存储效率,因此需要谨慎使用。 另外,在一些编译器中,也提供了特定的特性或者选项来控制结构体的对齐方式,可以根据具体的编译器和平台文档进行查阅和使用。 综上所述,在结构体嵌套中,我们需要注意结构体成员的字节对齐方式,并且可以使用编译器提供的对齐指令或者特性来控制结构体的对齐方式,以避免字节对齐带来的问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值