跳转到内容

Ada 编程/属性/'位序

来自维基教科书,开放的书籍,开放的世界

Ada. Time-tested, safe and secure.
Ada. 经久考验,安全可靠。

R'Bit_Order 是一个用于指定表示属性,用于指定位编号记录表示子句(对于记录类型)。 位排序是System.Bit_Order 类型,可以是System.Bit_Order,可以是

假设位序列被解释为整数值。常量System.Default_Bit_Order表示平台的本机位编号。

值得注意的是,位排序只影响记录表示子句的位编号,而不是其(多字节)字段的字节序[1]

存储单元按其地址排序。让我们看看占用多个字节的整数(假设字节为 8 位)。

  • 在大端(BE)机器上,整数的最高有效字节存储在最低地址。
  • 在小端(LE)机器上,其最低有效字节存储在最低地址。

因此,为了能够跨字节边界连续计数位,Ada 按照大端的最高有效位(MSB)0 到最低有效位(LSB)的顺序计数字节内的位,而在小端上则反过来。[2]

这就是它的样子(在大端上方便地从左到右写,在小端上从右到左写)

BE Byte    0 1 2 …  (counting bytes, i.e. addresses, left to right; higher addresses to the right)
LE Byte  … 2 1 0    (counting bytes right to left; higher addresses to the left)

         MSB         LSB
BE  Bit  0 1 2 3 4 5 6 7  (counting bits within a byte left to right)
LE  Bit  7 6 5 4 3 2 1 0  (counting bits right to left)

我们习惯了从左到右写,但是对于小端,正如你所见,从右到左写地址很方便。因此,在大端上,字节和位的序列按以下方式计数

Byte 00                      01                      02
Bit  00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 …
                             00 01 02 03 04 05 06 07 08 09 10 11 12 13 …  (we can equally begin to count at byte 01)

为了能够从小端连续写入和计数,我们必须像阿拉伯语或希伯来语脚本那样从右到左写(MSB 始终在左侧;这似乎是所有现代脚本中的一个全球性特征)

Byte                  02                      01                      00
Bit  … 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00
     … 13 12 11 10 09 08 07 06 05 04 03 02 01 00

在以下记录表示中(当然,只有指定 X 的两行中的一行可能存在)

for T use record
  X at 0 range 13 .. 17;  -- these two lines…
  X at 1 range  5 ..  9;  -- … are equivalent
end record;

组件 X 占用粗体所示的位。字节编号(0 或者 1)称为 Position,边界称为 First_Bit(13 或者 5)和 Last_Bit(17 或者 9);有相应的属性'Position'First_Bit'Last_Bit。(组件 X 的含义在这里无关紧要,只有它的大小相关)。正如你所见,X 是一个跨边界的组件。因此,将 'Bit_Order 属性应用于具有跨边界组件的记录的结果取决于 Ada 的版本。

Ada 95 仅在应用于存储单元内或项目完全填充存储单元序列时,才定义属性在非本机位序上的结果。通过将属性应用于记录,我们强制编译器以指示的方式计数位,独立于机器体系结构。

type Rec is record
  A at 0 range 0 .. 5;
  B at 0 range 6 .. 7;
end record;
for Rec'Bit_Order use High_Order_First;

组件 B 占用粗体所示的位

Byte 0
Bit  0 1 2 3 4 5 6 7

此记录的布局在 BE 和 LE 机器上将相同,即表示是与字节序无关的。

我们也可以这样定义此记录,并获得相同的与字节序无关的布局

type Rec is record
  A at 0 range 2 .. 7;
  B at 0 range 0 .. 1;
end record;
for Rec'Bit_Order use Low_Order_First;
Byte               0
Bit  7 6 5 4 3 2 1 0

但是,以下记录,其中 B 是跨边界的,仅在 BE 机器上有效,即在本机位序中。

type Rec is record
  A at 0 range 0 .. 5;
  B at 0 range 6 .. 9;
