6

出于教育目的(是 42 是),我正在重写 strncmp,一位同学刚刚来问我为什么要以这种方式转换我的返回值。我的建议是先进行类型转换,然后再取消引用。我的逻辑是我想将 char 字符串视为 unsigned char 字符串并取消引用它。

int strncmp(const char *s1, const char *s2, size_t n)
{
    if (n == 0)
        return (0);
    while (*s1 == *s2 && *s1 && n > 1)
    {
        n--;
        s1++;
        s2++;
    }
    return (*(unsigned char *)s1 - *(unsigned char *)s2);
}

他的目的是先取消引用,然后再进行类型转换,以确保它返回两个无符号字符之间的差异。像这样:

return ((unsigned char)*s1 - (unsigned char)*s2);

在讨论之后(我同意他的看法,我的演员阵容很奇怪),我们查找了一些生产就绪实现的源代码,令我们惊讶的是,Apple 似乎以与我相同的顺序进行转换/取消引用:

https://opensource.apple.com/source/Libc/Libc-167/gen.subproj/i386.subproj/strncmp.c.auto.html

因此问题是:在这种情况下有什么区别?为什么选择一个而不是另一个?

(我已经找到了以下内容;但它指定了不同大小的数据类型的强制转换/取消引用,而在字符/无符号字符的情况下,它应该没关系吧?

在C语言中,如果我强制转换和取消引用一个指针,我先做哪个有关系吗?)

4

2 回答 2

4

二进制补码系统(几乎是所有系统)上,它不会有任何区别。

第一个例子*(unsigned char *)x-- 将简单地将存储在该位置的数据的二进制值解释为unsigned char,因此如果存储在该位置的十进制值是-1,那么存储的十六进制值(假设CHAR_BIT=8)就是0xFF,那么它将是被解释为255它适合十六进制表示。

第二个示例(假设char在此编译器上签名)(unsigned char)*x-- 将首先获取存储在该位置的值,然后将其转换为无符号。因此,我们得到-1并在将其强制转换为 时unsigned char,标准规定要将负符号数转换为无符号值,您将比该类型可存储的最大值多加一个到负值,直到您有一个值它的范围。所以你得到-1 + 256 = 255

但是,如果您以某种方式处于一个补码系统中,则情况会有所不同。

同样,使用*(unsigned char *)x,我们将 的十六进制表示重新解释-1unsigned char,但这次十六进制表示是0xFE,它将被解释为254而不是255

回过头来(unsigned char)*x,它仍然只需要执行-1 + 256即可获得最终结果255

尽管如此,我不确定 a 的第 8 位是否char可以被 C 标准的字符编码使用。我知道它没有用于 ASCII 编码的字符串,这也是您最有可能使用的,因此在比较实际字符串时您可能不会遇到任何负值。


从有符号转换为无符号可以在 C11 标准的第 6.3.1.3 节中找到:

  1. 当整数类型的值转换为_Bool以外的其他整数类型时,如果该值可以用新类型表示,则保持不变。

  2. 否则,如果新类型是无符号的,则在新类型可以表示的最大值的基础上重复加减一,直到该值在新类型的范围内。

于 2019-11-18T23:49:18.197 回答
2

为什么选择一个而不是另一个?

下面以一种有趣的方式与非 2 的补码有所不同。

// #1
return (*(unsigned char *)s1 - *(unsigned char *)s2);
// *2
return ((unsigned char)*s1 - (unsigned char)*s2);

整数非 2 的补码编码(这些天几乎绝迹),有一个位模式,要么是要么-0陷阱表示

如果(unsigned char)*s1s1指向此类时使用代码,则-0将变为无符号 0 或可能发生陷阱。

随着-0成为一个,这将失去与一个空字符unsigned char的算术区别- 一个stings结尾的字符。 在 C 中,空字符是“所有位都设置为 0 的字节”。

为了防止这种(*(unsigned char *)s1情况,使用。

C 需要它:

7.24.1 字符串函数约定
对于本小节中的所有函数,每个字符都应被解释为好像它具有类型unsigned char(因此每个可能的对象表示都是有效的并且具有不同的值)。C17dr § 7.24.1.3

为此,OP 的代码有一个错误。在非 2 的恭维下,*s1不应将循环停止为-0.

// while (*s1 == *s2 && *s1 && n > 1)
while ((*(unsigned char *)s1 == (*(unsigned char *)s2 && (*(unsigned char *)s1 && n > 1)

对于迂腐的人, achar可能与 a 大小相同int。一些图形处理器已经做到了这一点。在这种情况下,为了防止溢出,可以使用以下方法。也适用于通常的 8 位char

// return (*(unsigned char *)s1 - *(unsigned char *)s2);
return (*(unsigned char *)s1 > *(unsigned char *)s2) - 
       (*(unsigned char *)s1 < *(unsigned char *)s2);

选择

int strncmp(const char *s1, const char *s2, size_t n) {
  const unsigned char *u1 = (const unsigned char *) s1;
  const unsigned char *u2 = (const unsigned char *) s2;
  if (n == 0) {
      return (0);
  }
  while (*u1 == *u2 && *u1 && n > 1) {
      n--;
      u1++;
      u2++;
  }
  return (*u1 > *u2) - (*u1 < *u2);
}
于 2019-11-19T00:45:59.897 回答