“strlen(s1) – strlen(s2)”永远不会小于零
我目前正在编写一个C程序,需要经常比较string长度,所以我写了下面的帮助函数:
int strlonger(char *s1, char *s2) { return strlen(s1) - strlen(s2) > 0; }
我注意到,即使当s1
长度比s2
短,函数也会返回true。 有人能解释这种奇怪的行为吗?
在处理同时包含有符号和无符号数量的expression式时,遇到的是C中出现的一些特殊行为。
当一个操作数被签名而另一个是无符号的操作被执行时,C将隐式地将签名的参数转换为无符号的,并且假定数字是非负的,执行操作。 这个约定常常导致关系运算符(如<
和>
直觉行为。
关于你的帮助函数,请注意,由于strlen
返回typessize_t
(一个无符号数量),所以差异和比较都是使用无符号算术计算的。 当s1
比s2
短时,差异strlen(s1) - strlen(s2)
应该是负数,而是变成一个大的无符号数,大于0
。 从而,
return strlen(s1) - strlen(s2) > 0;
即使s1
比s2
短,也返回1
。 要修复你的function,请使用下面的代码:
return strlen(s1) > strlen(s2);
欢迎来到C的精彩世界! 🙂
其他示例
由于这个问题最近受到了很多的关注,我想提供一些(简单的)例子,只是为了确保我得到这个想法。 我将假定我们正在使用一个使用二进制补码表示的32位机器。
在C中使用无符号/有符号variables时,理解的重要概念是, 如果在单个expression式中混合使用无符号和有符号数量 , 则将有符号值隐式转换为无符号数 。
示例#1:
考虑下面的expression式:
-1 < 0U
由于第二个操作数是无符号的,因此第一个操作数隐式转换为无符号,因此expression式等同于比较,
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 ,所以比较总是成立的。 因此,代码将尝试访问数组a
无效元素。
代码可以通过声明length
为int
来固定,也可以通过将for
循环的testing更改for
i < length
。
结论:您应该何时使用未签名?
我不想在这里陈述任何有争议的事情,但是这是我在C编写程序时经常遵守的一些规则。
-
不要只因为一个数字是非负的。 犯错很容易,这些错误有时候是非常微妙的(如例2所示)。
-
执行模块化运算时请使用。
-
使用位来表示集合时使用。 这通常是方便的,因为它允许您执行逻辑右移而不需要扩展符号。
当然,在某些情况下,你决定违反这些“规则”。 但是大多数情况下,遵循这些build议将使您的代码更易于使用,而且不易出错。
strlen
返回一个size_t
,它是一个unsigned
types的typedef
。
所以,
(unsigned) 4 - (unsigned) 7 == (unsigned) - 3
所有的unsigned
值都大于或等于0
。 尝试将由strlen
返回的variables转换为long int
。
Alex Lockwood的答案是最好的解决scheme(紧凑,清晰的语义等)。
有时,明确地转换为size_t
: ptrdiff_t
的签名forms是有意义的,例如
return ptrdiff_t(strlen(s1)) - ptrdiff_t(strlen(s2)) > 0;
如果你这样做的话,你需要确定size_t
值适合于ptrdiff_t
(它有一个更less的尾数位)。