我不是 Pascal 新手,但直到现在我仍然不知道为什么Delphi和Free Pascal通常将参数和返回值声明为有符号整数,而我认为它们应该始终为正数。例如:
Pos()
返回整数类型。有可能是负数吗?SetLength()
将参数声明NewLength
为整数类型。字符串是否有负长度?System.THandle
宣布为 Longint。手柄有负数吗?
Delphi 和 Free Pascal 中有许多类似的决定。这背后有哪些考虑?
我不是 Pascal 新手,但直到现在我仍然不知道为什么Delphi和Free Pascal通常将参数和返回值声明为有符号整数,而我认为它们应该始终为正数。例如:
Pos()
返回整数类型。有可能是负数吗?SetLength()
将参数声明NewLength
为整数类型。字符串是否有负长度?System.THandle
宣布为 Longint。手柄有负数吗?Delphi 和 Free Pascal 中有许多类似的决定。这背后有哪些考虑?
在 Pascal 中,整数(有符号)是基本类型。所有其他整数类型都是整数的子范围。(这在 Borland 方言中并不完全正确,在 TP 中给出了 longint,在 Delphi 中给出了 int64,但足够接近)。
一个重要的原因是,如果计算的中间结果为负,并且您使用无符号整数进行计算,则会触发范围检查错误,并且由于大多数较旧的编程语言不假设 2-complement 整数,结果(范围检查关闭)甚至可能是腐败的。
Thandle 案例要简单得多。Delphi 在 D4 之前没有适当的 32 位无符号,但只有 31 位基数。(由于 32 位无符号整数不是整数的子范围,所以后来的无符号整数是 int64 的子集,这将问题转移到了 uint64,它仅在 D2010 左右添加)
因此,在标头中的许多地方,在 winapi 使用无符号类型的地方使用了有符号类型,可能是为了避免第 32 位在这些版本中意外损坏,并且自定义卡住了。
但是winapi案例与一般案例不同。
稍后添加一些 Pascal(和 Modula2/3)实现通过将整数设置为大于字大小的大小来规避此陷阱,并要求所有数字类型声明适当的子范围,如下面的程序所示。
第一个假设一切都是整数的子集,第二个允许编译器再次缩小几乎所有内容以适应寄存器,特别是如果 CPU 有一些大于字操作的操作。(比如 x86,其中 32 位 * 32 位 mul 给出 64 位结果,或者可以使用状态位检测字大小溢出(例如,在不执行完整的 2*字大小相加的情况下为相加生成范围异常)
var x : 0..20;
y : -10..10;
begin
// any expression of x and y has a range -10..20
Turbo Pascal 和 Delphi 在其 16 位和 32 位产品中模拟两倍字长的整数类型。最高无符号类型的处理充其量是hacky。
好吧,首先THandle
声明不正确。它在 Windows 标头中未签名,在 Delphi 中也应如此。事实上,我认为这在最近发布的 Delphi 中得到了纠正。
我想签名而不是未签名的偏好在很大程度上是历史性的,并不是特别重要。但是,我可以想到一个重要的例子。考虑 for 循环:
for i := 0 to Count-1 do
如果i
是无符号且Count
为 0,则此循环从 0 运行到$FFFFFFFF
这不是您想要的。使用有符号整数循环变量可以避免这个问题。
Pascal 是其语法的受害者。等效的 C 或 C++ 循环没有这样的麻烦
for (unsigned int i=0; i<Count; i++)
由于语法差异和使用比较运算符作为停止条件。
这也可能是Length()
字符串或动态数组返回有符号值的原因。所以为了一致性,SetLength()
应该接受有符号的值。并且鉴于 的返回值Pos()
用于索引字符串,它也应该被签名。
这是关于该主题的另一个 Stack Overflow 讨论:我应该使用无符号整数来计算成员吗?
当然,我在这里胡乱猜测。也许没有设计,只是出于习惯,使用有符号值的先例被设定并被奉为神圣。
使用有符号整数的原因有很多,甚至在您不打算返回负值时也可能适用。
想象一下,我编写了调用 Pos 的代码,并且我想对结果进行数学运算。你宁愿让一个否定的结果(Pos('x',s)-5)
引发范围检查异常、下溢并成为一个非常大的无符号数,大约 40 亿,或者如果Pos('x',s)
返回,则为负数1
?对于很少考虑这些情况的新用户来说,任何一个都是问题的根源,但长期以来的传统是,通过使用Integer
结果,您的工作是检查负数和零结果,而不是将它们用作字符串偏移量。对于初学者和高级程序员来说,使用整数有一个优势,并且不会让“负”值滚动并变成大的无符号值或引发范围异常。
其次,请记住,在开始编程时,通常会Integer
在引入无符号类型(如Cardinal
. 初学者经常使用诸如Pos
之类的函数,使用会产生最不友好的副作用集的类型是有意义的。范围大于您绝对需要的范围没有负面影响(Pos 可能需要的范围是 1 到 maximum-string-length-in-delphi)。在 32 位 Delphi 中使用Cardinal
Pos 类型的好处为零,选择它肯定有缺点。
Once you get to 64-bit delphi, however, you could theoretically have strings LARGER than an Integer can hold, and moving to Cardinal wouldn't fix all your potential problems. However, the chance of anyone having a 2+ GB string is probably nil, and Delphi 64-bit compiler doesn't allow a >2 GB
string, anyway. In my testing, I can achieve an almost 1 GB String in 64 bit Delphi. So the practical length limit for a Win64 string is about a billion (1073741814) characters, which is using nearly 2 GB of actual RAM. At that limit, I either get EIntOverflow
or EAccessViolation
, and it seems I am hitting Delphi run time library (RTL) bugs, not properly defined limits, so your mileage may vary.