结构体类型的结构在内存的存储

简介: 结构体类型的结构在内存的存储

一,普通结构体类型

 

1,结构体的内存管理

       首先,要提醒的是,结构体的内存存储不单单是顺序存储,在计算机的内部,结构体是按照一定的规则进行存储的,我们观察以下代码:

#include<stdio.h>
struct s1
{
   int a;
   char n;
   char m;
}arr1;
int main()
{
   printf("%d", sizeof(arr1));
   return 0;
}

       经过上面现象分析,结构体中的成员在存储的时候具有不一样的存储形式,下面就跟大家详细讲解一下结构体成员和结构体是如何存储的。

       结构体在内存中的存储是按照一定的对齐规则,在了解对齐规则之前,我们要先明白结构体中的偏移量。偏移量是数据在内存中所对应结构体起始地址的偏移位置。例如,数组a中,a[0]的偏移量为0,a[1]偏移量为1,a[2]偏移量为2。用offsetof函数可以查看对齐数,其中,offsetof函数在头文件<stddef.h>中,具体代码如下:

#include<stdio.h>
#include<stddef.h>
struct s1
{
  int a;
  char b;
  float c;
}arr;
int main()
{
  fprintf(stdout, "%d\n", offsetof(struct s1, a));
  fprintf(stdout, "%d\n", offsetof(struct s1, b));
  fprintf(stdout, "%d\n", offsetof(struct s1, c));
  return 0;
}

   

2,结构体的对齐规则

结构体内存的对齐规则如下:

1,结构体的第一个成员放在与结构体变量的起始偏移量为0的位置

2,从第二个成员开始,往后的每个成员都要对齐到对齐数的整数倍。(对齐数:结构体成员自身大小和默认对齐数的最小值,其中,在VS编译器上自动默认对齐数为8,gcc编译器没有默认对齐数,对齐数就是各成员自身的大小)

3,结构体总大小为各个成员中最大对齐数的整数倍。

4,如果有嵌套结构体的情况,嵌套结构体对齐到自己的最大对齐数的整数倍处,而结构体的总大小是最大对齐数的整数倍。

       接下来我以详细代码的形式跟大家讲解,其它情况同理。

#include<stdio.h>
struct s1
{
  int a;//对齐数4
  char n;//对齐数1
  char m;//对齐数1
}arr1;
int main()
{
  printf("%d", sizeof(arr1));//输出8
  return 0;
}

        然而,结构体采用这种对齐规则其实是拿取空间来换时间效率。在32位机器上(4字节),机器一次性读取4字节的数据,即通过4个地址线引用,如果没有这种对齐,当读取的数据所占用的内存不在机器所一次读取的范围时,机器还要再次进行读取操作,这样的话时间效率将会浪费,而当有这样的对齐规则时,机器将会一次性的读取数据,时间效率将会提高,但会空间效率将会浪费。因此,我们可以理解为结构体的内存对齐是拿空间来换取时间的做法

       细想看来,如若我们我们既要满足对齐,提高时间效率,又要节省空间,我们要在结构体的成员所占空间小的尽量集中在一起,因为,空间小的成员,它的对齐数较小,可以在部分情况下节约空间。代码如下:

#include<stdio.h>
struct s1
{
  char a;
  int b;
  char c;
};
struct s2
{
  char a;
  char b;
  int c;
};
int main()
{
  fprintf(stdout, "%d %d", sizeof(struct s1),sizeof(struct s2));
  return 0;
}

3,结构体的对齐数

       经过以上的讲述,不难发现,如若对齐数不同,结构体的内存大小将会不同,而某人对齐数我们可用#pragma这个预处理命令进行修改。修改的形式为#pragma pack(n),即修改默认对齐数为n。我们还拿以上代码进行演示:

#include<stdio.h>
#pragma pack(1)//修改对齐数为1
struct s1
{
  char a;
  int b;
  char c;
};
struct s2
{
  char a;
  char b;
  int c;
};
#pragma pack(8)//修改对齐数为8
int main()
{
  fprintf(stdout, "%d %d", sizeof(struct s1),sizeof(struct s2));
  return 0;
}

       

 


二,位段类型

1,位段的形式

