What are the exact conditions under which type_name in sizeof(type_name) is evaluated? GCC evaluates f() in sizeof(int [(f(), 100)])
T

2

6

Context

The standard says (C17, 6.5.3.4 ¶2):

The sizeof operator yields the size (in bytes) of its operand, which may be an expression or the parenthesized name of a type. The size is determined from the type of the operand. The result is an integer. 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.

Confusingly, the wording doesn't distinguish between sizeof's two syntactic contexts:

  • sizeof unary-expression
  • sizeof(type-name)

I believe that type-name technically doesn't "have" a type, but rather "denotes" (names, specifies) one. Also, in the case of a type name as the argument, I would intuitively have phrased this in terms of evaluating all assignment-expressions within it – if not for accuracy, then for clarity.

In any event, I understand this wording to mean that in the case of sizeof(type), all expressions within type are evaluated exactly if (ie: if and only if) type denotes a VLA (variable-length array) type.

Problem

With the above in mind, the following code's last printf statement has surprising results:

#include <stdio.h>

void f(int i) {
    printf("side effect %d; ", i);
}

int main(void) {
    int n = 9;
    int a[n];
    printf("%zu\n", sizeof a);                   // 36
    printf("%zu\n", sizeof a[n++]);              // 4
    printf("%d\n", n);                           // 9
    printf("%zu\n", sizeof(int [n++]));          // 36
    printf("%d\n", n);                           // 10
    printf("%zu\n", sizeof(int [(f(0), 100)]));
      // side effect 0; 400 (type of operand: int, a non-VLA type)

    return 0;
}

(The argument int i of f – omitted in the question title – is intended for debugging purposes and for playing around, if one wants to distinguish different calls to that function.)

  • Plain and simple: int [(f(0), 100)] denotes type int [100], which is not a VLA type. So: Why does f(0) get evaluated?
  • More generally: What are the exact conditions under which type_name (or expressions within it) in sizeof(type_name) are evaluated?

Incidentally, sizeof(int [f(0), 100]) (without parentheses surrounding the comma expression denoting the array size) leads to an error, which I discuss in the following question: Why must a comma expression used as an array size be enclosed in parentheses if part of an array declarator?

Other references

Relevant places in the standard (C17 draft) for where the syntax of array declarators is discussed include: 6.7.7 ¶1, 6.7.6.2 ¶3.

This answer by Keith Thompson to a question about VLAs is relevant. But note that my question is not about VLAs per se (even though it uses them in the code above for contrastive purposes).

Trifocal answered 6/4 at 9:30 Comment(10)
First, int [100] is an array type. Its type is, quite literally, int [100]. That is basic to C and has always been that way. If you declare int a[100], then the type of a is int [100]. It's an array type. Second, in this context (f(0), 100) is an expression. The comma is a comma operator. When evaluated, it first evaluates f(0). It then evaluates 100, which of course is just 100.Kenwrick
@TomKarzes sizeof is supposed to be evaluated at compile-time, with only VLA arguments (or presumably types denoting such) triggering an exception.Trifocal
No, the argument to sizeof is a variable-length array, since (f(0), 100) is not a constant expression. Yes, the result will always be 100, but the first argument could have side effects, and in any case the compiler is not required to treat it as a constant expression. Constant expressions cannot contain function calls. Since it's not a constant expression, the declaration is for a VLA. You can test it by trying to use it in a static declaration, e.g. static int a[(f(0), 100)];. You'll get an error.Kenwrick
Consider the following contrastive example: printf("%zu\n", sizeof *(f(1), &a)); // side effect 1; 36 printf("%zu\n", sizeof (f(2), n)); // 4Trifocal
(f(0), 100) is not a constant expression. Period. Everything follows from that. What is it that you still don't understand?Kenwrick
@TomKarzes I can see that the standard (C17 draft) defines VLAs in a way that I wasn't aware of, in 6.7.6.2 ¶4. I asked the question because I find this definition unintuitive. In any event, would you like to post an answer to that effect?Trifocal
Re “I believe that type-name technically doesn't "have" a type, but rather "denotes" (names, specifies) one”: The standard text you quote does not say “have.” It says “is”: “If the type of the operand is a variable length array type, the operand is evaluated…” If the operand has the grammatical form (*type-name*), then it is a type, so it is evaluated…Selection
… To my recollection, the C standard does not explicitly say what it means to evaluate a type or a type-name, but C 2018 6.8 4 does say “… There is also an implicit full expression in which the non-constant size expressions for a variably modified type are evaluated…”Selection
@EricPostpischil Yes, but expanding this leads to "if the type of the type name [< operand] is a variable length array type", which is odd.Trifocal
Evaluation of VLA-typed operand of sizeof makes very little sense. It is enough to evaluate only size-expressions present in declarations of VLA-types. There is discussion about the actual meaning evaluation of sizeof. See www9.open-std.org/JTC1/SC22/WG14/www/docs/n3187.htmAnissa
P
5

