我需要一位真正的C大师的帮助来分析我的代码中的崩溃.不是为了解决崩溃; 我可以很容易地解决它,但在这之前我想了解这种崩溃是如何可能的,因为这对我来说似乎完全不可能.
此崩溃只发生在客户机器上,我无法在本地重现(因此我无法使用调试器逐步执行代码),因为我无法获取此用户数据库的副本.我的公司也不允许我只更改代码中的几行并为该客户进行自定义构建(因此我无法添加一些printf行并让他再次运行代码)当然客户的构建没有调试符号.换句话说,我的补偿能力非常有限.尽管如此,我可以确定崩溃并获得一些调试信息.但是,当我查看该信息然后在代码中,我无法理解程序流程如何能够到达相关行.代码应该在到达该行之前很久就已经崩溃了.我完全迷失在这里.
让我们从相关代码开始.这是非常少的代码:
// ... code above skipped, not relevant ... if (data == NULL) return -1; information = parseData(data); if (information == NULL) return -1; /* Check if name has been correctly \0 terminated */ if (information->kind.name->data[information->kind.name->length] != '\0') { freeParsedData(information); return -1; } /* Copy the name */ realLength = information->kind.name->length + 1; *result = malloc(realLength); if (*result == NULL) { freeParsedData(information); return -1; } strlcpy(*result, (char *)information->kind.name->data, realLength); // ... code below skipped, not relevant ...
那已经是它了.它在strlcpy中崩溃了.我甚至可以告诉你在运行时如何真正调用strlcpy .strlcpy实际上是用以下参数调用的:
strlcpy ( 0x341000, 0x0, 0x1 );
知道这一点很明显为什么strlcpy崩溃了.它尝试从NULL指针读取一个字符,这当然会崩溃.并且因为最后一个参数的值为1,所以原始长度必须为0.我的代码显然有一个错误,它无法检查名称数据是否为NULL.我可以解决这个问题,没问题.
我的问题是:
这个代码怎么能首先进入strlcpy?
为什么这个代码不会在if语句中崩溃?
我在我的机器上本地试了一下:
int main ( int argc, char ** argv ) { char * nullString = malloc(10); free(nullString); nullString = NULL; if (nullString[0] != '\0') { printf("Not terminated\n"); exit(1); } printf("Can get past the if-clause\n"); char xxx[10]; strlcpy(xxx, nullString, 1); return 0; }
此代码永远不会传递if语句.它在if语句中崩溃,这是绝对可以预期的.
因此,任何人都可以想到为什么第一个代码可以传递if-statement而不会崩溃,如果name-> data真的是NULL?这对我来说是完全神秘的.它似乎不具有确定性.
重要的额外信息:
两条评论之间的代码非常完整,没有遗漏任何内容.此外,应用程序是单线程的,因此没有其他线程可以意外地改变后台的任何内存.发生这种情况的平台是PPC CPU(G4,如果可以发挥任何作用).如果有人想知道"善意",这是因为"信息"包含一个名为"kind"的"union",而name又是一个struct(kind是一个union,每个可能的union值都是不同类型的struct); 但这一切在这里都不重要.
我很感激这里有任何想法.如果它不仅仅是一种理论,我会更感激,但如果有办法我可以证明这种理论确实适用于客户.
解我已经接受了正确的答案,但万一有人在Google上发现了这个问题,这就是真正发生的事情:
指针指向已经释放的内存.释放内存不会使其全部归零或导致进程立即将其返回系统.因此,即使错误地释放了内存,它也包含正确的值.在执行" if check " 时,有问题的指针不是NULL .
在那之后我分配一些新的内存,调用malloc.不确定malloc到底在做什么,但每次调用malloc或free都会对进程的虚拟地址空间的所有动态内存产生深远的影响.在malloc调用之后,指针实际上是NULL.不知何故,malloc(或某些系统调用malloc使用)将指针本身所在的已释放内存归零(不是它指向的数据,指针本身位于动态内存中).归零该内存,指针现在的值为0x0,在我的系统上等于NULL,并且当调用strlcpy时,它当然会崩溃.
因此,导致这种奇怪行为的真正错误是在我的代码中完全不同的位置.永远不要忘记:释放内存可以保持它的价值,但是你无法控制多久.要检查您的应用是否存在访问已释放内存的内存错误,只需确保释放内存在释放之前始终为零.在OS X中,您可以通过在运行时设置环境变量来完成此操作(无需重新编译任何内容).当然,这会使程序运行速度变慢,但是你会更早地捕获这些错误.
首先,取消引用空指针是未定义的行为.它可以崩溃,而不是崩溃,或将你的壁纸设置为海绵宝宝的照片.
也就是说,取消引用空指针通常会导致崩溃.因此,您的问题可能与内存损坏有关,例如,通过写一个字符串的结尾.这可能会导致延迟效果崩溃.我特别怀疑,因为malloc(1)
除非你的程序与其可用虚拟内存的结束相抵触,否则它很可能会失败,你可能会注意到这种情况.
编辑:OP指出它不是null的结果information->kind.name->data
.这是一个潜在的问题:
没有检查是否information->kind.name->data
为null.唯一的检查是
if (information->kind.name->data[information->kind.name->length] != '\0') {
假设information->kind.name->data
为null,但是information-> kind.name-> length是100,那么这个语句相当于:
if (*(information->kind.name->data + 100) != '\0') {
哪个不取消引用NULL而是取消引用地址100.如果这不会崩溃,并且地址100恰好包含0,则此测试将通过.
结构可能位于已经存在free()
的内存中,或者堆已损坏.在那种情况下,malloc()
可以修改内存,认为它是免费的.
您可以尝试在内存检查器下运行程序.一个支持Mac OS X的内存检查器是valgrind,虽然它仅支持Intel上的Mac OS X,而不支持PowerPC.
据我所知,标准未定义取消引用空指针的效果.
根据C标准6.5.3.2/4:
如果为指针分配了无效值,则unary*运算符的行为未定义.
所以可能会崩溃或者可能没有.