Constant expression initializer having possible overflow in C99

Is this really C99 code? If so, does it determine the behavior defined by the implementation?

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

From my understanding of the C99 standard, from Β§6.6 of the ISO C99 standard, this may be true:

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

  2. For constant expressions, initializers are allowed to use a larger latitude. Such a constant expression must be, or be evaluated, one of the following:

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

However, since there is the possibility of overflowing the add, this may not be considered a constant expression and therefore invalid C99 code.

Can someone please confirm the correctness of my reasoning?

Note that both GCC and Clang accept this code without warning, even when using -std=c99 -pedantic . However, when you click 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.

+6
source share
3 answers

From this thread of clang developers on a similar problem: Function pointer is compile-time constant when dropped to long, but not int? Justification that the standard does not require the compiler to support this (this script is not included in any of the tokens in 6.6p7 ), and although it is allowed to support these supported truncated addresses, it would be cumbersome:

I assume that sizeof (int) <sizeof (void (*) ()) == sizeof (long) on ​​your target . The problem is that a tool chain almost certainly cannot express a truncated address as a move.

C only requires an implementation to support initialization values ​​that are (1) constant binary data, (2) the address of an object, or (3) or an offset added to the address of an object. We are allowed, but not required, to support more esoteric things, such as subtracting two addresses or multiplying the address by a constant or , as in your case, truncating the upper bits of the address. . This type of calculation will require support for the entire chain of tools from assembler to the loader, including various file formats along the way. This support does not exist at all.

In your case, which overlays an integer type, does not correspond to any of the cases in 6.6 paragraph 7 :

For constant expressions in initializers, additional latitude is allowed. Such a constant expression should be or evaluated by one of the following:

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

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

Implementation can take other forms of constant expressions.

but neither clang nor gcc accept this.

+3
source

This code is not required to be accepted by the appropriate implementation. You cited the corresponding passage in your question:

  1. Initializers allow the use of greater latitude for constant expressions. Such a constant expression must be, or be evaluated, one of the following:
    • arithmetic constant expression,
    • null pointer constant
    • address constant or
    • an address constant for an object type plus or minus an integer constant expression.

(unsigned long)&x - none of these things. This is not an arithmetic constant due to C11 6.6 / 8:

Role operators in an arithmetic constant expression must convert only arithmetic types for arithmetic types

(pointer types are not arithmetic types, 6.2.5 / 18); and it is not a permanent address, since all permanent addresses are pointers (6.6 / 9). Finally, the plus or minus pointer ICE is another pointer, so this is not the case either.


However, 6.6 / 10 says that implementation can take other forms of constant expressions. I'm not sure if this means that the source code should be called poorly formed or not (poorly formed code requires diagnostics). Obviously, your compiler accepts some other constant expressions here.


The next problem is that casting from a pointer to an integer is determined by the implementation. It can also be undefined if the integer representation does not match a specific pointer. (6.3.2.3/6)

Finally, + 1 at the end does not matter. unsigned long arithmetic is well defined when adding and subtracting, so this is normal if and only if (unsigned long)&x is fine.

+2
source

First of all, your initializer is not necessarily a constant expression. If a has a local scope, then it is assigned an address at runtime when it hits the stack. C11 6.6 / 7 says that for a pointer to be a constant expression, it must be an address constant, which is defined in 6.6 / 9 as:

The address constant is a null pointer, a pointer to an lvalue designation of an object of static storage duration, or a pointer to a function designation; it must be created explicitly using a unary & operator or integer constant other than the type of the pointer, or implicitly using an expression such as an array or function.

(Emphasis mine)


As for your standard C code, yes it is. Conversions to pointers to integers are allowed, although they can have various forms of poorly defined behavior. Indicated in 6.5 / 6:

Any type of pointer can be converted to an integer type. In addition, as previously indicated, the result is determined by the implementation. If the result cannot be represented in an integer type, the behavior is undefined. The result should not be in the range of values ​​of any integer type.

To make sure that the pointer can fit in an integer, you need to use uintptr_t . But I do not think that the pointer to the integer conversion was the reason that you posted this question.


Regarding whether integer overflow will prevent its compile-time constant, I'm not sure where you got this idea from. I do not believe that your reasoning is true, for example (INT_MAX + INT_MAX) - this is a compile-time constant, and it is also guaranteed to overflow. (GCC gives you a warning.) In case of overflow, it causes undefined behavior.


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

It looks like you somehow compiled as C90, in which case gcc will tell you: "error: initializer is not computed at boot time". Or maybe there was a compiler error that was fixed in my version of gcc.

+1
source

Source: https://habr.com/ru/post/985183/


All Articles