9

C defines at least 3 levels of "constant expression":

  • constant expression (unqualified)
  • arithmetic constant expression
  • integer constant expression

6.6 paragraph 3 reads:

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 does this mean 1,2 is not a constant expression?

Paragraph 8 reads:

An arithmetic constant expression shall have arithmetic type and shall only have operands that are integer constants, floating constants, enumeration constants, character constants, and sizeof expressions. Cast operators in an arithmetic constant expression shall only convert arithmetic types to arithmetic types, except as part of an operand to a sizeof operator whose result is an integer constant.

What are the operands in (union { uint32_t i; float f; }){ 1 }.f? If 1 is the operand, then this is presumably an arithmetic constant expression, but if { 1 } is the operand, then it's clearly not.

Edit: Another interesting observation: 7.17 paragraph 3 requires the result of offsetof to be an integer constant expression of type size_t, but the standard implementations of offsetof, as far as I can tell, are not required to be integer constant expressions by the standard. This is of course okay since an implementation is allowed (under 6.6 paragraph 10) to accept other forms of constant expressions, or implement the offsetof macro as __builtin_offsetof rather than via pointer subtraction. The essence of this observation, though, is that if you want to use offsetof in a context where an integer constant expression is required, you really need to use the macro provided by the implementation and not roll your own.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • In `1,2` I think `1` is a constant expression and `2` is a contant expression. – Pawel Zubrycki Feb 04 '11 at 00:17
  • @Chris: Unions are valid as compound literals, but I question whether the result can be an arithmetic constant expression. @Pawel: My question there was about the expression `1,2` which uses the comma operator, which for some reason I can't explain seems to have been excluded from the operators allowed in constant expressions. – R.. GitHub STOP HELPING ICE Feb 04 '11 at 00:27
  • @R.. - I found that out about a minute ago after checking for myself, and deleted my comment. – Chris Lutz Feb 04 '11 at 00:28
  • @R..: Because there are two constant expressions, not one. Comma is excluded to separate expressions. – Pawel Zubrycki Feb 04 '11 at 00:45
  • @Pawel - That doesn't make sense. The comma is an operator, and as such using it creates an expression like any other operator. There's no reason I can see why the comma operator should be disallowed from being in a constant expression, unless the standards community were assuming that no one would ever use it with a first argument that has no side effects. – Chris Lutz Feb 04 '11 at 01:04
  • @Chris: That makes perfect sense. Who would ever use comma in one expression? It is operator which separates expressions. There must be one that do that, because how otherwise you would separate them? – Pawel Zubrycki Feb 04 '11 at 01:23
  • 1
    @Pawel - `1,2` is a single expression. It is comprised of two integer literals, `1` and `2`, as arguments to the comma _operator_ (6.5.17) to create one expression. R..'s question is, if both `1` and `2` are constant expressions, why is `1,2` a non-constant expression? – Chris Lutz Feb 04 '11 at 01:28
  • A quick test with gcc 4.3.4 shows it rejects both `enum {a=(1,2)};` and `enum {b=(struct {int i;}){1}.i};`. Of course, this has no bearing on the standard. – Joseph Quinsey Feb 04 '11 at 15:33
  • One perhaps-useful consequence of the comma operator being disallowed in constant expressions is that you could use `(void *)(0,0)` to construct a cast of 0 (as an `int`) to a pointer, which is different from a cast of the integer constant expression 0 to a pointer (the latter is a null pointer). Unfortunately though the standard allows implementation-defined additional constant expressions, so I suppose this is not reliable.... – R.. GitHub STOP HELPING ICE Mar 26 '11 at 18:26
  • @R..: Is `(void*)(1-1)` considered by the standard to be synonymous with `(void*)0`? – supercat Sep 02 '14 at 15:20
  • @supercat: Yes. Any integer constant expression with value zero, regardless of type or how it's expressed, is a null pointer constant, and if you cast it to `void *` it's a null pointer constant with known fixed type (`void *`) and thus synonymous for all purposes except stringifying via the preprocessor. :-) – R.. GitHub STOP HELPING ICE Sep 02 '14 at 15:35

2 Answers2

2

