Accessing Memory as a String


Question:

How do I access a chunck of memory as an Ada string with out
moving or copying the data in Ada95?

Answer:

Well we are not going to be able to find a
solution that is *formally* portable for something
like this, but we can certainly find a solution
that is from a pragmatic point of view portable
(between architectures and between compilers).

There are a couple of approaches.  First, let's
try to get a type that retains proper bounds
checking:

First, we want to deal with a constrained array
here.  Unconstrained arrays have bounds stored
in some implementation dependent format and will get
you into trouble.  So let's first define a subtype
that appropriately represents the range

    subtype XString is String (1 .. expr);

  Here expr can be dynamic, there is no problem in
  that.  However, note that we must define this
  subtype in an appropriate scope.  In this
  particular case, expr is something like

    Positive
     (System.Storage_Elements.To_Integer (B) -
      System.Storage_Elements.To_Integer (A) + 1);

  Now we can proceed in several ways

Method 1a.  Simply use an address clause.  This is
exactly a legitimate use of address clauses, so we
simply say

    S : XString;
    for S'Address use A;

  This will likely work on most implementations, assuming that
  they do not use dope vectors or descriptors for constrained
  arrays. This is a reasonable assumption, and certainly true
  of GNAT, but in practice it may be slightly more portable
  to use:

Method 1b.  Use Unchecked_Conversion

  type XString_Ptr is access all XString; function
  To_XString_Ptr is new Unchecked_Conversion
  (Address, XString_Ptr);

    SP :  XString_Ptr := To_String_Ptr (A);

  in practice, this works fine, since all
  implementations will use the same representation
  for constrained pointers and addresses.  Note that
  approach 1b will NOT work if you try to use
  "access string" instead of the
  pointer-to-constrained type, since then bounds get
  involved.

  However, there is no requirement that this work, so a bit
  cleaner, is to use:

Method 1c.  Use Address_To_Access_Conversions

    package XString_Ops is new
    Address_To_Access_Conversions (XString);

    subtype Xstring_Ptr is Xstring_Ops.Object_Pointer;

    SP : XString_Ptr := To_Pointer (A);

  Note:  although the formal of this package has a
  declaration (<>) implying you can use it with an
  unconstrained type such as String, you will get
  into trouble if you try, since we still have the
  bounds problem.

  Basically the issue with "access String" is that
  values of this type have bounds.  Where are these
  bounds stored?  Certainly your memory mapped gizmo
  has no bounds in sight, and even if it did your
  implementation might not have the same idea of how
  to store them.

Here are two rules to follow

  NEVER use unchecked conversion with pointers to
  unconstrained array types

  NEVER use Address_To_Access_Conversions with
  pointers to unconstrained array types.

  Violating these rules is a common cause of
  non-portable code and obscure bugs.  We find this
  coming up frequently in our work helping customers
  to port legacy Ada 83 code.

OK, those approaches work fine, but now for one
other approach that is a little more general and
flexible, but does not retain bounds checking.

  Define a type

  subtype Big_String is String (Positive); type
  Big_String_Ptr is access Big_String;

  This type acts very much like char* in C in that
  it is a pointer that can be used to access
  arbitrary sized character arrays.

Method 2b.  Like 1b but use Big_String_Ptr

Method 2c.  Like 1c but use Big_String_Ptr

  The advantage of Method 2b and 2c is that
  Big_String_Ptr can be used arround the place
  freely without worrying about specialized
  definitions of subtypes and their scopes.  This
  approach is also appropriate when you don't know
  the upper bound in advance, but it is computed as
  you go along, or changes as you go along.

  For an example of the use of "Big" arrays of this
  type, see the GNAT.Table package in the GNAT
  library, which uses this approach to get the
  effect of virtual origin addressing for arrays.

Method 3. Pointer Arithmetic

  Finally, yes, you could use address arithmetic
  (good old pointer arithmetic), and indeed Ada has
  much more flexible pointer arithmetic than C (in
  Ada, unlike C, you can do arbitrary address
  arithmetic, e.g.  compare any two addresses, in a
  portable manner).  However, this is much lower
  level and will kludge up your code unnecessarily.

Ada 83 notes.

  In Ada 83, method 1a is a bit more dubious, it may be
  erroneous, but in practice if the addressed area is not
  otherwise aliased, it should work fine. Method 1b is
  certainly safe (same comments apply to 2a and 2b). Method
  1c, probably the preferable approach for Ada 95 is of course
  not available in Ada 83.

Contributed by: Robert Dewar
Contributed on: June 6, 1999
License: Public Domain

Back