4

由于担心我发现我的大部分代码确实违反了 c99 规则,导致未定义行为的原因,我开始明确阅读 ISO/IEC 9899:TC3 草案文件。尤其是附录“J.2 未定义的行为”,其中大部分对我来说是合乎逻辑的,为什么很难编译违反这些规则的代码,或者在某些情况下,我至少可以认为“好吧,我不明白,有什么问题,但我'会'那样做”但有一点......

“调用请求大小为零的 calloc、malloc 或 realloc 函数返回的非空指针用于访问对象 (7.20.3)。”

(对于所有尚未阅读 ISO/IEC 9899:TC3“J.2 未定义行为”的人,本节仅解释哪些情况会遇到未定义行为)。

所以我脑子里有很多关于那个案子的问题。

首先:

为什么我要分配一个大小为零的内存块?

当我有这样的块时,我能用它做什么?

为了避免未定义的行为,我可能不想访问它指向的内存......

所以我做了更多的研究......寻找一些不同的 malloc() 手册页。并在 Linux malloc(3) 手册中找到:

“如果 size 为 0,则 malloc() 返回 NULL,或稍后可以成功传递给 free() 的唯一指针值。”

好吧,这对我有帮助的唯一一件事是:我现在向您和我自己提出了其他问题。在相同条件下使用相同参数调用函数可能会返回不同结果的情况并不难想象,好吧……但那些不同的结果大多不必如此不同。这就是让我建议的,指向所请求的零大小块的非空指针可能只是一个不需要的副作用。意思是不是

if ((void *ptr = malloc (0)) == NULL)
{
    /*...*/
}

这还不够吗?我必须像这样处理 *alloc 调用吗?

if (X <= 0)
{
    if ((*ptr = malloc (X)) != NULL)
    {
        exit (*);
    }
    else
    {
        /*...*/
    }
}
else
{
    if ((*ptr = malloc (X)) == NULL)
    {
        /*...*/
    }
}

但即使它的预期,得到这样一个

“以后可以成功传递给 free() 的唯一指针值”

,如何使用它?我可以改变它...我什至可以释放它(顺便说一句,这是否意味着我必须释放它,就像我应该对所有其他分配的内存做的那样,或者它只是一个>你被允许,不要破坏你的代码流

像这样制作任何指针有什么区别?

void *X = (void *)"1234abc";

我希望任何人都可以帮助我了解这种科学哲学,或者甚至像我一样对它感兴趣。

4

3 回答 3

3

C 不支持大小为零的对象,但 的参数malloc()是 type size_t,没有好的方法可以防止程序调用malloc(0). 这可能不是程序员的意图,但不一定是字面意思malloc(0);它更有可能是,一些计算的结果malloc(count)在哪里。count

至于允许两种不同行为的标准,这仅仅是因为现有实现(在编写原始标准时)做了不同的事情,而作者希望避免破坏现有代码。这样的代码可以说已经被破坏了,或者至少是不可移植的,但是通过允许任何一种行为,一个假设malloc(0)行为如何的程序可以继续在编写它的系统上工作。

如果你正在寻找一个连贯的解释,你不会找到一个。如果今天从头开始设计 C,那么malloc(0)几乎可以肯定,无论哪种方式,C 的行为都会被确定下来。要么,要么行为将被设为未定义,但使其实现定义意味着代码不必非常仔细地检查它是否没有将零传递给malloc().

事实上,委员会的决定记录在C99 基本原理,第 7.20.3 节,第 160-161 页。

它确实意味着:

void *ptr = malloc(0);
free(ptr);

将正常工作;free()如果它的参数是一个空指针,它什么也不做。

你能用 的结果做什么malloc(0)?好吧,如果malloc(1024)成功的话,你可以在分配的空间中存储 1024 个字节。您可以在分配的空间中不存储任何malloc(0)字节-- 这正是您所要求的。

于 2013-08-08T17:42:00.077 回答
0

许多内存分配例程都有一个它们可以支持的最小大小的分配块,并将任何分配请求扩展到该最小大小。虽然他们本可以malloc检查大小是否为零,如果为零则返回 null,但将所有小于最小大小的 malloc 请求(包括恰好为零字节的请求)填充到最小大小更简单、更容易。由于唯一可能请求零字节 malloc 的代码将请求一个大小在运行时确定的缓冲区,并且由于任何此类代码都希望释放它所请求的任何缓冲区而不考虑其大小,例如行为没有造成任何困难。

要考虑的另一件事是,如果realloc在一个块上多次调用,如果它有足够的可用空间,可能希望继续使用同一个内存块。如果分配给它的例如 256 字节的块的大小为零,则可能希望暂停使用以前占用的 256 字节用于除重新扩展该内存块之外的任何目的。如果调用realloc请求大小为零的内存块返回指向同一块的指针,则系统将能够识别后续请求以重新扩展该块并使用以前占用的空间来满足它。如果零字节realloc返回空指针,则将该块扩展为非零大小的请求与分配新块的请求无法区分。

使用realloc(或任何东西)的代码malloc相关的,就此而言)通常不会对内存分配和回收策略进行任何控制,但“malloc库”的某些实现可能会受益于拥有目前不需要但将来可能需要它的代码重新分配它的大小为零而不是释放它。请注意,虽然最适合一个库的使用模式在另一个库中可能不是最佳的,但以符合标准的方式使用“malloc 库”函数的代码应该可以在任何实现它们标准的机器上工作,即使不是最佳的- 兼容时尚。如果有人知道在接下来的一两年内,一个程序可能会在具有特定分配器的机器上运行,那么以最适合该分配器的方式编写代码可能会有所帮助。

于 2013-08-08T21:38:46.493 回答
0

您的代码可以更明确,如:

if (X <= 0)
{
    ptr = NULL;
}
else
{
    ptr = malloc(X);
    if (ptr == NULL)
    {
        /*...*/
    }
}

这可以提炼为:

 ptr = (X <= 0) ? NULL : malloc(X);
 if (ptr == NULL) { /* ... */}

今天,在 C 语言中,如果malloc无法获取请求的存储,则返回 NULL。此外,这段代码完全避免了这个问题,更重要的是避免了潜在的陷阱。

于 2013-08-09T01:55:02.147 回答