"strlen(s1) - strlen(s2)" is never less than zero

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

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

我已经注意到,即使 s1的长度比 s2短,函数也返回 true。有人能解释一下这种奇怪的行为吗?

4095 次浏览

当处理同时包含有符号量和无符号量的表达式时,C 中出现了一些特殊的行为。

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

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

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

返回 1,即使 s1s2短。要修复函数,使用以下代码:

return strlen(s1) > strlen(s2);

Welcome to the wonderful world of C! :)


Additional Examples

Since this question has recently received a lot of attention, I'd like to provide a few (simple) examples, just to ensure that I am getting the idea across. I will assume that we are working with a 32-bit machine using two's complement representation.

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

例一:

考虑下面的表达:

-1 < 0U

因为第二个操作数是无符号的,所以第一个操作数是 隐性投射到无符号的,因此表达式等效于比较,

4294967295U < 0U

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

Example #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;
}

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

Since parameter length is unsigned, the computation 0-1 is performed using unsigned arithmetic, which is equivalent to modular addition. The result is then UMax. The <= comparison is also performed using an unsigned comparison, and since any number is less than or equal to UMax, the comparison always holds. Thus, the code will attempt to access invalid elements of array a.

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

Conclusion: When Should You Use Unsigned?

I don't want to state anything too controversial here, but here are some of the rules I often adhere to when I write programs in C.

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

  • 施行同余关系时使用。

  • 当使用位来表示集合时使用 DO 。这通常很方便,因为它允许您执行逻辑右移而不需要符号扩展。

当然,也有可能在某些情况下你决定违反这些“规则”。但是大多数情况下,遵循这些建议将使您的代码更容易使用,并且更少出错。

strlen返回一个 size_t,它是 unsigned类型的 typedef

那么,

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

All unsigned values are greater than or equal to 0. Try converting the variables returned by strlen to long int.

Alex Lockwood 的 回答是最好的解决方案(紧凑、清晰的语义等)。

有时显式地转换为 size_t: ptrdiff_t的有符号形式是有意义的,例如。

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

如果您这样做,您将希望确保 size_t值符合 ptrdiff_t(它少了一个尾数位)。