我正在讨论这个练习(完整的源代码): http://c.learncodethehardway.org/book/ex29.html
代码的一部分如下所示:
int uppercase(const char *msg)
{
int i = 0;
// BUG: \0 termination problems
for(i = 0; msg[i] != '\0'; i++) {
printf("%c", toupper(msg[i]));
}
printf("\n");
return 0;
}
作者评论说有一个错误。后来他的指示说:
您是否关注我在libex29.c函数中的错误代码?看看,即使我使用for-loop他们仍然检查'\ 0'结尾?修复这个问题,这样函数总是需要一个字符串才能在函数内部使用。
但是,我没有看到这里的错误。如果存在空字符,则循环将终止。
其他人在这里看到了问题吗?
答案 0 :(得分:4)
唯一的“错误”是函数的行为和要求没有明确记录。
如果文档声明参数必须是指向字符串的有效指针(根据定义必须以null结尾),那么就我所知,函数是正确的(好吧,几乎 - 见下文),通过正确的论证完全是来电者的责任。 C标准库中充满了以这种方式运行的字符串函数。
如果文档声明函数本身负责检查有效参数,那么它需要准确地说明(1)要求是什么,以及(2)在给定无效参数的情况下函数应该如何表现。< / p>
它可以很容易地检查msg == NULL
- 但是当你发生这种情况时你必须指明它应该做什么。
它可以检查前N个字符中是否存在'\0'
终结符 - 但是你必须以某种方式指定N的值(显然作者需要增加长度参数)和< / em>你必须说明当这种情况发生时该功能应该如何表现。
无法检查无效的非null参数。例如,调用者可能会传递一个尚未初始化的指针,或者已传递给free()
的指针。该函数没有便携式(可能没有非便携式)方式来检查这种错误。
如果修改函数以获取长度参数(这是一个非常合理的更改,使函数更安全),那么它仍然无法检查所有可能的错误条件。调用者可以传递与实际数组长度不匹配的长度参数。
tolower仍然是来电者的责任。函数的规范是调用者和被调用者之间的契约。双方必须满足规范。如果没有任何这样的规范(除了函数声明,它给我们一些信息但不够),很难说该函数有一个“bug”。
一个小问题:我确实找到了可能函数中的真正错误。 toupper()
函数采用int
类型的参数,其值必须等于EOF
或0
到UCHAR_MAX
的范围。如果默认情况下对普通char
进行了签名,那么可能会有一个有效的char
值,该值为负且不等于EOF
。结果是未定义的行为。解决方法是将参数强制转换为unsigned char
:
printf("%c", toupper((unsigned char)msg[i]));
(EOF
特殊情况与此无关。)
现在这可能不是一个真正的错误。在没有任何规范的情况下,我们可能会假设字符串应该只包含具有非负值的字符。但应明确说明这种限制。
还存在潜在的可移植性问题:根据系统的不同,可能会有一个长度超过INT_MAX
字节的字符串。将i
设为size_t
而不是int
可以避免这种问题(不可否认)。同样,这可能被认为是一种隐含的限制 - 但这种限制应尽可能明确。
最后,函数被定义为返回int
结果,但它总是返回0
。将返回值用作错误指示符是有意义的。一个常见的惯例是返回0
表示成功,返回非零表示失败。如果此版本的函数旨在作为执行更多错误检查的改进版本的基础,则返回int
结果是合理的。但那时应该说明结果的含义。
答案 1 :(得分:2)
有两个问题:
&#34;字符串&#34;可能不会以空值终止。
输入指针msg
可能是NULL
。
你可以通过修复它(传递长度)或编写清晰的文档来解决第一个问题,就像c标准库函数一样。我更喜欢后者,因为在调用函数之前计算长度可能会导致两次遍历字符串,这是笨拙的。
您应该使用if块或类似构造来防范第二个问题。