作者
Richard A. O'Keefe <ok(at)cs(dot)otago(dot)ac(dot)nz>
状态
草案
类型
标准跟踪
创建
2008年8月27日
Erlang版本
OTP_R12B-4

EEP 22: 二进制的范围检查 #

摘要 #

模块可以请求对位域进行范围检查。

规范 #

添加一个新的指令。

-bit_range_check(Wanted).

其中 Wanted 为 ‘false’ 或 ‘true’。

回想一下,位串(或二进制)的一个段具有以下形式

Value [':' Size] ['/' Type_Specifier_List]

其中 Type_Specifier_List 包括诸如 ‘integer’、‘signed’ 和 ‘unsigned’ 之类的内容。目前,文档声明

"Signedness ... Only matters for matching and when the type
 is integer.  The default is unsigned."

SizeUnit 结合起来得到 Size_In_Bits。在线 Erlang 手册第 6.16 节并未说明在构建位串时,整数的最低 Size_In_Bits 位会被使用,其余位会被静默忽略,但事实确实如此。

指令 -bit_range_check(false) 明确了程序员希望发生这种类似 C 语言的截断的意图。

指令 -bit_range_check(true) 表示,如果 Value 不在范围 0 <= Value < 2**Size 内,则在

Value:Size/unsigned-integer-unit:1

或与之等效的其他构造中,这是一个被检查的运行时错误,并且如果 Value 不在范围 -(2**(Size-1)) <= Value < 2**(Size-1) 内,则在

Value:Size/signed-integer-unit:1

或与之等效的其他构造中,这是一个被检查的运行时错误。

引发的错误类似于 (1//0):Size/Type_Specifier_List 引发的错误,只是使用了 ‘badrange’ 而不是 ‘badarith’。

在没有 -bit_range_check 指令的情况下,整数位语法段的行为是实现定义的,并且可能会发生变化。

BEAM 系统会扩展新的指令,或与现有整数段指令相似的指令,但会检查范围。编译器会扩展,为 -bit_range_check(true) 指令范围内的 <<...>> 表达式生成这些指令。

-bit_range_check 指令不得出现在位语法模式或表达式之后,或另一个 -bit_range_check 指令之后。

动机 #

对于 Erlang 程序员来说,这种截断的发生一直是一个不愉快的意外。静默地破坏信息在 Erlang 中是陌生的:整数算术是无界的,而不是像某些(但不是全部)C 系统那样被包装的;element/2 不会使用模元组大小的索引,如果索引超出范围则会引发异常,等等。

在任何需要截断的情况下,Erlang 程序员已经可以编写

(Value rem 256):unsigned-integer

并且 Erlang 编译器可以注意到这一点并优化掉 ‘rem’ 操作,因此截断不仅在 Erlang 中是不寻常的,而且在这种特定情况下也是意料之外的。

它不仅出乎意料,而且还失去了发现错误的机会,因此它似乎是不受欢迎的。

Edwin Fine 问道:“添加可选的运行时检查来检测这种情况,而不会对 Erlang 运行时执行的正确性造成严重不利影响,这有多难?”

Björn Gustavsson 回答说:“最好在编译器中添加可选支持,以启用检查(针对整个模块,或针对二进制的各个段)。如果有人编写 EEP,我们会考虑实现它。”

这就是 EEP。

理由 #

Erlang/OTP 团队将旧行为视为一项功能,并希望保留它。特别是,他们希望那些期望旧行为的模块(目前)无需修改即可继续工作。

一种替代方法是添加新的语法,例如使用新的 ‘checked’ 说明符,以便

Value/checked-unsigned-integer

需要一个在 0..255 范围内的值。但是许多 Erlang 程序员会希望将其用作正常情况,并且不喜欢安全版本比不安全版本要多付出这么多努力来编写。

看来“想要截断/不想要截断”不是这个表达式或那个表达式的问题,而是这个程序员或那个程序员的问题,并且我们可以预期每个模块都由期望一种行为或期望另一种行为的人编写。

-bit_range_check(true).

指令添加到模块比什么都不做要多做一些工作,但是想要此行为的程序员应该能够设置他们的编辑环境,以便在他们创建新 Erlang 模块时在其模板中包含此行。

有几个问题

  • 这应该适用于位串以及整数吗?
  • 指令的名称应该是什么?
  • 指令的参数应该是什么?
  • 是否允许在模块中多次出现该指令?

位串:假设 X = <<5:3/unsigned-integer-unit:1>>。目前,<<X:2/bits>> 会静默地截断 X。这会从 X 的右侧丢弃位,得到 <<2:2>>。如果它的工作方式与整数相同,你可能会期望 <<1:2>>。这肯定非常奇怪。由于我们对整数的左侧进行截断和左侧填充,因此我们自然期望对位串的右侧进行填充,以配合对右侧的截断。但是 <<X:4/bits>> 不是 <<10:4>>,而是一个运行时异常。确实非常奇怪。程序员肯定希望有一种简单的方法来指示他们是否想要在左侧或右侧进行截断,以及在左侧或右侧进行填充。也许一个新的内置函数

set_bit_size(Bit_String, Desired_Width,
             Truncation, Padding, Fill)

Bit_String : a bit string
Desired_Width : a non-negative integer, the width wanted
Truncation: 'left' | 'right' | 'error';
    if bit_size(Bit_String) > Desired_Width
        truncate on the left/truncate on the right/
        report an error
Padding: 'left' | 'right' | 'error';
    if bit_size(Bit_String) < Desired_Width
        pad on the left/pad on the right/report an error
Fill: 0 | 1 | 'copy';
    pad with 0/pad with 1/pad with a copy of the
    last bit at the end where padding is done.

但是,这个想法只是部分成型,不属于当前提案的一部分。就目前情况而言,使用位语法并依赖隐式截断是从位串中提取前导位的最简单方法。

只要指令的名称能够揭示意图,它是什么并不重要。我建议使用 bit_range_check,因为它与检查、位语法中的范围有关,但是由于在本草案中它不适用于位串段,因此也许 bit_integer_range_check 会更好。

参数 false 和 true 似乎足够清楚。替代方案可能是类似

-bit_integer_range(check).
-bit_integer_range(no_check).

那也很好。

经典的 Pascal 编译器允许你执行类似以下的操作

{$I-}   (* disable index checks *)
(* code with no index checks *)
{$I+}   (* re-enable index checks *)

允许在模块中出现多个 -bit_range_check 指令可以让你在其他情况下使用新方法的模块中使用为旧方法编写的代码。我不认为我们希望鼓励这种做法:如果所有代码都遵循相同的规则,那么阅读模块会容易得多。

对于希望能够按任意顺序处理函数定义的 Erlang 编译器来说,这也更容易。编译器可以在处理任何位置的任何位语法形式之前,检查模块中是否存在这些指令之一。但是,如果人们在第一次看到 <<...>> 构造时,已经看到了可能影响其含义的任何指令,那么阅读模块会更容易。

如果需要,可以随时放宽对这些指令的数量和位置的限制。

向后兼容性 #

所有现有的 Erlang 代码仍然可以接受,并且语义不变。

参考实现 #

无,因为我仍然找不到编译器的使用方法。

版权 #

本文档已置于公共领域。