进阶C语言 第五章-------《动态内存管理》 (malloc、free,calloc、realloc、柔性数组、C/C++程序在内存)知识点+完整思维导图+基本练习题+深入细节+通俗易懂+建议收藏(二)

简介: 进阶C语言 第五章-------《动态内存管理》 (malloc、free,calloc、realloc、柔性数组、C/C++程序在内存)知识点+完整思维导图+基本练习题+深入细节+通俗易懂+建议收藏(二)

3.动态内存常见的错误

3.1对NUL指针的解应用操作

知识点:

在开辟一块空间时要加上判断是否开辟成功,否则假如没开辟成功的话就会对NULL地址进行解应用(NULL空指针不能进行访问,若访问就会报错:非法访问)

细节:

int main()
{
  int* ptr = (int*)malloc(40);
  if (ptr == NULL)
  {
    perror("malloc");
    return 1;
  }
  for (int i = 0; i < 10; i++)
  {
    *(ptr + i) = i + 1;
    printf("%d ", *(ptr + i));
  }
  return 0;
}

不加(在vs环境下)时会报警告,所以malloc的返回值一定要判断

image.png

3.2对动态内存开辟的空间越界访问

知识点:

我们要注意在malloc、realloc中他所要开辟的大小都是以byte为单位的,不能看成你所要开辟的某类型的个数,否则就很可能因为这样而导致越界的问题

细节:

如下面这段错误代码,就是错认为开辟了100个int类型大小的空间

int main()
{
  int* ptr = (int*)malloc(100);
  for (int i = 0; i < 100; i++)
  {
    ptr[i] = 0;
  }
  return 0;
}

以为开辟了100int的实际上只开辟了25个int(100byte)的空间,所以对于后面的75个空间都是非法访问的

3.3对非动态开辟的内存进行free释放

知识点:

对于free来说只能用来释放堆区上的动态开辟的空间,不能对非动态开辟的内存进行释放(可不能杀疯了)

细节:

int main()
{
    int a = 0;//正常开辟的变量存在栈区上
    int *p = &a;
    free(p);
    p = NULL;
    return 0;
}

此时因为对非动态内存开辟的空间进行释放,将会导致其程序崩溃。

image.png

3.4使用free释放动态内存开辟的一部分空间

知识点:

在用free释放空间时,我们不能只释放开辟的一部分空间,而是应该将所开辟的空间都释放掉,若释放一部分空间同样会报错

细节:

int main()
{
  int* p = (int*)malloc(100);
  if (p == NULL)
  {
    perror("malloc");
    return 1;
  }
  for (int i = 0; i < 25; i++)
  {
    *p = i;
    printf("%d ", *p);
    p++;
  }
  free(p);
  p = NULL;
  return 0;
}

此时因为后置++会有副作用(p = p + 1)会导致其p的地址发生改变,再对p地址进行释放(此时的p没有指到创建时的位置了)就会导致只对一部分动态内存开辟的空间进行释放而导致其程序崩溃。

image.png

3.5对同一块动态内存空间多次释放

知识点:

对于已经释放的空间其指向的指针已经变成了野指针若再次释放就会导致报错

细节:

int main()
{
  int* p = (int*)malloc(100);
  if (p == NULL)
  {
    perror("malloc");
    return 1;
  }
  // 使用...
  //释放
  free(p);
  // ...
  free(p);
  return 0;
}

对于以上这种情况,因为对一个野指针的空间进行释放所以会导致错误;

但是当我们养成良好的代码习惯:在free释放后将p置为NULL ,这样即使再次释放对于free来说当传进来的指针是NULL时就不会进行任何操作。

3.6动态内存空间的忘记释放

知识点:

当对使用完后空间忘记释放时,就会导致一个空间,即使没用了但仍然占着,就会导致空间的浪费。

对此又称:内存泄漏

细节:

void test()
{
  int* p = (int*)malloc(100);
    if(p == NULL)
  {
        perror("malloc");
        return;
    }
}
int main()
{
  test();
  return 0;
}

此时因为开辟了一片空间当是并没有对其进行释放,就会导致内存释放;

对此我们有两种解决方法:

1.直接在函数内部释放

void test()
{
  int* p = (int*)malloc(100);
    if(p == NULL)
    {
        perror("malloc");
        return;
    }
    free(p);
    p = NULL;
}
int main()
{
  test();
  return 0;
}

2.将开辟空间后所用的指针返回并进行接收在主函数内进行释放;