end record;
for Rec'Bit_Order use High_Order_First;
Byte  0                       1
Bit   0  1  2  3  4  5  6  7  0  1  2  3  4  5  6  7
#    00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15

在 LE 机器上编译时,编译器会抱怨 B 占用非连续存储并拒绝代码,因为字节序不受属性影响(记住,下一个带有位号 8 到 15 的字节必须放在左侧)

Byte                         1                       0
Bit    00 01 02 03 04 05 06 07 00 01 02 03 04 05 06 07
#      08 09 10 11 12 13 14 15 00 01 02 03 04 05 06 07

作为项目填充完整存储单元范围的记录示例,请参见

type Rec is record
  A at 0 range 0 ..  7;
  B at 1 range 0 .. 15;
end record;
for Rec'Bit_Order use High_Order_First;

这在两种体系结构上都有效,导致以下布局

BE    0                       1                       2
Bit  00 01 02 03 04 05 06 07 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
LE                         2                       1                       0
Bit  08 09 10 11 12 13 14 15 00 01 02 03 04 05 06 07 00 01 02 03 04 05 06 07

其中 A 填充普通字体中的位,B 填充粗体字体中的位。正如你所见,在 LE 上以这种奇怪的方式计数位并不重要,因为组件 B 完全填充了它的两个字节。

对于本机位序,没有变化。记录表示规范从 Ada 83 开始就有效。只有 'Bit_Order 属性在 Ada 95 中引入,并具有上述限制。

为了改善非本机位序的情况,Ada 2005 引入了机器标量的概念。机器标量是一个概念上的基于硬件的无符号整数,在其中计数位并按要求定位组件。

