Constraint_Error 使用不能被 8 整除的模块化类型时引发

Constraint_Error raised when using modular types not divisble by 8

我遇到了一个问题,即在 Ada 中使用不能被系统的 Storage_Unit(如运行时的 system.ads 中所定义)整除的模块化类型将引发 Constraint_Error 在访问时的运行时。我最初在使用最小运行时间的裸机系统上工作时遇到了这个问题,同时尝试通过将 12 位数组覆盖在内存中的缓冲区上来从缓冲区中读取 12 位值。有谁知道为什么会这样?

以下最小示例说明了我遇到的问题。我使用 AdaCore 的 GNAT 2019 对此进行了测试,使用随附的 zfp 运行时进行编译。使用标准运行时不会重现该问题。

with Ada.Text_IO; use Ada.Text_IO;

procedure Main is
   ----------------------------------------------------------------------------
   --  Modular type with standard size divisible by 8.
   ----------------------------------------------------------------------------
   type Type_One is mod 2 ** 16;
   type Buffer_Type_One is array (1 .. 128) of Type_One;

   ----------------------------------------------------------------------------
   --  Modular type with non-base 8 size.
   ----------------------------------------------------------------------------
   type Type_Two is mod 2 ** 12;
   type Buffer_Type_Two is array (1 .. 128) of Type_Two;

   type Buffer is array (1 .. 256) of Character;

   ----------------------------------------------------------------------------
   --  Example buffer.
   ----------------------------------------------------------------------------
   Test_Buffer : Buffer := (others => ' ');
begin
   ----------------------------------------------------------------------------
   --  Will not raise an exception.
   ----------------------------------------------------------------------------
   Test_One :
      declare
         Buffer_One : Buffer_Type_One
         with Import,
         Convention => Ada,
         Address    => Test_Buffer'Address;
      begin
         Put_Line ("Testing type one");
         for I in Natural range 1 .. 16 loop
            Put_Line ("Test: " & Buffer_One (I)'Image);
         end loop;
      end Test_One;

   ----------------------------------------------------------------------------
   --  Will raise a constraint error at runtime.
   ----------------------------------------------------------------------------
   Test_Two :
      declare
         Buffer_Two : Buffer_Type_Two
         with Import,
         Convention => Ada,
         Address    => Test_Buffer'Address;
      begin
         Put_Line ("Testing type two");
         for I in Natural range 1 .. 16 loop
            Put_Line ("Test: " & Buffer_Two (I)'Image);
         end loop;
      exception
         when Constraint_Error =>
            Put_Line ("Constraint error encountered.");
      end Test_Two;

end Main;

这里是我用来编译这个例子的工程文件:

project Test is

   for Object_Dir use "obj";
   for Exec_Dir use "build";
   for Create_Missing_Dirs use "True";

   for Languages use ("Ada");

   package Builder is
      for Executable ("main.adb") use "test";
   end Builder;

   for Main use ("main.adb");

   package Compiler is
      for Default_Switches ("Ada") use (
        "-gnat2012",
        "-gnatwadehl",
        "-gnatVa",
        "-gnaty3abcdefhiklmnoprstux"
      );
   end Compiler;

   for Runtime ("Ada") use "zfp";
end Test;

我似乎无法在 RM 中找到任何可以说明为什么会发生这种情况的内容。

编辑: 下面的 Simon Wright 已经弄清楚了为什么会这样。我天真的理解是,覆盖在指定内存地址的 Buffer_Type_Two 实例会将此位置的内存解释为 12 位值的序列。看来情况并非如此。看起来好像编译器将类型的大小四舍五入到 16 位,然后在从数组读取的 16 位值不符合 12 位类型时引发 Constraint_Error

如果有人能想出一种更好的方法来以顺序方式从内存中的某个位置读取一系列 12 位值,我将不胜感激,谢谢。

考虑到编译警告,代码确实不值得工作...

31.          Buffer_One : Buffer_Type_One
32.            with Import,
33.              Convention => Ada,
34.              Address    => Test_Buffer'Address;
                 |
    >>> warning: specified address for "Buffer_One" may be inconsistent with alignment
    >>> warning: program execution may be erroneous (RM 13.3(27))
    >>> warning: alignment of "Buffer_One" is 2
    >>> warning: alignment of "Test_Buffer" is 1

49.          Buffer_Two : Buffer_Type_Two
50.            with Import,
51.              Convention => Ada,
52.              Address    => Test_Buffer'Address;
                 |
    >>> warning: specified address for "Buffer_Two" may be inconsistent with alignment
    >>> warning: program execution may be erroneous (RM 13.3(27))
    >>> warning: alignment of "Buffer_Two" is 2
    >>> warning: alignment of "Test_Buffer" is 1

但这不是问题,碰巧:Type_Two是mod2**12,即mod4096,但Buffer_Two(1)中的值是16 #2020#(两个space个字符),十进制为8224。

"But why isn’t the stored value automatically masked to the 12 bits I asked for?"你说。出于效率原因,"the size of an object is not necessarily the same as the size of the type of [the] object"、GNAT RM 4.43 和 GNAT 期望 16 位字顶部的备用 4 位为零。就其本身而言,Type_Two 类型的值占用(’Object_Size 个)16 位。您可以通过在记录中包含 Type_Two 字段并指定其布局来获得所需的 12 位大小,但这确实会增加复杂性。

如果没有 -gnatVa(打开所有有效性检查选项),此处未检测到问题。

16#2020# 的物理值不在 Type_Two 的范围内,所以 Constraint_Error 需要 -gnatVa。

GNAT CE 2020 提高 Constraint_Error

取消注释 exception/when 部分会导致更多信息:

raised CONSTRAINT_ERROR : main.adb:51 invalid data

使用最新的 GNAT,您可以通过将 Buffer_Type_Two 定义为

来实现您想要的行为
type Buffer_Type_Two is array (1 .. 128) of Type_Two
  with Pack;

ARM 13.2(9) 警告说,对于 13 位值,这可能无法满足您的要求(尽管最近的 GNAT 可以)。

另一种选择是

type Buffer_Type_Two is array (1 .. 128) of Type_Two
  with Component_Size => 12;

结果是

...
Testing type two
Test:  32
Test:  514
Test:  32
Test:  514
...

对于 13 位,无论哪种方法,

...
Testing type two
Test:  32
Test:  257
Test:  2056
Test:  64
Test:  514
Test:  4112
Test:  128
Test:  1028
Test:  32
...

但是,对于嵌入式目标,您需要使用 -full- 运行时系统;对于其他人,正如上面@egilhh 所指出的,

ajxs.adb:14:09: packing of 12-bit components not supported by configuration