【动态内存管理助力程序优化与性能飞升】(下)

简介: 【动态内存管理助力程序优化与性能飞升】

【动态内存管理助力程序优化与性能飞升】(中):https://developer.aliyun.com/article/1424819


demo4:


#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void Test(void)
{
  char* str = (char*)malloc(100);
  strcpy(str, "hello");
  free(str);
  if (str != NULL)
  {
    strcpy(str, "world");
    printf(str);
  }
}
int main()
{
  Test();
  return 0;
}


问题:


       在这段C代码中,首先使用 malloc 动态地分配了 100 字节的内存来存储字符串 "hello"。然后,立即使用 strcpy 将 "hello" 复制到分配的内存块中。接着,使用 free 释放了分配的内存。

然后,代码尝试检查指针 str 是否为 NULL。然而,这是一个错误的做法。因为在调用 free 之后,指针 str 指向内存地址虽然不会发生改变,但是进行指针进行任何操作都是不安全的,并且会导致未定义的行为。


修改:


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void Test(void)
{
    char* str = (char*)malloc(100);
    strcpy(str, "hello");
    free(str); // 释放内存后,str 成为了悬挂指针
    str = NULL;
    // 不要在释放内存后使用指针
    // 这里不再使用 str 指针
}
int main()
{
    Test();
    return 0;
}


5. C/C++程序的内存开辟



C/C++程序内存分配的几个区域:


  1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结 束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是 分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返 回地址等。
  2. 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS(操作系统)回收 。分 配方式类似于链表。
  3. 数据段(静态区)(static):存放全局变量、静态数据。程序结束后由系统释放。


代码段:存放函数体(类成员函数和全局函数)的二进制代码。


有了这幅图,我们就可以更好的理解在《C语言初识》中讲的static关键字修饰局部变量的例子了。


       实际上普通的局部变量是在栈区分配空间的,栈区的特点是在上面创建的变量出了作用域就销毁。 但是被static修饰的变量存放在数据段(静态区),数据段的特点是在上面创建的变量,直到程序结束才销毁 所以生命周期变长。


6. 柔性数组


       也许你从来没有听说过柔性数组(flexible array)这个概念,但是它确实是存在的。 C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。

typedef struct st_type
{
    int i;
    int a[];//柔性数组成员
    //int a[0];//也可以写成这个
}type_a;


6.1 柔性数组的特点:


  • 结构中的柔性数组成员前面必须至少一个其他成员。
  • sizeof 返回的这种结构大小不包括柔性数组的内存。

  • 包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
type_a* ps = (type_a*)malloc(sizeof(type_a) + 40);


6.2 柔性数组的使用


#include<stdio.h>
#include<stdlib.h>
typedef struct st_type
{
  int i;
  int a[0];//柔性数组成员
}type_a;
int main()
{
  type_a* ps = (type_a*)malloc(sizeof(type_a) + 40);
  if (!ps)
  {
    perror("malloc");
    return 1;
  }
  ps->i = 10;
  int i = 0;
  for (i = 0; i < ps->i; i++)
  {
    ps->a[i] = i;
  }
  //空间不够,realloc增容
  /*
    ps 是要调整的内存地址
    size 调整之后新大小
    返回值为调整之后的内存起始位置。
  */
  type_a* p = (type_a*)realloc(ps, sizeof(type_a) + 60);
  if (!p)
  {
    perror("realloc");
    return 1;
  }
  ps = p;
  ps->i = 15;
  for (i = 0; i < ps->i; i++)
  {
    printf("%d ", ps->a[i]);
  }
    free(ps);
  ps = NULL;
  return 0;
}


运行结果:



6.3 柔性数组的优势


上述的 type_a 结构也可以设计为指针类型:


#include<stdio.h>
#include<stdlib.h>
typedef struct st_type
{
  int i;
  int* a;
}type_a;
int main()
{
  type_a* ps = (type_a*)malloc(sizeof(type_a));//与柔性数组保持一致
  if (!ps)
  {
    perror("malloc");
    return 1;
  }
  ps->i = 10;
  ps->a = (int*)malloc(40);
  if (!ps->a)
  {
    perror("malloc");
    return 1;
  }
  int i = 0;
  for (i = 0; i < ps->i; i++)
  {
    ps->a[i] = i;
  }
  //空间不够,realloc增容
  /*
    ps 是要调整的内存地址
    size 调整之后新大小
    返回值为调整之后的内存起始位置。
  */
  int* p = (int*)realloc(ps->a, 60);
  if (!p)
  {
    perror("realloc");
    return 1;
  }
  ps->a = p;
  ps->i = 15;
  for (i = 0; i < ps->i; i++)
  {
    printf("%d ", ps->a[i]);
  }
  free(ps->a);
  ps->a = NULL;
  free(ps);
  ps = NULL;
  return 0;
}


上述 代码1 和 代码2 可以完成同样的功能,但是 方法1 的实现有两个好处:


第一个好处是:方便内存释放

 如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉。


第二个好处是:这样有利于访问速度.

       连续的内存有益于提高访问速度,也有益于减少内存碎片。(其实,我个人觉得也没多高了,反正你跑不了要用做偏移量的加法来寻址)


扩展阅读:C语言结构体里的成员数组和指针

相关文章
|
1月前
|
存储 算法 Java
Java内存管理深度剖析与优化策略####
本文深入探讨了Java虚拟机(JVM)的内存管理机制,重点分析了堆内存的分配策略、垃圾回收算法以及如何通过调优提升应用性能。通过案例驱动的方式,揭示了常见内存泄漏的根源与解决策略,旨在为开发者提供实用的内存管理技巧,确保应用程序既高效又稳定地运行。 ####
|
1月前
|
存储 缓存 JavaScript
如何优化Node.js应用的内存使用以提高性能?
通过以上多种方法的综合运用,可以有效地优化 Node.js 应用的内存使用,提高性能,提升用户体验。同时,不断关注内存管理的最新技术和最佳实践,持续改进应用的性能表现。
122 62
|
28天前
|
存储 缓存 监控
如何使用内存监控工具来优化 Node.js 应用的性能
需要注意的是,不同的内存监控工具可能具有不同的功能和特点,在使用时需要根据具体工具的要求和操作指南进行正确使用和分析。
70 31
|
25天前
|
存储 缓存 监控
Docker容器性能调优的关键技巧,涵盖CPU、内存、网络及磁盘I/O的优化策略,结合实战案例,旨在帮助读者有效提升Docker容器的性能与稳定性。
本文介绍了Docker容器性能调优的关键技巧,涵盖CPU、内存、网络及磁盘I/O的优化策略,结合实战案例,旨在帮助读者有效提升Docker容器的性能与稳定性。
66 7
|
25天前
|
存储 算法 Java
Java 内存管理与优化:掌控堆与栈,雕琢高效代码
Java内存管理与优化是提升程序性能的关键。掌握堆与栈的运作机制,学习如何有效管理内存资源,雕琢出更加高效的代码,是每个Java开发者必备的技能。
49 5
|
26天前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
59 1
|
1月前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
52 6
|
1月前
|
监控 安全 程序员
如何使用内存池池来优化应用程序性能
如何使用内存池池来优化应用程序性能
|
1月前
|
存储 监控 Java
深入理解计算机内存管理:优化策略与实践
深入理解计算机内存管理:优化策略与实践
|
1月前
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
284 1

热门文章

最新文章