【问题标题】:Code crashes unless I put a printf statement in it除非我在其中放入 printf 语句,否则代码会崩溃
【发布时间】:2011-08-06 02:05:30
【问题描述】:

这是我正在使用的数组库中的一个 sn-p 代码。这在 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 被取出,它就会再次开始崩溃。我完全困惑为什么会这样。

【问题讨论】:

  • void ***array 天哪。
  • 它是一个指向结构体指针数组的指针。
  • 添加-Wall -Wextra时是否收到编译器的警告?
  • 你是怎么调用这个函数的?
  • 我真的希望这段代码检查来自malloc(3) 的返回值,以确保它没有返回错误。

标签: c debugging


【解决方案1】:

函数的最后一行没有按照预期进行。代码晦涩难懂。

似乎目标是分配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);

或者,如 cmets 和替代答案中所述:

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

顺便提一下,函数名实际上不应该以下划线开头。以下划线开头的外部名称保留给(C 编译器和库的)实现。


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

推荐

  1. 运行valgrind下的程序。如果您没有,请获取。
  2. 修改代码,使函数检查来自malloc() 的返回值,并返回一个指向分配数组结构的指针。
  3. 使用Michael Burr 的答案中列出的更清晰的代码。

【讨论】:

  • 我认为两个下划线是为编译器等保留的,而单个下划线是为库(就是这样)保留的。此特定功能不是公共接口的一部分,它只是发生崩溃的地方。进行建议的更改可以解决崩溃问题,但我仍然不清楚 printf 为什么会产生任何影响。它如何改变代码的行为?
  • 或者干脆*(ArrayHeader*)array += 1
  • @Alex: C Rationale §7.1.3 保留标识符说:“为实现者保留的还有以下划线开头的 all 外部标识符,以及所有其他标识符以下划线后跟大写字母或下划线”(强调原文)。标准本身说:“--所有以下划线开头的标识符以及大写字母或另一个下划线始终保留用于任何用途。--所有以下划线开头的标识符始终保留用作文件标识符普通名称空间和标记名称空间中的范围”。小心!
  • 现在我的问题是为什么这会起作用;带有*(char**)array 的原始代码用于一堆Windows 代码没有任何问题。为什么它在那里工作? gcc 有何不同之处使其在此处崩溃而不在此处崩溃?
  • @Alex:不确定,但很可能是意外,它没有在 Windows 上崩溃。您有可能超出了数组的边界(但没有注意到),或者您保留了足够的容量而没有超出数组的边界。但是,鉴于 printf() 的奇怪行为,很可能是某些东西扰乱了您的内存分配,并且写入超出范围。
【解决方案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 年后维护您的代码的可怜人。

【讨论】:

  • 您建议的 nmemb 字段在这里不是必需的,因为它仅用于结构指针,因此用于此的所有内容都将具有相同的大小。用 10000 崩溃替换 malloc 的内容也是如此。当mallocing 时,数组本身和标题的空间是malloced,*(char**)array += sizeof(ArrayHeader) 指向 ArrayHeader 后面的第一个位置,除非我误解了你。跨度>
  • 糟糕,那是我的错。你是对的,函数的最后一行做了它应该做的事情。更好的方法可能是*(ArrayHeader*)array += 1。我从帖子中删除了提示。
  • 当我尝试你的建议时,我收到错误error: invalid operands to binary + (have ‘ArrayHeader’ and ‘int’)
【解决方案3】:

既然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()失败处理。

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

【讨论】:

  • 我实际上已经做了一个非常相似的更改,但为了这个问题,我发布了原始代码,以确保我没有搞砸其他任何事情。不过还是谢谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-04-29
  • 1970-01-01
  • 2011-11-18
  • 2014-02-20
  • 1970-01-01
  • 1970-01-01
  • 2016-03-27
相关资源
最近更新 更多