According to the C17 Standard (6.5.3.4 The sizeof and _Alignof operators)

2 The sizeof operator yields the size (in bytes) of its operand, which may be an expression or the parenthesized name of a type. The size is determined from the type of the operand. The result is an integer. 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.

and (6.7.6.2 Array declarators)

4 If the size is not present, the array type is an incomplete type. If the size is * instead of being an expression, the array type is a variable length array type of unspecified size, which can only be used in declarations or type names with function prototype scope;146) such arrays are nonetheless complete types. If the size is an integer constant expression and the element type has a known constant size, the array type is not a variable length array type; otherwise, the array type is a variable length array type. (Variable length arrays are a conditional feature that implementations need not support; see 6.10.8.3.)

And at last (6.6 Constant expressions):

2 A constant expression can be evaluated during translation rather than runtime, and accordingly may be used in any place that a constant may be.

and

3 Constant expressions shall not contain assignment, increment, decrement, function-call, or comma operators, except when they are contained within a subexpression that is not evaluated.

As in this expression

sizeof(int [(f(0), 100)])

the sub-expression (f(0), 100) with the comma operator is not a constant sub-expression in the array declaration then there is declared a variable length array the size of which is evaluated at runtime.

Thus in all these calls of printf

printf("%zu\n", sizeof a);                   // 36
printf("%zu\n", sizeof(int [n++]));          // 36
printf("%zu\n", sizeof(int [(f(0), 100)]));

there are used variable length arrays. Their sizes can be determinated at runtime.

On the other hand, if you will write for example

printf("%zu\n", sizeof( (f(0), 100)));

then the function f() will not be called because in this case the comma operator is a subexpression of the constant expression with the sizeof operator.

Shortly speaking if in an array declaration the size of the array is not specified as a constant integer expression (and the comma operator is not a constant integer expression according to the quote above) then the array is a variable length array.

Popper answered 6/4 at 10:3 Comment(4)
What might a meaningful example of "except when they are contained within a subexpression that is not evaluated" be?Trifocal
@LoverofStructure It is the last example of the call of printf in my answer.Popper
In "except when they are contained within a subexpression that is not evaluated", does "they" refer to "constant expressions" or to the list of forbidden things? That is, for your last printf statement, the comma operator in (f(0), 100)) (or the whole expression) is "in a subexpression that is not evaluated"? If so, which exactly? It's not evaluated because its type is not a VLA type, unlike int [(f(0), 100)], which denotes a VLA type? In the latter, (f(0), 100) is evaluated because it's part of this VLA type?Trifocal
@LoverofStructure To determine the size of a variable length array its type specification must be evaluated. To determine the size of the expression (f(0), 100) it is enough to determine the type of the expression without its evaluation.Popper
S
3

I believe that type-name technically doesn't "have" a type, but rather "denotes" (names, specifies) one.

This is irrelevant. The text in C 2018 6.5.3.4 2 is not predicated on the operand “having” a type. It says the size is determined from the type of the operand. If the operand is ( type-name ), then the type of the operand is type-name.

(f(0), 100) is not an integer constant expression because 6.6 3 says:

Constant expressions shall not contain assignment, increment, decrement, function-call, or comma operators, except when they are contained within a subexpression that is not evaluated.

So int [(f(0), 100)] is a variable length array type because 6.7.6.2 4 says:

… If the size is an integer constant expression and the element type has a known constant size, the array type is not a variable length array type; otherwise, the array type is a variable length array type…

Therefore, 6.5.3.4 2 applies: “If the type of the operand is a variable length array type, the operand is evaluated;…” So, when sizeof(int [(f(0), 100)]) is evaluated, its operand (int [(f(0), 100)]) is evaluated. To my recollection, the C standard does not explicitly say what it means to evaluate a type-name, but 6.8 4 mentions:

… There is also an implicit full expression in which the non-constant size expressions for a variably modified type are evaluated…

So, for int [(f(0), 100)], it must be that a full expression containing (f(0), 100) is evaluated.

Selection answered 6/4 at 11:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.