6

I wrote the following as part of an answer to a question about sizeof, and its behavior with regard to C99 VLAs:

It would not be difficult to intentionally create a case where the semantics of count_of would effectively differ for a VLA but it might be difficult to create a readable, easily understandable/maintainable, and useful case (I haven't tried to).

After thinking about this, I'm not sure if this statement is actually true. To create a VLA in the first place, the compiler must first determine the amount of space the VLA will require.

For sizeof, we know that

If the type of the operand is a variable length array type, the operand is evaluated; otherwise, the operand is not evaluated and the result is an integer constant. (6.5.3.4/2)

and although VLA size is obviously a run-time determination, after evaluation (if any, including any side effects) of the size expression for the VLA declarator:

The size of each instance of a variable length array type does not change during its lifetime. Where a size expression is part of the operand of a sizeof operator and changing the value of the size expression would not affect the result of the operator, it is unspecified whether or not the size expression is evaluated. (6.7.5.2/2)

So, given

#define count_of(arr)  (sizeof(arr)/sizeof(arr[0]))

is there any case where the actual effective behavior of a macro such as this could differ for a VLA vs. an array declaration where the array-size expression is a constant expression (i.e. a plain-old pre-C99 fixed-size array)?

Community
  • 1
  • 1
frasnian
  • 1,973
  • 1
  • 14
  • 24

1 Answers1

5

The obvious answer is when arr is an expression containing a side effect. If sizeof's argument is evaluated, the side effect takes place. If it isn't evaluated, there is no side effect.

#include <stdio.h>
#define LENGTHOF(arr) (sizeof(arr) / sizeof(*(arr)))
void f() {
  puts("f");
}
int main() {
  const int n = 4;
  int array[n];
  return LENGTHOF(*(f(), &array)) - 4;
}

This is valid in C99, where array is a VLA, and in C++, where n is a constant expression and array is not a VLA. In C99, this prints f. In C++, this does not print anything.

  • Ouch. The comma operator strikes again... Edit : why the address-of on `array` ? Edit : got it. – Quentin Jan 20 '15 at 08:55
  • If you don't like the comma operator, you could have `int array[n][n];`, `int i = 0;`, and then `LENGTHOF(array[i=1])` for a similar effect. –  Jan 20 '15 at 08:59
  • But if I compile with `gcc -std=c90 -Wall`, this prints `f`. Is `gcc` wrong here? (gcc v4.7.2 on debian) – frasnian Jan 20 '15 at 09:29
  • @frasnian In C90, this program is not valid, but GCC is allowed to accept VLAs as an extension in C90 mode. You should get a message about it if you add `-pedantic` to the command-line options. –  Jan 20 '15 at 10:14
  • @hvd: Gotcha, thanks. I assumed (always a bad idea) that the bit in the gcc docs about disabling "certain GNU extensions...are disabled" when using `-std=c90` would cover VLAs. Also, once I got my thought process stuck in how VLAs are implemented and the evaluation ramifications with respect to VLAs, I started completely skipping over the evaluation bit in regards to `sizeof`, despite my original reasons for considering it in my answer in the linked question - and even quoting it again here - doh! (slapping self on forehead). – frasnian Jan 20 '15 at 10:31
  • @frasnian Yeah, `-std=c90` only disables those extensions that could possibly affect a valid C90 program, such as `//` comments (`int main() { return 0 //**/ 1; }`), new C99 keywords (`int main() { int inline = 0; return inline; }`), hex float literals (`#define A(x) B(x)`/`#define B(x) #x`/`#define x`/`int main() { return A(0.0p+x)[5]; }`), basically anything that could cause *any* C90 program, no matter how perverse, to be rejected or misinterpreted. That's not the case with VLAs, there is no valid C90 program that becomes invalid because of the addition of VLAs to the language. –  Jan 20 '15 at 10:41
  • I wonder why the Standard specifies that the argument to `sizeof` be evaluated in cases where no *type* is being created *within the sizeof expression itself*, or why the Standard would allow new types to be created within a `sizeof` expression or `typedef` in such a fashion as to depend upon arbitrary run-time evaluated expressions? – supercat Jun 11 '15 at 20:41
  • @supercat Probably simply because that was the easiest to specify, and corner cases such as these weren't really considered. After that, compilers implemented the specification to the letter, and now we're sort of stuck with it. –  Jun 11 '15 at 21:20
  • @hvd: I don't know what would have been difficult about specifying that the only time an array size could be a non-constant expression would be when declaring a variable of automatic scope. As it is, the present rules even allow *typedef* to be an executable statement; I'm not sure whether the behavior of gcc forbidding a `goto` or `case` label to bypass such a typedef is part of the standard, or just an effort at sanity, since I don't think I've seen anything in the standard contemplating a `typedef` as an executable statement. – supercat Jun 11 '15 at 21:31
  • @supercat It's right there in the first paragraph of the description of the `goto` statement: "A goto statement shall not jump from outside the scope of an identifier having a variably modified type to inside the scope of that identifier." And the standard does have `typedef` for one of the examples of VLA types, so that was definitely considered. –  Jun 11 '15 at 21:36
  • @hvd: Still seems needlessly complicated. Actually, I think `alloca` should have been salvaged by adding a corresponding `freea` function which was could be used in LIFO order to reverse allocations, along with `__STDC_ALLOCA_RELEASE` which would indicate whether `freea` would automatically be performed before a function return, `longjmp` call, both, or neither. Any compiler could be made to comply with such a standard merely by defining `alloca` as a synonym for `malloc`, `freea` as a synonym for `free`, and `__STDC_ALLOCA_RELEASE` as 0, though some implementations might offer faster... – supercat Jun 11 '15 at 21:45
  • ...ways to perform those functions for code that could guarantee a LIFO usage pattern. – supercat Jun 11 '15 at 21:48
  • @supercat That would've worked, I'll give you that. Opinions will vary on whether it would've made for a better language. My personal opinion is that VLAs in their C99 form do add some complexity to the language, but that that complexity is worth it because it makes the most common uses of VLAs work as expected, in particular when using `sizeof` on a previously-declared VLA object. But I can understand why you may feel differently. –  Jun 11 '15 at 21:58
  • @hvd: Neither variable-length arrays nor `alloca` as commonly implemented can be used to create a value stack, but statements for stack allocation/free could have done so easily, and could have been designed in such a way that code using them could run on older compilers merely by defining a compatibility header. As for VLAs, I wouldn't particularly oppose them if they didn't needlessly mangle the semantics of `sizeof`, though I'd consider them to be much less important than other features I would regard as "missing" from C (e.g. a family of fixed-sized unsigned types whose behavior... – supercat Jun 11 '15 at 22:06
  • ...is independent of the size of `int`, or an efficient means of defining loosely-overflow-checked integer arithmetic (such that e.g. `__overflow_checked{for(i=0; i – supercat Jun 11 '15 at 22:17