Based on your reading, 1,2 isn't a constant expression. I don't know why it isn't, just that I agree with you that it isn't (despite the fact that it probably should be).

6.5.2 specifies compound literals as a postfix operator. So in

(union { uint32_t i; float f; }){ 1 }.f

The operands are (union { uint32_t i; float f; }){ 1 } and f to the . operator. It is not an arithmetic constant expression, since the first argument is a union type, but it is a constant expression.

UPDATE: I was basing this on a different interpretation of the standard.

My previous reasoning was that (union { uint32_t i; float f; }){ 1 }.f met the criteria for a constant expression, and was therefore a constant expression. I still think it meets the criteria for a constant expression (6.6 paragraph 3) but that it is not any of the standard types of constant expressions (integer, arithmetic, or address) and is therefore only subject to being a constant expression by 6.6 paragraph 10, which allows implementation-defined constant expressions.

I'd also been meaning to get to your edit. I was going to argue that the "hack" implementation of offsetof was a constant expression, but I think it's the same as above: it meets the criteria for a constant expression (and possibly an address constant) but is not an integer constant expression, and is therefore invalid outside of 6.6 paragraph 10.

Chris Lutz
  • 73,191
  • 16
  • 130
  • 183
  • I wonder how the left hand operand of the . operrator can be a constant expression. It's a union type as you said. Initialization is performed without using an arithmetic operator, but it looks mighty suspicious. – Windows programmer Feb 04 '11 at 06:09
  • @Windows programmer - See 6.5.2.5 for a description of compound literals (added in C99) and 6.6 for a description of constant expressions. As far as I can tell it meets the requirements. – Chris Lutz Feb 04 '11 at 06:34
  • Now I wonder if "abcdef"[2] is a constant expression with value 'c'. I'm pretty sure that used to be regarded as not being a constant expression, but I can't find the reason now. – Windows programmer Feb 04 '11 at 06:35
  • @Windows programmer: paragraph 7 lists the types of constant expressions: arithmetic, null pointer, address constant, and address plus offset. Of these, strings could only be valid in the last 2, and the rules for them explicitly forbid any access to the value of an object. So `"abcdef"[2]` is not a constant expression. – R.. GitHub STOP HELPING ICE Feb 04 '11 at 07:08
  • Accepted this answer because it seems to answer my specific questions. But I'd still be happy to hear more on the topic. – R.. GitHub STOP HELPING ICE Feb 04 '11 at 07:16
  • @R.. - Me too, especially more on why commas are discluded. Disallowing `int a[1,2];` seems kind of arbitrary when you permit so many other things. – Chris Lutz Feb 04 '11 at 07:24
  • Hmm, OK "abcdef"[2] is still not a constant expression, but (struct { char a; char b; char c; char d; char e; char f; }){ 'a', 'b', 'c', 'd', 'e', 'f' }.c is one. The latter expression might take more space in memory and might have other irrelevant differences. I'd better go clean out my cat's litter box -- it smells better than this situation does. – Windows programmer Feb 04 '11 at 07:37
  • @Windows programmer - A constant expression is evaluated at compile time, and takes no space in memory. I don't understand what you're adding to this discussion. – Chris Lutz Feb 04 '11 at 07:42
  • Footnote 82 on page 76 and example 6 on page 77 are non-normative but they clarify that the normative text lets a compound literal occupy storage. This is why it seems odd that the standard allows some storage accesses in constant expressions but doesn't allow other storage accesses in constant expressions. I agree with you that the standard's wording has that effect, I'm just wondering if the committee intended that effect. – Windows programmer Feb 04 '11 at 08:01
  • @Windows programmer - They do, but they don't specify that said object _has_ to have storage. I'd say that no storage counts as a form of automatic storage. – Chris Lutz Feb 04 '11 at 08:49
  • @Windows programmer: A quick test with gcc 4.3.4 shows that neither of your two examples is considered a constant expression by it. So gcc is at least consistent. Of course, this has no bearing on the standard. – Joseph Quinsey Feb 04 '11 at 14:40
  • @Windows programmer: after further reading, I am satisfied that no expression containing a compound literal can ever be a constant expression. gcc seems to agree. – R.. GitHub STOP HELPING ICE Feb 05 '11 at 03:55
  • @Chris: 6.6 paragraph 7 specifies only 4 types of constant expressions: arithmetic (which explicitly can only have arithmetic types as operands), null pointer constants (even more restricted) and address constants (with or without offset). I don't see any room for aggregate-type constant expressions. – R.. GitHub STOP HELPING ICE Feb 05 '11 at 05:17
  • @R.. - True. I think it falls under the category of "constant expressible" (i.e. meets the general criteria for a constant expression) but isn't any of the standard constant expressions (so would have to be implementation-defined as per 6.6§10.) Amending my answer to this (and other things. Gotta keep it going somehow.) – Chris Lutz Feb 05 '11 at 05:32
