Can I return a function's local variable as the function return?

In the following code, type Four_Unbounded_Strings_Type is an array of 4 unbounded strings. I wrote the following function to create and populate such an array:

   function Make_Array return Four_Unbounded_Strings_Type is
      A: Four_Unbounded_Strings_Type;
   begin
      A(1) := Ada.Strings.Unbounded.To_Unbounded_String("String one.");
      A(2) := Ada.Strings.Unbounded.To_Unbounded_String("String two.");
      A(3) := Ada.Strings.Unbounded.To_Unbounded_String("String three.");
      A(4) := Ada.Strings.Unbounded.To_Unbounded_String("String four.");
      return A;
   end Make_Array;

Doing the preceding in C is a good way to make sneaky intermittents, invite exploits, and get fired. In C that local variable goes out of scope when the function returns, its contents are returned to the stack, and something else is free to overwrite it (or not), creating an intermittent. In C I could “cure” the problem by declaring the local variable static, but that has its own problems.

This function compiles and runs without errors or warnings when used in a small containing program, but my question is this: Is returning a function’s local variable safe in Ada?

It is safe. Your function returns the value of A and then destroys A. You can consider A being a temporal object.
You could simplify it using an aggregate:

   function Make_Array return Four_Unbounded_Strings_Type is
   begin
      return
       (  To_Unbounded_String ("String one."),
          To_Unbounded_String ("String two."),
          To_Unbounded_String ("String three."),
          To_Unbounded_String ("String four.")
      );
   end Make_Array;

or a return statement (I used Append, which is shorter):

function Make_Array return Four_Unbounded_Strings_Type is
   begin
      return A : Four_Unbounded_Strings_Type do
         Append (A (1), "String one.");
         Append (A (2), "String two.");
         Append (A (3), "String three.");
         Append (A (4), "String four.");
      end return;
end Make_Array;

You cannot compare it to C because C cannot return non-scalar objects from a function. It even cannot pass a non-scalar object as an argument. In both cases you need a pointer to.

6 Likes

Just to add some clarification, your function is returning a “copy” of A, not A itself. Unbounded_String will make copies on assignment, which the return statement is doing.

Or alternately it might be “building A inplace” (but I don’t recall if A is elegible for that or not). But either way, nothing dangling is being generated in this example barring a bug on Adacore’s implementation in GNAT or Unbounded_String. Language wise, everything is safe.

2 Likes

What people often fall over is trying to return an access to a local variable (which won’t exist after subprogram exit).

3 Likes

If somebody returns an access to a local variable, does that create a compile or runtime error, or does it do what C does and just intermittently do the wrong thing?

This is a compile-time error.
You can force it with things like unchecked-access and such, but that’s (typically) not ever what you want.

1 Like

What is a non-scalar object? Is it something that’s not an integer, real set or enumeration?

Yes, a struct in C. There is no arrays in C, but if they existed, then too. Even a scalar object cannot be passed mutable, so you need a pointer just to change an int.

Actually you can return a struct value from a function in C. The following code compiles:

struct mystruct {
  int i;
  char c;
};

struct mystruct f (void) {
   struct mystruct r;
   return r;
};

It’s just that (almost) nobody seems to do it, maybe because the compiler has to generate code to copy the struct and people want to avoid the cost of that.

It was added later (1989?). K&R did not have it. I think people rather do not trust the compiler.

Yep, in 89, which was the first ISO standard C I think? K&R definitely wasn’t a standardized version. I actually use a compiler that is based on K&R with additional features they’ve added over the years (so not standard C either).

No array in C ? The declaration int array_of_int[10]; allocates an array. (int *array_of_int; is not synonymous).

However, then the array_of_int value is a pointeur to the array. And declaring

void f(int a[])

And

void f(int *a)

Are synonymous.

Nope.

This proves it.
You see, because the thing called an “array” is really just a pointer, as you said “array_of_int value is a pointer” — because of the way that C uses an “array”, you can say index[array_of_int] to access array_of_int[index], showing how the “array” in C is really just semantic/syntactic sugar for pointer-arithmetic.

This “look at array_of_int funny and it becomes a pointer” property is, in fact, one of the reasons that C is so difficult to prove for safety, and why “buffer overflow” is so prominent in the CVE lists.

TL;DR — It’s reasonable to claim that, internal-labeling/-terminology aside, C doesn’t actually have an array type.

C does distinguish between arrays and pointers in some contexts. The function sizeof() comes to mind. If you call that on an array name in scope, it gives the actual size of the array. If you call it on a pointer, it gives the size of a pointer itself instead (beginners often misuse sizeof in this manner).

1 Like