什么非NULL内存地址可以自由地用作C中的错误值?

时间:2010-10-07 11:39:13

标签: c pointers error-handling memory-management

我需要为GMP库编写自己的内存分配函数,因为默认函数调用abort()并且在发生之后我无法想到恢复程序流(我在整个地方都调用了mpz_init) ,以及如何根据该调用周围发生的事情来处理故障变化。但是,文档要求函数返回的值不为NULL。

是否至少有一个地址范围始终可以保证无效?了解所有这些内容会很有用,因此我可以针对不同的错误代码使用不同的地址,甚至可能针对不同的错误系列使用不同的范围。

7 个答案:

答案 0 :(得分:4)

如果默认内存分配函数abort(),并且GMP的代码无法处理NULL,则GMP可能根本不准备处理内存分配失败的可能性。如果您返回故意无效的地址,GMP可能会尝试取消引用它,并立即崩溃,这与调用abort()一样糟糕。更糟糕的是,因为堆栈跟踪不会指出真正导致问题的原因。

因此,如果你要返回,你必须返回一个有效指针,一个没有被其他任何东西使用的指针。

现在,一个稍微恶劣的选择是使用setjmp()longjmp()来退出GMP例程。但是,这会使GMP处于不可预测的状态 - 您应该假设在此之后再也不能再调用GMP例程。它也可能导致内存泄漏......但这可能是你现在最不关心的问题。

另一个选择是在系统malloc中有一个保留池 - 即在应用程序启动时:

emergencyMemory = malloc(bignumber);

现在,如果malloc()失败,您执行free(emergencyMemory),并且希望您有足够的空间来恢复。请记住,这只会为您提供有限的余量 - 您必须希望GMP将返回您的代码(并且该代码将检查并查看已使用紧急池),然后才能真正耗尽内存。

当然,您也可以组合使用这两种方法 - 首先使用保留池并尝试恢复,如果失败,longjmp()输出,则显示错误消息(如果可以),以及优雅地终止。

答案 1 :(得分:3)

不,没有可移植范围的无效指针值。

您可以使用特定于平台的定义,也可以使用某些全局对象的地址:

const void *const error_out_of_bounds = &error_out_of_bounds;
const void *const error_no_sprockets = &error_no_sprockets;

[编辑:抱歉,错过了您希望将这些值返回到库中。正如bdonlan所说,你不能这样做。即使您发现某些“无效”值,库也不会期望它们。您的函数必须返回有效值,或abort。]

你可以在全局中做这样的事情:

void (*error_handler)(void*);
void *error_data;

然后在你的代码中:

error_handler = some_handler;
error_data = &some_data;
mpz_init(something);

在你的分配器中:

if (allocated_memory_ok) return the_memory;
error_handler(error_data);
abort();

在调用mzp_init之前设置错误处理程序和数据可能有点单调乏味,但根据行为在不同情况下的不同程度,您可能可以编写一些函数或宏来处理它。

但是,如果GMP库在分配失败后没有设计应对,那么你不能做的就是恢复并继续运行。在这方面你会受到你的工具的支配 - 如果库调用没有错误返回,那么谁知道它的内部结构将处于什么破坏状态。

但这是一个完全普遍的观点,而GMP是开源的。您可以找到mpz_init中实际发生的情况,至少对于特定的GMP版本。可能有一些方法可以提前确保你的分配器有足够的内存来满足请求,或者可能有某种方法可以在没有造成太大损害的情况下蠕动(比如bdonlon说,longjmp)。

答案 2 :(得分:1)

由于没有人提供正确答案,因此您可以安全地用作错误值的非NULL内存地址集与您为此目的创建的地址集相同。如果需要全局可见,只需声明static const char(或全局const char)数组N是您需要的错误代码数,并使用指针{{1此数组的元素为N错误值。

如果指针类型不是N而是其他东西,则可能需要使用该类型的对象而不是char *数组,因为将这些char指针转换为另一个指针类型不能保证工作。

答案 3 :(得分:0)

仅在当前主流操作系统(启用虚拟内存)和CPU架构上保证:

-1L(表示对于指针足够大的值中的所有位)

许多库使用它来标记释放的指针。有了这个,您可以轻松找到错误来自使用NULL指针或挂起引用。

适用于HP-UX,Windows,Solaris,AIX,Linux,Free-Net-OpenBSD以及i386,amd64,ia64,parisc,sparc和powerpc。

认为这样做足够了。看不出任何超过这两个值(0,-1)的原因

答案 4 :(得分:0)

如果你只是返回,例如16位或32位对齐指针,不均匀的指针地址(LSB等于1)将至少是“神秘的”,并且会创建一个机会来使用我最喜欢的虚假值0xDEADBEEF(对于32位)指针)或0xDEADBEEFBADF00D(对于64位指针)。

答案 5 :(得分:0)

您可以使用多个范围,它们是特定于操作系统和体系结构的。

通常大多数平台都会保留第一页(通常长度为4K字节),以捕获空指针的解除引用(加上轻微偏移的空间)。

您还可以指向保留的操作系统页面,在Linux上,这些页面占用从0xc00000000xffffffff的区域(在32位系统上)。从用户空间,您将无权访问此区域。

另一个选项(如果要分配多个此类值,则是使用mmapequivalent分配没有读取或写入权限的页面,并为每个不同的错误值使用此页面的偏移量。

最简单的解决方案,只是使用对0,(-1-2等)立即为负的值,或立即为正(1,{ {1}},...)。您可以非常肯定这些地址位于无法访问的页面上。

答案 6 :(得分:0)

一种可能性是采用保证存在的C库地址,因此永远不会被malloc或类似地址返回。为了最容易移植,这应该是对象指针而不是函数指针,但在大多数架构上,转换((void*)main)可能都没问题。我想到的一个数据指针是environ,但它是POSIX,或stdin等,不能保证是“真正的”变量。

要使用此功能,您只需使用以下内容:

extern char** environ; /* guaranteed to exist in POSIX */
#define DEADBEAF ((void*)&environ)