位端也是运用结构体的结构形式,但与结构体有两个不同:

       1,位段的成员必须是整型家族的类型,即可以是int,unsigned int,signed int,char,signed char,unsigned char。

       2,位段的成员名后边有一个冒号和一个数字,这个数字表示此成员所占的比特位大小,若有数据不加冒号和数字,则内存大小按照正常形式所占用

位段的表现形式如下:

struct a
{
   int a : 2;//a占2比特位
   int b : 5;//b占5比特位
   int c : 10;//c占10比特位

    int d;//按正常形式,占4字节
};

2,位段的内存分配

       位段在内存中是按照变量类型开辟的,int型开辟4字节,char型开辟1字节,然后查看开辟内存是否够后面成员所占内存的需要,如若够用,则系统不会再次开辟空间,用上一个所开辟的空间,如若不够用,则再根据成员的数据类型开辟空间。注意:虽然系统是根据成员变量类型来开辟空间的,但是成员所占的而空间大小是冒号后面的那个数字,如若数据太大,占用的空间不够用,则会在系统中当数据转换成二进制时,根据二进制的形式丢掉多余的数据。如图:


三,枚举类型

      枚举类型是限定有限个数据,例如星期几这类可以一一列举出来的变量类型,对于这些情况,我们就可考虑使用枚举类型惊醒求解。枚举类型的形式如下:

enum color//枚举变量enum color的声明
{
   red,//默认值为0,此数值可以修改
   blue,//默认值为1,此数值可以修改
   yellow//默认值为2,此数值可以修改
};
enum color a, b, c;//定义枚举变量a,b,c

       要说明的是,枚举类型没有太多细节,处理以上的结构式,其它的基本与结构体一样,只需明白以上的知识点即可,我就不做过多的说明了。


四,共用体类型

1,共用体的特点

       共用体也叫联合体,联合体的成员和整个共用体是共用同一块内存空间的,这样,一个联合体变量的大小至少是最大成员的大小,因为联合体至少有能力保存最大的那个成员的数据。

联合体的结构式:

union s1//共用体类型
{
   int a;
   char b;

   int c;
};
union s1 m, n;//建立n,m共用体变量

       在以上代码中,共用体和数据a,b,c共占用一块内存空间,也就是说他们的起始地址是一样的。代码如下:

#include<stdio.h>
union s1
{
  int a;
  char b;
  int c;
}s;
int main()
{
  fprintf(stdout, "%p\n", &s);
  fprintf(stdout, "%p\n", &s.a);
  fprintf(stdout, "%p\n", &s.b);
  fprintf(stdout, "%p\n", &s.c);
  return 0;
}

所以,当共用体其中一个成员数据改变时,去其它成员的数据也会随之改变。如图所示:

2,共用体的大小计算

       根据共用体的特点我们知道,共用体整个大小必须大于或等于最大成员的大小。共用体的最终内存空间并不简单等于最大成员的内存空间。

       当最大成员的大小不是最大对齐数的整数倍时,就要对齐到最大对齐数的整数倍,如下:

#include<stdio.h>
#include<stddef.h>
union s1
{
  char a[6];//占6字节空间,对齐数为1
  int c;//占4字节空间,对齐数为4
}s;//最大内存为6字节,但最大对齐数为4,要对齐到4的整数倍,所以为8
int main()
{
  s.c = 0x01050408;
  fprintf(stdout, "%d\n", sizeof(s));
  return 0;
}

        当最大成员是最大对齐数时,共用体的大小为最大成员的大小,如下:

#include<stdio.h>
union s1
{
  char a;//占1字节空间,对齐数为1
  int c;//占4字节空间,对齐数为4
}s;//最大内存为4字节,最大对齐数也为4,所以为4
int main()
{
  fprintf(stdout, "%d\n", sizeof(s));
  return 0;
}


总:有关结构体类似的式子的要点以及内存管理的注意点已经讲述完了,学习这些知识是为了如何灵活并巧妙的为今后复杂的问题进行运用,在C/C++中,地址和内存管理的运用比较复杂,在以后深入的学习中,基本都是要通过这样的复杂空间管理来进行地址的运用。

