受this question的启发,关于SQLite3中的以下代码:
static int strlen30(const char *z){
const char *z2 = z;
while( *z2 ){ z2++; }
return 0x3fffffff & (int)(z2 - z);
}
伴随着commit message说这个函数有助于int
溢出。
我对这部分特别感兴趣:
const char *z2 = z;
while( *z2 ){ z2++; }
对我来说,这个循环前进z2
,直到z2
指向null终止符。然后z2-z
产生字符串长度。
为什么不对此部分使用strlen()
并重写如下:
return 0x3fffffff & (int)(strlen(z));
为什么使用循环+减法代替strlen()
?什么可以循环+减法做strlen()
不能做什么?
答案 0 :(得分:7)
我无法告诉你为什么他们必须重新实施它,以及为什么他们选择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。:
无符号整数,声明为无符号整数,应遵守算术模2 n 的定律,其中n是该特定整数大小的值表示中的位数。 41)
...
41):这意味着无符号算术不会溢出,因为无法用结果无符号整数表示的结果 type是以模数减少的模数,该数字大于可由结果无符号整数表示的最大值 型
标准的那一部分没有定义有符号整数的溢出行为。如果我们看看 5。表达式,5。:
如果在评估表达式期间,结果未在数学上定义或未在其类型的可表示值范围内,则行为未定义,除非此类表达式是常量表达式 (5.19),在这种情况下,该程序是不正确的。 [注意:大多数现有的C ++实现忽略整数 溢出。除零处理,使用零除数和所有浮点形成余数 机器之间的例外情况各不相同,通常可以通过库函数进行调整。 ]
到目前为止溢出。
至于减去两个指向数组元素的指针, 5.7 Additive operators ,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中)给出了有符号long int的最小幅度,但不是最大值:
#define LONG_MAX +2147483647
现在int
的最大值是sqlite - strlen30()
的返回类型?让我们跳过C ++引用再次将我们转发到C标准,我们将在附件E的C99中看到int
的最小最大值:
#define INT_MAX +32767
ptrdiff_t
不大于signed long
,不小于32位。 int
被定义为至少16位长。int
的结果。strlen30
确实应用了bitwise或者指针 - 减法 - 结果: | 32 bit |
ptr_diff |10111101111110011110111110011111| // could be even larger
& |00111111111111111111111111111111| // == 3FFFFFFF<sub>16</sub>
----------------------------------
= |00111101111110011110111110011111| // truncated
通过将指针减法结果截断为最大值3FFFFFFF 16 = 1073741823 10 来防止不良行为。
我不确定他们为什么选择这个值,因为在大多数机器上只有most significant bit tells the signedness。选择最小INT_MAX
可能比标准更有意义,但1073741823确实有点奇怪而不知道更多细节(尽管它当然完全符合其功能上面的注释:截断到30位并防止溢出)
并像这样重写:
return 0x3fffffff & (int)(strlen(z));
我的猜测是他们想要避免潜在的间接性。另一个优点可能是对标准库的依赖性较少,如果您编写非托管应用程序,这可能很有用。
顺便说一句,如上所述,(int)(strlen(z))
如果ptrdiff_t的最大值> 1,则可能会产生未定义的行为。 INT_MAX
,所以(int)(0x3fffffff & strlen(z))
会更好。
答案 1 :(得分:1)
为什么重新实现strlen作为循环+减法?
我怀疑真正的答案是程序员感觉如此,但另一个潜在的理由/合理化是循环是内联的(与strlen30
本身是否相关),而在许多系统上strlen
是一个外联函数调用(例如Linux / GCC)。如果绝大多数字符串是空的或短的(尽管对长字符串采用“特殊”处理),那么对于常见情况可能会产生轻微的性能提升。仅凭这种可能性就足以让代码开心的程序员获得密钥。对于更长的字符串,我希望库strlen
通常是最优的(允许它缺乏应用程序特定字符串长度的知识)。
有些系统甚至可能无法从这种内联中受益,因为strlen
提供了自己的内联,或内联/外联混合,快速内联检查空,一个字符,可能是两个字符串然后一个电话。