How can I verify that the expression is constant in C?

Let's say I have a scenario where I need to make sure that the value used in my code is a compile-time constant (for example, perhaps Dragon's interpretation of P10 rule 2 is β€œfixed loop constraints”). How can I provide this at the language level in C?

C supports the concept of an integer constant expression at the language level. It should be possible to devise a way to take advantage of this so that only values ​​that match this specification can be used in expressions, right? eg:.

for (int i = 0; i < assert_constant(10); ++i) {... 

Some partial solutions that are not really general enough to be useful in several situations:

  • Bitfields: the classic static_assert implementation static_assert in C to C11 was to use a bit field whose value would be illegal if the condition is not met :

     struct { int _:(expression); } 

    Although this can be easily wrapped for use as part of an expression, it is not general at all - the maximum value of expression "[can] not exceed the width of an object of the type to be specified and the expression is omitted" (C11 6.7.2.1), which creates a very small the portable limit of expression (typically 64). It can also be negative.

  • Enumerations: a enum requires that any initializing expressions be integer constant expressions. However, an enum declaration cannot be embedded in an expression (as opposed to a struct definition) requiring its own operator. Since identifiers in the list of enumerators are added to the surrounding area, we also need a new name each time. __COUNTER__ not standardized, so there is no way to achieve this from within the macro.

  • Case:, the argument expression in the case string must be an integer constant. But this requires the surrounding switch . This is not much better than enum , and this is what you do not want to hide inside the macro (since it will generate real operators, even if it is easy for them to remove the optimizer).

  • Array declaration: since C99, the size of the array does not even have to be constant, that is, it will not generate the desired error in any case. This is also again a statement that requires a name to be entered into the surrounding area, which suffers the same problems as enum .

Of course, is there some way to hide the constant check of the macro, which is repeated, passes the value through (so it can be used as an expression) and does not require an operator line or introduces additional identifiers?

+5
source share
1 answer

It turns out there is a way!

Although locally distributed arrays are allowed to have variable lengths in C, the standard explicitly requires such arrays to not have an explicit initializer. We can forcefully disable the VLA language function by providing the array with a list of initializers, which will cause the size of the array to be an integer constant expression (compile-time constant):

 int arr[(expression)] = { 0 }; 

The contents of the initializer does not matter; { 0 } will always work.

This is still slightly inferior to the enum solution because it requires an instruction and enters a name. But, unlike enumerations, arrays can be made anonymous (as compound literals):

 (int[expression]){ 0 } 

Since a compound literal has an initializer as part of the syntax, there is no way for it to be a VLA, so it is still guaranteed for it that expression will be a compile-time constant.

Finally, since anonymous arrays are expressions, we can pass them to sizeof , which gives us the path to the original value of expression :

 sizeof((char[expression]){ 0 }) 

This has the added bonus of ensuring that the array will never be allocated at runtime.

Finally, with a bit more packaging, we can even handle zero or negative values:

 sizeof((char[(expression)*0+1]){ 0 }) * 0 + (expression) 

This ignores the actual value of expression when setting the size of the array (which will always be 1), but still considers its constant status; it also ignores the size of the array and returns only the original expression, so the restrictions on the size of the array β€” must be greater than zero β€” need not be applied to the return value. expression duplicated, but for what macros (and if it is compiled, it will not be recounted, because a. it is constant, and b) the first use is within sizeof ). In this way:

 #define assert_constant(X) (sizeof((char[(X)*0+1]){ 0 }) * 0 + (X)) 

For bonus points, we can use a very similar method to implement the static_switch expression, combining the array sizes with C11 _Generic (this probably does not have a lot of practical applications, but can replace some cases of nested trojans that are not popular):

 #define static_switch(VAL, ...) _Generic(&(char[(VAL) + 1]){0}, __VA_ARGS__) #define static_case(N) char(*)[(N) + 1] char * x = static_switch(3, static_case(0): "zero", static_case(1): "one", static_case(2): "two", default: "lots"); printf("result: '%s'\n", x); //result: 'lots' 

(We take the address of the array to create an explicit pointer type to the array, rather than letting the implementation decide whether _Generic arrays support pointers or not.)

This is slightly more restrictive than assert_constant because it will not allow negative values. Assuming +1 both in the control expression and in all case values, we can at least let it take zero.

+5
source

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


All Articles