什么是使用零大小的元件?

时间:2013-08-08 17:22:09

标签: c memory-management c99 undefined-behavior

由于担心我的最后几天/几周,当我发现我的大部分代码确实破坏了c99规则,导致未定义的行为时,我开始明确阅读ISO / IEC 9899:TC3草案文件。 特别是附录“J.2未定义的行为” 对我来说,大部分原因都是合理的,为什么编译破坏这些规则的代码很难,或者在某些情况下我至少可以认为“好吧,我不明白,问题是什么,但我会“这样做” 但有一点......

  

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

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

因此,关于这个案子我脑子里有很多问题。

首先:

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

当我遇到这样的障碍时,我能用它做什么?

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

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

  

“如果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()”

,  如何使用它?  我可以改变它OK ...  我甚至被允许释放它(BTW是否意味着我必须释放它,因为我应该对所有其他分配的内存也是如此,或者它只是一个&gt;你被允许,不要破坏你的代码流

只做这样的指针会有什么不同?

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

我希望任何人都可以帮助我学习科学哲学,或者就像我对它一样感兴趣。

3 个答案:

答案 0 :(得分:3)

C不支持零大小的对象,但malloc()的参数类型为size_t,并且没有好办法阻止程序调用{​​{1}}。它可能不是程序员的意图,但它不一定是malloc(0);它更可能是malloc(0),其中malloc(count)是一些计算的结果。

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

如果你正在寻找一个连贯的解释,你就不会找到一个。如果今天从头开始设计C,那么malloc(0)的行为几乎肯定会以某种方式被钉死。要么是这样,要么行为本来是未定义的,但是使它实现定义意味着代码不必仔细检查它没有将零传递给malloc(0)

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

确实意味着:

malloc()

将正常工作;如果void *ptr = malloc(0); free(ptr); 的参数是空指针,则free()不执行任何操作。

你对malloc(0)的结果怎么办?好吧,如果malloc(1024)成功,您可以在分配的空间中存储1024个字节。您可以在malloc(0)分配的空间中存储 no 字节 - 这正是您所要求的。

答案 1 :(得分:0)

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

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

使用realloc(或与此有关的任何malloc)的代码通常无法控制内存分配和回收策略,但“malloc库”的某些实现可能会受益于拥有目前不需要的代码,但将来可能需要它的代码将其重新分配为0而不是释放它。请注意,尽管对于一个库最佳的使用模式在另一个库中可能是次优的,但是以符合标准的方式使用“malloc库”函数的代码应该在任何实现它们的机器上工作,即使不是最佳的。 - 兼容时尚。如果有人知道在接下来的一两年内,程序可能会在具有特定分配器的机器上运行,那么以对该分配器最佳的方式编写代码可能会有所帮助。该程序将来可能必须在其分配模式不再是最佳的机器上运行,但如果该机器更快并且具有更多RAM(可能是这种情况),则次优性能可能不会模式化。

答案 2 :(得分: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。此外,此代码完全避免了这个问题,更重要的是避免了潜在的陷阱。