Search code examples
cvariablespointersmacrosvariable-types

Check if a macro argument is a pointer or not


Is there some "nice" way to check if a variable passed to a macro is a pointer? e.g.

#define IS_PTR(x) something
int a;
#if IS_PTR(a)
printf("a pointer we have\n");
#else
printf("not a pointer we have\n");
#endif

The idea is that this is not done run-time but compile-time, as in: we get different code depending on if the variable is a pointer or not. So I would like IS_PTR() to evaluate to some kind of constant expression in some way. Am I going about this idea all the wrong way?


Solution

  • You can get a complete answer to this by combining what @alx and @zneak have, and throwing in __builtin_choose_expr:

    #define is_same_type(a, b)  __builtin_types_compatible_p(typeof(a), typeof(b))
    
    #define is_pointer_or_array(p)  (__builtin_classify_type(p) == 5)
    
    #define decay(p)  (&*__builtin_choose_expr(is_pointer_or_array(p), p, NULL))
    
    #define is_pointer(p)  is_same_type(p, decay(p))
    

    So, __builtin_choose_expr(expr, a, b) is equivalent to expr ? a : b, except that the types of a and b don't need to be compatible, and there are no conversions done. In case the input is not a pointer nor an array, NULL will be of different type, so is_pointer() will evaluate to false.

    If we use the demo supplied by @Netherwire with a few tweaks, we'll see that the above can't handle incomplete structs (compile error). Still, we get no false-positives while also not failing on non-pointers/arrays. I think this should be good enough for most use-cases.

    Here's @Netherwire's demo, with a few modifications:

    char c;
    printf("c is a pointer: %s\n", is_pointer(c) ? "Yes" : "No");
    
    unsigned long long ll;
    printf("ll is a pointer: %s\n", is_pointer(ll) ? "Yes" : "No");
    
    double d;
    printf("d is a pointer: %s\n", is_pointer(d) ? "Yes" : "No");
    
    unsigned char *cp;
    printf("cp is a pointer: %s\n", is_pointer(cp) ? "Yes" : "No");
    
    struct TM *tp;
    printf("tp is a pointer: %s\n", is_pointer(tp) ? "Yes" : "No");
    // printf("*tp is a pointer: %s\n", is_pointer(*tp) ? "Yes" : "No");  // error
    
    char a[42];
    printf("a is a pointer: %s\n", is_pointer(a) ? "Yes" : "No");
    
    printf("1 is a pointer: %s\n", is_pointer(1) ? "Yes" : "No");
    
    printf("\"str\" is a pointer: %s\n", is_pointer("str") ? "Yes" : "No");
    

    This outputs:

    c is a pointer: No
    ll is a pointer: No
    d is a pointer: No
    cp is a pointer: Yes
    tp is a pointer: Yes
    a is a pointer: No
    1 is a pointer: No
    "str" is a pointer: No
    

    Huzzah! We have no false-positives, plus the defines were able to handle more than just arrays.