1

If 1,2 would be a constant expression, this would allow code like this to compile:

{ // code        // How the compiler interprets:
  int a[10, 10]; // int a[10];

  a[5, 8] = 42;  // a[8] = 42;
}

I don't know whether it is the real reason, but I can imagine that emitting an error for this (common?) mistake was considered more important than turning 1,2 into a constant expression.

UPDATE: As R. points out in a comment, the code about is not longer a compiler error since the introduction of VLAs.

Sjoerd
  • 6,837
  • 31
  • 44
  • @Chris More common than the need for `1,2` to be a constant-expression. – Sjoerd Feb 04 '11 at 06:07
  • I think the reason 1,2 isn't a constant expression is that int b[2] = { 1,2 }; is designed to initialize both elements not just initialize b[0] to 2. Meanwhile, { int a[10]; a[5, 8] = 42; } is valid code, maybe too simple for ioccc but maybe destined for thedailywtf. – Windows programmer Feb 04 '11 at 06:11
  • Also consider if an enum declaration has an expression specifying the value of an enum constant, and imagine if that expression could be 1,2. – Windows programmer Feb 04 '11 at 06:37
  • 1
    Obviously a comma in an initializer is not a comma operator; this has nothing to do with whether the comma operator is valid in a constant expression. Just like in a function argument list, you need parentheses to use a comma operator in such contexts. Note that the comma operator is perfectly valid in initializers for automatic variables, e.g. `int x = (1,2);` – R.. GitHub STOP HELPING ICE Feb 04 '11 at 06:40
  • 1
    @Sjoerd: I would say the need to use a comma operator in a constant expression could be very real with macros. Suppose you have a macro whose implementation under certain `#ifdef` doesn't use one of its arguments. In order to ensure that it still evaluates each argument exactly once, you might do something like `#define FOO(a,b) ((a),BAR((b)))`. If not for the arbitrary rule that comma is not valid in constant expressions, `FOO` could be used in static initializers. – R.. GitHub STOP HELPING ICE Feb 04 '11 at 06:42
  • "Note that the comma operator is perfectly valid in initializers for automatic variables, e.g. int x = (1,2);" -- not when an initializer consists of a list of assignment-expressions, which are not fully general expressions. – Windows programmer Feb 04 '11 at 06:51
  • "In order to ensure that it still evaluates each argument exactly once" -- that is trivial to do, regardless of whether macros are involved. (0*(a) + (b)). However, it gets evaluated at compile time not execution time, so I wonder what you gain. – Windows programmer Feb 04 '11 at 06:54
  • 1
    @Windows programmer: I'm assuming the same macro might be useful both for constant expressions and runtime use. And your `0*(a)` trick breaks as soon as I tell you `a` has a non-arithmetic type... :-) – R.. GitHub STOP HELPING ICE Feb 04 '11 at 07:15
  • @Sjoerd: actually your answer makes a great trick question: What's the difference between `int a[5];` and `int a[1,5];`? The former is an ordinary array and the latter is a VLA. :-) – R.. GitHub STOP HELPING ICE Feb 05 '11 at 05:54
  • @R. Great trick question! And yes, good point that int a[1,5] is not a compile error anymore in C since VLA have been introduced. – Sjoerd Feb 05 '11 at 13:34
  • I don't really understand the rules for when `sizeof` evaluates its operand, but I wonder if this could be used to create some atrociously obfuscated code... – R.. GitHub STOP HELPING ICE Feb 05 '11 at 13:51