6

Is this valid C99 code? If so, does it define an implementation-defined behavior?

int a;
unsigned long b[] = {(unsigned long)&a+1};

From my understanding of the C99 standard, from §6.6 in the ISO C99 standard, this might be valid:

  1. An integer constant expression shall have integer type and shall only have operands that are integer constants (...) Cast operators in an integer constant expression shall only convert arithmetic types to integer types, except as part of an operand to the sizeof operator.

  2. More latitude is permitted for constant expressions in initializers. Such a constant expression shall be, or evaluate to, one of the following:

    • an arithmetic constant expression,
    • (...)
    • an address constant for an object type plus or minus an integer constant expression.

However, because there is the possibility of the addition overflowing, this might not be considered a constant expression and therefore not valid C99 code.

Could someone please confirm if my reasoning is correct?

Note that both GCC and Clang accept this code without warnings, even when using -std=c99 -pedantic. However, when casting to unsigned int instead of unsigned long, that is, using the following code:

int a;
unsigned long b[] = {(unsigned int)&a+1};

Then both compilers complain that the expression is not a compile-time constant.

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
anol
  • 8,264
  • 3
  • 34
  • 78
  • Where does it say that the integer expression overflowing stops it from being a constant expression? – unwind Apr 15 '15 at 14:11
  • I assumed that since the cast to `int` instead of `long` might overflow (or lead to undefined behavior), the compilers rejected it as "not a constant expression"; therefore, I assumed that they should apply the same logic to the first case and reject it as well. – anol Apr 15 '15 at 14:14
  • Can you clarify whether you meant this at file scope or at block scope? – M.M Apr 15 '15 at 14:52
  • These variables were declared at file scope (as globals). – anol Apr 15 '15 at 15:21
  • your assigning the 'address' of the second byte of 'a' into an array. Seems like there would be some problems in placing an address into a long unsigned int array entry. – user3629249 Apr 16 '15 at 19:16

3 Answers3

2

From this clang developers thread on a similar issue: Function pointer is compile-time constant when cast to long but not int? the rationale is that the standard does not require the compiler to support this(this scenario is not included in any of bullets in 6.6p7) and although it is allowed to support this supporting truncated addresses would be burdensome:

I assume that sizeof(int) < sizeof(void(*)()) == sizeof(long) on your target. The problem is that the tool chain almost certainly can't express a truncated address as a relocation.

C only requires the implementation to support initializer values that are either (1) constant binary data, (2) the address of some object, or (3) or an offset added to the address of some object. We're allowed, but not required, to support more esoteric things like subtracting two addresses or multiplying an address by a constant or, as in your case, truncating the top bits of an address away. That kind of calculation would require support from the entire tool chain from assembler to loader, including various file formats along the way. That support generally doesn't exist.

Your case, which is casting a pointer to a integer type does not fit any of the cases under 6.6 paragraph 7:

More latitude is permitted for constant expressions in initializers. Such a constant expression shall be, or evaluate to, one of the following:

  • an arithmetic constant expression,
  • anull pointer constant,
  • an address constant, or
  • an address constant for an object type plus or minus an integer constant expression.

but as mentioned in the post compiler are allowed to support other forms of constant expression:

An implementation may accept other forms of constant expressions.

but neither clang nor gcc accept this.

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
2

This code is not required to be accepted by a conforming implementation. You quoted the relevant passage in your question:

  1. More latitude is permitted for constant expressions in initializers. Such a constant expression shall be, or evaluate to, one of the following:
    • an arithmetic constant expression,
    • a null pointer constant,
    • an address constant, or
    • an address constant for an object type plus or minus an integer constant expression.

(unsigned long)&x is none of those things. It's not an arithmetic constant because of C11 6.6/8:

Cast operators in an arithmetic constant expression shall only convert arithmetic types to arithmetic types

(pointer types are not arithmetic types, 6.2.5/18); and it is not an address constant because all address constants are pointers (6.6/9). Finally a pointer plus or minus an ICE is another pointer, so it is not that either.


However 6.6/10 says that an implementation may accept other forms of constant expressions. I'm not sure whether this means the original code should be called ill-formed or not (ill-formed code requires a diagnostic). Clearly your compiler is accepting some other constant expressions here.


The next issue is that casting from pointer to integer is implementation-defined. It may also be undefined if there is no integer representation corresponding to the particular pointer. (6.3.2.3/6)

