【问题标题】:C strcmp implementation using subtraction of characters使用字符减法的C strcmp实现
【发布时间】:2016-04-23 21:15:39
【问题描述】:

不久前我看到了strcmp 的这种实现,我有一个纯粹用于教育目的的问题。为什么需要将输入转换为 16 位整数,进行数学运算,然后再转换回 8 位? 8bit做减法有什么问题?

int8_t strcmp (const uint8_t* s1, const uint8_t* s2)
{
  while ( *s1 && (*s1 == *s2) )
  {
    s1++; 
    s2++;
  }

  return (int8_t)( (int16_t)*s1 - (int16_t)*s2 );
}

注意:代码假定为 16 位 int 类型。

编辑: 有人提到,默认情况下,C 会转换为int(假设为 32 位)。即使代码明确声明要转换为 16 位 int 也是这种情况吗?

【问题讨论】:

  • 不符合标准的,请勿使用。
  • 关于您的EDIT,是的,情况仍然如此。理论上,这些值被转换为int16_t,然后转换为int(如果intint16_t 宽,它可能不是,尽管它至少与int16_t 一样宽)。
  • 也许重点是保证不可移植性。 int8_tint16_t 在没有这些大小的本机硬件类型的系统上不存在。
  • 如果 *s1 是 255 而 *s2 是 1 怎么办?然后(int16_t)*s1 - (int16_t)*s2 产生 254,(int8_t)254 产生一个实现定义的值,可能是 -2。 -2 *s2 所以这个结果是错误的。
  • (int16_t)*s1 - (int16_t)*s2 中的强制转换是不必要的,因为所有小于 int 的类型必须在任何算术运算之前提升为 int

标签: c


【解决方案1】:

strcmp(a,b)函数预计会返回

  • <0 如果string a < string b
  • >0 如果string a > string b
  • 0 如果string a == string b

测试实际上是在同一位置的两个字符串中的第一个字符不同(0,字符串终止符,也可以)。

由于该函数需要两个uint8_t(无符号字符),开发人员可能担心对两个无符号字符进行比较会得到一个介于0255 之间的数字,因此永远不会出现负值回来。例如,118 - 236 将返回 -118,但在 8 位上它将返回 138

因此程序员决定强制转换为int_16,有符号整数(16 位)。

如果给出正确的负值/正值,这可能会奏效(假设函数返回 int_16 而不是 int_8)。

(*edit:来自@zwol下方的评论,整数提升是不可避免的,因此这个int16_t转换是不必要的)

但是,最终的int_8 演员阵容打破了逻辑。由于返回的值可能从-255255,因此在转换为int_8 后,其中一些值的符号会反转。

例如,255 - 0 给出正值 255(在 16 位上,所有低 8 位为 1,MSB 为 0)但在 int_8 世界中(8 位的有符号整数)这是负数, -1,因为我们只有最后的低 8 位设置为二进制 11111111,或十进制 -1


绝对不是一个好的编程示例。

苹果的working function更好
for ( ; *s1 == *s2; s1++, s2++)
    if (*s1 == '\0')
        return 0;
return ((*(unsigned char *)s1 < *(unsigned char *)s2) ? -1 : +1);

(Linux 是用汇编代码实现的……)

【讨论】:

  • 无限清晰!但是,出于教育目的的最后一个问题。当从 int_16 向下转换到 int_8 时,LSB 或 MSB 位是否保留,还是依赖于实现?
  • int16_t 转换为int8_t 的结果是实现定义的。大多数实现只是丢弃高位。
  • 由于整数提升,无论是否转换为int16_t,实际减法将在int 上完成。 int 允许与int16_t 为同一类型,但不允许int8_t 为同一类型,因此整数提升是不可避免的;没有办法(在标准范围内)强制 C 对[u]int8_t 数量进行实际运算。 (注意int8_tchar 之间的区别;在CHAR_BIT &gt;= 16intchar 可能是同一类型的实现上;但是,这样的实现根本不能提供int8_t!)
  • @zwol - 当CHAR_BIT &gt;= 16intchar 可以具有相同的底层表示,但它们仍然是不同的类型。否则重载将是相当脆弱的......
  • @zwol - 哎呀,我通常不阅读 C 问题,但通过后门得到了这个问题。
