如何在C中取消对空指针的引用而不使程序崩溃?
我需要一个真正的C专家的帮助来分析代码中的崩溃。不是为了修复撞车事故;我可以很容易地修复它,但在这样做之前,我想了解这个崩溃是如何可能的,因为对我来说它似乎完全不可能 此崩溃只发生在客户机器上,我无法在本地复制它(因此我无法使用调试器单步执行代码),因为我无法获取此用户数据库的副本。我的公司也不允许我只更改代码中的几行,并为该客户定制构建(因此我不能添加一些printf行并让他再次运行代码),当然,该客户有一个没有调试符号的构建。换句话说,我的除虫能力非常有限。尽管如此,我还是可以确定崩溃并获得一些调试信息。然而,当我查看这些信息,然后查看代码时,我无法理解程序流是如何到达所讨论的行的。代码应该在到达那一行之前很久就崩溃了。我在这里完全迷路了 让我们从相关代码开始。代码非常少:如何在C中取消对空指针的引用而不使程序崩溃?,c,unix,crash,powerpc,C,Unix,Crash,Powerpc,我需要一个真正的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崩溃的原因就相当明显了。它试图从空指针读取一个字符,这当然会崩溃。由于最后一个参数的值为1,因此原始长度必须为0。我的代码显然有一个bug,它无法检查名称数据是否为空。我可以解决这个问题,没问题
我的问题是:这段代码怎么可能首先到达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语句中崩溃,这是意料之中的
那么,如果name->data真的为NULL,有人能想到为什么第一个代码可以在不崩溃的情况下通过if语句吗?这对我来说是完全神秘的。这似乎不是决定性的
重要的额外信息:两条注释之间的代码非常完整,没有遗漏任何内容。此外,应用程序是单线程的,因此没有其他线程会意外地改变后台内存。发生这种情况的平台是一个PPC CPU(G4,以防它可以扮演任何角色)。如果有人想知道“kind”,这是因为“information”包含一个名为“kind”的“union”,而name又是一个结构(kind是一个union,每个可能的union值都是一个不同类型的结构);但这一切在这里并不重要 我很感激这里的任何想法。如果这不仅仅是一个理论,而是如果有一种方法可以证明这个理论对客户来说是正确的,我会更加感激 解决方案 我已经接受了正确的答案,但为了防止有人在谷歌上发现这个问题,下面是实际发生的情况: 指针指向已经释放的内存。释放内存不会使其全部为零,也不会导致进程立即将其返回给系统。因此,即使内存被错误地释放,它仍然包含正确的值。执行“如果检查”时,相关指针不为空 检查之后,我分配一些新内存,调用malloc。不确定malloc在这里到底做了什么,但每次调用malloc或free都会对进程虚拟地址空间的所有动态内存产生深远的影响。在malloc调用之后,指针实际上是空的。不知何故,malloc(或malloc使用的某些系统调用)将指针本身所在的已释放内存(不是指针指向的数据,指针本身在动态内存中)归零。将内存归零后,指针现在的值为0x0,在我的系统上等于NULL,当调用strlcpy时,它当然会崩溃
因此,导致这种奇怪行为的真正bug位于我的代码中完全不同的位置。永远不要忘记:释放的内存保持它的值,但它超出了您的控制多久。要检查应用程序是否存在访问已释放内存的内存错误,只需确保释放的内存在释放前始终归零即可。在OSX中,您可以通过在运行时设置环境变量来实现这一点(无需重新编译任何内容)。当然,这会大大降低程序的速度,但您会更早地发现这些错误。您可能遇到堆栈损坏。您所引用的代码行可能根本没有被执行。据我所知,取消引用空指针的效果未被标准定义 根据C标准6.5.3.2/4: 如果为指针指定了无效值,则一元*运算符的行为将被取消定义
因此,可能会发生崩溃,也可能不会。标准未定义取消引用空指针的行为。它不能保证崩溃,而且通常不会崩溃,除非您确实尝试写入内存 哇,真奇怪。有一件事在我看来确实有点可疑,尽管它可能不会起作用: 如果信息和数据是好的指针(非null),但information.kind.name为null,会发生什么情况。直到strlcpy行,您才解除对该指针的引用,因此,如果该指针为null,则在此之前它可能不会崩溃。当然,在您取消引用数据[1]以将其设置为\0之前,它也会崩溃,但由于某种侥幸,您的程序可能恰好具有对0x01的写访问权限,而不是0x00
另外,我看到您在一个位置使用信息->name.length,而在另一个位置使用信息->kind.name.length,不确定这是打字错误还是需要的。首先,取消引用空指针是未定义的行为。它可以崩溃,而不是崩溃,或设置你的墙
if (information->kind.name->data[information->kind.name->length] != '\0') {
if (*(information->kind.name->data + 100) != '\0') {
if (information->kind.name->data[information->kind.name->length] != '\0') {
char * p = NULL;
p += i;
int a = 0xffff0000;
short b = (short) a; //b could be 0 if lower bits are used
if (information->kind.name->data[information->kind.name->length] != '\0') {
if (*result == NULL)
freeParsedData(information);
return -1;
}
if (*result == NULL)
{
freeParsedData(information);
return -1;
}
if (*result == NULL) {
freeParsedData(information);
return -1;
}
if(false)
{
if(something == stuff)
{
doStuff();
.. snip ..
if(monkey == blah)
some->garbage= nothing;
return -1;
}
}
crash();
if (information->kind.name->data[information->kind.name->length] != '\0') {
if (information == NULL) {
return -1;
}
if (information->kind == NULL) {
return -1;
}