The __safe_cmp is defined as in minmax.h
#define __safe_cmp(x, y) \
(__typecheck(x, y) && __no_side_effects(x, y))
#define __no_side_effects(x, y) \
(__is_constexpr(x) && __is_constexpr(y))
#define __typecheck(x, y) \
(!!(sizeof((typeof(x) *)1 == (typeof(y) *)1)))
#define __is_constexpr(x) \
(sizeof(int) == sizeof(*(8 ? ((void *)((long)(x) * 0l)) : (int *)8)))
From my understanding,
- This macro is used to do check during compiling so that no un-compatible surprise when runtime
- If no warning during compiling, the result of this macro should always be 1 (true)
I also checked the discussion about __is_constexpr in Linux Kernel's __is_constexpr Macro
Still have some items are not clear,
- the returned value by __typecheck is wrapped by sizeof, the aim is only to retain the result as constant expression? which means the == operator doesn't returned as constant expression? In c11 standard 6.5.9, it says the returned type is int
The result has type int. For any pair of operands, exactly one of the relations is true
- does (typeof(x) *)1 == (typeof(y) *)1 always return 1? In 6.5.9, it also says both are pointers to the same object (including a pointer to an object and a subobject at its beginning) or function, this means if the value of pointers are same, the pointers should be equal? even if the types are different? I tested (int, long), (int, struct), (int, void), (long, int*) and so on with this expression, all are returned 1 under linux, is there any false case for this condition?
5 Otherwise, at least one operand is a pointer. If one operand is a pointer and the other is a null pointer constant, the null pointer constant is converted to the type of the pointer. If one operand is a pointer to an object type and the other is a pointer to a qualified or unqualified version of void, the former is converted to the type of the latter.
6 Two pointers compare equal if and only if both are null pointers, both are pointers to the same object (including a pointer to an object and a subobject at its beginning) or function, both are pointers to one past the last element of the same array object, or one is a pointer to one past the end of one array object and the other is a pointer to the start of a different array object that happens to immediately follow the first array object in the address space
if item 1 and 2 are correct, sizeof(equal expr) is to covert "equal expr"'s result to integer constant expression, why need to do this? I would thought sizeof is to covert the result to true(even when false) so that the macro will always return true. I notice the comments said to keep constant expression avoid VLA warning? Is there any example if none constant expression result retained?
in __is_constexpr, "(long)(x) * 0l", when x is constant expression, it will return null pointer constant and the expression will return (int *)null (using type of third operand), mentioned in Linux Kernel's __is_constexpr Macro from 6.5.15.6, if the expression is something like (long)(x++) * 0l, which should also be null, but it's not a null pointer constant? so that the result will be (void *)null? and then sizeof(*(void *)) == 1 (sizeof(void)). How to verify this difference from code level since the printf of %p seems to be same without sizeof
#include <stdio.h> #define check(x) ((void *)((long)(x) * 0l)) #define _check(x) (8 ? check(x) : (int *)8) #define __check(x) sizeof(*(_check(x))) int main(int args, char** argv) { int x = 0; printf("%p\n", check(x++)); // (nil) printf("%p\n", check(1)); // (nil) printf("%p\n", _check(x++)); // (nil) printf("%p\n", _check(1)); // (nil) printf("%ld\n", __check(x++)); // 1 printf("%ld\n", __check(1)); // 4 return 0; }
Another question is why using third operand's type as returned type when second operand is null pointer constant in c11. NULL is defined as ((void *)0) in linux and doesn't point to any real object, why not use void* as the returned type directly?
There is also a function called typecheck defined in typecheck.h. what is the different between the 2 functions, the implementation is also to compare with the pointer between x and y' types, generate warning/error during compiling and returned always 1 which it's a constant expression.
#define typecheck(type,x) \ ({ type __dummy; \ typeof(x) __dummy2; \ (void)(&__dummy == &__dummy2); \ // always false? 1; \ })
BTW, is there any doc or book to explain these kind questions except c11?
Appreciate for your help!
===============================================================
Update:
The question about __is_constexpr,
int main(int args, char** argv)
{
int x = 1;
int a = (long)x * 0l;
int b = (long)1 * 0l;
return 0;
}
0000000000001129 <main>:
1129: f3 0f 1e fa endbr64
112d: 55 push %rbp
112e: 48 89 e5 mov %rsp,%rbp
1131: 89 7d ec mov %edi,-0x14(%rbp)
1134: 48 89 75 e0 mov %rsi,-0x20(%rbp)
1138: c7 45 f4 01 00 00 00 movl $0x1,-0xc(%rbp)
113f: c7 45 f8 00 00 00 00 movl $0x0,-0x8(%rbp)
1146: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)
114d: b8 00 00 00 00 mov $0x0,%eax
1152: 5d pop %rbp
1153: c3 ret
The above code shows that the 2 expressions were translated to 0 during compiling, seems to be the same.
In gcc (version 11.3.0), below code found in c-typeck.c,
static bool
null_pointer_constant_p (const_tree expr)
{
/* This should really operate on c_expr structures, but they aren't
yet available everywhere required. */
tree type = TREE_TYPE (expr);
return (TREE_CODE (expr) == INTEGER_CST
&& !TREE_OVERFLOW (expr)
&& integer_zerop (expr)
&& (INTEGRAL_TYPE_P (type)
|| (TREE_CODE (type) == POINTER_TYPE
&& VOID_TYPE_P (TREE_TYPE (type))
&& TYPE_QUALS (TREE_TYPE (type)) == TYPE_UNQUALIFIED)));
}
According to debug result with cc1,
- int a = (long)x * 0l is treated as MULT_EXPR when checking TREE_CODE (expr), false
- int b = (long)1 * 0l is treated as INTEGER_CST when checking TREE_CODE(expr), true
And the second expression is NULL pointer constant during compiling and then seems to be matched with the rules of ternary conditional operator.
I'm not familiar with gcc internal, not sure if the analysis is right.
===============================================================
Update based on Nate Eldredge's answer. Really thanks a lot for Nate Eldredge's patient to answer all questions in detals.
- The purpose of using sizeof is to avoid the expr evaluated
- I would thought the assembly results of 2 expressions are same after compiled by gcc on linux platform and then assumed there should no difference after that
- According to the answer. The evaluated result of the "==" expression is implementation-defined, may be optimized by compiler itself and not be obliged to. The result using gcc on linux platform may seem to be OK and may be not the same as other compiler and platform
- The rule here is, if the result is not determinant, more important if the result doesn't need, just keep the expr not evaluated. Such as, using sizeof and so on. The line 3 in typecheck is false and may also be run in some cases even in runtime
- for __is_constexpr
- sizeof is same as mentioned above and also distinguish the pointer type, sizeof((void)) and sizeof((int))
- I would thought ((long)(x) * 0l) should be optimized by compiler, calculated to 0l, converted to (void*)0 first and then the expr should also be null pointer constant, according to the debug in gcc, the expr is not a constant expression since x is not determinant even the result seems to be 0l and then the expression can be used to distinguish constant expression or not
- Why using the type of third operand instead of void* when null pointer constant returned of second operand
- Per the answer, also to emit warning when type incompatible
- I think I need to think more about 6.5.15p8, and the raw description is if one of the operands is null pointer constant and will use the type of another. Otherwise, if a null pointer returned, such as "(void*)((long)(x++) * 0l)", it seems to return "(void*)0" directly. I would thought, if null pointer constant, return it directly(only not to use the type of another operand) and if false then (int*) returned directly
- also need to read more careful on 6.2.7
Thanks again for Nate Eldredge's patient and kindly reply.