【解决方案2】:

实际上,差异必须至少在 16 位中完成¹,原因很明显,结果的范围是 -255 到 255 并且不适合 8 位。但是,sfstewman 正确地指出,由于隐式整数提升无论如何都会发生这种情况。

最终转换为 8 位是不正确的,因为它可能会溢出,因为范围仍然不适合 8 位。无论如何,strcmp 确实应该返回普通的int


¹ 9 就足够了,但位通常以 8 个为一组。

【讨论】:

  • 我认为程序员只关心结果的符号,只要保留它就可以了
  • 在减法之前,操作数将始终提升为int,它必须至少包含 16 位值,这使得操作数的 8 位性质无关紧要。请参阅整数提升规则 (C11 6.1.1.3p2)。
  • 学究模式:“差异必须至少在9位...”
【解决方案3】:

输入数据是无符号的 8 位,因此为避免截断和溢出/下溢的影响,应将其转换为至少 9 位有符号,因此使用 int16。

【讨论】:

  • 然后他们通过返回 int8_t! 来破坏结果!
【解决方案4】:
return (int8_t)( (int16_t)*s1 - (int16_t)*s2 );

这可能意味着以下两个选项之一:

  • 要么程序员对隐式类型提升在 C 中的工作方式感到困惑。无论转换为 int16_t,两个操作数都将隐式转换为 int。因此,如果int 是例如 32 位,则代码是无意义的。否则,如果int 与特定系统的int16_t 等效,则根本不会发生任何转换。

  • 或者程序员非常了解类型提升的工作原理,并且正在编写需要确认禁止隐式类型提升的标准的代码,例如 MISRA-C。在这种情况下,如果int 在给定系统上是 16 位,则代码非常有意义:它强制显式类型提升以避开来自编译器/静态分析器的警告。

我猜第二个选项最有可能,并且此代码是为小型微控制器系统编写的。

【讨论】:

  • 你的第二个猜测是正确的。它适用于 MISRA-C
  • 我不确定这样的标准如何“禁止”隐式类型提升,还是仅适用于某些表达式? (例如,原始代码中的表达式*s1 == *s2 也涉及隐式类型提升,至少在概念上是这样,尽管在这种情况下,这种提升对==!= 运算符没有影响。
  • @IanAbbott:MISRA 标准的目的是要求代码不仅要在 C 规则下工作,而且还要编写成在具有规则的语言中以相同的方式工作与“int”的大小无关。不幸的是,C 规则中的一些怪癖使得编写行为与 int-size 无关的代码变得困难。例如,uint16_t x=65535; x*=x; 将在所有定义了uint16_tint 为16 位或64 位的平台上将x 设置为1,但在int 为32 位的某些机器上,它可能会否定法律时间和因果关系。
  • @IanAbbott MISRA 关于何时显式转换以避免类型提升的规则相当复杂,并且取决于 MISRA-C 的版本。 == 的操作数的类型提升不应该是危险的,因为结果是 int 1 或 0,而不是提升的类型。
  • @rici 如果您想编写自己的兼容 MISRA-C 的标准库函数,则必须重新定义标准函数定义,因为 C 标准库本身不兼容 MISRA。投射到 int8_t 并返回它在小型微控制器系统中非常有意义。
【解决方案5】:

如果int16_t 由于溢出而不存在,某些值会导致两个数字之间的差异不同。在int8_t 中,您的范围是-128 到127,在uint8_t 中,您的范围是0 到255,而在int16_t 中,您的范围是-32,768 到32,767。

uint8_t 转换为int8_t 将导致超过 127 的值由于溢出而改变符号,因此这可以防止这种情况发生,但是输出应该是 int16_t,因为如果您有 255 - 0结果,这将是一个截断的返回。

【讨论】:

    猜你喜欢
    • 2016-04-24
    • 1970-01-01
    • 1970-01-01
    • 2016-07-21
    • 1970-01-01
    • 2013-11-29
    • 2012-12-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多