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

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

【动态内存管理助力程序优化与性能飞升】(中):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月前
|
存储 缓存 安全
C语言中的内存管理与优化技巧
C语言中的内存管理与优化技巧
37 0
|
22天前
|
存储 编译器 C语言
深入探索C语言动态内存分配:释放你的程序潜力
深入探索C语言动态内存分配:释放你的程序潜力
30 0
|
3天前
|
存储 开发者 Python
优化Python代码中的内存占用:实用技巧与最佳实践
本文将介绍如何优化Python代码中的内存占用,通过实用技巧和最佳实践,有效减少内存消耗,提升代码性能和可扩展性。
|
29天前
|
缓存 算法 Java
Java内存管理:优化性能和避免内存泄漏的关键技巧
综上所述,通过合适的数据结构选择、资源释放、对象复用、引用管理等技巧,可以优化Java程序的性能并避免内存泄漏问题。
28 5
|
1月前
|
存储 设计模式 缓存
C++享元模式探索:轻松优化内存使用和性能提升之道
C++享元模式探索:轻松优化内存使用和性能提升之道
41 0
|
1月前
|
缓存 算法 编译器
C/C++编译器内存优化技术:内存优化关注程序对内存的访问和使用,以提高内存访问速度和减少内存占用。
C/C++编译器内存优化技术:内存优化关注程序对内存的访问和使用,以提高内存访问速度和减少内存占用。
41 0
|
1月前
|
存储 缓存 算法
深入探究LRU缓存机制:优化内存利用与提升性能
深入探究LRU缓存机制:优化内存利用与提升性能
157 1
|
1月前
|
缓存 资源调度 Go
内存模型与调度器在Go语言并发编程中的应用与优化
【2月更文挑战第16天】Go语言以其独特的并发编程模型而备受瞩目,其中内存模型和调度器是支撑其高效并发执行的关键机制。本文将探讨内存模型与调度器在Go语言并发编程中的应用场景,并介绍一些优化策略,帮助开发者更好地利用这些机制,提升程序的性能和稳定性。
|
1月前
|
监控 Java 编译器
Go语言内存与并发性能综合优化策略
【2月更文挑战第11天】Go语言以其高效的并发处理能力和简洁的内存管理机制成为了现代软件开发中的热门选择。然而,在实际应用中,如何综合优化Go程序的内存使用和并发性能,仍然是一个值得探讨的话题。本文将深入探讨Go语言内存与并发性能的综合优化策略,包括内存布局优化、并发模式设计、资源池化以及性能监控与分析等方面,旨在帮助开发者全面提升Go程序的整体性能。
|
1月前
|
Java 编译器 Go
Go语言内存管理优化实践
【2月更文挑战第11天】随着Go语言在各个领域的应用日益广泛,对其性能的要求也越来越高。内存管理作为影响程序性能的关键因素之一,对Go语言的优化显得尤为重要。本文将深入探讨Go语言的内存管理机制,并提供一系列实用的内存优化策略,帮助开发者更加高效地利用内存资源,提升Go程序的整体性能。