📜 ⬆️ ⬇️

Detect integer constant expressions in the macro [along with Linus]

We offer you a translation of a recent letter about the ambiguous idea from the Linux Kernel Mailing List mailing list, which caused the traditional reaction of Linus Torvalds. Necessary to understand the explanation provided at the end of the post.

Letter

Sender: Martin Wecker
Date: Tue, 20 Mar 2018 22:13:35 +0000
Topic: Detecting Integer Constant Expressions in a Macro
Hello Linus,

I have an idea:
')
A test for integer constant expressions, which returns the integer constant expression ( ICE ) itself, which should be suitable for transmission in __builtin_choose_expr , and looks like this:

 #define ICE_P(x) (sizeof(int) == sizeof(*(1 ? ((void*)((x) * 0l)) : (int*)1))) 

By the way, in this expression x itself is not calculated in gcc , although this is not guaranteed by the standard (I did not check this fact in older versions of gcc .)

Linus Torvalds Answer

Sender: Linus Torvalds <>
Date: Tue, 20 Mar 2018 16:08:30 -0700
Subject: Re: Detection of integer constant expressions in a macro
On Tue, Mar 20, 2018 at 3:13 PM, Martin Wecker
<Martin.Uecker@med.uni-goettingen.de> wrote:
I have an idea:
No, this is not an "idea."
This is either the work of a genius, or completely sick on the head.
Until the end is not sure yet, so I can not say with accuracy.
The test for integer constant expressions, which returns the integer constant expression itself, which should be suitable for transmission in __builtin_choose_expr, and looks like this:

 #define ICE_P(x) (sizeof(int) == sizeof(*(1 ? ((void*)((x) * 0l)) : (int*)1))) 
OK, here I see that (void *)((x)*0l)) becomes NULL when x is ICE . Good. With a constant, we have:

 sizeof( 1 ? NULL : (int *) 1) 

and the rule here is the following: if one of the sides of a ternary operator with pointers is NULL , then its end result is a different type (int *) .

So yes, the expression above returns sizeof(int) .

And if it is not ICE, then the first pointer is still of type (void *), but it is not NULL .

And yes, the type conversion rules for a ternary operator with two pointers, each of which is non- NULL , are different - so now it returns "void *" .

So now the end result is (sizeof(*(void *)(x)) , which in gcc is usually different from int .

So here I see two problems:


However, both of these problems may not have much value, and all this may be the norm.
By the way, in this expression, x itself is not calculated in gcc , although this is not guaranteed by the standard (I did not check it in older versions of gcc .)
Oh, as for me, by the standard, it is guaranteed that the sizeof() operator does not calculate the value of the argument, only its type.

I am delighted with your truly amazing and disgusting "hack". It represents the most real work of art.

I'm sure this will not work or will cause warnings for various reasons, but
it is still just fine .

Linus

Explanation


Let's try to understand what is happening in this code.

 #define ICE_P(x) (sizeof(int) == sizeof(*(1 ? ((void*)((x) * 0l)) : (int*)1))) 

We define the macro ICE_P(x) . P is a lispovy predicate according to the rules of naming. ICE stands for integer constant expression. We want to return true if x is an integer constant expression, and false otherwise.

This expression will be true if the right side of the comparison is equal to sizeof(int) . Let's try to deploy it.

 sizeof(*(1 ? ((void*)((x) * 0l)) : (int*)1)) 

This expression returns the size of the type pointed to by the ternary expression. Digging deeper.

 1 ? ((void*)((x) * 0l)) : (int*)1 

Understandably, the left side is always returned, because 1 is always true . As Linus explains, when x is ICE, the left side becomes NULL . So, we have two possible options:

When is x ICE: 1 ? ((void*)(NULL)) : (int*)1 1 ? ((void*)(NULL)) : (int*)1
When is x not ICE: 1 ? ((void*)(NOT-NULL)) : (int*)1 1 ? ((void*)(NOT-NULL)) : (int*)1

The only difference is whether the void* on the left is NULL or not.

If it is NULL (x is ICE), the expression returns type int*
If it is not NULL (x is not ICE), the expression returns void*

Basically, a ternary expression can turn NULL void * into int * , but when void * is not NULL , it turns int * void * instead. Now we can return to the original expression, and we get the following:

If x is ICE: sizeof(int) == sizeof(*(int *))
When x is not ICE: sizeof(int) == sizeof(*(void *))

Void * dereferencing is not a valid operation, but sizeof is magic, it is fully computed at compile time. In gcc, the code sizeof(*(void *)) is 1.

Here is a sample code to test this macro, icep.c :

 /*   : gcc icep.c -o icep && ./icep  : $ gcc icep.c -o icep && ./icep ICE_P(1): 1 ICE_P('c'): 1 ICE_P(rand()): 0 */ #include <stdio.h> #include <stdlib.h> #define ICE_P(x) (sizeof(int) == sizeof(*(1 ? ((void*)((x) * 0l)) : (int*)1))) #define CHECK(x) printf("ICE_P(%s): %d\n", #x, ICE_P(x)) int main() { CHECK(1); CHECK('c'); CHECK(rand()); return 0; } 


Additional explanation


The key expression here is just x * 0 . If x is an integer constant, the compiler can perform a calculation, and the integer by zero is zero. If x is not an integer constant, then the compiler cannot perform this computation, and it is unknown whether it is zero. This result is cast to an “empty” pointer ( void pointer ). This is how we know if NULL or not (since the void pointer to zero is the definition of NULL ).

Another key to understanding this expression is type a ? b : c a ? b : c . It is clear that b and c can have different types, and in this case, the compiler must figure out the “common” type of these expressions. Here c is clearly a pointer to an int . But NULL compatible with other types of pointers. So if b is NULL , then the generic type is int* , since it describes both expressions. However, if it is not statically known whether b NULL , then the only type that matches void* and int* is void* .

This leads us to the fact that we make sizeof(*(void*)) , when x is not an integer constant expression, and sizeof(*(int*)) , when x is it itself.

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


All Articles