Finally, the + 1 on the end makes no difference. unsigned long arithmetic is well-defined on addition and subtraction, so it is OK if and only if (unsigned long)&x is OK.

M.M
  • 138,810
  • 21
  • 208
  • 365
1

First of all, your initializer is not necessarily a constant expression. If a has local scope, then it is assigned an address in run-time, when it gets pushed on the stack. C11 6.6/7 says that in order for a pointer to be a constant expression, it has to be an address constant, which is defined in 6.6/9 as:

An address constant is a null pointer, a pointer to an lvalue designating an object of static storage duration, or a pointer to a function designator; it shall be created explicitly using the unary & operator or an integer constant cast to pointer type, or implicitly by the use of an expression of array or function type.

(Emphasis mine)


As for whether your code is standard C, yes it is. Pointer conversions to integers are allowed, although they may come with various forms of poorly specified behavior. Specified in 6.5/6:

Any pointer type may be converted to an integer type. Except as previously specified, the result is implementation-defined. If the result cannot be represented in the integer type, the behavior is undefined. The result need not be in the range of values of any integer type.

To safely ensure that the pointer can fit into the integer, you need to use uintptr_t. But I don't think pointer to integer conversion was the reason you posted this question.


Regarding whether an integer overflow would prevent it from being a compile time constant, I'm not sure where you got that idea from. I don't believe your reasoning is correct, for example (INT_MAX + INT_MAX) is a compile time constant and it is also guaranteed to overflow. (GCC gives you a warning.) In case it overflows, it will invoke undefined behavior.


As for why you get errors about the expression not being a compile-time constant, I don't know. I can't reproduce it on gcc 4.9.1. I tried declaring a with both static and automatic storage duration but no difference.

Sounds like you somehow accidentally compiled as C90, in which case gcc will tell you "error: initializer element is not computable at load time". Or maybe there was a compiler bug which has been fixed in my version of gcc.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • 1
    `INT_MAX + INT_MAX` is not a constant expression. See 6.6/4 "Each constant expression shall evaluate to a constant that is in the range of representable values for its type." I also take that clause to mean that constant expressions may not contain UB of any form. – M.M Apr 15 '15 at 15:05
  • gcc only complains about the expression not being a constant in the second case (if I cast to `int` instead of `long`), not in the first case. I just compiled gcc from trunk (5.0.0) and it also rejects the second version. [This online C99 compiler](http://www.tutorialspoint.com/compile_c99_online.php), which claims to use GCC 4.9.2, also produces the same error. – anol Apr 16 '15 at 11:32
  • @anol No, it does not give that warning either. Which gives us the conclusion that the code you have posted is not the code you are using. – Lundin Apr 16 '15 at 11:50
  • @MattMcNabb In the case of integer overflow, I would rather think you get UB before the compiler even decides whether something is a constant expression or not. And since any binary combination of bits can be expressed as a two's complement number, then in practice the compiler will most likely produce some gibberish number upon encountering the overflow UB and then use that as the constant expression. I think you'll notice this on any compiler out there. For example, for the constant expression `INT_MAX + INT_MAX`, GCC gives you the value -2 and the size 4 bytes. – Lundin Apr 16 '15 at 12:01
  • @Lundin I pasted this code on the website: `int a; unsigned long b[] = {(unsigned int)&a+1};`, hit Compile, and got both a warning and an error: `warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]` and `error: initializer element is not constant`. – anol Apr 16 '15 at 12:57
  • @Lundin UB is a run-time property. The compiler can refuse to translate a program if it is guaranteed to cause UB when run; however that's not necessarily true in this case (e.g. `void foo() { int x[] = { INT_MAX + INT_MAX }; }` and the program does not necessary call `foo()`). If no diagnostic is produced then it's a compiler bug since this is under the *Constraints* section. – M.M Apr 16 '15 at 20:56
  • @anol That code gives "cast from pointer to integer..." warning but nothing about initializer being not constant. – Lundin Apr 17 '15 at 06:11
  • 1
    With the code put three comments above, and `gcc 4.9.1-16ubuntu6` (i.e. the one from Ubuntu 14.10 as of now), I get a warning and an error (as @anol) when compiling in 64-bit mode and nothing (as @lundin) in 32-bit mode (`gcc -m32 file.c`). – Virgile Apr 17 '15 at 06:51