检查宏参数是否是指针

lfx*_*ove 10 c variables macros pointers variable-types

是否有一些"好"的方法来检查传递给宏的变量是否是一个指针?例如

#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
Run Code Online (Sandbox Code Playgroud)

这个想法是,这不是运行时而是编译时,如下所示:我们根据变量是否为指针得到不同的代码.所以我希望IS_PTR()以某种方式评估某种常量表达式.我是怎么回事这个想法的?

zne*_*eak 8

在 Clang 和 GCC 上,如果是具有指针类型的对象,__builtin_classify_type(P)则计算结果为 5 。P


Jen*_*edt 6

#if当你暗示你的问题时,通过预处理器肯定无法观察到它.预处理器不知道类型,只知道从它们构造的标记和表达式.

C11有一个新功能,可以让你观察特定的指针类型,但不是一般的"指针".你可以做点什么

#define IS_TOTOP(X) _Generic((X), default: 0, struct toto*: 1)
Run Code Online (Sandbox Code Playgroud)

或者如果你想要宏也适用于数组

#define IS_TOTOPA(X) _Generic((X)+0, default: 0, struct toto*: 1)
Run Code Online (Sandbox Code Playgroud)

已经有一些编译器实现了这个,即clang,对于gcc和其他编译器,你已经可以使用一些内置函数模拟该功能,参见P99.


小智 6

您可以通过组合 @alx 和 @zneak 所拥有的内容并添加 __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))
Run Code Online (Sandbox Code Playgroud)

因此,__builtin_choose_expr(expr, a, b)相当于,只不过和expr ? a : b的类型不需要兼容,并且不进行任何转换。如果输入不是指针也不是数组,则将是不同的类型,因此将计算为 false。abNULLis_pointer()

如果我们使用 @Netherwire 提供的演示并进行一些调整,我们将看到上面的内容无法处理不完整的结构(编译错误)。尽管如此,我们没有得到误报,同时也没有在非指针/数组上失败。我认为这对于大多数用例来说应该足够好了。

这是@Netherwire 的演示,做了一些修改:

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");
Run Code Online (Sandbox Code Playgroud)

这输出:

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
Run Code Online (Sandbox Code Playgroud)

好哇!我们没有误报,而且定义不仅能够处理数组。


Net*_*ire 5

我或多或少发现了一个_Generic这个问题的解决方案。

\n\n

警告:可能会触发误报(请参阅下面的示例)。

\n\n
#define __INTERNAL_CHECK_POINTER(x) _Generic((x),\\\n          int: 0,       unsigned int: 0,\\\n         long: 0,      unsigned long: 0,\\\n    long long: 0, unsigned long long: 0,\\\n        float: 0,             double: 0,\\\n  long double: 0,                       \\\n      default: 1)\n\n/**\n * Determines whether the variable has likely a pointer type (but may be triggered false-positive)\n */\n#define IS_LIKELY_A_POINTER(x) ((sizeof(x) == sizeof(void*)) && __INTERNAL_CHECK_POINTER(x) ? 1 : 0)\n
Run Code Online (Sandbox Code Playgroud)\n\n

演示:

\n\n
char c = 0;\nprintf("c is a pointer: %s\\n", IS_LIKELY_A_POINTER(c) ? "Yes" : "No");\n\nunsigned long long l = 0;\nprintf("l is a pointer: %s\\n", IS_LIKELY_A_POINTER(l) ? "Yes" : "No");\n\ndouble d = 0.0;\nprintf("d is a pointer: %s\\n", IS_LIKELY_A_POINTER(d) ? "Yes" : "No");\n\nunsigned char* cp = 0;\nprintf("cp is a pointer: %s\\n", IS_LIKELY_A_POINTER(cp) ? "Yes" : "No");\n\nstruct tm* tp = 0;\nprintf("tp is a pointer: %s\\n", IS_LIKELY_A_POINTER(tp) ? "Yes" : "No");\n\nchar ia[] = {0, 1, 2, 3, 4, 5, 6, 7};\nprintf("ia is a pointer: %s\\n", IS_LIKELY_A_POINTER(ia) ? "Yes" : "No");\n
Run Code Online (Sandbox Code Playgroud)\n\n

这将输出:

\n\n
c is a pointer: No\nl is a pointer: No\nd is a pointer: No\ncp is a pointer: Yes\ntp is a pointer: Yes\nia is a pointer: Yes // false-positive!\n
Run Code Online (Sandbox Code Playgroud)\n\n

如果您(像我一样)正在寻找一些日志记录(绘制或不绘制*特定变量)并且您不是在寻找防失败结果,请尝试此操作,它可能会有所帮助。干杯!

\n\n

注意不会在 MSVC 下编译;使用 gcc/clang/等。相反,或者使用此条件进行您自己的后备实现:

\n\n
#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)\n    // use _Generic code\n#else\n    // \xc2\xaf\\_(\xe3\x83\x84)_/\xc2\xaf\n#endif\n
Run Code Online (Sandbox Code Playgroud)\n