我已经阅读并想知道 sqlite 的源代码
static int strlen30(const char *z){
const char *z2 = z;
while( *z2 ){ z2++; }
return 0x3fffffff & (int)(z2 - z);
}
为什么使用strlen30()
而不是strlen()
(在 string.h 中)?
与此更改一起出现的提交消息指出:
[793aaebd8024896c] 签入的一部分 [c872d55493] 永远不要使用 strlen()。使用我们自己的内部 sqlite3Strlen30() 保证永远不会溢出整数。额外的显式强制转换以避免令人讨厌的警告消息。(CVS 6007)(用户:drh 分支:主干)
(这是我从Why reimplement strlen as loop+subtraction?中的回答,但它已关闭)
我无法告诉您他们必须重新实现它的原因,以及为什么他们选择int
ifsize_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 Fundamental Types , 4. 中说:
声明为无符号的无符号整数应遵守算术模 2 n的定律,其中 n 是该特定大小的整数的值表示中的位数。41)
...
41):这意味着无符号算术不会溢出,因为不能由得到的无符号整数类型表示的结果以比得到的无符号整数类型可以表示的最大值大一的数字为模减少
该标准的那部分没有定义有符号整数的溢出行为。如果我们看5. Expressions , 5.:
如果在计算表达式期间,结果未在数学上定义或不在其类型的可表示值范围内,则行为未定义,除非此类表达式是常量表达式 (5.19),在这种情况下程序有病-形成。[注意:大多数现有的 C++ 实现忽略整数溢出。除以零的处理,使用零除数形成余数,所有浮点异常因机器而异,通常可以通过库函数进行调整。]
到目前为止溢出。
至于减去两个指向数组元素的指针,5.7 加法运算符,6.:
当两个指向同一个数组对象的元素的指针相减时,结果就是两个数组元素的下标之差。结果的类型是实现定义的有符号整数类型;此类型应与cstddef标头 (18.1) 中定义为 ptrdiff_t 的类型相同。[...]
看18.1:
内容与标准 C 库头文件stddef.h相同
因此,让我们看一下 C 标准(不过,我只有 C99 的副本),7.17 通用定义:
- 用于 size_t 和 ptrdiff_t 的类型不应具有大于signed long int 的整数转换等级,除非实现支持足够大的对象以使其成为必要。
没有进一步保证ptrdiff_t
。然后,附件 E(仍在 ISO/IEC 9899:TC2 中)给出了带符号的 long int 的最小幅度,但不是最大值:
#define LONG_MAX +2147483647
现在的最大值是什么int
,返回类型是sqlite - strlen30()
什么?让我们跳过 C++ 引用,它再次将我们转发到 C 标准,我们将在 C99 附件 E 中看到 的最小最大值int
:
#define INT_MAX +32767
ptrdiff_t
不大于signed long
,不小于 32 位。int
刚刚定义为至少 16 位长。int
您平台的结果。strlen30
确实按位或在指针减结果上应用: | 32 bit |
ptr_diff |10111101111110011110111110011111| // could be even larger
& |00111111111111111111111111111111| // == 3FFFFFFF<sub>16</sub>
----------------------------------
= |00111101111110011110111110011111| // truncated
这通过将指针减法结果截断为最大值 3FFFFFFF 16 = 1073741823 10来防止未定义行为。
我不确定他们为什么选择这个值,因为在大多数机器上,只有最重要的位告诉 signness。与标准相比,选择 minimum 可能是有意义的INT_MAX
,但是 1073741823 在不了解更多细节的情况下确实有点奇怪(尽管它当然完美地完成了他们函数上方的评论所说的:截断到 30 位并防止溢出)。
CVS 提交消息说:
永远不要使用 strlen()。使用我们自己的内部 sqlite3Strlen30() 保证永远不会溢出整数。额外的显式强制转换以避免令人讨厌的警告消息。(CVS 6007)
我找不到对此提交的任何进一步参考或解释他们如何在那个地方溢出。我认为这是某些静态代码分析工具报告的错误。