//该函数进行了malloc开辟空间,返回开辟空间的起始地址
//记得后面要释放
int * test()
{
  int* p = (int*)malloc(100);
  if (p == NULL)
  {
    perror("malloc");
    return;
  }
  return p;
}
int main()
{
  int * ptr = test();
  free(ptr);
  ptr = NULL;
  return 0;
}

并且对于这种传递回开辟空间的地址的函数来说最后要进行注释一下,避免别人使用时忘记释放


内存泄漏的危害:

当你不进行内存释放的话,每当你用下该函数就会导致一部分空间被占用,以此往复就会导致内存被占满而导致程序挂掉。


4.动态内存常见问题、笔试题

问题有:非法访问、内存泄漏(具体已标注释)

image.png

void GetMemory(char* p)//p有自己的独立空间
{
  p = (char*)malloc(100);
}
void Test(void)
{
  char* str = NULL;
  GetMemory(str);//此处为传值调用,就会导致p其实只是str的零时拷贝并不会改变str,所以str并没有开辟好空间,仍然为NULL
  strcpy(str, "hello world");//因为str仍然为NULL所以就会有非法访问问题(访问了NULL地址)
  printf(str);
}
//并且因为p开辟了一个空间且后面也并没有free,也会导致内存泄漏
int main()
{
  Test();
  return 0;
}

问题:非法访问,当函数调用往后会将函数前所借用的内存空间归还给操作系统。(返回栈空间地址问题)

char *GetMemory(void)
{
    char p[] = "hello world";
    return p;
}
void Test(void)
{
    char *str = NULL;
    str = GetMemory();
    printf(str);
//此处虽然是p处的地址,但是p指向的空间已经被收回了,所以就会导致访问到别收回的空间非法访问
}

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

知识点:

一般来说,在内存中有着:内核区、栈区、堆区、静态区(数据段)、代码段、内存映射段

image.png

6.柔性数组

知识点

柔性数组是在c99标准下的

在结构体中的最后一个元素允许是未知大小的数组,这个数组就被称为柔性数组

struct s
{
  int a;
  char b;
  char arr[];//柔性数组成员
  //char arr[0];写0或者不写0是一样的,数组的大小是未知的,
};

细节点:

1.在柔性数组成员前至少有一个成员

2.sizeof返回大小时不包含柔性数组的大小

image.png

3.包含柔性数组的结构应该用malloc来进行动态内存分配,并且分配适应的内存并且应该大于结构体的大小,目的是为了适应柔性数组预期的大小 。

  struct s *ptr = (struct s*)malloc(sizeof(struct s) + sizeof(char) * 10);//后面开辟的10个char的空间是柔性数组所需的空间
  //再通过ptr来访问结构体

并且还可以再通过realloc的方式来增容


柔性数组就好比一个结构体内指针成员,他们都是开辟一块空间,但是用指针成员的话,会相对来说比较麻烦即柔性数组相对于的好处:

不需要内存的单独释放

不需要开辟内存时单独开辟

访问速度较块(没有较多的内存碎片)

若用指针则反之

具体如下:

struct s
{
  int a;
  char b;
  char arr[];//柔性数组成员
  //char arr[0];写0或者不写0是一样的,数组的大小是未知的,
};
struct s1
{
  int a;
  char b;
  char *p;
};
int main()
{
  struct s *ptr = (struct s*)malloc(sizeof(struct s) + sizeof(char) * 10);
    //后面开辟的10个char的空间是柔性数组所需的空间(直接一起开辟)
  //再通过ptr来访问结构体
  printf("%d\n", sizeof(struct s));
  free(ptr);
  ptr = NULL;
  struct s1* ps = (struct s1*)malloc(sizeof(struct  s1));
  ps->a = 100;
  ps->b = 'a';
  ps->p = malloc(10 * sizeof(char));//单独开辟
  if (ps->p == NULL)
  {
    perror("malloc");
    return 1;
  } 
  //使用  ....
  //单独释放
  free(ps->p);
  ps->p = NULL;
  return 0;
}

image.png

本章完。预知后事如何,暂听下回分解。

