将具有长度字段的 C 灵活数组成员转换为 Ada

Translating C flexible array members with length fields to Ada

在编写与 C 代码接口的绑定时,我发现将大量具有灵活数组成员的结构实例转换为 Ada 时存在问题

struct example {
    size_t length;
    int    body[];
};

有人告诉我 Ada 可以用可区分的类型复制这样的行为,但我找不到一种方法来使用 length 字段作为判别式,同时保持结构的布局所以记录可用于与 C 代码交互,例如

type Example (Count : Integer := Length) is record
   Length : Unsigned_64;
   Body   : Integer (1 .. Count);
end record;

有没有办法用那个数组创建一个类似的类型?我现在一直默认获取该位置的地址并自己声明数组以供使用,有没有更好的方法?提前致谢。

C 数组索引始终从 0 开始。 如果要复制 C 结构,请记住判别式是记录的成员。

type Example (Length : Integer) is record
   body : array(0 .. Length - 1) of Integer;
end record;

这里是一个例子,Ada 代码调用 C 代码,将这种类型的 objects 从 Ada 传递给 C,再从 C 传递给 Ada。 C header 是 c_example.h:

typedef struct {
    size_t length;
    int    body[];
} example_t;

extern void example_print (example_t *e /* in */);
// Print the contents of e.

extern example_t * example_get (void);
// Return a pointer to an example_t that remains owned
// by the C part (that is, the caller can use it, but should
// not attempt to deallocate it).

C代码为c_example.c:

#include <stdio.h>
#include "c_example.h"

void example_print (example_t *e /* in */)
{
   printf ("C example: length = %zd\n", e->length);

   for (int i = 0; i < e->length; i++)
   {
      printf ("body[ %d ] = %d\n", i, e->body[i]);
   }
}  // example_print

static example_t datum = {4,{6,7,8,9}};

example_t * example_get (void)
{
   return &datum;
}  // example_get

C-to-Ada 绑定在 c_binding.ads:

中定义
with Interfaces.C;

package C_Binding
is
   pragma Linker_Options ("c_example.o");
   use Interfaces.C;

   type Int_Array is array (size_t range <>) of int
      with Convention => C;

   type Example_t (Length : size_t) is record
      Bod : Int_Array(1 .. Length);
   end record
      with Convention => C;

   type Example_ptr is access all Example_t
      with Convention => C;

   procedure Print (e : in Example_t)
      with Import, Convention => C, External_Name => "example_print";

   function Get return Example_Ptr
      with Import, Convention => C, External_Name => "example_get";

end C_Binding;

测试主程序为flexarr.adb:

with Ada.Text_IO;
with C_Binding;

procedure FlexArr
is

   for_c : constant C_Binding.Example_t :=
      (Length => 5, Bod => (55, 66, 77, 88, 99));

   from_c : C_Binding.Example_ptr;

begin

   C_Binding.Print (for_c);

   from_c := C_Binding.Get;

   Ada.Text_IO.Put_Line (
      "Ada example: length =" & from_c.Length'Image);

   for I in 1 .. from_c.Length loop
      Ada.Text_IO.Put_Line (
         "body[" & I'Image & " ] =" & from_c.Bod(I)'Image);
   end loop;

end FlexArr;

我这样构建程序:

gcc -c -Wall c_example.c
gnatmake -Wall flexarr.adb

这是 ./flexarr 的输出:

C example: length = 5
body[ 0 ] = 55
body[ 1 ] = 66
body[ 2 ] = 77
body[ 3 ] = 88
body[ 4 ] = 99
Ada example: length = 4
body[ 1 ] = 6
body[ 2 ] = 7
body[ 3 ] = 8
body[ 4 ] = 9

所以它似乎有效。然而,Ada 编译器 (gnat) 给了我一些来自 C_Binding 包的警告:

c_binding.ads:14:12: warning: discriminated record has no direct equivalent in C
c_binding.ads:14:12: warning: use of convention for type "Example_t" is dubious

这意味着虽然这种接口方法适用于 gnat,但它可能不适用于其他编译器,例如 Ada 编译器,它们将 Bod 组件数组与记录的 fixed-size 部分分开分配(无论是这样的编译器会接受 Convention => C for this record type is questionable)。

为使界面更便携,请进行以下更改。在 C_Binding 中,将 Length 从判别式更改为普通组件,并使 Bod 成为 fixed-size 数组,使用一些最大大小,这里以 1000 个元素为例:

   type Int_Array is array (size_t range 1 .. 1_000) of int
      with Convention => C;

   type Example_t is record
      Length : size_t;
      Bod    : Int_Array;
   end record
      with Convention => C;

在测试主程序中,更改for_c的声明以用零填充数组:

   for_c : constant C_Binding.Example_t :=
      (Length => 5, Bod => (55, 66, 77, 88, 99, others => 0));

为了速度,您可以让数组中未使用的部分未初始化:"others => <>"。

如果找不到合理的小的最大大小,应该可以将 C 绑定定义为实际大小的通用。但这变得相当混乱。

注意,如果所有的record/struct object都是在C端创建的,而Ada端只对它们进行读写,那么binding中定义的最大大小只用于索引边界检查并且可以非常大而不影响内存使用。

在这个例子中,我让 Ada 端从 1 开始索引,但是如果你想让它更类似于 C 代码,你可以将它更改为从零开始。

最后,在 non-discriminated 的情况下,我建议将 Example_t 设为“有限”类型(“类型 Example_t 是有限的记录 ...”),这样您就无法分配该类型的整个值,也不比较它们。原因是当C端提供一个Example_t object给Ada端时,object的实际大小可能小于Ada端定义的最大大小,但是一个Ada赋值或比较会尝试使用最大大小,这可能会使程序读取或写入不应读取或写入的内存。

判别式本身就是记录的一个组成部分,必须存储在某个地方。

这段代码,

type Integer_Array is array (Natural range <>) of Integer;

type Example (Count : Natural) is record
   Bdy : Integer_Array (1 .. Count);
end record;

-gnatR 编译以显示表示信息,说

for Integer_Array'Alignment use 4;
for Integer_Array'Component_Size use 32;

for Example'Object_Size use 68719476736;
for Example'Value_Size use ??;
for Example'Alignment use 4;
for Example use record
   Count at 0 range  0 .. 31;
   Bdy   at 4 range  0 .. ??;
end record;

所以我们可以看到 GNAT 决定将 Count 放在前 4 个字节中,就像 C 一样(好吧,这是一个常见的 C 习惯用法,所以我想它是 C 结构组件的定义行为按源顺序分配)。

因为这是用来和C接口的,所以可以这么说,

type Example (Count : Natural) is record
   Bdy : Integer_Array (1 .. Count);
end record
  with Convention => C;

但由于 编译器对此表示怀疑(它警告您标准未指定构造的含义)。

我们至少可以确认我们希望 Count 在前 4 个字节中,添加

for Example use record
   Count at 0 range 0 .. 31;
end record;

但我认为这不会阻止使用不同方案的不同编译器(例如,两个结构,第一个包含 Count 和第二个的地址 Bdy)。