4

This question arose from a remark Eric Postpischil made in another thread.

I have a hard time understanding the use of variable length arrays (VLAs) as function parameters:

  • The array size is not checked.
  • The array size is not recoverable from the array because the standard type adjustment array -> pointer applies for VLAs as well, as the sizeof() calls below demonstrate; even though it would be perfectly possible to pass the whole array on the stack, just as VLAs are created on the stack when they are defined.
  • The size must be passed as an additional parameter, as with pointers.

So why does the language permit to declare function with VLA parameters if they do not offer any advantage and are adjusted like any other array argument to a pointer? Why is the size expression evaluated if it is not used by the language (e.g. to check the size of the actual argument) and is not obtainable inside the function (one still has to pass an explicit variable for that)??

In order to make clearer what I'm baffled about consider the following program (live example here). All function declarations are apparently equivalent. But as Eric pointed out in the other thread, the parameter size expression in the function's declaration is evaluated at run time. The size expression is not ignored.

It is unclear to me what benefit that would have because the size and its evaluation has no effect (beyond possible side effects). In particular, to repeat myself, that information cannot be used by code inside the function. The most obvious change would have been to pass VLAs on the stack like structures. They are, after all, usually also on the stack on the caller side. But like with arrays of constant length the type is adjusted already at declaration time to a pointer — all declarations below are equivalent. Nonetheless the useless and discarded array size expression is evaluated.

#include <stdio.h>

// Nothing to see here.
extern void ptr(int *arr);

// Identical to the above.
extern void ptr(int arr[]);

// Still identical. Is 1 evaluated? Who knows ;-).
extern void ptr(int arr[1]);

// Is printf evaluated when called? Yes.
// But the array is still adjusted to a pointer.
void ptr(int arr[printf("Call-time evaluation of size parameter\n")]){}

// This would not compile, so the declarations above must be equivalent.
// extern void ptr(int **p);

