Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Variable Length Arrays were mandatory in C99, optional in C11, and are mandatory again in C23.

I'm curious how these dynamics play out in less popular compilers. If a compiler implemented VLAs in C99, they almost certainly still have that feature for backwards compatibility even after they support C11.

Is there any compiler which appeared on the scene between C11 and C23, and during that window, chose not to support VLAs and thus C99? It's not like C11 itself was very widely adopted, precisely because of the long implementation and industry rollout windows.



Uh, are they mandatory again?

                       For these reasons, we propose to make variably-modified types
    mandatory in C23. VLAs with automatic storage duration remain an optional language
    feature due to their higher implementation overhead and security concerns on some
    implementations (i.e. when allocated on the stack and not using stack probing).
from N2778 (https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2778.pdf)


Thanks for the link. As I understood it, it's just the support for the syntax that is mandatory.

> Variable length arrays with automatic storage duration are a conditional feature that implementations need not support


The syntax and also the semantics. For instance, you can take the sizeof() a variably-modified type, or the offsetof() one of its fields, and the compiler has to do all the layout calculations implied by the type declaration at runtime. These features are partially what motivated the mandatory support. The only part that is still optional is using such types by value as stack variables (i.e., variables with automatic storage duration).


How do you create a variable array on the heap?

confused


With `malloc`, and converting the pointer to the correct type.

    void function(int n) {
      int (*arr)[n] = malloc(sizeof(*arr));
    }


wow, that's wild


That is exactly how you create any other type of object on the heap.


Snarky or just know a lot? It changes quite a bit how the compiler works. It has to know to make sure malloc gets the array element size argument multiplied in runtime by n. To a mere user it broke my mental shorthand of how a C compiler works.


> It has to know to make sure malloc gets the array element size argument multiplied in runtime by n

Um, C compilers already do that with arrays with compile-time lengths.

    #include <stdio.h>

    void main(void) {
        char x[20][30];
        printf("%zu\n%zu\n", sizeof(x), sizeof(x[0]));
    }
prints

    600
    30
so you can have "char *y = malloc(sizeof(x)); memcpy(y, x, sizeof(x));" and it must work since C89 at least. The main problem with VLAs is that they make exact the stack frame size unknown until runtime which complicates function prologues/epilogues but that's the problem in the codegen part of the backend, the semantics machinery is mostly in the place already.

P.S. And yes, uecker is a member of the ISO C WG14 and GCC contributor, according to his profile.


I think there is a real difference: in the static case, the compiler can just recurse into the type definition at any point, compute fully-baked sizes and offsets, and cache them for the rest of the compilation. But in the dynamic case, you end up with the novel dataflow of types that depend on runtime values, and more machinery is necessary to track those dependencies.

Of course, this runtime tracking has always been necessary for C99 VLA support, but I can easily see how it would be surprising for someone not deeply familiar with VLAs, especially given how the naive mental model of "T array[n]; is just syntax sugar for T *array = alloca(n * sizeof(T));" is sufficient for almost all of their uses in existing code. (In any case, it's obviously not "creating an object on the heap" that's the unusual part here!)


> more machinery is necessary to track those dependencies.

Well, is it much more machinery? IIRC doing

    void f(size_t n) {
        int x[n];
        n += 1;
        ...
does not resize x, so there is no dataflow dependency or rather, x depends on a hidden const variable so no additional dataflow analysis necessary.


Yet

  void f(size_t n, int cond) {
      if (cond) { n += 1; }
      int x[n];
      ...
does resize x depending on the value of cond, so the size can't necessarily be known until the point where the type (int[n] in this case) is named.

Also, the compiler has to make sure it keeps around implicit locals to store the variable layouts, so that code like

  void f(size_t n) {
      typedef int array[n];
      n += 1;
      array x;
      ...
functions as specified. This kind of pattern is one of the bigger things setting the feature apart from just "syntax sugar for alloca()".


It only works at the same level, the moment they get passed in as arguments, they decay into pointers even if using [].


I'm not tuned in to the nuances here but I note that whoever felt they understood it well enough to update Wikipedia summarized this as "The C23 standard makes VLA types mandatory again. Only creation of VLA objects with automatic storage duration is optional."

https://en.wikipedia.org/wiki/Variable-length_array#C99

If that phrasing isn't accurate I'm sure they'd appreciate an edit.


VLAs were optional in C11 and compilers that supported them in C99 also supported them in C11. The only important compiler not supporting VLAs is MSVC, but this compiler also did not support other features from C99. People using MSVC were stuck using an obsolete version of C for a long time. Recently this changed and MSVC started to support C11 and C17 - skipping C99.


Portable code couldn't use VLAs anyway, it was never supported in MSVC.


Portable C code could not use MSVC until recently, because MSVC was stuck with C89 because MS wanted everybody to switch to C++.


Among others.


VLA support is mandatory in C23? I'd like to know the rationale behind this decision. Can you provide any references? Thanks.


If I understand right, this is mandatory:

  void example(int n) { printf("%zu", sizeof(int[n])); }
  void example2(int n, int (*a)[n]) { }
This is optional:

  void example3(int n) { int x[n]; }




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: