Variable length array parameter size expression with side effects

variable length array c++
variable length array c vector
variable length array declaration not allowed at file scope c
const int array size c++
c variable length array in struct
whether array size can be of variable expression
error variable length array used
c++ dynamic array size

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;
}

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

Array declaration - cppreference.com, The number of those objects (the array size) never changes during the array lifetime. this qualifies the pointer type to which this array parameter is transformed constant expression, the declarator is for an array of variable size. If the size expression of a VLA has side effects, they are guaranteed to beĀ  A function parameter can be a variable length array. The necessary size expressions must be provided in the function definition. The compiler evaluates the size expression of a variably modified parameter on entry to the function. For a function declared with a variable length array as a parameter, as in the following, void f(int x, int a[][x]);

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.

EXP44-C. Do not rely on side effects in operands to , The sizeof operator yields the size (in bytes) of its operand, which may be an type of the operand is a variable length array type (VLA); then the expression is This rule is similar to PRE31-C. Avoid side effects in arguments to unsafe macros. the variable n used in each sizeof expression and instead increments n safelyā€‹Ā  A possible exception is when the type of the operand is a variable length array type (VLA); then the expression is evaluated. When part of the operand of the sizeof operator is a VLA type and when changing the value of the VLA's size expression would not affect the result of the operator, it is unspecified whether or not the size expression is

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     

C++ Code Smell: Size of variable length arrays should be positive, Size of variable length arrays should be positive. Code Smell "void *" should not be used in function parameters or return type Unevaluated operands should not have side effects Boolean expressions should not be gratuitous. In the above example, we see that function parameters of oneDArray and twoDArray are declared with variable length array type. The size of variable length array in c programming must be of integer type and it cannot have an initializer. We know that two array types are compatible if: Both arrays must have compatible element types.

The CERT C Coding Standard: 98 Rules for Developing Safe, , side. effects. in. operands. to. sizeof,. _Alignof,. or. _Generic. Some operators do do not pass an operand which would otherwise yield a side effect, as the side operator yields the size (in bytes) of its operand, which may be an expression or a variable length array (VLA) type, in which case the expression is evaluatedā€‹. Zero-length array declarations are not allowed, even though some compilers offer them as extensions (typically as a pre-C99 implementation of flexible array members). If the size expression of a VLA has side effects, they are guaranteed to be produced except when it is a part of a sizeof expression whose result doesn't depend on it:

The CERTĀ® C Coding Standard, Second Edition: 98 Rules for , side. effects. in. operands. to. sizeof,. _Alignof,. or. _Generic. Some operators do do not pass an operand which would otherwise yield a side effect, as the side operator yields the size (in bytes) of its operand, which may be an expression or a variable length array (VLA) type, in which case the expression is evaluatedā€‹. First, the "variable". A variable is simply an 'unknown value' that we want to calculate or assign a value to. Look at the following example. x = 100 y = 200 z = 300 [ x , y , z ] This has exactly the same result as the first expression: [ 100, 200, 300]. We are just presenting it in a different way. Variable are often handy just like we use

MISRA.SIZEOF.SIDE_EFFECT, Operand of sizeof has side effects. However, if the operand contains a variableā€‹-length array type then the array size expression will be evaluated if necessary. If the result can be determined without evaluating the array size expression then is the type ā€œarray of n pointers to function with parameter type array of v int32_t ā€. Variable-length arrays (VLAs) have a non-constant size that is determined (and which can vary) at run time; they are supported by the ISO C99 standard. Use of VLAs in the kernel has long been discouraged but not prohibited, so there are naturally numerous VLA instances to be found.

Comments
  • @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.
  • @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..
  • @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??
  • @JeanFrancois This is not a dup of the well-known question you linked.
  • @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.
  • 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.
  • @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.
  • 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).
  • 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).
  • 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.
  • @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. :-)