10

In standard C (I mean C99 or C11) we have the so-called integer constant expressions, which are constant expressions whose operands are all constant integers. There are other constraints, as to avoid comma operators in the expression.

However, other non-integer objects (even non-constant) are allowed in some special cases.
For example, if the sizeof operator is applied to an object whose size is known in translation time, this is allowed as part of an integer constant expression (note that sizeof always return an integer value).

Moreover, the explicit cast to an integer type is sometimes allowed too.
The standard C99 establish the following rule:

Standard C99, Section 6.6(par.6):

An integer constant expression) shall have integer type and shall only have operands that are integer costants, enumeration constants, character constants, sizeof expressions whose results are integer constants, and floating constants that are the immediate operands of casts.

Standard C99

My question is: Which is the exact meaning of "floating constants that are the immediate operands of casts"?

A floating constant is something like 3.14e+3, or well 0x2.11p-5.
That is, is not a general constant expression of type float, but only a floating point literal.
Then, I understand that only something like this is allowed in the definition above:

 (int) 3.14

but are not allowed operations involving floating literals.
This exclude cases as the following:

 (int) -3.14  /* The minus sign is not part of the constant: it is an operator */
 (int) (3.14) /* The parenthesis are an operator acting on the literal 3.14 */

The last case does not need to perform any floating point arithmetical operations in translation time, and it is equivalent to have the same without parenthesis: (int) 3.14.
However, it is not the immediate operand of a cast.

So, do we have to consider that (int) (3.14) is [part of] a valid integer constant expression or not (according to definition)?

On the other hand, the compiler GCC (with options: -std=c99 -pedantic-errors) gives me that (int) (3.14) is a valid integer constant expression, for example in a declaration as the following:

 #define BITW (int) (3.14)
 struct { unsigned bitfield: BITW } test;  // Translation success

(However, by doing #define BITW (int) (+3.14) it fails to translate, as expected).

pablo1977
  • 4,281
  • 1
  • 15
  • 41
  • 1
    Just a note about `sizeof`, it's a compile-time only operator, so it's always an "integer constant expression". I don't think there's any type whose size could *not* be an integer. – Some programmer dude Feb 23 '14 at 18:52
  • 3
    @JoachimPileborg: That's not true. See e.g. http://stackoverflow.com/q/14995870/978917 – ruakh Feb 23 '14 at 18:57
  • @ruakh Always those damn VLAs, always forget about them. Been doing to much C++ lately I guess. :) – Some programmer dude Feb 23 '14 at 19:00
  • @Joachim Pileborg: This is not true. In C99 there are VLA types, and they cannot appear with `sizeof` in an integer constant expression. – pablo1977 Feb 23 '14 at 19:00
  • 1
    That said, it would be fair to say that taking the `sizeof` of an object whose size is known at compile-time is certainly not "non-constant". – millimoose Feb 23 '14 at 19:02
  • Not only that; VLAs + sizeof provide a very powerful way to evaluate "is expression X an integer constant expression?" programmatically. – R.. GitHub STOP HELPING ICE Feb 23 '14 at 19:03
  • @millimoose: In the sense of "integer constant expressions", such a `sizeof` *can be* a non-integer-constant-expression. For example, in: `const int n = 42; sizeof(char[n]);` the result of not an integer constant expression because `char[n]` has VLA type (even though `n` is known at compile time). – R.. GitHub STOP HELPING ICE Feb 23 '14 at 19:04
  • @R.. – Yes, but for the purpose of language lawyering, it matters what the operand is. That is, the specification doesn't go: "since we can't **ensure** `sizeof(…)` is constant, it's **never** considered a constant." (My guess is that legacy considerations alone would prevent that, what with VLAs being a later addition.) – millimoose Feb 23 '14 at 19:06
  • 'The minus sign is not part of the constant: it is an operator' -- this is wrong. A + or - sign is part of a floating constant. – david.pfx Mar 01 '14 at 15:02
  • @david.pfx: I think you are not right. In the standard, the syntax of a floating point constant doesn't specify the presence of a + or - sign. So, they are unary operators. – pablo1977 Mar 01 '14 at 17:46
  • @david.pfx: Moreover, the `sign-part` corresponds only to the `exponent-part` of the floating constant. – pablo1977 Mar 01 '14 at 17:48
  • Oops. Mea culpa. So if a sign is not part of the `floating constant`, you cannot use negative values anywhere a floating constant is required? That seems like a deficiency in the standard, and one most likely only honoured in the breach. – david.pfx Mar 02 '14 at 00:05
  • @david.pfx: if you take the negative of a floating constant, it is an **arithmetic** constant expression, but you cannot cast it to an integer as part of an intended **integer** constant expression. However, if you cast a floating constant to integer, and then you take its negative, you can obtain an analogous behaviour. So, it is not really necessary in a ICE. – pablo1977 Mar 02 '14 at 00:18

