Is using array arguments in C considered bad practice?

Viewed 1029

When declaring a function that accesses several consecutive values in memory, I usually use array arguments like

f(int a[4]);

It works fine for my purposes. However, I recently read the opinion of Linus Torvalds.

So I wonder if the array arguments are today considered obsolete? More particularly,

  • is there any case where the compiler can utilize this information (array size) to check out-of-bound access, or
  • is there any case where this technique brings some optimization opportunities?

In any case, what about pointers to arrays?

void f(int (*a)[4]);

Note that this form is not prone to "sizeof" mistakes. But what about efficiency in this case? I know that GCC generates the same code (link). Is that always so? And what about further optimization opportunities in this case?

2 Answers

If you write

void f(int a[4]);

that has exactly the same meaning to the compiler as if you wrote

void f(int *a);

This is why Linus has the opinion that he does. The [4] looks like it defines the expected size of the array, but it doesn't. Mismatches between what the code looks like it means and what it actually means are very bad when you're trying to maintain a large and complicated program.

(In general I advise people not to assume that Linus' opinions are correct. In this case I agree with him, but I wouldn't have put it so angrily.)

Since C99, there is a variation that does mean what it looks like it means:

void f(int a[static 4]);

That is, all callers of f are required to supply a pointer to an array of at least four ints; if they don't, the program has undefined behavior. This can help the optimizer, at least in principle (e.g. maybe it means the loop over a[i] inside f can be vectorized).

Your alternative construct

void f(int (*a)[4]);

gives the parameter a a different type ('pointer to array of 4 int' rather than 'pointer to int'). The array-notation equivalent of this type is

void f(int a[][4]);

Written that way, it should be immediately clear that that declaration is appropriate when the argument to f is a two-dimensional array whose inner size is 4, but not otherwise.

sizeof issues are another can of worms; my recommendation is to avoid needing to use sizeof on function arguments at almost any cost. Do not contort the parameter list of a function to make sizeof come out "right" inside the function; that makes it harder to call the function correctly, and you probably call the function a lot more times than you implement it.

Unless it is the operand of the sizeof or unary & operators, or is a string literal used to initialize a character 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 in the array.

When you pass an array expression as an argument to a function:

int arr[100];
...
foo( arr );

what the function actually receives is a pointer to the first element of the array, not a copy of the array. The behavior is exactly the same as if you had written

foo( &arr[0] );

There's a rule that function parameters of type T a[N] or T a[] are "adjusted" to T *a, so if your function declaration is

void foo( int a[100] )

it will be interpreted as though you wrote

void foo( int *a )

There are a couple of significant consequences of this:

  • Arrays are implicitly passed "by reference" to functions, so changes to the array contents in the function are reflected in the caller (unlike literally every other type);

  • You can't use sizeof to determine how many elements are in the passed array because there's no way to get that information from a pointer. If your function needs to know the physical size of the array in order to use it properly, then you must pass that length as a separate parameter1.

In my own code, I do not use array-style declarations in function parameter lists - what the function receives is a pointer, so I use pointer-style declarations. I can see the argument for using array-style declarations, mostly as a matter of documentation (this function is expecting an array of this size), but I think it's valuable to reinforce the pointer-ness of the parameter.

Note that you have the same problem with pointers to arrays - if I call

foo( &arr );

then the prototype for foo needs to be

void foo( int (*a)[100] );

But that's also the same prototype as if I had called it as

void bar[10][100];

foo( bar );  

Just like you cannot know whether the parameter a points to a single int or the first in a sequence of ints, you can't know whether bar points to a single 100-element array, or to the first in a sequence of 100-element arrays.


  1. This is why the gets function was deprecated in after C99 and removed from the standard library in C2011 - there's no way to tell it the size of the target buffer, so it will happily write input past the end of the array and clobber whatever follows. That's why it was such a popular malware exploit.

Related