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.
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.
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?
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.
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).
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).