43

While I was browsing cppreference, I saw a strange type array in function parameters like this:

void f(double x[volatile], const double y[volatile]);

So, What is the purpose of the volatile keyword appearing inside an array subscript? What does it do?

Matteo Italia
  • 115,256
  • 16
  • 181
  • 279
msc
  • 30,333
  • 19
  • 96
  • 184
  • 5
    The target of that syntax is simply to make the code unreadable ;) Will add that to the "never do that" in our coding guidelines :-) – Klaus Nov 19 '17 at 13:55

2 Answers2

40

The volatile keyword is used to declare an array type of a function parameter.

Here, double x[volatile] is equivalent to double * volatile x.

The cppreference says :

In a function declaration, the keyword volatile may appear inside the square brackets that are used to declare an array type of a function parameter. It qualifies the pointer type to which the array type is transformed. The following two declarations declare the same function:

void f(double x[volatile], const double y[volatile]);

void f(double * volatile x, const double * volatile y);

This syntax only valid in C language in function parameters.

msc
  • 30,333
  • 19
  • 96
  • 184
  • 27
    C++ syntax makes even less sense than I thought, I think we just hit a new high of uglyness. I hereby propose to ban alcohol from WG21 meetings. – Matteo Italia Nov 19 '17 at 13:14
  • 10
    @MatteoItalia That's not C++ syntax. –  Nov 19 '17 at 13:56
  • 8
    @hvd: my suggestion is then to be routed to WG14. – Matteo Italia Nov 19 '17 at 14:19
  • 6
    @MatteoItalia: C offers even more ugliness, since there's an exception to the rule: given `int foo(int myArray[static 5])`, the `static` qualifier does not mean the array is static, but rather guarantees that all of the storage from `myArray[0]`..`myArray[4]` may be presumed by the compiler to be readable without side-effects. – supercat Nov 19 '17 at 19:29
  • 3
    @supercat The guarantee is in fact that `x` pointers to an array element and the expressions `x[0]` through `x[4]` denote other elements of the same array (i.e. they do not read out of bounds of that array). The term "side-effects" has a specific meaning in C – M.M Nov 19 '17 at 22:32
  • 1
    @supercat: my suspect is that the C committee was angry because C++ had one extra legal-but-completely-unrelated (ab)use of the `static` keyword (`static` methods), so they had to invent one for their language. Now I think we are even (local static-storage duration variables and internal-linkage variables being the other two, shared between C and C++). – Matteo Italia Nov 20 '17 at 00:49
  • @M.M: The C Standard doesn't really say what it means for an array of a particular type to exist in allocated storage. If 32-byte block of memory from `calloc()` used as though it's a `float[]`, it should be possible to pass it to a function that expects a `uint32_t[static 8]` and populates some elements while ignoring others, even though at the time the pointer is passed no `uint_t[8]` would yet exist at that address unless one views allocated storage as a union of all possible types--a view that would have been workable under C89 but is broken by the "effective type" rule. – supercat Nov 20 '17 at 15:59
14

In general, this C (and C only!) feature allows to specify any type qualifier inside the array brackets; the exact standard quotation is:

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.

(C99, §6.7.5.3, ¶7, emphasis added)

This means that this is not just limited to volatile, but const and restrict are allowed as well (see Type qualifiers, §6.7.3 ¶1).

The point of this hack is essentially to let you add a type qualifier to the parameter (not to the element of the array) and still retain the array syntax for the declaration; without this syntax, you are forced to fall back to writing it out as a pointer (which is what it boils down to anyway except for the static case, which AFAIK doesn't have an equivalent pointer syntax).

I suspect that the idea is mostly to make the syntax slightly less awkward for multidimensional arrays; quoting §6.7.5.3 ¶21:

void f(double (* restrict a)[5]);
void f(double a[restrict][5]);
void f(double a[restrict 3][5]);

are all equivalent, but 2 and 3 may convey slightly better that this is not intended just a pointer, but an array, and still allowing some place to put the restrict qualifier.

Also, as said above, there seem to be no way to have something like

void f(double a[restrict static 3][5]);

(which "also specifies that the argument corresponding to a in any call to f must be a non-null pointer to the first of at least three arrays of 5 doubles", ibidem) with the "regular" pointer syntax.

Still, I'd stay away from this syntax; it's extremely obscure, rarely used (I don't think I ever needed to add a type qualifier to an array parameter - again, the parameter itself, not the element type; restrict is the only use case that can make sense) - and not portable to C++ (which is generally relevant if you are writing a library).

Matteo Italia
  • 115,256
  • 16
  • 181
  • 279
  • If `volatile` can make sense in your code, this is quite useful. – edmz Nov 19 '17 at 19:13
  • 3
    @edmz: a `volatile` *parameter* is definitely bizarre... `volatile` means that the compiler should stay alert that the value stored there may change behind his back (due e.g. to interrupt handlers, signals or other hardware stuff); this is generally either stuff that is shared between multiple actors (e.g. some global variable) or something at a fixed location in memory known to the hardware/OS; a parameter instead is local to the function by definition, so I don't really get what kind of usefulness could `volatile` have here. – Matteo Italia Nov 19 '17 at 21:40
  • @edmz: (remember: `double x[volatile]` means `double * volatile x`, so it's `x` that is `volatile`, not what it points to) – Matteo Italia Nov 19 '17 at 21:42
  • @MatteoItalia: `modify_asynchronously(&a);` – Nate Eldredge Nov 20 '17 at 01:10
  • @NateEldredge: copy it to a `volatile` local variable beforehand and use it instead. – Matteo Italia Nov 20 '17 at 06:08
  • *"and not portable to C++ (which is generally relevant if you are writing a library)"* - I'm sorry, but this argument is a non-starter IMO. Should a C++ library not use a well established practice of returning a unique_ptr "because C"? There's no harm in using C only features if your target is C exclusive. – StoryTeller - Unslander Monica Nov 20 '17 at 12:37
  • @StoryTeller: *if your target is C exclusive* - which doesn't happen that often nowadays; being callable by other, higher level languages is a major selling point of C libraries (through FFI-like libraries or with straight header inclusion in case of C++). If you only care about the 13 or so C99 applications in the world good for you, but generally when I write C libraries I prefer to keep my API entrypoints low-tech and C++ friendly on purpose, especially since it's quite cheap. I even saw many C++ libraries provide a C89 API exactly for the sake of compatibility with the rest of the world. – Matteo Italia Nov 20 '17 at 12:58
  • That's why I said _"if `volatile` can make sense"_. Having, say, an interrupt-driven spin loop (waiting for an update of the address, like `NULL -> meaningful_addr`) will help you guard against the compiler which could turn it to `while (1)`. Naturally, I am _not_ saying this is everyday's meal - actually you might live without knowing about it as there're still alternatives (you've pointed out one). – edmz Nov 20 '17 at 19:02