2 Answers2

5

While poor choice of wording may imply that (int) (3.14) does not qualify as an integer constant expression, I think this is simply a bug in the wording. There is an analogous issue that's arguably a bug with the specification of NULL: it's allowed to be any null pointer constant, where null pointer constant is defined as either:

  1. an integer constant expression with value 0, or
  2. such an expression cast to void *.

However, to be useful as a definition for NULL, the expression should be properly parenthesized; all existing implementations I'm aware of do this. But by a strict reading, while (void *)0 is a null pointer constant, ((void *)0) is not (it's the application of the parentheses operator to a null pointer constant).

Ideally language should be added somewhere in the specification clarifying that parentheses do not affect things like this.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • I feel like C99 `6.5.1 p5` covers this. – Shafik Yaghmour Feb 23 '14 at 18:59
  • I feel like that's where the text covering this issue should be, but it's missing. – R.. GitHub STOP HELPING ICE Feb 23 '14 at 19:00
  • I guess it depends how you read it, I could see arguments both ways. – Shafik Yaghmour Feb 23 '14 at 19:01
  • 1
    @ShafikYaghmour: I disagree because 6.6p6 says: "floating constant", 6.5.1 defines "primary expressions" and 6.4.4 defines "floating constant" as a floating "literal". – pablo1977 Feb 23 '14 at 19:11
  • @ShafikYaghmour 6.5.1p5 fairly unambiguously gives `((void *)0)` the same type (`void *`) and value (a null pointer) as `(void *)0`, but there is nothing there to even remotely suggest that any other properties (such as null-pointer-constant-ness) are transferred, even though it is widely accepted that `((void *)0)` is indeed intended to be a null pointer constant. –  Feb 23 '14 at 19:12
  • 1
    Like @hvd said, of course they have the same type and value. The question is whether the expression qualifies as a *null pointer constant* which has a specific definition, just like *integer constant expression* does, independent of the value and type of the expression. – R.. GitHub STOP HELPING ICE Feb 23 '14 at 19:13
  • @R: So far it seems to me that you are in the right understanding of this topic. – pablo1977 Feb 23 '14 at 19:14
  • 1
    By the way, a general exception saying that parentheses don't matter would also allow `char s[] = ("hello");`, which is currently treated as a constraint violation by GCC (silently accepted by default, a warning with `-pedantic`, an error with `-pedantic-errors`), but silently accepted by clang regardless of options. –  Feb 23 '14 at 19:24
  • @hwd, no this example isn't one. An initializer of an array can't be an expression. There is a special case made that string literals are allowed as initializers for character arrays. Putting `()` arround it effectively *makes* it an expression, and so a constraint violation. – Jens Gustedt Feb 24 '14 at 12:34
  • @Jens: How is that different from putting `()` around `3.14` making it an expression rather than a floating point literal (aka floating point constant, which is a confusing name)? – R.. GitHub STOP HELPING ICE Feb 24 '14 at 13:20
  • It didn't wanted to say that it makes it different from that. I only wanted to point out that for the case of array initialization with string literals there is an additional difficulty. The constraint part for initialization (6.7.9 p14) explicitly states "string literal" and not something like compile time constant `char`-array. `("hello")` is such constant `char`-array and not a string literal, I think. Also from the standard it is not clear if `()` does array to pointer conversion. As far as I remember there are different opinions about that. – Jens Gustedt Feb 24 '14 at 14:12
  • @Jens: If `()` caused the array to decay, using pointer-to-array types would be very difficult (e.g. `(*parray)` is needed to bind the `*` more tightly than a subsequent `[]`...) and the common `sizeof(array)` that people usually use in place of `sizeof array` would be wrong. – R.. GitHub STOP HELPING ICE Feb 24 '14 at 15:03
  • @R.., I didn't say that this is the correct interpretation, but `()` is not listed under the exceptions for array to pointer decay. That is why some people argue for that. – Jens Gustedt Feb 24 '14 at 16:29
  • Technically I can't see where `()` constitutes an operator; I suspect this is part of why the standard does not address it properly... – R.. GitHub STOP HELPING ICE Feb 24 '14 at 16:49
  • 1
    @R..: Good observation. However, in the note **74)** (the only place where the standard talks about **precedence of operators**) says: `[...] an operand contained between any of the following pairs of operators: grouping parentheses () (6.5.1), [...]`. This is assuming that `()` is an **operator**. However, it is the only explicit place where this is clear. Besides, an operator is something that performs some actions on their operands. In **6.5.1p5** is described a parenthesized expression as performing actions that **changes nothing**. Is implicit there that `()` is an operator? Probably yes. – pablo1977 Mar 01 '14 at 16:55
  • C11 7.1.2/5 "Any definition of an object-like macro described in this clause shall expand to code that is fully protected by parentheses where necessary, so that it groups in an arbitrary expression as if it were a single identifier." means the parentheses are required (otherwise `sizeof NULL` fails for `#define NULL (void *)0` etc.), it's not just a QoI issue as you seem to suggest. In light of this,I think it is reasonable to interpret 7.19 as saying that NULL is permitted to expand to a null pointer constant surrounded by parentheses, even if that is not in fact a null pointer constant – M.M Feb 21 '18 at 12:05
