do language features invoke a preference?
Absolutely, yes. Because of how C treats array expressions, passing an already-allocated array (whether static, automatic, or dynamic) as a parameter to a function is far more preferable to having the array allocate space and return a pointer to the caller.
First, the basics:
Except when it is the operand of the sizeof
or unary &
operators, or is a string literal used to initialize another array in a declaration, an expression of type "N-element array of T
" will be converted ("decay") to an expression of type "pointer to T
", and the value of the expression will be the address of the first element of the array1. IOW, array expressions lose their "array-ness" in most circumstances.
When you call a function with an array parameter, such as
int arr[N];
...
foo( arr );
the expression arr
in the call to foo will "decay" from type int [N]
to int *
, and what foo
receives will be a pointer value, not an array object:
void foo( int *arr ) // T a[N] and T a[] are treated as T *a
{ // in a function parameter declaration
...
}
You can't return an array object from a function - C doesn't allow functions to return array types (a prototype like int foo()[10]
is not allowed). Because of the conversion rule above, return arr;
winds up returning a pointer to the first element:
int *foo( void )
{
int arr[N];
...
return arr; // equivalent to return &arr[0]
}
Except arr
ceases to exist once foo
exits2, so that returned pointer value is invalid, and attempting to dereference it will lead to undefined behavior.
At this point, you have two choices - make the function responsible for dynamically allocating the target buffer and passing the result back to the caller, or make the caller responsible for allocating the target buffer (dynamically or not) and passing it to the function.
Remember that standard C does not define any sort of automatic garbage collection for dynamic memory - all dynamic memory must be explicitly deallocated by calls to free
.
The first option splits memory management duties between the function and the caller, which is not desirable. Whoever uses the function has to be aware that the function is dynamically allocating memory and that he is responsible for freeing that memory when he's done with it. If the user of the function doesn't pay attention to the documentation and doesn't free the memory returned from the function, he winds up with a memory leak.
The second option puts all the memory management responsibility on one party. The called function gets a pointer and the max buffer size - it doesn't have to worry about whether the pointed-to memory is statically, automatically, or dynamically allocated. This method is safer and more robust, which is why you see it more often in C code.
In Java, arrays are treated the same as pretty much any other data type (they don't "decay" to a pointer type), so it's more natural to have the function simply return an array to the caller. Even though arrays are allocated from the heap when they're created, you don't have to worry about deallocating them when you're done - they'll be automatically garbage-collected when their reference count goes to 0.
- "An array is just a pointer" is a mis-statement of this rule. Arrays are not pointers. Array expressions are converted to pointer expressions under most circumstances, but an array object is not a pointer.
- If we declare
arr
with the static
keyword, it will persist after the function exits, but the code will no longer be re-entrant.