我已经阅读并思考了SQLite的源代码。
static int strlen30(const char *z){
const char *z2 = z;
while( *z2 ){ z2++; }
return 0x3fffffff & (int)(z2 - z);
}
为什么要使用
strlen30()
而不是 strlen()
(在 string.h 中)?我已经阅读并思考了SQLite的源代码。
static int strlen30(const char *z){
const char *z2 = z;
while( *z2 ){ z2++; }
return 0x3fffffff & (int)(z2 - z);
}
strlen30()
而不是 strlen()
(在 string.h 中)?这个变更所附带的提交信息如下:
[793aaebd8024896c] check-in [c872d55493] 的一部分,从不使用 strlen()。使用我们自己内部的 sqlite3Strlen30(),它保证永远不会溢出整数。额外显式转换以避免烦人的警告消息。(CVS 6007) (用户:drh 分支:trunk)
(这是我在为什么要重新实现strlen作为循环减法的答案,但它已经关闭了)
我不能告诉你他们为什么不得不重新实现它,以及为什么他们选择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):这意味着无符号算术不会溢出,因为不能由结果无符号整数类型表示的结果将对可以由结果无符号整数类型表示的最大值加1取模。
标准没有为有符号整数定义溢出行为。如果我们看一下5.表达式,5.:
如果在表达式求值期间,结果在其类型的可表示值范围之外或未被数学定义,则其行为未定义,除非这样的表达式是常量表达式(5.19),在这种情况下,程序是不合法的。[注:大多数现有的C++实现都忽略整数溢出。处理除零、使用零除数形成余数以及所有浮点异常的方式因机器而异,并且通常可以通过库函数进行调整。]
至此,溢出问题解决了。
至于两个指向数组元素的指针之间的减法,5.7 加法运算符,6.:
当从同一数组对象的元素指针中减去两个指针时,结果是两个数组元素下标的差。结果的类型是一个实现定义的有符号整数类型;这个类型应该是在头文件(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中)给出了signed long int的最小值,但没有最大值:
#define LONG_MAX +2147483647
现在问题来了,sqlite-strlen30()
的返回类型int
的最大值是多少?让我们跳过转到C标准的C++引用,看一下在 C99 中,附录 E 中 int
的最小最大值:
#define INT_MAX +32767
ptrdiff_t
通常不大于signed long
,而signed long
不小于32位。int
被定义为至少16位长。int
的结果。strlen30
对指针相减的结果应用了按位或操作: | 32 bit |
ptr_diff |10111101111110011110111110011111| // could be even larger
& |00111111111111111111111111111111| // == 3FFFFFFF<sub>16</sub>
----------------------------------
= |00111101111110011110111110011111| // truncated
这样做是为了避免指针相减的结果被截断,最大值为3FFFFFFF16 = 107374182310,这样可以防止未定义的行为。
我不确定他们为什么选择了这个确切的值,因为在大多数机器上,只有最高有效位说明符号。也许选择最小的INT_MAX
与标准相符合更有意义,但是没有更多的细节,1073741823看起来确实有点奇怪(尽管它完全按照函数上方的注释所说的那样:截取30位并防止溢出)。
size_t
定义为 uint32_t
和 ptrdiff_t
定义为 int32_t
是合法的,并且在从一个 3G 对象的末尾减去指针到开头时可以做任何事情。 - supercatCVS提交信息如下:
永远不要使用strlen()。使用我们自己的内部sqlite3Strlen30(),它保证永远不会溢出整数。额外的显式转换以避免烦人的警告消息。(CVS 6007)
我找不到关于这个提交的进一步参考或解释,也不知道他们在那个地方如何溢出。我相信这是某些静态代码分析工具报告的错误。
size_t
无法适应int
。因此,它们会裁剪最高有效位。我看不出这如何有助于溢出 - 无论你是否称其为裁剪,它都是裁剪。 - sharptooth