《操作系统导论》 知识总结 第14章 插叙:内存操作API

本章主要关注的问题是:

关键问题:如何分配和管理内存

在 UNIX/C 程序中,理解如何分配和管理内存是构建健壮和可靠软件的重要基础。通常使用哪些接口?哪些错误需要避免?

内存类型

在运行一个C程序的时候,会分配两种类型的内存。

第一种称为栈内存,它的申请和释放操作是编译器来隐式管理的,所以有时也称为自动内存**。假设需要在func()函数中为一个整形变量x申请空间,我们只需要这样:

void func() {
    int x; // declares an integer on the stack...
}

编译器完成剩下的事情,确保在你进入 func() 函数的时候,在栈上开辟空间。当你从该函数退出时,编译器释放内存。

第二种类型的内存是堆内存,将某些信息存在于函数调用之外,其中所有的申请和释放操作都由程序员显式地完成。下面的例子展示了如何在堆上分配一个整数,得到指向它的指针:

void func() {
    int *x = (int *)malloc(sizeof(int));
    ...
}

注意到栈和堆的分配都发生在这一行:编译器看到指针的声明(int *x)时,知道为一个整型指针分配空间,随后,当程序调用malloc()时,它会在堆上请求整数的空间,函数返回这样一个整数的地址(失败时则返回NULL),然后将其存储在栈中以供程序使用。

malloc()调用

malloc 函数非常简单:传入要申请的堆空间的大小,它成功就返回一个指向新申请空间的指针,失败就返回 NULL。

在命令行输入 man malloc:

#include <stdlib.h> 
... 
void *malloc(size_t size);

malloc 只需要一个 size_t 类型参数,该参数表示你需要多少个字节。大多数程序员并不会直接传入数字(比如 10)。替代方案是使用各种函数

例如,为了给双精度浮点数分配空间:

double *d = (double *) malloc(sizeof(double));

也可以传入一个变量的名字(而不只是类型)给 sizeof():

int *x = malloc(10 * sizeof(int)); 
printf("%d\n", sizeof(x));

在第一行,我们为 10 个整数的数组声明了空间。但是,当我们在下一行使用 sizeof()时,它将返回一个较小的值,例如 4( 32 位计算机上)或 8( 64 位计算机上)。原因是在这种情况下,sizeof()认为我们只是问一个整数的指针有多大,而不是我们动态分配了多少内存。

int x[10]; 
printf("%d\n", sizeof(x));

在这种情况下,编译器有足够的静态信息,知道已经分配了 40 个字节。

注意:如果为字符串声明空间,需使用以下用法:

malloc(strlen(s) + 1)

使用函数 strlen()获取字符串的长度,并加上 1,以便为字符
结束符留出空间。

free()调用

调用free()释放不再使用的堆内存:

int *x = malloc(10 * sizeof(int)); 
... 
free(x);

该函数接受一个参数,即一个由 malloc()返回的指针。

常见错误

忘记分配内存

下面这段代码会可能会导致段错误,因为我们没有给dst分配相应的内存就使用它。

char *src = "hello";
char *dst; // oops! unallocated
strcpy(dst, src); // segfault and die

没有分配足够的内存

另一个相关的错误是没有分配足够的内存,有时称为缓冲区溢出。

char *src = "hello";
char *dst = (char *)malloc(strlen(src)); // too small! 
strcpy(dst, src); // work properly

这段程序能否正确运行,取决于如何实现malloc()和许多其他细节。当字符串拷贝执行时,它会在超过分配空间的末尾处写入一个字节,在某些情况下这是无害的,它可能会覆盖不再使用的变量。但是在另一些情况下,这些溢出可能具有令人难以置信的危害。有些malloc()实现总是分配一些额外的空间,因此程序实际上不会在其他某个变量的值上涂写,并且工作得很好。

忘记初始化分配的内存

如果我们正确地调用了malloc(),但是忘记在新分配的空间中填写一些值,那么程序可能会从堆中读取某些未知或有害的数据。

忘记释放内存

如果程序员忘记释放掉申请的内存空间,就会发生内存泄露(memory leak)。在长时间运行的应用程序或系中,这是一个巨大的问题,因为缓慢泄露的内存会导致内存不足,此时需要重新启动。因此,当用完一段内存时,应该确保释放它。请注意,使用垃圾收集语言在这里没有什么帮助:如果你仍然拥有对某块内存的引用,那么垃圾收集器就不会释放它,因此即使在较现代的语言中,内存泄露仍然是一个问题。

在用完之前释放内存

有时候程序会在用完之前释放内存,这种错误称为悬挂指针(dangling pointer)。随后的使用可能会导致程序崩溃或覆盖有效的内存(例如,调用了free(),但随后再次调用malloc()来分配其他内容,这重新利用了错误释放的内存)。

反复释放内存

程序有时还会不止一次地释放内存,这被称为重复释放,这样做的结果是未定义的。

错误地调用free()

free()期望我们只传入之前从malloc()得到的一个指针,如果传入其他值,坏事就有可能发生,我们应该避免这种无效的释放。

下面贴几张陈浩老师ppt中的常见错误:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
就不翻译了,想必都看得懂。

底层操作系统支持

malloc()和free()不是系统调用,而是库调用。malloc()库管理虚拟地址空间内的空间,但是它本身是建立在一些系统调用之上的,这些系统调用会进入操作系统,来请求更多内存或者将一些内容释放回系统。一个这样的系统调用叫作brk,它被用来改变程序分断(break)的位置:堆结束的位置。它需要一个参数(新分断的地址),从而根据新分断是大于还是小于当前分断,来增加或减小堆的大小。另一个调用sbrk要求传入一个增量,但目的是类似的。brk与sbrk被内存分配库使用,并不能直接调用它们。此外,还可以通过mmap()调用从操作系统获取内存。通过传入正确的参数,mmap()可以在程序中创建一个匿名内存区域——这个区域不与任何特定文件相关联,而是与交换空间相关联,并且它也可以像堆一样对待并管理。

参考

https://www.cnblogs.com/shuo-ouyang/p/12774975.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值