一个程序如何检测NULL是以整数类型还是指针类型定义的?

17

C语言允许将NULL定义为任何空指针常量,换句话说,任何评估为0的整数常量表达式或这样的表达式转换为void *。我的问题是关于定义的选择是否真的很重要,即是否一个本来正确的程序可能会依赖于使用哪种定义。对于这个问题,我想忽略像将NULL传递给变参函数或缺少原型的函数等问题,因为我已经单独处理过了。假设sizeof NULL == sizeof(void *)并且sizeof NULL == sizeof(T),其中T是某个整数类型,因此sizeof不足以回答NULL是否具有指针类型的问题。

显然,C11提供了一种区分NULL或任何其他表达式类型的方法:使用_Generic关键字。

C99还提供了一种看起来可靠的晦涩方式:

int null_has_ptr_type()
{
    char s[1][1+(int)NULL];
    int i = 0;
    return sizeof s[i++], i;
}

是否有其他方法可以确定符合C标准的程序中 NULL 的类型? 在C89中有吗?


1
如果NULLvoid *类型,那么char c = NULL;会生成编译器警告(因此需要进行强制转换)。 - Jonathan Leffler
1
函数 null_has_ptr_type 真的有效吗?在我的电脑上,它在两种情况下都返回0。 - md5
4
您需要一个支持C99标准的C编译器,包括可变长度数组(VLA)和sizeof正确实现的功能。如果s是VLA,则会评估i ++,否则不会评估。如果NULL是指针,那么s将是VLA,如果是int,则不是。或者类似于这样... GCC在两种情况下会给出不同的警告。@ Kirilenko - Mat
@Kirilenko:我很想知道是哪个编译器/版本给出了错误的答案。 - R.. GitHub STOP HELPING ICE
2
@R..:http://gcc.gnu.org/gcc-4.4/c99status.html 表示“破损”的VLA支持。4.5表示可以。 (4.6.1可行。) - Mat
显示剩余11条评论
4个回答

6

通过问题、回答和评论,我们得出以下结论:

  1. C11方式很容易(_Generic)。
  2. 由于有缺陷的编译器,C99方式相当不可靠。
  3. 由于typedef,字符串化方法已死路一条。
  4. 没有找到其他方法。

因此,答案似乎是没有可靠的C11之前的方法,似乎也没有有效的C99之前的方法。


4

获取NULL的字符串定义,然后进行完整检查。这里有一个非常简单的方法:

#define XSTR(x) #x
#define STR(x) XSTR(x)

if (STR(NULL)[0] == '(') {
   ...
}

但是我不知道你会如何处理可能出现的__null


@R..,我提出了一个非常简单的想法,是有原因的; - AProgrammer
这个是否适用于任意层次的宏间接引用?例如 #define NULL _NULL1, #define _NULL1 _NULL2, #define _NULL2 _NULL3, ..., #define _NULL999 0?我认为不行.. - R.. GitHub STOP HELPING ICE
1
@R..: 理论上已经确定了可以检测到这个定义。在实际中是否需要一个稳健的解决方案呢? - Oliver Charlesworth
@R..,这不是问题(在将参数传递给另一个宏之前,参数已完全展开),需要两个级别,因为它与#和##一起使用。 - AProgrammer
2
@Oli:不,实际上我更感兴趣的是应用程序可能会“无意中”依赖于定义的方式。 - R.. GitHub STOP HELPING ICE
1
@R..,由于您排除了可变参数函数(其中差异可能导致未定义行为),我不认为存在意外依赖的合理情况。如果存在这样的情况,我期望它已经被解决或至少成为传统知识了(特别是 (void*)0 对于C实现来说很常见,但对于C ++来说是不符合标准的)。 - AProgrammer

3

你不能将宏转换为字符串,然后查看该字符串吗?

# include <stddef.h>
# include <stdio.h>
# include <string.h>

# define STRINGIFY(x) STRINGIFY_AUX(x)
# define STRINGIFY_AUX(x) #x

int main(void)
{
  const char *NULL_MACRO = STRINGIFY(NULL);

  if (strstr("void", NULL_MACRO) != NULL)
    puts("pointer");
  else
    puts("integer");
}

如果你添加了以下代码(通常情况下null是指针类型),它将正确打印出"integer"

# undef NULL
# define NULL 0

NULL不能像(int) ((void *) 0)这样,因为标准没有规定将空指针常量转换为整数类型仍然是空指针常量。

此外,标准也这样说关于整数常量表达式(C11,6.6/6):

一个整数常量表达式117)必须具有整数类型,只能有整数常量、枚举常量、字符常量、结果为整数常量的sizeof表达式、_Alignof表达式以及作为转换操作符的浮点常量。在整数常量表达式中,强制转换操作符只能将算术类型转换为整数类型,除了作为sizeof_Alignof运算符的操作数。

编辑:实际上这对于像这样的内容不起作用:

# define NULL (sizeof(void *) - sizeof(void *))

(感谢注意)这无法以OP需要的简单方式检查,需要一些工作(简单解析)。

编辑2:还有typedef正如评论所指出的。


2
@netcoder:这是另一种情况。当您将值为0的整数常量表达式转换为void *时,它仍然是空指针常量。但是将指针强制转换为整数类型永远不会产生整数常量表达式。 - R.. GitHub STOP HELPING ICE
1
显然,最难处理的情况是 #define NULL __null,其中 __null 是编译器的内置函数。 - AProgrammer
1
@R.. 我之前的评论是错误的。不管怎样,你很幸运,没有以v开头的标准整数类型 :) 我认为你的想法可以实现,而且很容易。 - effeffe
1
@DanielFischer 如果我们在这个问题中添加 typedef,我认为在 C11 标准之前的 C 中绝对无法检索到 NULL 的类型,对吗?它可能是任何东西。 - effeffe
2
但是 typedef 不仅仅是等效类型,它是相同类型的另一个名称,因此将其强制转换为 GenericPointer 就等于将其强制转换为 void* - Daniel Fischer
显示剩余23条评论

1
这里有一种晦涩的方法:如果程序使用表达式&*NULL,则当NULL具有整数类型时,它将无法编译,但如果NULL具有指针类型,则可以编译。
C99对于这种特殊情况有措辞:
如果&运算符的操作数是一元*运算符的结果,则不会评估该运算符或&运算符,并且结果就像两者都被省略一样,除了运算符的约束仍然适用且结果不是lvalue。
运算符的约束并未被违反:&的操作数是一元*运算符的结果,一元*运算符的操作数具有指针类型(因为我们假设NULL是这样定义的)。

我认为这个问题是在寻找一个能够编译的东西,无论NULL是指针类型还是整数类型。只有当NULL是整数类型时,NULL+NULL才会编译。 - Keith Thompson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接