🥭分配内存:malloc()和free()
2021-1-24
| 2023-8-2
0  |  阅读时长 0 分钟
type
status
date
slug
summary
tags
category
icon
password
Property
 
 
存储类别有一个共同之处:在确定用哪种存储类别后,根据已制定好的内存管理规则,将自动选择其作用域和存储期。然而,还有更灵活地选择,即用库函数分配和管理内存。
所有程序都必须预留足够的内存来储存程序使用的数据。这些内存中有些是自动分配的。例如,以下声明:
为一个float类型的值和一个字符串预留了足够的内存,或者可以显式指定分配一定数量的内存:
该声明预留了100个内存位置,每个位置都用于储存int类型的值。声明还为内存提供了一个标识符。因此,可以使用x或place识别数据。回忆一下,静态数据在程序载入内存时分配,而自动数据在程序执行块时分配,并在程序离开该块时销毁。
 
C 能做的不止这些。可以在程序运行时分配更多的内存。主要的工具是malloc()函数,该函数接受一个参数:所需的内存字节数。malloc()函数会找到合适的空闲内存块,这样的内存是匿名的。也就是说, malloc()分配内存,但是不会为其赋名。然而,它确实返回动态分配内存块的首字节地址。因此,可以把该地址赋给一个指针变量,并使用指针访问这块内存。因为char表示1字节,malloc()的返回类型通常被定义为指向char的指针。
然而,从ANSI C标准开始,C使用一个新的类型:指向void的指针。该类型相当于一个“通用指针”。malloc()函数可用于返回指向数组的指针、指向结构的指针等,所以通常该函数的返回值会被强制转换为匹配的类型。在ANSI C中,应该坚持使用强制类型转换,提高代码的可读性。然而,把指向 void的指针赋给任意类型的指针完全不用考虑类型匹配的问题。如果 malloc()分配内存失败,将返回空指针。
 
用 malloc()创建一个数组。除了用 malloc()在程序运行时请求一块内存,还需要一个指针记录这块内存的位置:
以上代码为30个double类型的值请求内存空间,并设置ptd指向该位置。 注意,指针ptd被声明为指向一个double类型,而不是指向内含30个double类型值的块。数组名是该数组首元素的地址。因此,如果让ptd指向这个块的首元素,便可像使用数组名一样使用它。也就是说,可以使用表达式ptd[0]访问该块的首元素,ptd[1]访问第2个元素,以此类推。可以使用数组名来表示指针,也可以用指针来表示数组。
 
现在,我们有3种创建数组的方法:
  • 声明数组时,用常量表达式表示数组的维度,用数组名访问数组的元素。可以用静态内存或自动内存创建这种数组。
  • 声明变长数组(C99新增的特性)时,用变量表达式表示数组的维度,用数组名访问数组的元素。具有这种特性的数组只能在自动内存中创建。
  • 声明一个指针,调用malloc(),将其返回值赋给指针,使用指针访问数组的元素。该指针可以是静态的或自动的。
使用第2种和第3种方法可以创建动态数组。这种数组和普通数组不同,可以在程序运行时选择数组的大小和分配内存。例如,假设n是一个整型变量。在C99之前,不能这样做:
但是,可以这样做:
这比变长数组更灵活
 
通常,malloc()要与free()配套使用。free()函数的参数是之前malloc()返回的地址,该函数释放之前malloc()分配的内存。因此,动态分配内存的存储期从调用malloc()分配内存到调用free()释放内存为止。设想malloc()和free()管理着一个内存池。每次调用malloc()分配内存给程序使用,每次调用free()把内存归还内存池中,这样便可重复使用这些内存。free()的参数应该是一个指针,指向由 malloc()分配的一块内存。不能用 free()释放通过其他方式(如,声明一个数组)分配的内存。malloc()和free()的原型都在stdlib.h头文件中。
notion image
 
使用malloc(),程序可以在运行时才确定数组大小。它把内存块的地址赋给指针 ptd,然后便可以使用数组名的方式使用ptd。如果内存分配失败,可以调用 exit()函数结束程序,其原型在stdlib.h中。EXIT_FAILURE的值也被定义在stdlib.h中。标准提供了两个返回值以保证在所有操作系统中都能正常工作:EXIT_SUCCESS(或者,相当于0)表示普通的程序结束, EXIT_FAILURE 表示程序异常中止。一些操作系统(包括 UNIX、Linux 和 Windows)还接受一些表示其他运行错误的整数值。
 
 

