你好, 欢迎｡ 现在我们了解了动态内存分配,
我们必须实现它, 我们必须实际知道如何在c代码中实现它｡
我们将讨论三个非常重要的函数, malloc, calloc,
realloc.
你可以看到它们有一些名字, 其中内存分配称为loc和reallocation｡
分配内存最简单和最常用的方法是通过名为malloc的标准库函数｡
这是在运行时分配动态内存｡
为了调用此函数, 您需要包含标准lib头文件｡
您可以指定要配置为参数的内存字节数｡
从这个函数返回的是分配的内存的第一个字节的地址｡
因为你得到的是一个返回的地址, 指针是放置它的唯一地方｡
因此, 这就是为什么动态内存分配只能通过指针的概念来实现,
因为它返回地址｡
你只能在指针里放一个地址, 指针存储的是地址, 指针存储的是地址.
这是你如何做的, 非常简单的语法｡
通常, 在创建指针时, 您不必这样做｡
您可以创建指针并将其设置为null｡
然后在下一行中, 您可以malloc｡
但这里是创建int指针的标准语法, 指向int类型的指针.
然后你会注意到你调用了malloc｡
在这里, 我们说malloc, 我们要为malloc创建100个字节,
然后我们要把它转换成一个整型指针｡
因为malloc只返回一个地址,
所以必须将其转换为一个结束指针, 因为该地址存储的是一个int指针｡
这是执行此操作的标准方法｡
现在你请求100个字节的内存, 并将其分配给内存块的地址｡
pNumber将指向已分配的100个字节开头的第一个int位置｡
这意味着因为整数是4个字节一段, 它可以保存25个int值｡
同样, 这是假设您有4个字节｡
现在, 根据您运行的系统, 它们可能更大, 也可能更小｡
所以你应该明白问题所在｡
这段代码在不同的系统上是不起作用的因为你是基于一个假设进行分配的, 基于int大小的假设.
因此, 在常量为100的情况下,
以这种方式使用malloc并不是很好｡
你要做的就是使用sizeof运算符｡
所以最好是去掉int是4字节的假设｡
相反,
如果要在malloc参数中为25个整数分配空间, 请指定要分配的整数的数量, 然后将其乘以整数的大小｡
这将使代码更易于移植｡
这些代码现在可以在不同的系统､ 不同的操作系统､ 不同的体系结构上工作｡
你仍然把返回值转换为地址, 因为它必须指向一个int类型,
所以你说int星星. 这里唯一的区别是使用了sizeof运算符｡
当你使用malloc或calloc或realloc分配内存时, Sizeof操作符非常重要｡
对于要分配的类型,
你总是使用sizeof运算符, 然后乘以你想要的特定个数,
所以如果你想存储25, int, 那么你就用25.
因此, 上面malloc的参数清楚地表明,
应该提供足够的字节来容纳25个int类型的值｡
还有, 正如我提到的, 注意演员阵容｡
这会将函数返回的地址转换为int类型指针和int类型指针｡
Malloc返回一个void类型的指针, 这就是它如此有用的原因｡
它会返回那个地址, 但它是一个空指针.
请记住, 我们讨论过void指针,
以及为什么void指针很有用, 因为现在你可以有一个函数, 它可以接受不同的参数, 并且它可以返回一个void指针｡
现在你又看到了返回空指针的强大功能,
但是要使用它, 要给它赋值, 要取消引用, 你必须强制转换它｡
如果我们不想投这个, 我们可以说void星星p number｡
我们不强制转换它, 但是一旦我们想取消引用它,
我们就必须最终强制转换它｡
但这就是为什么我们必须从malloc中转换｡ 它返回一个地址, 你想让它指向一个int类型,
它返回一个void类型.
您可以请求任意数量的字节｡
但是, 这取决于您的系统以及系统上的内存大小｡
因此, 如果您尝试创建或请求大量字节, 或者存在一些限制,
则可能会失败｡
而当它失败时, 它返回一个null值, malloc将返回null｡
这很好,
因为指针指向空值时, 这意味着它们没有指向任何内存, 所以你不会无意中损坏或改变任何内存｡
所以在你执行malloc之后, 在使用它之前, 立即检查null总是一个好主意,
就像你赋值数据或者解引用指针一样｡
你要确保你确实指向了某个东西｡
所以这里, 我们给指针分配了一些内存, 如果不是,
我们就说｡
请记住, null是零｡
所以你可以直接用一个not在上面｡ 你不需要把它和null做比较｡
你可以, 你可以把它和空值比较, 这样它更容易读, 也许更显式.
但是你可以直接说不是p个数字, 这实际上就是说不是1, null是0｡
因此, 这将检查是否为null｡
如果它为null, 则基本上意味着无法分配内存｡
如果你不能在程序中分配内存, 你就有麻烦了｡
所以在这段代码中, 处理内存分配失败时,
通常应该中止, 调用exit, 退出程序.
因为如果你的程序不能分配内存, 你的程序就被淘汰了｡
通常情况下, 如果无法分配内存, 这就是所谓的意外中止｡
至少当你这样做的时候, 你可以显示一条终止程序的消息｡
这要比在你不能够分配内存的情况下允许程序继续运行要好得多｡
因为你可能会继续你可能会尝试使用那个内存,
它将是空的, 所以程序无论如何都会崩溃, 但你不一定知道为什么｡
您必须查看调用堆栈｡ 调试该错误将花费更长的时间｡
正在释放内存｡
我之前提到过, 由于程序员负责分配内存, 因此,
当不再使用内存时, 必须将其清除, 因为内存是一种至关重要的资源,
尤其是在嵌入式系统上｡
如果你不需要内存, 就不应该使用它｡ 所以你必须释放记忆｡
你总是想在不再需要的时候释放内存｡
当程序存在时, 您在堆上分配的内存将自动释放, 这很好,
因为它确实做了一些清理｡
但是, 最好在使用完内存后显式地释放内存,
即使是在退出之前｡
它更干净, 总是做它的自由或总是释放内存后, 你分配它,
你想遵循这个规则｡ 这是一条严厉的严格规定｡
如果分配了内存, 则始终释放内存｡
当您动态配置某些内存, 但未保留指涉, 因此无法释放它时,
就会发生内存遗漏｡
也许你已经超出了你不能引用的范围｡ 你不释放它｡
这叫做内存泄漏｡ 已分配内存, 但不再使用｡
这通常发生在循环中, 并且在循环中分配内存时｡
你在这个圈子里｡ 您分配此内存｡
你在循环中使用它, 然后跳出循环, 不再释放它｡
每个循环迭代都在分配它｡
因此, 要释放动态分配的内存,
您必须仍然能够访问该地址｡
所以通常在使用完内存后, 你需要一个空闲的内存,
通常是在你分配内存的方法中｡
然而,
如果你把内存传递给另一个函数, 那么这是那个函数的责任, 但通常你想释放内存和你分配给它的函数｡
要为动态分配的内存块释放内存（其地址已存储在指针中）, 只需调用free函数即可｡
这也在标准库中｡ 你只需要把指针传递给free函数｡
通常, 在传递指针后, 你会把它设为null,
因为这意味着你没有为它分配内存.
free函数有一个void星星类型的形参,
所以你可以传入任何指针类型, 它都会工作｡
你不必真的投出它或类似的东西｡
只要p编号包含分配内存时返回的地址, 整个内存块都将被释放以供进一步使用｡
其他程序可以使用该内存,
您的程序现在也可以使用该内存, 这在嵌入式系统上非常重要｡
然后就像我提到的, 你总是想把指针设置为null来表示指针不再指向任何东西｡
该内存已被释放｡
还有另一个很好的函数calloc｡
这与malloc相比有几个优点｡
它与malloc非常相似, 但它所做的是将内存分配为给定大小的多个元素,
并且初始化内存｡
这就是区别, 初始化｡ 我讲过初始化的重要性｡
这就是为什么你总是想初始化数据, 这样你就能知道, 那里有你所期望的数据｡
当您取消初始化数据时, 其中可能包含您没有任何线索的数据｡
因此, 请始终使用某个值初始化数据｡
而calloc更好一点, 因为它为你做了｡
它有两个参数, 有两个参数｡
需要空间的数据项的数量, 每个数据项的大小｡
它在标准的lib头文件中声明｡
你叫它就像你叫malloc一样｡ 看起来差不多｡
在这个例子中,
我们创建了一个整数指针, 并分配了75个sizeof int的整数｡
所以它需要两个参数｡
它更简洁一些因为你不需要做乘法.
在malloc中, 您必须执行75个时间大小的事件｡
这里你只要说75, 你就创建了75个整型数, 每个整型数的大小是4个字节,
或者系统从运算符的大小返回的任何值｡
但同样重要的是, 它也会初始化所有数据,
如果它不能分配内存, 返回值可能为空, 这反过来你不应该中止.
但它与使用malloc非常相似, 除了初始化之外｡
所有的整型都初始化为零, 这是一个很大的优势.
使用这两个参数也会更容易一些｡
我们来讨论另一个函数realloc｡
realloc函数使您能够重用或扩展以前使用malloc或calloc分配的内存｡
因此, 这是一种在运行时动态更改分配内存量的方法｡
所以你一开始可以创建这么多的内存, 然后你意识到,
这是不够的内存｡ 你能做的就是调用realloc, 它会重新分配内存.
它需要两个参数值, 一个指针包含一个地址,
该地址先前由malloc调用转换｡
所以你有一个指针, 它分配了一些内存或者calloc,
然后是你想分配的新内存的大小和字节数｡
这将分配第二个参数指定的内存量, 也就是第二个参数｡
它会传送先前配置的内存内容, 并由指标所指涉, 因此它会自动知道您先前配置了多少内存,
因为您传入了指标｡
然后, 它实际上会添加到新分配的内存中｡
并返回一个指向新内存的空指针｡
所以当你调用realloc的时候, 你必须像其他的一样重新定义它｡
如果无法重新分配, 它将返回null,
这同样可能是一个致命错误, 您应该中止｡
这个操作最重要的特性是realloc保留了原始内存区域的内容, 你只是扩展了那个内存｡
它可能是碎片, 因为您可能无法按顺序进行, 但您仍然有所有的内存,
您可以分配给现在的数据｡
让我们看一个例子｡
在本例中, 我们将使用malloc, 然后重新分配内存, 最后释放内存｡
我们在这里创建了一个字符指针｡
我们没有将其初始化为null, 我们可能应该这样做｡
但是它没有为它所指向的对象分配任何内存｡
它有分配给存储指针的内存,
指针只是一个地址｡ 但如果你试图去那个地址, 你的程序将是主机｡
所以你要做的就是调用malloc｡
这里我们说malloc 15｡
这将为字符指针创建15个字节｡
我们强制转换它, 因为它返回一个void指针｡
所以现在str是一个指针, 它有内存分配给它所指向的对象｡
然后我们可以用str copy把一些数据传递给它所指向的对象, 一个字符串,
然后我们把这个字符串打印出来｡
然后我们意识到我们分配的15个字节不够大｡
然后我们调用realloc, 传入指针str, 我们想再重新分配25个字节, 然后返回一个空指针,
因为我们必须再次强制转换.
现在我们仍然会记住杰森｡
现在我们可以调用string concatenated, 我们连接到字符串com,
现在它是Jason. 然后打印出来｡
最后一点,
但并非最不重要的一点, 可能是这张幻灯片中最重要的一点, 因此我们不会有内存泄漏, 这就是我们调用free｡
调用malloc时总是调用free｡
让我们看看代码块中的示例｡
下面是代码块编辑器中的代码示例｡
首先,
你应该注意到, 我们包含了标准的lib头文件, 因为malloc函数,
realloc函数, 以及free函数都在这里.
另外, 因为我们使用了一些str函数, 连接和复制,
所以我们包括了string｡ h.工作人员
在第八行, 我们要创建字符指针｡
没有为其分配内存｡
我们应该将它初始化为null, 因为它还没有指向任何东西｡
在第11行, 我们调用malloc｡
我们可能应该说是char的15倍, 以防char的字节大小不同, 然后我们把它转换成字符指针,
因为malloc返回一个void｡
然后我们复制它｡
如果我们试图复制这个字符串,
但没有在第11行做任何操作, 程序就会崩溃, 因为我们没有为字符串所指向的内容分配内存｡
然后我们只打印出字符串和地址｡
现在我们称之为realloc｡
我们需要存储多于15个字节的数据, 所以我们将重新分配25个字节,
我们可能需要再次执行sizeof char, 以保持一致｡
我们将其转换, 然后将其连接到包含jason的现有内存位置｡
我们要打印出Jason的照片 com的网站｡
最后但同样重要的是, 在第20行, 我们释放了内存, 这非常重要｡
让我们继续运行这个程序｡
你会看到当我们第二次打印出来的时候, 我们有了连接｡
第一个字符串, 我们分配了内存, 地址是000C2918｡
然后, 我们通过重新分配并将传递给它的指针的现有大小作为参数来创建一个新地址｡
所以000C0CD8实际上是有更多的数据分配给它, 现有的数据在jason还有25个字节｡
所以你会注意到连接是有效的｡
因为当我们重新分配时, 我们把内容jason放在那个内存中, 我们只是扩展了它｡
所以使用malloc, calloc和free realloc调用有很多好处｡
你想使用这些, 你想理解这些｡ 使用起来非常简单的函数｡
在最后一张幻灯片中, 让我们回顾一下一些指导原则｡
你想避免分配大量的小内存, 这是没有效率的｡
当您在堆上分配内存时, 它确实会产生一些开销｡
如果您分配较小的内存块, 则会比分配较少的较大内存块产生更多的开销｡
因此,
分配所需的内存, 但不要每两个语句就重新分配一次, 要分配大块内存｡
只有在需要的时候才能保留记忆｡ 内存是一种稀缺资源｡
当它在堆上时, 它就更稀缺了｡
因此, 当你用完一个内存块时, 调用free｡
始终确保提供释放已分配的内存的功能, 在编写分配内存的代码时,
确定将在代码中的何处释放内存｡
请确保您可以访问该指针｡
如果需要使指针成为全局指针, 那么就需要这样做｡
但是通常无论你在那个函数中分配了什么, 你都应该在那个函数中释放它｡
如果你有其他函数为你释放内存, 你就认为这不好｡
这里有太多的耦合和依赖性｡
创建它的人应该是删除它的人｡
不要把责任推给其他职能部门｡
在释放堆之前, 请确保不会无意中覆盖已在堆上分配的内存地址,
这将导致内存泄漏｡
因此, 如果您尝试向其写入太多字节或出现溢出, 则可能会出现问题｡
因此, 在循环中分配内存时要特别小心,
因为这可能会导致缓冲区溢出问题｡
我希望这对你有帮助｡ 动态内存分配是一件大事｡
我将给予你一个挑战, 帮助你更熟悉这一点,
但要确保你理解这个概念｡ -谢谢—谢谢