“ strlen(s1)-strlen(s2)”从不小于零


77

我目前正在编写一个C程序,该程序需要经常比较字符串长度,因此我编写了以下帮助程序函数:

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

我注意到即使s1长度比短,该函数也会返回true s2。有人可以解释这种奇怪的行为吗?


27
那是一种Fortran-66风格的写作方式return strlen(s1) > strlen(s2);
乔纳森·勒夫勒

11
@TimThomas:您为什么要在这个问题上提供赏金?您说它没有得到足够的重视,但是您似乎对Alex Lockwood的回答很满意。不知道要获得赏金还需要什么!:)
eggyal

11
这是一次意外,我不知道赏金是什么。-_-有点尴尬...
Adrian Monk 2012年

5
我想这对Alex Lockwood很有好处,因为他的出色答案将引起更多关注……所以所有人都投票赞成Alex Lockwood的答案!!:D
阿德里安·和尚

5
我认为这是更好地为@TimThomas保持赏金开到最后允许的日期,使他的问题也得到一些attention..He在不知不觉中失去了他的名誉100点,让他得到一些回来..
Krishnabhadra

Answers:


175

您遇到的是在处理包含有符号和无符号数量的表达式时,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中使用无符号/有符号变量时要理解的重要概念是,如果在单个表达式中混合有无符号和有符号数量则将符号值隐式转换为unsigned

范例1:

考虑以下表达式:

-1 < 0U

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

4294967295U < 0U

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

范例2:

考虑下面的代码,尝试对数组的元素求和a,其中元素的数量由parameter给出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

结论:什么时候应该使用Unsigned?

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

  • 请勿仅因为数字为非负数而使用。容易犯错误,并且这些错误有时非常难以捉摸(如示例2所示)。

  • DO执行模运算时使用。

  • 当使用位表示集时,请务必使用。这通常很方便,因为它允许您执行逻辑右移而无需符号扩展。

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


46
另一个很好的例子,如何减少编写使程序正确。
Kerrek SB 2012年

3
@TimThomas它必须强制转换一种方式,而将无符号转换为有符号将丢失信息,即值空间的一半。
罗恩侯爵

7
严格地,在两个size_t值之间执行减法运算,这两个值被保证为无符号的,并且无符号的算术包装以适当的二的幂为模。可以进行有符号/无符号转换的唯一位置是该result > 0部分,其中resultsize_t两个大小相减得到的值。
乔纳森·勒夫勒

9
它不,它转换。术语“强制转换”仅是指由带括号的类型名称组成的显式强制转换运算符。强制转换运算符明确指定转换;转换可以是显式的也可以是隐式的。
基思·汤普森

2
我发现负整数在我的代码中非常罕见,因此我采用相反的方法并使用它,unsigned int除非有某些具体原因不这样做。这样做的好处是所有操作都定义明确(甚至是“环绕”),尽管公认的是,在处理某些不平等时可能需要格外小心。
约书亚·格林

25

strlen返回size_t其为typedef用于unsigned类型。

所以,

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

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


ptrdiff_t是正确的便携式转换。在64位系统上,long int通常是32位有符号整数(在64位系统上,指针是64位的)。实际上,用于x86和x86_64的Visual C ++和gcc都使用32位长。
Fooz先生2012年

3
我以为ptrdiff_t是指针的减法,而不是size_t值的减法……
Lister先生,2012年

4
没有“size_t值减法”的POSIX类型。C简单地将其定义为size_t因为它是整数类型并且类型匹配。您可能会说那是off_t,但实际上是文件偏移量。因此,您要做的最好的事情是,由于size_t需要保留平台可以处理的任何索引,因此它也可以表示任何指针值,因为它可以用于从中索引字节0。因此,ptrdiff_t需要的位数与相同size_t,因此仅是的signed版本size_t
Mike DeSimone 2012年

1

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

有时,它是有意义的显式转换为一个签名的形式size_tptrdiff_t例如

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.