free()的重要性

动态内存的数量在编译时是固定的,在程序运行期间也不会改变。自动变量使用的内存数量在程序执行期间自动增加或减少。但是动态分配的内存数量只会增加,除非用 free()进行释放。
例如,假设有一个创建数组临时副本的函数,其代码框架如下:
第1次调用gobble()时,它创建了指针temp,并调用malloc()分配了16000字节的内存(假设double为8字节),假设遗漏了free()。当函数结束时,作为自动变量的指针temp也会消失。但是它所指向的16000字节的内存却仍然存在。由于temp指针已被销毁,所以无法访问这块内存,它也不能被重复使用,因为代码中没有调用free()释放这块内存。 第2次调用gobble()时,又创建了指针temp,分配了16000字节的内存。第1次分配的16000字节内存已不可用,所以malloc()分配了另外一块16000字节的内存。函数结束时,该内存块也无法被再访问和再使用。 循环要执行1000次,所以在循环结束时,内存池中有1600万字节被占用。实际上,也许在循环结束之前就已耗尽所有的内存。这类问题被称为内存泄漏(memory leak)。在函数末尾处调用free()函数可避免这类问题发生。
 

calloc()函数

分配内存还可以使用calloc(),典型的用法如下:
和malloc()类似,在ANSI之前,calloc()也返回指向char的指针;在ANSI之后,返回指向void的指针。如果要储存不同的类型,应使用强制类型转换运算符。calloc()函数接受两个无符号整数作为参数(ANSI规定是size_t类型)。第1个参数是所需的存储单元数量,第2个参数是存储单元的大小。在该例中,long为4字节,所以,前面的代码创建了100个4字节的存储单元,总共400字节。
用sizeof(long)而不是4,提高了代码的可移植性。这样,在其他long不是4字节的系统中也能正常工作。 calloc()函数还有一个特性:它把块中的所有位都设置为0(在某些硬件系统中,不是把所有位都设置为0来表示浮点值0)。 free()函数也可用于释放calloc()分配的内存。
 

动态内存分配和变长数组

变长数组(VLA)和调用 malloc()在功能上有些重合。例如,两者都可用于创建在运行时确定大小的数组:
不同的是,变长数组是自动存储类型。因此,程序在离开变长数组定义所在的块时,变长数组占用的内存空间会被自动释放,不必使用 free()。另一方面,用malloc()创建的数组不必局限在一个函数内访问。例如,可以这样做:被调函数创建一个数组并返回指针,供主调函数访问,然后主调函数在末尾调用free()释放之前被调函数分配的内存。另外,free()所用的指针变量可以与 malloc()的指针变量不同,但是两个指针必须储存相同的地址。但是,不能释放同一块内存两次。
对多维数组而言,使用变长数组更方便。当然,也可以用 malloc()创建二维数组,但是语法比较繁琐。如果编译器不支持变长数组特性,就只能固定二维数组的维度,如下所示:
 
 

存储类别和动态内存分配

存储类别和动态内存分配有何联系?看一个理想化模型。可以认为程序把它可用的内存分为 3部分:一部分供具有外部链接、内部链接和无链接的静态变量使用;一部分供自动变量使用;一部分供动态内存分配。
静态存储类别所用的内存数量在编译时确定,只要程序还在运行,就可访问储存在该部分的数据。该类别的变量在程序开始执行时被创建,在程序结束时被销毁。 然而,自动存储类别的变量在程序进入变量定义所在块时存在,在程序离开块时消失。因此,随着程序调用函数和函数结束,自动变量所用的内存数量也相应地增加和减少。这部分的内存通常作为栈来处理,这意味着新创建的变量按顺序加入内存,然后以相反的顺序销毁。
动态分配的内存在调用 malloc()或相关函数时存在,在调用 free()后释放。这部分的内存由程序员管理,而不是一套规则。所以内存块可以在一个函数中创建,在另一个函数中销毁。正是因为这样,这部分的内存用于动态内存分配会支离破碎。也就是说,未使用的内存块分散在已使用的内存块之间。另外,使用动态内存通常比使用栈内存慢。 总而言之,程序把静态对象、自动对象和动态分配的对象储存在不同的区域
notion image
  • C
  • 随机数函数和静态变量类型限定符
    目录