我需要一个真正的C古鲁的帮助来分析我的代码崩溃。 不是为了修理这个事故。 我可以很容易地解决这个问题,但是在这之前我想知道这个崩溃甚至是可能的,因为这对我来说是完全不可能的。
这个崩溃只发生在客户机器上,我不能在本地重现它(所以我不能通过debugging器的代码),因为我无法获得此用户的数据库的副本。 我的公司也不会允许我只是在代码中修改几行代码,并为这个客户做一个定制的构build(所以我不能添加一些printf行,并让他再次运行代码),当然,客户的构build没有debugging符号。 换句话说,我的debibuging能力是非常有限的。 尽pipe如此,我可以确定崩溃并获得一些debugging信息。 然而,当我看到这些信息,然后在代码中,我无法理解程序stream程如何能够达到所讨论的线路。 代码应该已经崩溃了很久之前到达该行。 我完全迷失在这里。
// ... code above skipped,not relevant ... if (data == NULL) return -1; @R_689_4045@ion = parseData(data); if (@R_689_4045@ion == NULL) return -1; /* Check if name has been correctly terminated */ if (@R_689_4045@ion->kind.name->data[@R_689_4045@ion->kind.name->length] != ' ') { freeParsedData(@R_689_4045@ion); return -1; } /* copy the name */ realLength = @R_689_4045@ion->kind.name->length + 1; *result = malloc(realLength); if (*result == NULL) { freeParsedData(@R_689_4045@ion); return -1; } strlcpy(*result,(char *)@R_689_4045@ion->kind.name->data,realLength); // ... code below skipped,not relevant ...
这已经是了。 它崩溃了。 我甚至可以告诉你,在运行时如何真正调用strlcpy。 strlcpy实际上被称为以下参数:
铿锵声在Windows 7 64位崩溃
为什么这个CreateFile()调用在Windows 8上导致BSOD?
创build并发送崩溃报告
进程崩溃时,操作系统是否刷新cpucaching?
在Windows 7上禁用对“不稳定”应用程序的特殊处理
strlcpy ( 0x341000,0x0,0x1 );
知道这是一个相当明显的为什么strlcpy崩溃。 它试图从NULL指针读取一个字符,这当然会崩溃。 由于最后一个参数的值为1,所以原始长度必须是0.我的代码在这里显然有一个错误,它没有检查名称数据是NULL。 我可以解决这个问题,没问题。
我的问题是:
这个代码如何能够摆在首位呢?
为什么这个代码不会在if语句中崩溃?
我在本地机器上尝试过:
int main ( int argc,char ** argv ) { char * nullString = malloc(10); free(nullString); nullString = NULL; if (nullString[0] != ' ') { printf("Not terminatedn"); exit(1); } printf("Can get past the if-clausen"); char xxx[10]; strlcpy(xxx,nullString,1); return 0; }
这段代码永远不会通过if语句。 它在if语句中崩溃,这绝对是预期的。
所以任何人都可以想到,为什么第一个代码可以通过if语句没有崩溃,如果name->数据真的是NULL的任何原因? 这对我来说是完全神秘的。 这似乎并不确定。
重要的额外信息:
这两个评论之间的代码是真的完整的 ,什么都没有被遗漏。 此外,该应用程序是单线程的 ,所以没有其他线程可以意外地改变后台的任何内存。 发生这种情况的平台是一个PPC cpu(一个G4,如果可以发挥任何作用)。 如果有人想知道“kind”,这是因为“@R_689_4045@ion”包含一个名为“kind”的“union”,name又是一个结构体(kind是一个联合体,每个可能的union值都是一个不同types的结构体)。 但这一切都不应该在这里真正重要。
我很感激这里的任何想法。 如果这不仅仅是一个理论,我更感激,但是如果有一种方法,我可以certificate这个理论对于客户是真的。
解
我已经接受了正确的答案,但为了万一任何人在Google上发现这个问题,以下是真正发生的事情:
指针指向已经释放的内存。 释放内存不会使其全部为零或导致进程立即将其返回给系统。 所以即使内存被错误地释放,它也包含了正确的值。 有问题的指针在执行“ if check ”时不是NULL。
之后检查我分配一些新的内存,调用malloc。 不确定malloc究竟在做什么,但是每次调用malloc或free都会对进程的虚拟地址空间的所有dynamic内存产生深远的影响。 在malloc调用之后,指针实际上是NULL。 不知何故,malloc(或某些系统调用malloc使用)将已释放的指针本身所在的内存(不是指向它的数据,指针本身在dynamic内存中)置零。 调零内存,指针现在的值为0x0,在我的系统上等于NULL,当调用strlcpy时,它当然会崩溃。
所以导致这种奇怪行为的真正的错误是在我的代码完全不同的位置。 永远不要忘记:释放记忆保持它的价值,但它超出了你的控制时间。 要检查您的应用程序是否有访问已释放内存的内存错误,只要确保释放的内存在释放之前始终为零。 在OS X中,您可以通过在运行时设置环境variables来完成此操作(无需重新编译任何内容)。 当然,这会让程序变慢一些,但是你会很早就发现这些错误。
C for Linux中的崩溃报告
为什么应用程序运行3周后会popup一个错误,“控件没有父窗口”?
非托pipeWindows进程崩溃的方法?
当我从Perl调用命令时,如何禁止“通知Microsoft”崩溃对话框?
结构可能位于已经free()的内存中,或者堆已损坏。 在这种情况下, malloc()可能会修改内存,认为它是免费的。
你可以尝试在内存检查器下运行你的程序。 一个支持Mac OS X的内存检查器是valgrind ,虽然它只支持Intel上的Mac OS X,而不支持PowerPC。
首先,解引用空指针是未定义的行为。 它可以崩溃,不会崩溃,或设置您的墙纸海绵宝宝的图片。
也就是说,解引用空指针通常会导致崩溃。 所以你的问题可能是内存腐败相关的,例如,从你的一个字符串的末尾写作。 这可能会导致延迟效果崩溃。 我特别怀疑,因为malloc(1)很可能不会失败,除非你的程序正在与可用的虚拟内存相抵触,你可能会注意到如果是这样的话。
编辑:OP指出,它不是空的结果,但@R_689_4045@ion->kind.name->data 。 那么这是一个潜在的问题:
没有检查@R_689_4045@ion->kind.name->data是否为空。 唯一的检查是
if (@R_689_4045@ion->kind.name->data[@R_689_4045@ion->kind.name->length] != ' ') {
我们假设@R_689_4045@ion->kind.name->data是null,但是@R_689_4045@ion-> kind.name-> length是100,那么这个语句就相当于:
if (*(@R_689_4045@ion->kind.name->data + 100) != ' ') {
哪些不解除引用NULL,而是取消引用地址100.如果这没有崩溃,地址100碰巧包含0,那么这个测试将通过。
据我所知,取消引用空指针的效果是不确定的。
根据C标准6.5.3.2/4:
如果指针指定了一个无效值,则一元运算符的行为是不确定的。
所以可能会崩溃或可能不会。
您可能遇到堆栈损坏。 您所引用的代码行可能根本不会被执行。
我的理论是@R_689_4045@ion->kind.name->length是一个非常大的值,所以@R_689_4045@ion->kind.name->data[@R_689_4045@ion->kind.name->length]实际上是指有效的内存地址。
取消引用NULL指针的行为是不确定的标准。 不保证会崩溃,而且除非您真正尝试写入内存,否则往往不会。
作为参考,当我看到这一行时:
if (@R_689_4045@ion->kind.name->data[@R_689_4045@ion->kind.name->length] != ' ') {
我看到三个不同的指针解引用:
信息
您检查信息非空,但不是名称,而不是数据。 是什么让你确定他们是正确的?
我也在这里回应其他一些关于别的可能会损害你的堆的情绪。 如果你正在windows上运行,可以考虑使用gflags来做一些事情,比如页面分配,这可以用来检测你或者其他人是否正在写一个超过缓冲区的结尾,并且在你的堆上。
看到你在Mac上 – 忽略gflags的评论 – 它可能会帮助其他读这个的人。 如果你运行的是比OS X更早的东西,那么有很多方便的Macsbugs工具来强调这个堆(比如堆争夺命令'hs')。
我对strlcpy的char * cast感兴趣。
类型数据*的大小可能与系统上的char *大小不同? 如果字符指针较小,则可以获得可能为NULL的数据指针的子集。
例:
int a = 0xffff0000; short b = (short) a; //b Could be 0 if lower bits are used
编辑 :拼写错误已更正。
if (@R_689_4045@ion->kind.name->data[@R_689_4045@ion->kind.name->length] != ' ') {
说信息 – > kind.name->长度很大。 至少大于4096,在具有特定编译器的特定平台上(也就是说,大多数* gix编译器和gcc编译器)代码将导致内存读“kind.name-> data + @R_689_4045@ion-> kind.name – >长度。
在较低的级别,读取是“读取地址(0 + 8653)”(或任何长度的内存)。 在* nixes上通常会将地址空间中的第一页标记为“不可访问”,这意味着取消引用读取内存地址0到4096的NULL指针将导致硬件陷阱传播到应用程序并使其崩溃。
通过读取第一页,你可能会碰到有效的映射内存,例如一个共享库或其他发生在那里映射 – 而内存访问将不会失败。 没关系。 解引用NULL指针是未定义的行为,没有要求它失败。
缺少最后一个if语句后面的“{”,意味着上面跳过的“…不相关…”代码中的某些内容正在控制对整个代码段的访问。 在所有粘贴的代码中只有strlcpy被执行。 解决方案:不要使用if语句而不用大括号来阐明控制。
考虑这个…
if(false) { if(something == stuff) { doStuff(); .. snip .. if(monkey == blah) some->garbage= nothing; return -1; } } crash();
只有“crash()” 得到执行。
我会在valgrind下运行你的程序。 您已经知道NULL指针有问题,因此需要对该代码进行配置。
valgrind存在于这里的好处是它检查每一个指针引用,并检查这个内存位置是否曾经被声明过,并且会告诉你行号,结构和其他你关心内存的东西。
正如其他人所提到的,引用0的内存位置是一个“que血清,血清”的东西。
我的C狡猾的蜘蛛感觉告诉我,你应该打破那些结构走
if (@R_689_4045@ion->kind.name->data[@R_689_4045@ion->kind.name->length] != ' ') {
线像
if (@R_689_4045@ion == NULL) { return -1; } if (@R_689_4045@ion->kind == NULL) { return -1; }
等等。
哇,多数民众赞成在奇怪的。 有一件事对我来说看起来有些可疑,尽管它可能没有贡献:
如果信息和数据是好的指针(非null)会发生什么,但是@R_689_404[email protected]是空的。 你不要直接引用这个指针直到strlcpy行,所以如果它是null,它可能不会崩溃,直到那时。 当然,早于你做引用数据[1]把它设置为 0,这也应该会崩溃,但是由于侥幸,你的程序可能碰巧具有对0x01而不是0x00的写访问权限。
另外,我看到你使用@R_689_4045@ion-> name.length在一个地方,但信息 – > kind.name.length在另一个,不知道如果这是一个错字或如果多数民众喜欢。
尽管解除引用空指针会导致未定义的行为,但不一定会导致崩溃,但您应该检查@R_689_4045@ion->kind.name->data的值,而不是@R_689_4045@ion->kind.name->data[1]的内容 – @R_689_4045@ion->kind.name->data[1] 。
char * p = NULL;
p [i]就像
p += i;
这是一个有效的操作,即使在空指针上。 然后指向内存位置0x0000 [i] i
你应该总是检查信息 – > kind.name->数据是否为空,但在这种情况下
在
if (*result == NULL) freeParsedData(@R_689_4045@ion); return -1; }
你错过了{
它应该是
if (*result == NULL) { freeParsedData(@R_689_4045@ion); return -1; }
这是这种编码风格的一个很好的理由,而不是
if (*result == NULL) { freeParsedData(@R_689_4045@ion); return -1; }
在那里你可能不会发现丢失的大括号,因为你已经习惯了代码块的形状而没有将它与if子句分开。
* result = malloc(realLength); // ???
新分配的内存段的地址存储在变量“result”中包含的地址所引用的位置。
这是意图吗? 如果是这样,则可能需要修改。
根据我的理解,这个问题的特殊情况是无效的访问导致尝试读取或写入,使用空指针。 这里的问题的检测是非常依赖硬件。 在某些平台上,使用NULL指针访问内存进行读或写操作将导致异常。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。