int main()
{
    ptr(0);
    ptr(0);

    return 0;
}
Peter - Reinstate Monica
  • 15,048
  • 4
  • 37
  • 62
  • 1
    @ChronoKitsune: This question is not primarily about array decay. Actually, it does not involve array decay at all; the specific issue involved is on the other side, parameter type adjustment. But that is not the primary issue. – Eric Postpischil Jan 26 '19 at 17:09
  • 1
    @ChronoKitsune The question *involves* array decay (or rather, parameter type adjustment), but it is not the primary focus of the question. The question asks about the time the size arguments are evaluated. I was also wondering about the rationale of allowing VLAs as function parameters if they are treated like ordinary arrays. You know, they could be passed by value, like structs; with fixed-size arrays that would make only limited sense, as outlined in [a Q&A I posted a while ago.](https://stackoverflow.com/a/33291699/3150802). – Peter - Reinstate Monica Jan 26 '19 at 21:45
  • @ChronoKitsune So why is the size of the VLA parameter evaluated when it is not used at all, and is not even the size of the actual argument?? – Peter - Reinstate Monica Jan 26 '19 at 21:46
  • 1
    @JeanFrancois This is not a dup of the well-known question you linked. – Peter - Reinstate Monica Jan 26 '19 at 21:54
  • 1
    @JeanFrancois:This question is **not** about when or why arrays are converted to pointers or when array parameter types are adjusted to pointer types. This question is about evaluation of size expressions in array parameters. Please do not mark questions as duplicates inappropriately. – Eric Postpischil Jan 26 '19 at 21:57
  • @PeterA.Schneider My mistake. The question should not be closed as a duplicate as I originally thought. –  Jan 26 '19 at 22:00

3 Answers3

3

... possible to omit the size variable and ... the size which will be evaluated at run time, ...; but to which avail?

The top level size info is lost , in the array argument is now a pointer parameter. Yet with 2D VLA function arguments which turn into pointer to a 1D array, code knows about that array dimension.

void g(size_t size, size_t size2, int arr[size][size2]) {
  printf("g: %zu\n", sizeof(arr));
  printf("g: %zu\n", sizeof(arr[0]));
}

int main(void) {
  int arr[10][7];
  g(10, 7, arr);
}

Output

g: 8   pointer size
g: 28  7 * int size

Alternatively, pass a pointer to the array.

void g2(size_t size, size_t size2, int (*arr)[size][size2]) {
  printf("g2: %zu\n", sizeof(arr));
  printf("g2: %zu\n", sizeof(*arr));
}

int main(void) {
  int arr[10][7];
  g2(10, 7, &arr);
}

Output

g2: 8    pointer size
g2: 280  10 * 7 * int size
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • I am aware of the syntax and semantics of 2D arrays and the irrelevance of the leftmost size in an array parameter (because it is actually a pointer declaration). My question was rather why VLAs are not handled smarter, e.g. passed by value like structs. – Peter - Reinstate Monica Jan 26 '19 at 21:59
  • @PeterA.Schneider To be fair, your edited post now has "My question was rather why VLAs are..." a few hours after 2 answers. Best to add clear questions in the initial post. I only found the one, paraphrased in this answer. – chux - Reinstate Monica Jan 26 '19 at 22:10
  • Sorry for the late edit. I only expressed bafflement in the first version of the question and added the explicit questions reflecting the bafflement only after I understood that it was unclear what I was asking, if anything (maybe it's venting and groping for a mental hold more than anything). The question was triggered by the realization that in modern C the size expression in the *declaration* of the function is evaluated at all (while in C89 it is ignored, because it is not even an array declaration, and the information is lost, as you explain correctly). – Peter - Reinstate Monica Jan 27 '19 at 05:21
  • 1
    Good point that you can have a versatile function taking a pointer to a 2D array whose second dimension is variable, too. *That* is indeed impossible with fixed size arrays (one would need an array of pointers for that which is more complicated and usually requires dynamic allocation). – Peter - Reinstate Monica Jan 27 '19 at 13:40
3

C 2018 6.9.1 discusses function definitions and tells us in paragraph 10:

On entry to the function, the size expressions of each variably modified parameter are evaluated…

Per 6.7.6 3, a variably modified type is one that has a variable length array type in its declarators, possibly nested. (So int a[n] is variably modified since it is a variable-length array, and the fixed-length int (*a[3])[n] is also variably modified since nested within it is a variable-length array type.)

In the case of void foo(int n, int a[][n]), we see the n must be evaluated because the compiler needs the size to calculate addresses for expressions such as a[i][j]. However, for void foo(int n, int a[n]), this need does not exist, and it is not clear to me whether the text quoted above applies to the type of the parameter before adjustment (int a[n]) or after adjustment (int *a).

As I recall, when this first came to my attention a few years ago, I found both a compiler that did evaluate the expression and a compiler that did not, for a direct array parameter. Calling foo which had been defined with void foo(int a[printf("Hello, world.\n")]) {} would or would not print the string depending on the compiler. Currently, compiling with Apple LLVM 10.0.0 and clang-1000.11.45.5 on macOS 10.14.2 does print the string. (As mentioned above, for a nested array type, the expression must be evaluated, and all compilers I tried exhibited that. Unfortunately, I do not currently recall which compilers these were.)

It is not clear the array size is useful to the compiler. This aspect of the C standard might not have been completely worked out. There is a feature that adds some meaning to the size; if the size is declared with static:

void foo(int a[static SomeExpression]) { … }

then, per 6.7.6.3 7, a must point to at least SomeExpression elements. This means a must not be null, which the compiler can use to optimize some things. However, I do not have any examples of how the number itself can assist with optimization or other aspects of compilation.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • The question was of course a spin-off of your remark in the other thread. Remarkable C feature and unknown to me until today. Thanks for answering. – Peter - Reinstate Monica Jan 26 '19 at 22:00
  • 1
    @PeterA.Schneider: `int (puts) (); int main(p, q) int p; char *q [(puts) (&*"Hello, world.")]; {}` is a complete C program that compiles with no warnings in Clang even with `-std=c11 -pedantic -Wall`. Never use it. :-) – Eric Postpischil Jan 26 '19 at 22:10
1

I do not see any practical use of the VLAs as function parameter.

Only pointers to the the arrays make sense as make traversing the arrays easier and give correct size information

int foo(int (*p)[2])
{

    printf("sizeof int = %zu\n", sizeof(int));
    printf("p + 0:%p\n", (void *)p);
    printf("p + 1:%p\n", (void *)(p + 1));
}

void g(size_t size, size_t size2, int (*arr)[size][size2]) 
{
  printf("g: %zu\n", sizeof(*arr));
  printf("g: %zu\n", sizeof(*arr[0]));
}


int main()
{
    foo(0);
    g(5,5,0);

    return 0;
}

sizeof int = 4                                                                                                                                                                                                                                              
p + 0:(nil)                                                                                                                                                                                                                                                 
p + 1:0x8                                                                                                                                                                                                                                                   
g: 100                                                                                                                                                                                                                                                      
g: 20     
0___________
  • 60,014
  • 4
  • 34
  • 74