相关文章
|
5月前
|
安全 C语言 C++
比较C++的内存分配与管理方式new/delete与C语言中的malloc/realloc/calloc/free。
在实用性方面,C++的内存管理方式提供了面向对象的特性,它是处理构造和析构、需要类型安全和异常处理的首选方案。而C语言的内存管理函数适用于简单的内存分配,例如分配原始内存块或复杂性较低的数据结构,没有构造和析构的要求。当从C迁移到C++,或在C++中使用C代码时,了解两种内存管理方式的差异非常重要。
221 26
|
10月前
|
存储 程序员 编译器
玩转C++内存管理:从新手到高手的必备指南
C++中的内存管理是编写高效、可靠程序的关键所在。C++不仅继承了C语言的内存管理方式,还增加了面向对象的内存分配机制,使得内存管理既有灵活性,也更加复杂。学习内存管理不仅有助于提升程序效率,还有助于理解计算机的工作原理和资源分配策略。
|
6月前
|
C语言 C++
c与c++的内存管理
再比如还有这样的分组: 这种分组是最正确的给出内存四个分区名字:栈区、堆区、全局区(俗话也叫静态变量区)、代码区(也叫代码段)(代码段又分很多种,比如常量区)当然也会看到别的定义如:两者都正确,记那个都选,我选择的是第一个。再比如还有这样的分组: 这种分组是最正确的答案分别是 C C C A A A A A D A B。
130 1
|
12月前
|
存储 缓存 编译器
【硬核】C++11并发:内存模型和原子类型
本文从C++11并发编程中的关键概念——内存模型与原子类型入手,结合详尽的代码示例,抽丝剥茧地介绍了如何实现无锁化并发的性能优化。
525 68
|
9月前
|
存储 Linux C语言
C++/C的内存管理
本文主要讲解C++/C中的程序区域划分与内存管理方式。首先介绍程序区域,包括栈(存储局部变量等,向下增长)、堆(动态内存分配,向上分配)、数据段(存储静态和全局变量)及代码段(存放可执行代码)。接着探讨C++内存管理,new/delete操作符相比C语言的malloc/free更强大,支持对象构造与析构。还深入解析了new/delete的实现原理、定位new表达式以及二者与malloc/free的区别。最后附上一句鸡汤激励大家行动缓解焦虑。
|
10月前
|
安全 C语言 C++
彻底摘明白 C++ 的动态内存分配原理
大家好,我是V哥。C++的动态内存分配允许程序在运行时请求和释放内存,主要通过`new`/`delete`(用于对象)及`malloc`/`calloc`/`realloc`/`free`(继承自C语言)实现。`new`分配并初始化对象内存,`delete`释放并调用析构函数;而`malloc`等函数仅处理裸内存,不涉及构造与析构。掌握这些可有效管理内存,避免泄漏和悬空指针问题。智能指针如`std::unique_ptr`和`std::shared_ptr`能自动管理内存,确保异常安全。关注威哥爱编程,了解更多全栈开发技巧。 先赞再看后评论,腰缠万贯财进门。
468 0
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
371 5
|
C语言
C语言编程中,错误处理至关重要,能提升程序的健壮性和可靠性
C语言编程中,错误处理至关重要,能提升程序的健壮性和可靠性。本文探讨了C语言中的错误类型(如语法错误、运行时错误)、基本处理方法(如返回值、全局变量、自定义异常处理)、常见策略(如检查返回值、设置标志位、记录错误信息)及错误处理函数(如perror、strerror)。强调了不忽略错误、保持处理一致性及避免过度处理的重要性,并通过文件操作和网络编程实例展示了错误处理的应用。
395 4
|
11月前
|
存储 程序员 编译器
什么是内存泄漏?C++中如何检测和解决?
大家好,我是V哥。内存泄露是编程中的常见问题,可能导致程序崩溃。特别是在金三银四跳槽季,面试官常问此问题。本文将探讨内存泄露的定义、危害、检测方法及解决策略,帮助你掌握这一关键知识点。通过学习如何正确管理内存、使用智能指针和RAII原则,避免内存泄露,提升代码健壮性。同时,了解常见的内存泄露场景,如忘记释放内存、异常处理不当等,确保在面试中不被秒杀。最后,预祝大家新的一年工作顺利,涨薪多多!关注威哥爱编程,一起成为更好的程序员。
533 0
|
网络协议 物联网 数据处理
C语言在网络通信程序实现中的应用,介绍了网络通信的基本概念、C语言的特点及其在网络通信中的优势
本文探讨了C语言在网络通信程序实现中的应用,介绍了网络通信的基本概念、C语言的特点及其在网络通信中的优势。文章详细讲解了使用C语言实现网络通信程序的基本步骤,包括TCP和UDP通信程序的实现,并讨论了关键技术、优化方法及未来发展趋势,旨在帮助读者掌握C语言在网络通信中的应用技巧。
356 2