4

For information, gcc maintainer and developer Joseph Myers has a discussion on its personal page about issues with constant expressions in the C99 Standard. The original text appeared on comp.std.c 10 years ago but has been expanded later.

http://www.polyomino.org.uk/computer/c/const-exprs-issues.txt

Among the issues with constant expressions he raised, the ones you described are discussed in his point (5):

(5) What are the "operands" (6.6 paragraphs 6 and 8) of a constant expression? Presumably it means the "ultimate" operands in some sense, recursing down various operators to their operands, but this isn't specified. Presumably compound literals such as (const int){0} are not meant to be constant expressions (being references to anonymous variables), but it is hardly clear from the text that 0 isn't the operand here. When compound literals appear in sizeof expressions whose results are not integer constants in unevaluated parts of expressions, whether the expressions are arithmetic constant expressions may depend on what casts are present in the compound literals. Also, one would expect that parentheses are meant to be purely syntactic rather than having "operands", so that (int)(0.0) is an integer constant expression just as (int)0.0 is, and ((void *)0) is a null pointer constant, but this is nowhere stated.

In his personal page referring to this text, it is written:

I also have a discussion of issues with constant expressions (not in the form of the pre-DRs above; parts may become DRs following implementation experience).

To my knowledge the terminology question raised by point (5) has not been further raised in a DR.

ouah
  • 142,963
  • 15
  • 272
  • 331
  • The C standard defines *operand* in 6.4.6p2: "An *operand* is an entity on which an operator acts." But yes, here, this is not clear when considering `1 + (2 || 1 / 0)` (an ICE), `1 + sizeof(i)` (also an ICE) and `1 + (2 || i)` (not an ICE). If one needs to recurse, this shouldn't be done through `sizeof`. And the C standard is not consistent, as it says in 6.2.5p9: "A computation involving unsigned operands can never overflow", which is wrong when considering the leaf operands of an expression. – vinc17 Feb 07 '17 at 16:17