1

I have been following the C language hack for detecting integer constant expressions using Macros - Idea by Martin Uecker. https://lkml.org/lkml/2018/3/20/805

When I started playing around with the code, I found a weird behavior by interchanging the expressions of the ternary operator.

See the below code,

#include <stdio.h>
#include <stdlib.h>

// https://lkml.org/lkml/2018/3/20/805
#define ICE_P(x) (sizeof(int) == sizeof(*(1 ? ((void*)((x) * 0l)) : (int*)1)))
//Why is this working?
#define ICE_P2(x) (sizeof(int) == sizeof(*(1 ? (int*)1 : ((void*)((x) * 0l)))))

int main() {

    int a = 5;
    int b = ICE_P(a);
    int c = ICE_P2(a);

    int d = ICE_P(5);
    int e = ICE_P2(5);   
    return 0;
}

ICE_P(x) works perfectly, as described in the above link. However, when I interchanged the left and right expressions of the ternary operator, the program is still behaving as the same which I was not expecting.

When ICE_P2 is called, 1 ? (int*)1 : ((void*)((x) * 0l)) will be evaluated.


Scenario with Correct behavior.

  • ICE_P2(5) - (void*)((5)*0l)) will be NULL

According to the standard, if one side of the ternary operator gets evaluated to 'NULL' then irrespective of the conditional expression the value other than NULL will be returned.

So, in this case always, (sizeof(int) == sizeof(int*)) will be evaluated and the result will be 1.

Scenario with the behavior I'm not understanding.

  • ICE_P2(a) - (void*)((a)*0l)) will not be NULL

My expectation:

  • Now, 1 ? (int*)1 : (void*)(<Not NULL>) should be evaluated to (int*)1 as 1 is always true.
  • Overall expression macro will be evaluated as (sizeof(int) == sizeof(*(int*)))
  • And the result is 1.

Actual:

  • Now, 1 ? (int*)1 : (void*)(<Not NULL>) is evaluating the value as (void*)(<Not NULL>).
  • Overall expression macro will be evaluated as (sizeof(int) == sizeof(void*))
  • And the result is 0.

For more understanding on the ICE_P refer to the stackoverflow question or this reddit post.

Clifford
  • 88,407
  • 13
  • 85
  • 165
  • 5
    Possible duplicate of [Explain Macro which tests for Integer Constant Expression](https://stackoverflow.com/questions/49481217/explain-macro-which-tests-for-integer-constant-expression) – Bo Persson Apr 02 '18 at 17:30
  • GCC defines the value of `sizeof(void)` so the program works on any GCC version. You have to save the file with .c or .h extension not the .cpp or .hpp – Nagarjuna Manchineni Apr 02 '18 at 18:02
  • See the [godblot link](https://godbolt.org/g/Qq86nt). Yes, if you enable the stricter flags this program doesn't work. That's why this is a hack. As Linus said: "That is either genius, or a seriously diseased mind. - I can't quite tell which." – Nagarjuna Manchineni Apr 02 '18 at 18:15

1 Answers1

7

So let's get through the standard. C11 6.3.2.3p3

  1. An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant. [...]

In the construct (void*)((x) * 0l), the value is clearly always 0 for any integral value, therefore it always results in a null pointer given any kind of integer value on most platforms, but to a null-pointer constant iff the x is an integer constant expression.

Now, the question is why does x ? (void *)0 : (int *)1 work the same as x ? (int *)1 : (void *)0 when wrapped in sizeof(* (...)). For that we need to read 6.5.11p6:

  1. If both the second and third operands are pointers or one is a null pointer constant and the other is a pointer, the result type is a pointer to a type qualified with all the type qualifiers of the types referenced by both operands. Furthermore, if both operands are pointers to compatible types or to differently qualified versions of compatible types, the result type is a pointer to an appropriately qualified version of the composite type; if one operand is a null pointer constant, the result has the type of the other operand; otherwise, one operand is a pointer to void or a qualified version of void, in which case the result type is a pointer to an appropriately qualified version of void.

So if either the 2nd or 3rd expession is a null pointer constant, the result of the conditional operator has the type of the other expression, i.e. both x ? (void *)0 : (int *)1 and x ? (int*)1 : (void *)0 have type int *!

And in other case, i.e. x ? (void *)non_constant : (int *)1 the latter part of the bolded text says that the type of that expression must be void *.

The type is completely decided during the compilation phase, based on the types of the second and the third operands; and the value of the first operand plays no role in it. If this is then used in a sizeof then the entire expression becomes just another integer constant expression.


Then comes the dubious part: sizeof(* ...). The pointer is "dereferenced", and the sizeof of the dereferenced value is used in calculations. ISO C does not allow the evaluation of sizeof(void) -actually, it doesn't even allow the dereference of a void * - but GCC defines it as sizeof(void).

As Linus said: "That is either genius, or a seriously diseased mind. - I can't quite tell which."


By the way, for many similar uses, the GCC builtin __builtin_constant_p would be sufficient - but while it would return 1 for all integer constant expressions, it would return 1 for any other lvalues for which the optimizer can substitute a constant in code; the difference being that only constant integer expressions can be used as for example as non-VLA array dimensions, in static initializers and as bitfield widths.

  • x*0 is constant lvalue, mapped to a void pointer size gives 1, to an integer pointer size it gives 4, just i'm having hard time understanding the compiler's choice !!! – Abr001am Apr 02 '18 at 18:44
  • ok i think i get it, if one just [proxies](https://ideone.com/GDyIiV) the macro, he wouldn't get the expected result, since the macro deals with integer constants intheir naked form, without storing them in a variable, for compiler choice i think the compiler knows prealably what x is when it is `5`, so it assumes it as integer and knows its type from the beginning, while when x is set in a variable, compiler doesn't know which type so it leaves it user defined, hence it casts the major type in a generic `void`, i hope you understood @n.m. – Abr001am Apr 02 '18 at 19:33