1

I'm looking at different ways of passing string to C functions. Here is a example on 4 different way to to that and the code is tested and it works.

with Interfaces.C; use Interfaces.C;
with Interfaces.C.Strings; use Interfaces.C.Strings;
with System; use System;
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Real_Time; use Ada.Real_Time;
procedure Main is

   procedure Strcpy (Target : out String; Source : in String) with Import, Convention => C, External_Name => "strcpy";
   procedure Strcpy (Target : Address; Source : Address) with Import, Convention => C, External_Name => "strcpy";
   procedure Strcpy (Target : out char_array; Source : in char_array) with Import, Convention => C, External_Name => "strcpy";
   procedure Strcpy (Target : chars_ptr; Source : chars_ptr) with Import, Convention => C, External_Name => "strcpy";

   Source        : String := "Duration: " & Character (nul);
   Target_String : String (Source'Range) := (others => ' ');
   Target_String_Address : String (Source'Range) := (others => ' ');
   Target_char_array : char_array (size_t (Source'First) .. size_t (Source'Last)) := (others => ' ');
   Target_chars_ptr : chars_ptr := New_Char_Array (Target_char_array);

   T : Time;
   D : Time_Span;
   N : constant := 100000000;
begin

   T := Clock;
   for I in 1..N loop
      Strcpy (Target_String, Source);
   end loop;
   D := Clock - T;
   Put_Line (Target_String & To_Duration(D)'Img);

   T := Clock;
   for I in 1..N loop
      Strcpy (Target_String_Address'Address, Source'Address);
   end loop;
   D := Clock - T;
   Put_Line (Target_String_Address & To_Duration(D)'Img);

   T := Clock;
   for I in 1..N loop
      Strcpy (Target_char_array, To_C (Source));
   end loop;
   D := Clock - T;
   Put_Line (To_Ada (Target_char_array) & To_Duration(D)'Img);

   T := Clock;
   for I in 1..N loop
      Strcpy (Target_chars_ptr, New_String (Source));
   end loop;
   D := Clock - T;
   Put_Line (Value (Target_chars_ptr) & To_Duration(D)'Img);

end;

Measurement

╔════════════╦════════════╦═════════════╗
║    Type    ║ Conversion ║ Duration[s] ║
╠════════════╬════════════╬═════════════╣
║ String     ║            ║ 0.564774366 ║
║ Address    ║            ║ 0.535110315 ║
║ char_array ║ To_C       ║ 2.938592901 ║
║ chars_ptr  ║ New_String ║ 6.790939748 ║
╚════════════╩════════════╩═════════════╝

What method should I use with good performance in consideration?

Passing String'Address will get the best performance but should I do it?

Is there any disadvantages of any of Strcpy procedures?

Is there any benefits of any of Strcpy procedures?

Jossi
  • 1,020
  • 1
  • 17
  • 28
  • This question is only tangentially related to C. Tags edited. – John Bollinger Apr 10 '15 at 13:50
  • 1
    Ada already has all the functionality you need to copy strings or portions of strings, so there's no need to go through C's strcpy() to do this. In fact, a good Ada compiler should be able to generate a single machine instruction, if there is one, to copy a block of bytes, or at least to generate the tightest possible loop, which would be as good as a strcpy() could do. So all you're doing is at best adding another call instruction, and at worst going through extra logic to append a null byte to a string. In other words, going through C to copy strings will slow you down. – ajb Apr 11 '15 at 04:08
  • Remember that there is a difference between "works on my current compiler version" and "guaranteed to work by the standard". – Jacob Sparre Andersen Apr 11 '15 at 07:35
  • Performance is not something you guess. It is something you measure. Please add your measurements of the performance of the four variants. My guess (having read section B.3 in the LRM a few times) is that you will see closer results than expected. – Jacob Sparre Andersen Apr 11 '15 at 07:46
  • @ajb The only reason I'm using `strcpy` is so that others can test the code. – Jossi Sep 06 '15 at 11:41

1 Answers1

3

I'm not going to measure performance for you. I expect that you are able to do that yourself.

There is no way that you can get a guarantee that Standard.String objects can be passed to any C function in a way that always works, as Ada compilers aren't required to expect the individual characters in a Standard.String to be aliased. If you ensure that Interfaces.C.char is equivalent to Standard.Character, that your objects of type Standard.String are packed, and that your objects of type Standard.String are null-terminated, then I expect that it will work in most cases.

The packing and equivalence is something you have to check experimentally as a part of your automatic build and test procedures.

You can enforce the null-termination by using a subtype of Standard.String with a dynamic predicate for the formal parameters to your imported C functions:

with Ada.Characters.Latin_1;

package Strings_For_C is
   NUL : constant Character := Ada.Characters.Latin_1.NUL;

   subtype C_String is Standard.String
     with Dynamic_Predicate => C_String'Length >= 1 and then
                               C_String (C_String'Last) = NUL;
end Strings_For_C;

If you assume that B.3(70) in the LRM is followed (technically it is only implementation advice, but I think it is a safe assumption), then you can use C_String directly in the interface to imported C programs in place of char* arguments:

function Open (Pathname : in     Strings_For_C.C_String;
               Flags    : in     Interfaces.C.int)
  return Interfaces.C.int;
Jacob Sparre Andersen
  • 6,733
  • 17
  • 22
  • The `Dynamic_Predicate` is a neat trick. I'm doing a lot of interfacing with C functions so would you recommend to abandon `Standard.String` and start using `C.char` and `C.char_array` as part of all string handling in Ada code structure? – Jossi Apr 12 '15 at 12:23
  • No, I would not recommend that. – Jacob Sparre Andersen Apr 13 '15 at 15:33