我目前正在编写一个需要频繁比较字符串长度的C程序,所以我编写了以下辅助函数:
int strlonger(char *s1, char *s2) {
return strlen(s1) - strlen(s2) > 0;
}
我注意到即使s1
的长度短于s2
,该函数也会返回true。有人可以解释这种奇怪的行为吗?
答案 0 :(得分:174)
您遇到的是在处理包含有符号和无符号数量的表达式时C中出现的一些特殊行为。
当执行一个操作,其中一个操作数被签名而另一个操作数是无符号时,C将隐式地将signed参数转换为unsigned并执行操作,假设数字是非负数。此约定通常会导致<
和>
等关系运算符的非直观行为。
关于你的辅助函数,请注意,由于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中使用无符号/有符号变量时要理解的重要概念是如果在单个表达式中混合使用无符号和有符号数量,则有符号值将隐式转换为无符号。 / p>
考虑以下表达式:
-1 < 0U
由于第二个操作数是无符号的,第一个操作数是隐式转换为无符号,因此表达式等同于比较,
4294967295U < 0U
当然是假的。这可能不是您期望的行为。
考虑以下代码,尝试对数组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 ,因此比较始终成立。因此,代码将尝试访问数组a
的无效元素。
可以通过将length
声明为int
,或将for
循环的测试更改为i < length
来修复代码。
我不想在此陈述任何有争议的内容,但这里有一些我在C中编写程序时经常遵守的规则。
不要 只是因为一个数字是非负的。很容易出错,这些错误有时会发生令人难以置信的微妙(如例#2所示)。
DO 在执行模块化算术时使用。
DO 在使用位表示集合时使用。这通常很方便,因为它允许您执行逻辑右移而无需签署延期。
当然,在某些情况下,您可能会决定违反这些“规则”。但通常情况下,遵循这些建议将使您的代码更易于使用,并且不易出错。
答案 1 :(得分:25)
strlen
会返回size_t
,typedef
类型为unsigned
。
所以,
(unsigned) 4 - (unsigned) 7 == (unsigned) - 3
所有unsigned
值都大于或等于0
。尝试将strlen
返回的变量转换为long int
。
答案 2 :(得分:1)
Alex Lockwood的answer是最好的解决方案(紧凑,清晰的语义等)。
有时,明确转换为size_t
:ptrdiff_t
的签名形式是有意义的,例如
return ptrdiff_t(strlen(s1)) - ptrdiff_t(strlen(s2)) > 0;
如果你这样做,你需要确定size_t
值适合ptrdiff_t
(尾数位少一个)。