这是我不理解的代码,它只是反转一个字符串。
#include <stdio.h>
void strrev(char *p)
{
char *q = p;
while(q && *q) ++q;
for(--q; p < q; ++p, --q)
*p = *p ^ *q,
*q = *p ^ *q,
*p = *p ^ *q;
}
int main(int argc, char **argv)
{
do {
printf("%s ", argv[argc-1]); strrev(argv[argc-1]);
printf("%s\n", argv[argc-1]);
} while(--argc);
return 0;
}
我不理解的唯一一段代码就是这一段:while(q && *q) ++q;
,它用于查找eos
。
是不是与while(*q) ++q;
相同,因为q
永远不会是0?代码的作者如何确保q
或*q
为0?
答案 0 :(得分:29)
David Heffernan的评论是正确的。该代码是令人震惊的。
您要问的代码的一点是,如果它为null,则跳过取消引用q
。因此,代码的作者认为q
可以为null。在什么情况下q
可以为空?最明显的是:如果p
为空。
因此,让我们看看p
为空时代码的作用。
void strrev(char *p) // Assumption: p is null
{
char *q = p; // now q is null
while(q && *q) ++q; // The loop is skipped, so q and p are both still null.
for(--q;
所以我们做的第一件事是递减q,它是null。可能这将包围,我们将得到包含最大可能指针的结果q。
p < q;
由于null小于除null之外的所有内容,并且q不再为null,因此这是真的。我们进入循环......
++p, --q)
*p = *p ^ *q,
及时取消引用null。
*q = *p ^ *q,
*p = *p ^ *q;
}
顺便说一句,在Coverity,我们将此称为“前向空缺陷” - 即代码路径指示值可能为null的模式,然后在相同的代码路径上假定它不是空的。这是非常普遍的。
好的,如果将null作为参数,则此代码完全被破坏。还有其他方法可以打破吗?如果我们给它一个空字符串会发生什么?
void strrev(char *p) // Assumption: *p is 0
{
char *q = p; // *q is 0
while(q && *q) ++q; // The second condition is not met so the body is skipped.
for(--q; // q now points *before valid memory*.
p < q // And we compare an invalid pointer to a valid one.
我们在C中有一个保证,即当你从指向有效内存的指针中减去一个,然后将该指针与另一个指针进行比较时,该比较是否合理?因为这让我觉得非常危险。我不太清楚C标准是否足以说明这是否是未定义的行为。
此外,这段代码使用了可怕的“用xor交换两个字符”技巧。 为什么人们会这样做?它会生成更大,更慢的机器代码,更难以阅读,理解和维护。如果你想交换两件事,交换它们。
它还使用逗号运算符将多个语句放在一个语句中,以避免for
主体周围的括号恐怖。这种古怪的目的是什么?代码的目的不是要显示你知道多少运算符,首先是与代码的读者进行通信。
该函数还修改了其形式参数,这使得调试变得困难。
答案 1 :(得分:5)
代码
while(q && *q)
是
的简写while(q != NULL && *q != '\0')
因此,您正在测试q
(开头等于p
)是否为NULL。这意味着使用NULL参数调用的函数不会在此while循环中崩溃。 (但它仍然会在第二个循环中崩溃)。
答案 2 :(得分:1)
它与
while(*q) ++q;
相同,因为q
永远不会是0
吗?
while(q && *q)
用于确保q
不是NULL
且*q
不是NUL
。如果while(*q)
未指向q
,则使用NULL
也是合法的。
void string_rev(char *p)
{
char *q = p;
if(q == NULL)
return;
while(*q) ++q;
for(--q; p < q; ++p, --q)
*p = *p ^ *q,
*q = *p ^ *q,
*p = *p ^ *q;
}
如果代码的作者如何确保
q
或*q
将成为0
?
while(q && *q)
指向q
并且循环在进入循环体之前终止,则 NULL
,p
可以是NULL
指针。否则,通过该操作,它不能是NULL
指针。现在循环终止取决于*q
。在q
到达字符串末尾后,*q
变为0
并且循环终止。