C API设计:当malloc返回NULL时该怎么办?

时间:2012-01-29 03:13:51

标签: c malloc api-design

假设我正在用C编写一个小型库 - 比如一些数据结构。如果我无法分配内存,该怎么办?

这可能非常重要,例如我需要一些内存来初始化数据结构,或者我正在插入一个键值对,并希望将它包装在一个小结构中。它也可能不太重要,例如像pretty_print函数那样构建内容的良好字符串表示。但是,它通常比你的平均错误更严重 - 可能没有任何意义继续下去。如果malloc返回NULL,则大量使用stderr在线直接退出程序。我猜测很多真正的客户端代码也会这样做 - 只是弹出一些错误,或者将其写入malloc,然后中止。 (而且很多实际代码可能根本不检查NULL的返回值。)

有时返回success是有道理的,但并非总是如此。错误代码(或只是一些布尔get_error值),无论是返回值还是输出参数都可以正常工作,但似乎它们可能会混乱或损害API的可读性(然后再次,也许这在某种程度上是预期的语言如C?)。另一个选择是具有某种内部错误状态,调用者可以随后查询,例如,使用malloc函数,但是你必须小心线程安全,它可能很容易错过;无论如何,人们往往会对检查错误松懈,如果这是一个单独的功能,他们可能不知道它,或者他们可能不会打扰(但我猜这是他们的问题)。

(我有时会看到void *my_malloc(size_t size) { void *result = NULL; while (result == NULL) result = malloc(size); return result; } 包裹在一个只会再次尝试直到内存可用的函数中...

{{1}}

但这似乎有点愚蠢,也许很危险。)

处理此问题的正确方法是什么?

7 个答案:

答案 0 :(得分:13)

如果分配以阻止前进的方式失败,库代码唯一可接受的解决方案是退出已在部分完成的操作中进行的任何分配和其他更改并返回调用者的失败代码。只有调用应用程序才能知道正确的方法是什么。一些例子:

  1. 音乐播放器可能只是中止或返回初始/停止状态并再次等待用户输入。
  2. 字处理器可能需要将当前文档状态的紧急转储存储到恢复文件中,然后才能中止。
  3. 高端数据库服务器可能需要拒绝并退出整个事务并向客户报告。
  4. 如果您遵循经常建议但后退的想法,即您的图书馆应该在分配失败时中止调用者,那么您将拥有许多程序,以确定他们因此而无法使用您的库,您使用的程序的用户当分配失败导致其有价值的数据被丢弃时,您的库将极度生气。

    编辑:有人反对我的回答中提到的一个“中止”阵营的一个反对意见是,在有过度使用的系统上,即使是malloc似乎成功的来电也可能会失败内核尝试为分配的虚拟内存实例化物理存储。这忽略了这样一个事实:任何需要高可靠性的人都会禁用过度使用,以及(至少在32位系统上)分配失败更可能是由于虚拟地址空间耗尽而不是物理存储耗尽。

答案 1 :(得分:4)

只需以通常的方式返回错误。由于我们讨论的是API,您不知道从哪个环境调用,因此只需返回NULL或遵循您已经使用的任何其他错误处理过程。你不想永远循环,因为调用者可能并不真的需要那个内存,他们宁愿只知道你无法处理它,或者调用者可能有一个他们可以发送错误的用户界面。

大多数API都会有某种返回值,表示所有函数都存在错误,其他API要求调用者调用特殊的“check_error”函数来确定是否存在错误。您可能还需要一个“get_error”函数来返回一个错误字符串,调用者可以选择将该字符串显示给用户或包含在日志中。它应该是描述性的:“某某API在函数中遇到了错误:无法分配内存”。管他呢。足够当有人得到错误时,他们知道是什么组件扔了它,当他用日志消息给你发电子邮件时,你确切地知道出了什么问题。

当然你也可以崩溃,但这会阻止调用者关闭他们可能正在做的任何其他事情并且看起来很难看,如果你的代码在调用它时会有死亡的习惯而不是返回错误,那么人们就是去寻找一个不会主动试图杀死他们程序的图书馆。

答案 2 :(得分:3)

用于线性代数函数调用的BLAS标准API使用与此处给出的“返回错误代码”建议稍有不同的方法:它调用特定的文档函数,然后返回。

现在,该库还提供了这个记录的函数的实现,它打印了一个有用的错误消息和(如果可能)堆栈跟踪,然后中止。这是处理事物的一种方式,这意味着临时用户不会遇到奇怪的问题,因为他们忘记检查错误代码。

然而,这是一个特定文档功能的关键点是,它意味着用户选择提供他们自己的该功能实现,这将覆盖默认功能。并且该实现可以执行许多操作 - 它可以设置用户随后检查的全局错误代码,或者它可以执行某些尝试清理某些内存并继续执行的操作。

这对于实现和用户来说都是一个重量级的解决方案,但是在明显错误代码不合适的情况下,它提供了很大的灵活性。

编辑以添加更多细节:BLAS函数是xerbla(或C接口中的cblas_xerbla),期望在链接时覆盖它 - 假设是静态链接。还有一些关于如何在this header from Apple中调整动态库的相关注释(参见文件底部附近的SetBLASParamErrorProc的注释) - 在动态链接的情况下,回调需要是在运行时注册。

另见“R”的有用说明。在下面的评论中关于如何覆盖不幸全局,如果用户直接或间接通过第二个库使用您的库,并且用户和第二个库都想要覆盖处理程序,这可能会导致问题。

答案 3 :(得分:3)

对于图书馆,您有两种选择。没有应用程序合作,您可以做的就是将错误传递回应用程序。

通过应用程序合作,您可以做更多事情。例如,您可以提供应用程序以在malloc返回NULL时注册库调用的回调。您可以将回调传递给您需要的字节数以及您需要它们的紧急程度。 (例如,“将完全无法操作”,“将不得不中止操作”,等等。)然后,应用程序作者可以决定是否给你内存。

在应用程序级别,您可以做更多事情。例如,您可以malloc将一堆内存块用作“紧急池”。如果malloc失败,您可以从池中释放块并开始减载,缓存减少或其他任何其他选择以减少内存消耗。

但是除了通知合作的应用程序之外,你通常不能在库中做很多事情。

答案 4 :(得分:2)

在像Java或C#这样的语言中,明确的答案通常是“抛出异常!”。

在C中,一种常见的方法是同步处理错误(例如,使用结果代码和/或标记值,如“null”)。

还可以生成异步信号(很像Java“异常”......或者是一个严厉的“abort()”)。使用此方法,您还可以允许用户安装自定义“错误处理程序”。

以下是使用setjmp / longjmp的示例:

以下是一些有趣的想法:

这里讨论了使用C回调进行错误处理的优点/缺点:

答案 5 :(得分:2)

您可以编写首先不需要malloc的软件 - 安全关键的东西。

您只需确保在执行开始时分配,定义所需内容并确保算法不会超出这些障碍。

很难,但并非不可能。

答案 6 :(得分:1)

设计软件以清楚地处理内存不足问题并且仍在进行中很困难。 很少有真正的应用程序认真尝试这样做。作为库作者,唯一合理的做法是向调用者报告错误。 (返回失败;抛出异常;取决于语言等。)

你绝对不想循环和阻止一般的库。 @R有一个好点,如果发生故障,请尝试将状态恢复到原始状态。

处理内存不足和磁盘空间不足的问题可能需要在应用的所有部分进行协调。您可能希望预先分配应急记忆。你可能会像你想要的那样循环/重试mallocs,但是在超时之间会有一些延迟。它实际上超出了典型库的范围。