【柔性数组与局部性原理】

简介: 柔性数组概念对于柔性数组,也许你之前从未听说过,柔性数组,顾名思义,就是数组,它的柔性柔在元素个数可以动态变化,即柔性数组是未知大小的,数组大小可以动态变化的。在结构体中,最后一个元素允许是未知大小的数组,这个数组就叫柔性数组成员。下面直接举例子来说明柔性数组。

柔性数组概念

对于柔性数组,也许你之前从未听说过,柔性数组,顾名思义,就是数组,它的柔性柔在元素个数可以动态变化,

即柔性数组是未知大小的,数组大小可以动态变化的。

在结构体中,最后一个元素允许是未知大小的数组,这个数组就叫柔性数组成员。

下面直接举例子来说明柔性数组。

struct S
{
  int n;
  int arr[]; 
};

这就是一个柔性数组,柔性数组是在结构体中是未知大小的。

struct S
{
  int n;
  int arr[0]; 
};

上面这种写法也可以。


c9fb27f7582940ceaf8799ac44f6bc5a.png

当我们进行编译时,编译通过。

柔性数组特点:

下面举例说明柔性数组的特点:

假设需要动态开辟一块内存空间,我们可以这样操作:

typedef struct S
{
  int n;
  int arr[];
};

当我们计算这个柔性数组的大小时,结果如下:

9ef3dd4663434963bdac2578a8a595d4.png

看到结果是4,可能你会疑惑,别着急,这就是柔性数组的第一个特点:

特点1:sizeof计算结构体大小时,不包括柔性数组的大小

所以计算上面的结构体的大小时,仅计算n大小,在进行结构体内存对齐后,结果是4 。

特点2:柔性数组成员之前必须至少有一个其他成员。

特点3:包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。

什么意思呢?举个例子:

typedef struct S
{
  int n;
  int arr[0]; 
}S;
int main()
{
  struct S s = { 0 };
  struct S* ps = (S*)malloc(sizeof(S) + 5 * sizeof(int));
  return 0;
}

看上面的代码:当我们动态申请一块内存时,申请的空间需要大于结构体本身的大小。

在后面增加适当的空间以适应柔性数组的大小。

下面举实际例子来说明柔性数组的一些好处:

typedef struct S
{
  int n;
  int arr[0]; 
}S;
int main()
{
  struct S s = { 0 };
  struct S* ps = (S*)malloc(sizeof(S) + 5 * sizeof(int));
  if (ps == NULL)
  {
    printf("%s\n", strerror(errno));
    return;
  }
  ps->n = 100;
  int i = 0;
  for (i = 0; i < 5; i++)
  {
    ps->arr[i] = i;
  }
}

在内存中申请一块空间,这块空间在内存中是连续的。

517eeff9b27b4fa49dd2a83a4b4d2f01.png然后将n 和arr分别赋值

48ad8761e304423fbdcca5d00d97fbbc.png

如果想使用的空间不够,那就重新申请空间

struct S*ptr=realloc(ps, 44);

44 = 24(原结构体大小) + 20(5*int)

也就是再开辟五个int类型的空间

int main()
{
  struct S s = { 0 };
  struct S* ps = (struct S*)malloc(sizeof(struct S) + 5 * sizeof(int));
  if (ps == NULL)
  {
    printf("%s\n", strerror(errno));
    return;
  }
  ps->n = 100;
  int i = 0;
  for (i = 0; i < 5; i++)
  {
    ps->arr[i] = i;
  }
  struct S*ptr=realloc(ps, 44);
  if (ptr != NULL)
  {
    ps = ptr;
  }
  for (i = 5; i < 10; i++)
  {
    ps->arr[i] = i;
  }
  for (i = 0; i < 10; i++)
  {
    printf("%d ", ps->arr[i]);
  }
  //释放
  free(ps);
  ps = NULL;
  return 0;
}

然后进行赋值,再打印出来,整个完整代码如上:

结果如下:

250181979b594592a997cebae0362b0a.png

使用柔性数组动态开辟内存,这块空间是连续的。

下面来看另一种开辟内存的方法:

struct S
{
  int n;
  int* arr;
};
int main()
{
  struct S* ps = (struct S*)malloc(sizeof(struct S));
  ps->arr = malloc(sizeof(int) * 5);
  int i = 0;
  for (i = 0; i < 5; i++)
  {
    ps->arr[i] = i;
  }
  //加大内存,调整大小
  int *ptr=  realloc(ps->arr, 10 * sizeof(int));
  if (ptr != NULL)
  {
    ps->arr = ptr;
  }
  for (i = 5; i < 10; i++)
  {
    ps->arr[i] = i;
  }
  for (i = 0; i < 10; i++)
  {
    printf("%d ", ps->arr[i]);
  }
  free(ps->arr);//free要有先后顺序
  free(ps);
  ps = NULL;
  return 0;
}

看不懂代码?请看下图:



78145ed35dc340ae808a9a933a6fbb3e.png

首先申请了第一块空间结构体指针ps,该指针指向了上面的S结构体,第二次申请的空间是 ps所指向的结构体成员指针arr,为这个arr成员申请了一块空间,然后增加空间。


