Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C function: array argument length

This question builds on this one, which describes how the following are equivalent:

int f(int a[10]) { ... } // the 10 makes no difference
int f(int a[]) { ... }
int f(int *a) { ... } 

In documentation about Function prototype scope, the following example is provided:

int f(int n, int a[n]); // n is in scope, refers to first param

This lead me to question to what extent the following are equivalent:

// 1.
int f(int n, int a[n]) { ... }
int f(int n, int *a) { ... }
// my guess: exactly equivalent

// 2.
int x = 10; int f(int a[x]) { ... }
int x = 10; int f(int *a) { ... }
// my guess: exactly equivalent

// 3.
int f(int a[n], int n) { ... }
int f(int *a, int n) { ... }
// my guess: not equivalent: first f won't compile, second will

Concluding question:

  • provided both compile, are int f(int arr[n]) and int f(int *arr) always equivalent?
  • why does the compiler want to lookup identifier n, even though its value will not be used.
    • my guess: the only reason to have included the n in the source code was as documentation to indicate the minimum length the array should have. Ensuring that n is in scope, and has correct type, ensures this documentation makes sense
like image 659
user615536 Avatar asked Oct 27 '25 08:10

user615536


2 Answers

provided both compile, are int f(int arr[n]) and int f(int *arr) always equivalent?

No. The C standard is defective on this point; it does not say whether the n in int arr[n] is evaluated or not.

int arr[n] nominally declares a variable-length array (supposing n is an identifier for some object, not a macro for a constant). The C standard says that sizes of variable length arrays are evaluated, and it also says the parameter declaration int arr[n] is automatically adjusted to be int *arr, but it is not clear on which order these occur in. GCC and Clang differ on this point; Clang evaluates the array size, but GCC does not. For an unqualified object n, this makes no difference, but, if n is volatile or the array size is some other expression with side effects, it does make a difference.

why does the compiler want to lookup identifier n, even though its value will not be used.

The compiler needs to parse the code to determine its grammatical structure. There are valid keywords that can appear there, such as static, const, restrict, volatile, and _Atomic. Other keywords may be an error. Or you could have some other expression, such as n * 3. If you do have n there and it is an identifier for an int, the compiler can ignore it. If you have n there and it is an identifier for a function, the compiler should issue a diagnostic message. Whatever is there, the compiler needs to analyze it and either accept it or issue an appropriate diagnostic message.

Array sizes in parameter declarations are more useful in multidimensional arrays, as in void f(size_t m, size_t n, int Array[m][n]). In this, the m is as above and may be omitted, but the n is used and is critical.

like image 62
Eric Postpischil Avatar answered Oct 28 '25 22:10

Eric Postpischil


For starters according to the C Standard (6.7.6.3 Function declarators (including prototypes)):

7 A declaration of a parameter as "array of type" shall be adjusted to "qualified pointer to type", where the type qualifiers (if any) are those specified within the [ and ] of the array type derivation. If the keyword static also appears within the [ and ] of the array type derivation, then for each call to the function, the value of the corresponding actual argument shall provide access to the first element of an array with at least as many elements as specified by the size expression.

and secondly (6.7.6.2 Array declarators )

4 If the size is not present, the array type is an incomplete type. If the size is * instead of being an expression, the array type is a variable length array type of unspecified size, which can only be used in declarations or type names with function prototype scope;146) such arrays are nonetheless complete types. If the size is an integer constant expression and the element type has a known constant size, the array type is not a variable length array type; otherwise, the array type is a variable length array type. (Variable length arrays are a conditional feature that implementations need not support; see 6.10.8.3.)

The highlighted phrase in the first quote from the C Standard is applied to parameter declarations having variable length array types, non-variable array types (when the size of an array is specified with an integer constant expression) and incomplete array types.

In this example of function declarations

int f(int a[10]) { ... } // the 10 makes no difference
int f(int a[]) { ... }
int f(int *a) { ... } 

parameters having an array type are adjusted by the compiler to pointers to element types of the arrays. So all three function declarations (pay attention to that only one definition must be provided) declare the same one function.

The same is valid for this example of function declarations

// 1.
int f(int n, int a[n]) { ... }
int f(int n, int *a) { ... }

In the first declaration the second parameter has a variable length array type. You may also write the declaration (that is not the function definition) the following way

int f(int , int [*]);

As for the third example

// 3.
int f(int a[n], int n) { ... }
int f(int *a, int n) { ... }

then if n is not declared in file scope before the first function declaration then the compiler evidently will issue an error that the identifier (or name) n is not declared. However if the variable n will be declared before the function declaration then the above two declarations will be equivalent.

Here is a demonstration program

//gcc 7.4.0

#include  <stdio.h>

int f( int [*], int );

int f( int *, int );

int n = 10;

int f(int a[n], int n) 
{
    for ( int i = 0; i < n; i++ )
    {
        printf( "%d ", a[i] );
    }
    
    putchar( '\n' );
    
    return 0;
}

int main(void)
{
    int a[20];
    const size_t N =  sizeof( a ) / sizeof( *a );
             
    for ( size_t i = 0; i < N; i++ )
    {
        a[i] = i;
    }
             
    f( a, N );              
}

The program output is

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
like image 32
Vlad from Moscow Avatar answered Oct 28 '25 23:10

Vlad from Moscow



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!