现在我们需要一个更详细的记录示例(相应的属性 'Position、'First_ 和 'Last_Bit(在本机位序中)返回的值作为注释给出;注意,返回的位号始终从组件开始的字节开始计数)

for T use record
  I at 0 range  0 .. 15;  -- at 0 range 0 .. 15
  A at 2 range  0 .. 12;  -- at 2 range 0 .. 12
  B at 2 range 13 .. 17;  -- at 3 range 5 ..  9
  C at 2 range 18 .. 23;  -- at 4 range 2 ..  7
  D at 5 range  0 ..  7;  -- at 5 range 0 ..  7
end record;

假设 I 是一个 16 位(有符号或无符号)整数,其他组件是某种未指定的类型,具有给定的尺寸要求。整个记录的大小为 48。这就是它在 BE 和 LE 机器上的样子

Byte 0       1       2       3       4       5
  BE 012345678901234501234567890123456789012301234567
     IIIIIIIIIIIIIIIIAAAAAAAAAAAAABBBBBCCCCCCDDDDDDDD

     DDDDDDDDCCCCCCBBBBBAAAAAAAAAAAAAIIIIIIIIIIIIIIII
  LE 765432103210987654321098765432105432109876543210
Byte        5       4       3       2       1       0

在下面,我们将展示为了实现表示的与字节序无关性,我们能走多远。结果将是,当正确使用新的 Ada 2005 功能时,我们只需要在从一种体系结构传输到另一种体系结构后交换字节。

对于以下内容,我们将假设我们是在一个大端机器上,因此我们将相应的属性附加到表示中

for T use record
  I at 0 range  0 .. 15;
  A at 2 range  0 .. 12;
  B at 2 range 13 .. 17;
  C at 2 range 18 .. 23;
  D at 5 range  0 ..  7;
end record;
for T'Bit_Order use High_Order_First;

当这在小端机器上编译时,所有具有相同 Position 的组件将被组合在一起并放入匹配的机器标量中。机器标量位于给定的 Position 处,但在内部,位从相反方向计数。

让我们以第一个组件 I 为例。它使用 16 位,因此可以使用 16 位机器标量。它位于字节 0 处,但在内部,编译器将从高位端计数。这就是它的样子(NNBO - 非本机位序)

                                     IIIIIIIIIIIIIIII
NNBO                                 0123456789012345
Byte        5       4       3       2       1       0

现在到下一个 Position 2。相应的组件 A、B、C 总共使用三个字节,因此需要一个 32 位机器标量。它位于字节 2 处,在内部计数将再次从相反方向开始。这就是它的样子

                                     IIIIIIIIIIIIIIII
NNBO 012345678901234567890123456789010123456789012345
Byte        5       4       3       2       1       0

位计数从字节 5 的最高有效位开始,一直持续到字节 2 的最低有效位。组件 A、B、C 在此标量内部根据各自的范围进行定位。因此,我们得到了以下布局

     AAAAAAAAAAAAABBBBBCCCCCC        IIIIIIIIIIIIIIII
NNBO 012345678901234567890123456789010123456789012345
Byte        5       4       3       2       1       0

我们立即看到与组件 D 的冲突,它的范围已被 A 占用,编译器当然会报错并拒绝代码。解决方案很简单:只需将 D 的位置从 5 更改为 (在 BE 上) 等效行,如下所示

for T use record
  I at 0 range  0 .. 15;
  A at 2 range  0 .. 12;
  B at 2 range 13 .. 17;
  C at 2 range 18 .. 23;
--D at 5 range  0 ..  7;
  D at 2 range 24 .. 31;
end record;
for T'Bit_Order use High_Order_First;

并且,鼓声响起,在 LE 上,我们现在有(为了比较,还给出了本机布局)

     AAAAAAAAAAAAABBBBBCCCCCCDDDDDDDDIIIIIIIIIIIIIIII
NNBO 012345678901234567890123456789010123456789012345
Byte        5       4       3       2       1       0

     IIIIIIIIIIIIIIIIAAAAAAAAAAAAABBBBBCCCCCCDDDDDDDD
  BE 012345678901234501234567890123456789012345678901
Byte 0       1       2       3       4       5

在非本机位顺序中,由相应属性“Position”、“First_”和“Last_Bit”返回的值与记录规范中给出的值完全一致。

作为一项额外服务,GNAT 的编译输出将为您提供在机器标量中以本机位顺序计算的值

       range
"I"   0 .. 15
"A"  19 .. 31
"B"  14 .. 18
"C"   8 .. 13
"D"   0 ..  7

     AAAAAAAAAAAAABBBBBCCCCCCDDDDDDDDIIIIIIIIIIIIIIII
NNBO 012345678901234567890123456789010123456789012345
  LE 109876543210987654321098765432105432109876543210
Byte        5       4       3       2       1       0

这是我们在当前 Ada 标准下所能达到的极限。

数据传输

[编辑 | 编辑源代码]

让我们将此记录的值从本机大端机传输到小端机。为了演示目的,跨边界项目的最高位部分显示为大写,最低位部分显示为小写。

     IIIIIIIIiiiiiiiiAAAAAAAAaaaaaBBBbbCCCCCCDDDDDDDD
  BE 012345678901234501234567890123456789012345678901
Byte 0       1       2       3       4       5

字节将按给定顺序传输。由于位顺序属性不会在传输后重新排序字节,因此在目标机器上,我们将以乱序接收数据

     DDDDDDDDbbCCCCCCaaaaaBBBAAAAAAAAiiiiiiiiIIIIIIII
NNBO 012345678901234567890123456789010123456789012345
Byte        5       4       3       2       1       0

我们所要做的就是交换字节 0↔1、2↔5、3↔4,以达到预期的结果

     AAAAAAAAaaaaaBBBbbCCCCCCDDDDDDDDIIIIIIIIiiiiiiii
NNBO 012345678901234567890123456789010123456789012345
Byte        5       4       3       2       1       0

以下两组表示子句在任何机器/编译器(Ada 2005 及更高版本)中指定了相同的寄存器布局

type Device_Register is
    record
       Ready : Status_Flag;
       Error : Error_Flag;
       Data  : Unsigned_16;
    end record;

for  Device_Register use
    record
       Ready at 0 range  0 .. 0;
       Error at 0 range  1 .. 1;
       -- Reserved bits
       Data  at 0 range 16 .. 31;
    end record;
for Device_Register'Size      use 32;
for Device_Register'Bit_Order use System.Low_Order_First;

pragma Atomic (Device_Register);

如果位顺序修改,则必须反转记录表示子句中所有元素的位编号

type Device_Register is
    record
       Ready : Status_Flag;
       Error : Error_Flag;
       Data  : Unsigned_16;
    end record;

for  Device_Register use
    record
       Ready at 0 range 31 .. 31;   -- Bit numbering has changed
       Error at 0 range 30 .. 30;   -- Bit numbering has changed
       -- Reserved bits
       Data  at 0 range  0 .. 15;   -- Bit numbering has changed (but byte order is not affected)
    end record;
for Device_Register'Size      use 32;
for Device_Register'Bit_Order use System.High_Order_First; -- Reverse bit order

pragma Atomic (Device_Register);

两者可以在同一台机器上互换使用。但请注意,在两台具有不同字节序的机器中数据字段将具有本机字节序,而与表示子句中指定的位顺序无关。

不正确的用法

[编辑 | 编辑源代码]

'Bit_Order 属性并非旨在将数据在大小端机器之间转换(它影响位编号,而不是字节顺序)。当指定非本机位顺序时,编译器不会生成代码来重新排序多字节字段。[3][4][5]

参考资料

[编辑 | 编辑源代码]
  1. 请注意,当 ARM 在 'Bit_Order 属性的定义中谈论“大端”和“小端”时,它实际上指的是位字节序,而不是字节字节序。(如今,术语字节序通常保留用于讨论字节顺序,尽管它也可以用于位编号。)因此,在这种情况下,当 ARM 说“小端”时,它指的是“LSB 0”,而当它说“大端”时,它与“MSB 0”相同:

    «High_Order_First(在口语中称为“大端”)表示存储元素的第一个位(位 0)是最重要的位(将表示组件的位序列解释为无符号整数)。Low_Order_First(在口语中称为“小端”)表示相反:第一个位是最不重要的位。» [LRM, §13.5.3(2)]

  2. ISO/IEC 8652:1987. "13.4 Record Representation Clauses". Ada 83 Reference Manual. The range defines the bit positions of the storage place, relative to the storage unit. The first storage unit of a record is numbered zero. The first bit of a storage unit is numbered zero. The ordering of bits in a storage unit is machine_dependent and may extend to adjacent storage units. {{cite book}}: Unknown parameter |chapterurl= ignored (|chapter-url= suggested) (help)
  3. AI95-00133-01 (1996-05-07). "Controlling bit ordering". Class: binding interpretation. Ada Rapporteur Group. Bit_Order clauses are concerned with the numbering of bits and not concerned with data flipping interoperability.
  4. ISO/IEC 8652:2007. "13.5.3 Bit Ordering (9/2)". Ada 2005 Reference Manual. Retrieved 2008-06-02. Bit_Order clauses make it possible to write record_representation_clauses that can be ported between machines having different bit ordering. They do not guarantee transparent exchange of data between such machines. {{cite book}}: Unknown parameter |chapterurl= ignored (|chapter-url= suggested) (help)
  5. Thomas Quinot (2013). "Gem #140: Bridging the Endianness Gap". AdaCore. Retrieved 2013-01-31. the order in which the bytes that constitute machine scalars are written to memory is not changed by the Bit_Order attribute -- only the indices of bits within machine scalars are changed. {{cite web}}: Unknown parameter |month= ignored (help)


据作者所知,记录表示子句是 Ada 语言的独特功能,因此不需要在其他编程语言中使用类似于属性 'Bit_Order 的功能。在其他编程语言中,通常的做法是显式使用掩码和位运算,因此必须始终使用本机位编号。

也可以看看

[编辑 | 编辑源代码]

维基教科书

[编辑 | 编辑源代码]

Ada 参考手册

[编辑 | 编辑源代码]

Ada 理论

[编辑 | 编辑源代码]

参考文献和注释

[编辑 | 编辑源代码]

进一步阅读

[编辑 | 编辑源代码]
华夏公益教科书