相关文章
|
Arthas 存储 算法
深入理解JVM,包含字节码文件,内存结构,垃圾回收,类的声明周期,类加载器
JVM全称是Java Virtual Machine-Java虚拟机JVM作用:本质上是一个运行在计算机上的程序,职责是运行Java字节码文件,编译为机器码交由计算机运行类的生命周期概述:类的生命周期描述了一个类加载,使用,卸载的整个过类的生命周期阶段:类的声明周期主要分为五个阶段:加载->连接->初始化->使用->卸载,其中连接中分为三个小阶段验证->准备->解析类加载器的定义:JVM提供类加载器给Java程序去获取类和接口字节码数据类加载器的作用:类加载器接受字节码文件。
1197 55
|
存储
阿里云轻量应用服务器收费标准价格表:200Mbps带宽、CPU内存及存储配置详解
阿里云香港轻量应用服务器,200Mbps带宽,免备案,支持多IP及国际线路,月租25元起,年付享8.5折优惠,适用于网站、应用等多种场景。
3750 0
|
12月前
|
弹性计算 定位技术 数据中心
阿里云服务器配置选择方法:付费类型、地域及CPU内存配置全解析
阿里云服务器怎么选?2025最新指南:就近选择地域,降低延迟;长期使用选包年包月,短期灵活选按量付费;企业选2核4G5M仅199元/年,个人选2核2G3M低至99元/年,高性价比爆款推荐,轻松上云。
1247 11
|
存储 缓存 NoSQL
内存管理基础:数据结构的存储方式
数据结构在内存中的存储方式主要包括连续存储、链式存储、索引存储和散列存储。连续存储如数组,数据元素按顺序连续存放,访问速度快但扩展性差;链式存储如链表,通过指针连接分散的节点,便于插入删除但访问效率低;索引存储通过索引表提高查找效率,常用于数据库系统;散列存储如哈希表,通过哈希函数实现快速存取,但需处理冲突。不同场景下应根据访问模式、数据规模和操作频率选择合适的存储结构,甚至结合多种方式以达到最优性能。掌握这些存储机制是构建高效程序和理解高级数据结构的基础。
1264 1
|
存储 弹性计算 固态存储
阿里云服务器配置费用整理,支持一万人CPU内存、公网带宽和存储IO性能全解析
要支撑1万人在线流量,需选择阿里云企业级ECS服务器,如通用型g系列、高主频型hf系列或通用算力型u1实例,配置如16核64G及以上,搭配高带宽与SSD/ESSD云盘,费用约数千元每月。
1783 0
|
存储 NoSQL Redis
阿里面试:Redis 为啥那么快?怎么实现的100W并发?说出了6大架构,面试官跪地: 纯内存 + 尖端结构 + 无锁架构 + EDA架构 + 异步日志 + 集群架构
阿里面试:Redis 为啥那么快?怎么实现的100W并发?说出了6大架构,面试官跪地: 纯内存 + 尖端结构 + 无锁架构 + EDA架构 + 异步日志 + 集群架构
阿里面试:Redis 为啥那么快?怎么实现的100W并发?说出了6大架构,面试官跪地: 纯内存 + 尖端结构 +  无锁架构 +  EDA架构  + 异步日志 + 集群架构
|
SQL 存储 缓存
【赵渝强老师】达梦数据库的内存结构
本文介绍了达梦数据库管理系统的内存结构,包括内存池、缓冲区、排序区和哈希区。内存池分为共享内存池和运行时内存池,能够提高内存申请与释放效率,并便于监控内存使用情况。缓冲区涵盖数据缓冲区、日志缓冲区、字典缓冲区和SQL缓冲区,用于优化数据读写和查询性能。排序区和哈希区分别提供排序和哈希连接所需的内存空间,通过合理配置参数可提升系统效率。文内附有具体配置示例及视频讲解,帮助用户深入理解达梦数据库的内存管理机制。
711 0
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
2262 13
|
Java
JVM运行时数据区(内存结构)
1)虚拟机栈:每次调用方法都会在虚拟机栈中产生一个栈帧,每个栈帧中都有方法的参数、局部变量、方法出口等信息,方法执行完毕后释放栈帧 (2)本地方法栈:为native修饰的本地方法提供的空间,在HotSpot中与虚拟机合二为一 (3)程序计数器:保存指令执行的地址,方便线程切回后能继续执行代码
274 3