代码崩溃,除非我在其中放入printf语句

时间:2011-04-15 05:59:32

标签: c debugging

这是我正在使用的数组库的代码片段。这在windows上运行正常,但是当我在linux上用gcc编译时如果在这个函数中崩溃了。在尝试缩小问题范围时,我向其添加了一个printf语句,代码停止崩溃。

void _arrayCreateSize( void ***array, int capacity )
{
    (*array) = malloc( (capacity * sizeof(int)) + sizeof(ArrayHeader) );
    ((ArrayHeader*)(*array))->size = 0;
    ((ArrayHeader*)(*array))->capacity = capacity;
    // printf("Test!\n");
    *(char**)array += sizeof(ArrayHeader);
}

一旦取出printf,它就会再次开始撞击我。我完全不知道为什么会这样。

3 个答案:

答案 0 :(得分:5)

该函数的最后一行没有按预期执行。代码对于不可穿透性而言是模糊不清的。

由于第一个内存分配中的int,目标似乎是分配一个sizeof(int)数组。至少,如果你要分配一个结构指针数组,你需要使用sizeof(SomeType *),一些指针类型的大小(sizeof(void *)会这样做)。如上所述,这将在64位环境中失败。

为数组分配一个结构头(ArrayHeader),然后是数组。返回的值应该是数组的正确开始;大概可以通过从指针中减去找到ArrayHeader。这是罪恶的丑陋,并且无法维持。它可以工作,但它需要非常小心,并且(正如Brian Kernighan所说)“如果你在编写代码时尽可能聪明,那么你将如何进行调试?”。

不幸的是,最后一行是错误的:

void _arrayCreateSize( void ***array, int capacity )
{
    (*array) = malloc( (capacity * sizeof(int)) + sizeof(ArrayHeader) );
    ((ArrayHeader*)(*array))->size = 0;
    ((ArrayHeader*)(*array))->capacity = capacity;
    // printf("Test!\n");
    *(char**)array += sizeof(ArrayHeader);
}

它会将sizeof(ArrayHeader) * sizeof(char *)添加到地址,而不是预期的sizeof(ArrayHeader) * sizeof(char)。因此,最后一行应该是:

*(char *)array += sizeof(ArrayHeader);

或者,正如评论和备选答案中所述:

*(ArrayHeader *)array += 1;
*(ArrayHeader *)array++;

我顺便注意到函数名称不应该以下划线开头。以下划线开头的外部名称保留给(C编译器和库)的实现。


问题是“为什么printf()陈述'修复'事物”。答案是因为它解决了问题。你有一个Heisenbug,因为滥用了已分配的内存,printf()的存在设法稍微改变了代码的行为。

建议

  1. valgrind下运行该程序。如果你没有它,那就去吧。
  2. 修改代码,以便函数检查来自malloc()的返回值,因此它返回指向已分配数组的结构的指针。
  3. 使用Michael Burr的答案中列出的更清晰的代码。

答案 1 :(得分:2)

添加看似无关的printf()语句时,任意随机崩溃通常表示堆已损坏。编译器有时会直接在堆本身上存储有关已分配内存的信息。覆盖该元数据会导致令人惊讶的运行时行为。

一些建议:

  • 您确定需要void ***吗?
  • 尝试使用malloc()将您的参数替换为10000。它现在有效吗?


此外,如果您只想要存储一些元数据的数组,那么您当前的代码是一种糟糕的方法。一个干净的解决方案可能会使用如下结构:

struct Array {
    size_t nmemb;    // size of an array element
    size_t size;     // current size of array
    size_t capacity; // maximum size of array
    void *data;      // the array itself
};

现在,您可以将类型为Array的对象传递给知道Array类型的函数,并将Array->data强制转换为适当的类型。内存布局甚至可能与您当前的方法相同,但访问元数据要容易得多,尤其是更明显。

您的主要受众是5年后必须维护您的代码的穷人。

答案 2 :(得分:1)

既然是Jonathan Leffler has pointed out what the bug was,我是否可以建议以一种不那么令人费解的方式编写函数?:

void _arrayCreateSize( void ***array, int capacity )
{
    // aloocate a header followed by an appropriately sized array of pointers
    ArrayHeader* p = malloc( sizeof(ArrayHeader) + (capacity * sizeof(void*)));

    p->size = 0;
    p->capacity = capacity;

    *array = (void**)(p+1);   // return a pointer to just past the header 
                              //   (pointing at the array of pointers)
}

混合您自己想要的malloc()失败处理。

我认为这可能会帮助下一个需要查看它的人。