【问题标题】:Why use "strlen30()" instead of "strlen()"?为什么使用“strlen30()”而不是“strlen()”?
【发布时间】:2011-10-14 02:27:51
【问题描述】:

我已经阅读并想知道 sqlite 的源代码

static int strlen30(const char *z){
  const char *z2 = z;
  while( *z2 ){ z2++; }
  return 0x3fffffff & (int)(z2 - z);
}

为什么使用strlen30() 而不是strlen()(在string.h 中)??

【问题讨论】:

  • 不幸的是,SQLite 消息来源只说了显而易见的事情 - “计算一个字符串长度,该长度限制为可以存储在 32 位有符号整数的低 30 位中。”。
  • 也许 sqlite 的其他部分无法处理大于 1073741823 字节的字符串——假设它们更小是解决方案(我不买这个)。

标签: c sqlite strlen


【解决方案1】:

(这是我来自Why reimplement strlen as loop+subtraction? 的回答,但已关闭)


我不能告诉你为什么他们必须重新实现它,以及为什么他们选择int 而不是size_t 作为返回类型。但是关于功能:

/*
 ** Compute a string length that is limited to what can be stored in
 ** lower 30 bits of a 32-bit signed integer.
 */
static int strlen30(const char *z){
    const char *z2 = z;
    while( *z2 ){ z2++; }
    return 0x3fffffff & (int)(z2 - z);
}



标准参考

标准在 (ISO/IEC 14882:2003(E))3.9.1 基本类型, 4.:

声明为无符号的无符号整数应遵守算术模 2n 的定律,其中 n 是该特定整数大小的值表示中的位数。 41)

...

41):这意味着无符号算术不会溢出,因为结果不能由生成的无符号整数表示 type 以比结果无符号整数可以表示的最大值大一的数字为模减少 输入

标准的那部分没有定义有符号整数的溢出行为。如果我们看5。表达式, 5.:

如果在计算表达式期间,结果未在数学上定义或不在其类型的可表示值范围内,则行为未定义,除非此类表达式是常量表达式 (5.19),在这种情况下,程序是非良构的。 [注意:大多数现有的 C++ 实现忽略整数 溢出。除以零的处理,使用零除数形成余数,以及所有浮点数 例外情况因机器而异,通常可以通过库函数进行调整。 ]

到目前为止溢出。

至于两个指向数组元素的指针相减,5.7 加法运算符,6.:

当两个指向同一个数组对象的元素的指针相减时,结果是两个数组元素的下标之差。结果的类型是实现定义的有符号整数类型;此类型应与 cstddef 标头 (18.1) 中定义为 ptrdiff_t 的类型相同。 [...]

18.1

内容同标准C库头文件stddef.h

那么让我们看看 C 标准(不过我只有 C99 的副本),7.17 通用定义

  1. 用于 size_t 和 ptrdiff_t 的类型不应具有整数转换等级 大于signed long int,除非实现支持对象 大到足以使这成为必要。

没有对ptrdiff_t 做进一步的保证。然后,附录 E(仍在 ISO/IEC 9899:TC2 中)给出了有符号长整数的最小幅度,但不是最大值:

#define LONG_MAX +2147483647

现在int 的最大值是多少,sqlite - strlen30() 的返回类型是多少?让我们跳过 C++ 引用,它再次将我们转发到 C 标准,我们将在 C99 附件 E 中看到 int 的最小最大值:

#define INT_MAX +32767



总结

  1. 通常ptrdiff_t不大于signed long,不小于32bits。
  2. int 被定义为至少 16 位长。
  3. 因此,减去两个指针可能会得到不适合您平台的int 的结果。
  4. 我们从上面记得,对于有符号类型,不适合的结果会产生未定义的行为。
  5. strlen30 确实按位或​​在指针减结果上应用:

          | 32 bit                         |
ptr_diff  |10111101111110011110111110011111| // could be even larger
&         |00111111111111111111111111111111| // == 3FFFFFFF<sub>16</sub>
          ----------------------------------
=         |00111101111110011110111110011111| // truncated

通过将指针减法结果截断为最大值 3FFFFFFF16 = 107374182310 来防止未定义行为。

我不确定他们为什么选择这个值,因为在大多数机器上,只有most significant bit tells the signedness。与标准相比,选择最小值 INT_MAX 可能是有意义的,但 1073741823 在不了解更多细节的情况下确实有点奇怪(尽管它当然完美地完成了他们函数上方的评论所说的:截断到 30 位并防止溢出)。

【讨论】:

  • 感谢您如此详细的回答!我很高兴你回答我的小问题。我认为“溢出”可能是编程语言的永恒问题......
  • 我猜他们选择了这个上限以允许对结果执行某种类型的整数数学运算而不会引起 UB 的愤怒,但我认为他们的逻辑有些错误,因为它会对于 64 位机器来说是合法的,其中单个对象被限制在小于 4 gigs 以将 size_t 定义为 uint32_tptrdiff_tint32_t,并且在减去指针末尾附近的指针时做任何它喜欢的事情3-gig 对象从一个到开始。
【解决方案2】:

CVS 提交消息说:

永远不要使用 strlen()。使用我们自己的内部 sqlite3Strlen30() 保证永远不会溢出整数。额外的显式强制转换以避免令人讨厌的警告消息。 (CVS 6007)

我找不到对此提交的任何进一步参考或解释他们如何在那个地方溢出。我认为是某个静态代码分析工具报错。

【讨论】:

  • 原因很简单——size_t 不适合 32 位系统上的 int。所以他们裁剪了最重要的位。我看不出这对溢出有什么帮助 - 无论您是否称其为裁剪,裁剪都是裁剪。
  • 实际上 20 亿个符号就足够了,不需要 TB。
  • @Rafał 谢谢你的回答!我了解到在发布问题之前我必须阅读 CVS 提交消息... thx!(^o^)/
【解决方案3】:

进行此更改的commit message 声明:

[793aaebd8024896c] 签入的一部分 [c872d55493] 永远不要使用 strlen()。使用我们自己的内部 sqlite3Strlen30() 保证永远不会溢出整数。额外的显式强制转换以避免令人讨厌的警告消息。 (CVS 6007)(用户:drh 分支:主干)

【讨论】:

  • 提交信息很糟糕。使用&amp; 进行裁剪如何帮助溢出我想知道的整数?
  • @jeff 谢谢你的回答!我想再次阅读源代码并记住您的答案。如果英文有错误,我很抱歉。
  • @hority 你的英语绝对零问题,所以无需道歉!
  • @sharptooth:因为只有无符号整数具有明确定义的溢出行为。对于有符号整数,它没有明确定义。此外,没有定义两个字符指针的差异是否具有与普通整数相同的字节大小。提交消息清晰简洁,但确实错过了指向详细功能请求或类似内容的指针。
  • @phresnel:您能否以详细答案的形式解释一下?这真的很有趣,但我不明白。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-03-04
  • 1970-01-01
  • 1970-01-01
  • 2013-12-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多