在两次申请的过程中,两块空间ps 和 arr所指向的空间 都是在堆区上,结构体是在栈区上,所以释放的时候,只释放申请出来的两块空间,不需要释放结构体空间。


对比两段代码,可以看出不同点:


在第一段代码中,申请的空间是连续的,第二段代码中,申请的空间是不连续的。


第一段代码申请的空间只需free一次,第二段代码申请的空间需要free两次,并且free的先后顺序有要求,需要先释放arr指向的空间,再释放ps。如果释放顺序相反,会导致arr指向的空间就失踪了, 也就是没人能知道arr指向的空间在哪。


所以,在这里使用柔性数组的好处有:

1.方便释放空间。

2.利于内存访问

第二点如何理解?

下面引出局部性原理的概念:

局部性原理

局部性原理是指CPU访问存储器时,无论是存取指令还是存取数据,所访问的存储单元都趋于聚集在一个较小的连续区域中。

意思是:当我们访问一块空间时,接下来有 80%的可能性会访问它周边的数据。

来看下面:


78239079706a4d3ba9c7f93541493b00.png

一个存储器层次结构图如上:

直接画出主要的布局:


d3b2822bd7f94fea8e88f1881f50c37e.png

cpu在访问空间时,首先会在寄存器中读取数据,如果寄存器中没有那块需要读取的空间,就会去高速缓存中寻找,如果高速缓存中也没有这块内存区域,它就会去硬盘中寻找。

这样层层往下寻找。

如上图:

假如需要读取的空间是连续的,那么这块空间放在寄存器中时,cpu先读取一小部分空间,根据局部性原理:cpu有80%的几率读取周围的空间,这样就能够提高访问效率。

这就是使用柔性数组的好处

假如读取的空间是不连续的,而是碎片式的访问。这样读取效率就会降低。

所以,柔性数组的好处在于:

1.方便释放空间。

2.利于内存访问

相关文章
|
4月前
|
监控 应用服务中间件 nginx
Agentic 时代必备技能:手把手为 Dify 应用构建全链路可观测系统
本文讲述 Dify 平台在 Agentic 应用开发中面临的可观测性挑战,从开发者与运维方双重视角出发,系统分析了当前 Dify 可观测能力的现状、局限与改进方向。
759 66
|
8月前
|
存储 缓存 监控
手动清除Ubuntu系统中的内存缓存的步骤
此外,只有系统管理员或具有适当权限的用户才能执行这些命令,因为这涉及到系统级的操作。普通用户尝试执行这些操作会因权限不足而失败。
1403 22
|
11月前
|
存储 缓存 文件存储
uv安装python及其依赖的加速方法
国内在使用uv的时候,可能会涉及到装python的速度太慢的问题,为了解决这个问题,可以使用`UV_PYTHON_INSTALL_MIRROR`这个环境变量。除此以外,对于多人协作场景,`UV_CACHE_DIR`也是一个有用的环境变量。本文会介绍这两个变量。
6993 10
|
人工智能 供应链 安全
阿里云 Confidential AI 最佳实践
本次分享的主题是阿里云 Confidential AI 最佳实践,由阿里云飞天实验室操作系统安全团队工程师张佳分享。主要分为三个部分: 1. Confidential AI 技术背景与挑战 2. Confidential AI 技术架构与应用场景 3. Confidential AI 技术实践与未来展望
516 2
|
消息中间件
RabbitMQ 的持久化防丢失
RabbitMQ 的持久化防丢失
261 0
|
机器学习/深度学习 算法框架/工具 数据安全/隐私保护
Pillow:Python图像处理的强大工具
**Pillow**是Python的图像处理库,源自PIL,支持多种格式如JPEG、PNG等。本文介绍了Pillow的基本用法和高级特性。安装Pillow后,可通过`Image.open()`打开图像,再使用`resize()`、`crop()`、`rotate()`等方法进行图像操作,如缩放、裁剪和旋转。此外,Pillow还支持颜色转换、滤镜应用(如模糊)、图像增强(亮度、对比度调整)和处理流水线。在实际应用中,Pillow常用于生成网页缩略图、添加水印及配合机器学习库进行图像预处理。【6月更文挑战第6天】
545 1
计科一二班数据结构《实验十报告》参考答案
计科一二班数据结构《实验十报告》参考答案
88 0
计科一二班数据结构《实验十报告》参考答案
|
人工智能 运维 物联网
通信技术发展
通信技术发展
386 4
|
安全 算法 Java
强大!基于Spring Boot 3.3 六种策略识别上传文件类型
【10月更文挑战第1天】在Web开发中,文件上传是一个常见的功能需求。然而,如何确保上传的文件类型符合预期,防止恶意文件入侵,是开发者必须面对的挑战。本文将围绕“基于Spring Boot 3.3 六种策略识别上传文件类型”这一主题,分享一些工作学习中的技术干货,帮助大家提升文件上传的安全性和效率。
834 0
【CPP】插入排序:直接插入排序、希尔排序
【CPP】插入排序:直接插入排序、希尔排序
134 0

热门文章

最新文章