• c语言动态内存管理


    笔记记录于小甲鱼的课程,十分感谢!
    关于C的个人学习之路!内存管理是C语言的需要重点关注部分!所以对于动态内存管理的学习需要特别记录一下。

    学前须知:
    数组对长度要求是静态的。全局数组在系统初始化时在数据段为其分配空间,如果数组大小不明确,数据段无法为其分配空间,因为还有别的数据需要在数据段分配空间;局部变量数组在创建函数调用栈时也要求数组指定大小,因为局部变量是在栈底分配空间的,如果数组大小不明,栈的后续增长就不知道从哪里开始了。
    一般对于这种需求,都是临时根据需要申请动态内存来用。

    动态内存管理

    C99后,允许了程序员定义变长数组,但是数组一旦创建,就不能再修改其长度。

    为了更灵活的让C语言管理内存资源,C语言中提供了几个库函数来让我们进行内存管理。

    这几个库函数都包含在stdlib.h的头文件中

    malloc

    函数原型:void *malloc(size_t size);

    malloc函数向系统申请分配size个字节的空间,并返回一个指向这块空间的指针。
    值得注意的是:如果调用成功,它返回的指针类型是void类型的(void *),使得该指针可以被转换成任何类型的数据;如果调用失败,返回值是NULL。另外,如果size参数设置为0,返回值也可能是NULL,但这并不意味着函数调用失败。

    malloc申请的内存空间位于内存的堆上,如果你不主动去释放存在堆上的内存资源,它会一直占据着内存空间。s所以当你不使用它的时候,请务必要主动去释放这块内存资源。怎么释放呢?就需要用到free函数

    malloc函数不仅可以申请存储基本数据类型的存储空间,事实上,它还可以申请一块任意尺寸的内存空间。
    对于任意尺寸的内存空间来说,由于申请得到的空间是连续的,所以我们经常会使用数组来进行索引。

    free

    函数原型void free(void *ptr);

    free函数释放ptr参数指向的内存空间。该内存空间必须是由malloc、calloc或者realloc函数申请的。否则,该函数将导致未定义行为(试图去释放栈上面的空间)。如果ptr参数是NULL,则不执行任何操作。注意:该函数并不会修改ptr参数的值,所以调用后它仍然指向原来的地方(变为非法空间,因为原来的数据不存在了,里面的数据会是一个无法预料的随机值,是没有意义的)
    也可以理解为:释放后就变成野指针了,一般在free函数结束后要让该指针置为NULL,如:ptr=NULL;

    在C语言编程中,会经常遇到内存泄漏(不合理的申请大量内存空间的行为或丢失内存块地址)而造成程序崩溃的问题,这是因为申请的动态内存没有及时释放导致的。
    ps:丢失内存块地址出现在你在申请了一块内存空间并且付给一个指针变量后,指针变量在没有释放掉该申请的内存块的前提下就改变了指向,那原本申请的那块内存块因为我们不知道地址而丢失了。
    为了防止内存泄漏,我们应该在使用完一块内存空间后,调用free函数来释放掉。时刻记住,mallocfree这两个函数是成对编写的。

    总结一下,导致内存泄漏主要有两种情况:

    • 隐式内存泄漏,即用完内存块没有及时使用free函数释放
    • 丢失内存块地址

    calloc

    malloc不会自动的帮助程序员初始化内存空间(置0),所以要自己写循环初始化内存空间。这样其实是非常麻烦的事情,我们可以使用标准库提供专门处理内存空间的以mem开头的函数来进行初始化。

    memset有三个参数,分别是内存块的指针,要初始化的常量值,内存块的连续尺寸是多大

    上述的做法其实还是挺麻烦的,我们可以使用另外一个动态内存管理的函数,calloc函数来对申请的内存进行初始化。

    函数原型:void *calloc(size_t nmenb,size_t size);

    calloc函数在内存中动态申请nmenb个长度为size的连续内存空间(即申请的总空间尺寸为nmenb * size),这些内存空间全部被初始化为0。

    calloc函数与malloc函数的一个重要区别是:

    • calloc函数在申请完内存后,自动初始化该内存空间为零
    • malloc函数不进行初始化操作,里边的数据是随机的

    realloc

    如果第一次申请的内存空间不够用,我们可以选择第二次申请一个更大的内存空间,并且把原来的数据使用memcpy复制过去。但是,这样的操作太麻烦啦,我们可以使用realloc函数来对内存空间进行重新分配。
    ps:memcpy的三个参数分别为:目标指针,被拷贝的源指针,要拷贝的数据个数。

    函数原型:void *realloc(void *ptr, size_t size);

    要注意以下几点:

    • realloc函数修改ptr指向的内存空间大小为size字节
    • 如果新分配的内存空间比原来大,则旧内存块的数据不会发生改变;如果新的内存空间大小小于旧的内存空间,可能会导致数据丢失,要慎用!
    • 该函数将移动内存空间的数据并返回新的指针(即开辟一块新空间-->拷贝数据到新空间-->改变指针的指向)
    • 如果ptr参数为NULL,那么调用该函数就相当于调用malloc(size)。
    • 如果size参数为0,并且ptr参数不为NULL,那么调用该函数就相当于调用free(ptr)
    • 除非ptr参数为NULL,否则ptr的值必须由先前调用malloc、calloc或realloc函数返回(即必须要是指向堆的指针,而不能是一个指向栈/指向局部变量的指针 )

    下面是一个使用realloc函数实现“动态”数组存储的案例:

    C语言的内存布局

    C语言的内存布局规律

    • 代码段(Text segment)通常是指用来存放程序执行代码(如函数)的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。
    • 数据段(Initialized data segment)通常用来存放已经初始化的全局变量和局部静态变量。
    • BSS段(BSS segment/Uninitialized data segment)通常是指用来存放程序中未初始化的全局变量和静态局部变量的一块内存区域。BSS是英文Blcok Started by Symbol的简称,这个区段中的数据在程序运行前将被自动初始化为数字0。(在linux中可以使用size命令来查看编译后的程序的上述上个内存段的大小)

    堆:

    堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可以动态拓展或缩小。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上;当利用free等函数释放内存时,被释放的内存从堆中被剔除。

    栈:

    也叫做堆栈,栈是函数执行的内存区域,通常和堆共享同一片区域。栈中存放的有局部变量,函数的参数,函数的返回值。

    栈和堆的区别:

    • 申请方式:
      • 堆由程序员手动申请
      • 栈由系统自动分配
    • 释放方式:
      • 堆由程序员手动释放
      • 栈由系统自动释放(调用完函数后,相关的数据就清空了)
    • 生存周期:
      • 堆的生存周期由动态申请到程序员主动释放为止,不同函数之间均可以自由访问
      • 栈的生存周期由函数调用开始到函数返回时结束,函数之间的局部变量不能互相访问
    • 发展方向:
      • 堆和其他区段一样,都是从低地址向高地址发展
      • 栈则相反,是由高地址向低地址发展
  • 相关阅读:
    多测师讲解unittest介绍及自动化测试实现流程_高级讲师肖sir
    多测师讲解常用的测试工具分为10类_高级讲师肖sir
    多测师讲解接口测试 _面试题003_高级讲师肖sir
    多测师接口测试 --常见的接口面试题目002---高级讲师肖sir
    Linux的mysql搭建
    学完爬虫的笔记
    postman测试实例--断言
    jemeter安装步骤
    selenium的显示等待和隐式等待区别
    loadrunner录制不了浏览器
  • 原文地址:https://www.cnblogs.com/deehuang/p/14394800.html
Copyright © 2020-2023  润新知