Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Translating C flexible array members with length fields to Ada

Tags:

ada

While writing bindings for interfacing with C code, I am finding issues translating the numerous instances of structs with flexible array members to Ada, as such

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

I've been told for Ada that behaviors like that can be replicated with discriminated types, but I cannot find a way to use the length field as a discriminant, while maintaining the layout of the structure so that the records can be used to interface with the C code, something like

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

Is there any way to create a type like that with that array? I've been defaulting for now to grabbing the address of that location and declaring the array myself on place for use, is there any better way? Thanks in advance.

like image 411
Rottenheimer2 Avatar asked Aug 31 '25 22:08

Rottenheimer2


1 Answers

Here is an example in which Ada code calls C code, passing objects of this kind from Ada to C and from C to Ada. The C header is 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).

The C code is 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

The C-to-Ada binding is defined in 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;

The test main program is 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;

I build the program thusly:

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

And this is the output from ./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

So it seems to work. However, the Ada compiler (gnat) gives me some warnings from the C_Binding package:

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

This means that while this interfacing method works with gnat, it might not work with other compilers, for example Ada compilers that allocate the Bod component array separately from the fixed-size part of the record (whether such a compiler would accept Convention => C for this record type is questionable).

To make the interface more portable, make the following changes. In C_Binding, change Length from a discriminant to an ordinary component and make Bod a fixed-size array, using some maximum size, here 1000 elements as an example:

   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;

In the test main program, change the declaration of for_c to pad the array with zeros:

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

For speed, you could instead let the unused part of the array be uninitialized: "others => <>".

If you cannot find a reasonably small maximum size, it should be possible to define the C binding to be generic in the actual size. But that is getting rather messy.

Note that if all the record/struct objects are created on the C side, and the Ada side only reads and writes them, then the maximum size defined in the binding is used only for index bounds checking and can be very large without impact on the memory usage.

In this example I made the Ada side start indexing from 1, but you can change it to start from zero if you want to make it more similar to the C code.

Finally, in the non-discriminated case, I recommend making Example_t a "limited" type ("type Example_t is limited record ...") so that you cannot assign whole values of that type, nor compare them. The reason is that when the C side provides an Example_t object to the Ada side, the actual size of the object may be smaller than the maximum size defined on the Ada side, but an Ada assignment or comparison would try to use the maximum size, which could make the program read or write memory that should not be read or written.

like image 80
Niklas Holsti Avatar answered Sep 07 '25 19:09

Niklas Holsti