77

我目前正在编写一个需要频繁比较字符串长度的 C 程序,因此我编写了以下辅助函数:

int strlonger(char *s1, char *s2) {
    return strlen(s1) - strlen(s2) > 0;
}

我注意到即使s1长度小于s2. 有人可以解释这种奇怪的行为吗?

4

3 回答 3

176

您遇到的是在处理包含有符号和无符号数量的表达式时在 C 中出现的一些特殊行为。

当执行一个操作数有符号而另一个无符号的操作时,C 将隐式地将有符号参数转换为无符号并假设数字为非负数来执行操作。这种约定通常会导致关系运算符(例如<和)出现不直观的行为>

关于您的辅助函数,请注意,由于strlen返回类型size_t(无符号数量),差异和比较都是使用无符号算术计算的。当s1短于s2时,差strlen(s1) - strlen(s2)应该是负数,而是变成一个大的无符号数,大于0。因此,

return strlen(s1) - strlen(s2) > 0;

1即使s1短于也返回s2。要修复您的功能,请改用以下代码:

return strlen(s1) > strlen(s2);

欢迎来到美妙的 C 世界!:)


其他示例

由于这个问题最近受到了很多关注,我想提供一些(简单的)示例,以确保我能理解这个想法。我将假设我们正在使用使用二进制补码表示的 32 位机器。

在 C 中使用无符号/有符号变量时要理解的重要概念是,如果在单个表达式中混合了无符号和有符号量,则有符号值将隐式转换为无符号

示例 #1:

考虑以下表达式:

-1 < 0U

由于第二个操作数是无符号的,所以第一个操作数隐式转换为无符号,因此表达式等价于比较,

4294967295U < 0U

这当然是错误的。这可能不是您所期望的行为。

示例 #2:

考虑以下代码,它试图对数组的元素求和a,其中元素的数量由参数给出length

int sum_array_elements(int a[], unsigned length) {
    int i;
    int result = 0;

    for (i = 0; i <= length-1; i++) 
        result += a[i];

    return result;
}

此函数旨在演示由于从有符号到无符号的隐式转换而导致的错误是多么容易出现。length将参数作为无符号传递似乎很自然;毕竟,谁会想要使用负长度?停止标准i <= length-1似乎也很直观。但是,当使用length等于的参数运行时0,这两者的组合会产生意想不到的结果。

由于参数length是无符号的,因此0-1使用无符号算法执行计算,这相当于模加法。结果是UMax。比较<=也使用无符号比较来执行,并且由于任何数字都小于或等于UMax,因此比较始终成立。因此,代码将尝试访问 array 的无效元素a

可以通过声明length为 anint或将for循环的测试更改为 来修复代码i < length

结论:什么时候应该使用无符号?

我不想在这里陈述任何太有争议的东西,但这里有一些我在用 C 编写程序时经常遵守的规则。

  • 不要仅仅因为一个数字是非负的就使用。很容易犯错误,而且这些错误有时非常微妙(如示例 #2 所示)。

  • 在执行模运算时使用

  • 使用位表示集合时使用。这通常很方便,因为它允许您在没有符号扩展的情况下执行逻辑右移。

当然,在某些情况下,您可能决定违反这些“规则”。但通常情况下,遵循这些建议将使您的代码更易于使用且不易出错。

于 2012-05-06T22:21:33.317 回答
25

strlen返回一个类型size_t的 a 。typedefunsigned

所以,

(unsigned) 4 - (unsigned) 7 == (unsigned) - 3

所有unsigned值都大于或等于0。尝试将返回的变量转换strlenlong int.

于 2012-05-06T22:21:44.353 回答
1

亚历克斯洛克伍德的答案是最好的解决方案(紧凑、清晰的语义等)。

size_t有时,显式转换为:的有符号形式确实有意义ptrdiff_t,例如

return ptrdiff_t(strlen(s1)) - ptrdiff_t(strlen(s2)) > 0;

如果这样做,您需要确定该size_t值适合 a ptrdiff_t(尾数位少一个)。

于 2012-06-02T00:26:19.593 回答