【问题标题】:Ways a program might detect whether NULL is defined with integer or pointer type?程序可能检测 NULL 是用整数还是指针类型定义的方式?
【发布时间】:2012-12-23 18:44:10
【问题描述】:

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 中可以使用吗?

【问题讨论】:

  • char c = NULL; 如果NULL 的类型为void *(因此存在转换),则生成编译器警告。
  • 函数null_has_ptr_type真的有用吗?它在我的计算机上两种情况下都返回 0。
  • return sizeof s[i++], i; 将返回 i。
  • 在这种情况下,您需要一个支持 C99 的 C 编译器,包括 VLA 和 sizeof 的正确实现。如果s 是VLA,则评估i++,否则不是。如果 NULL 是指针,s 将是 VLA,而不是 int。或者类似的东西...... GCC 在这两种情况下都会给出不同的警告。 @Kirilenko
  • @R..: gcc.gnu.org/gcc-4.4/c99status.html 声明“损坏”VLA 支持。 4.5说没问题。 (4.6.1 有效。)

标签: c language-lawyer


【解决方案1】:

通过问题、答案和 cmets,我认为我们确立了:

  1. C11 方式很简单 (_Generic)。
  2. 由于编译器存在缺陷,C99 方式相当不可靠。
  3. 由于typedef,字符串化方法是一条死胡同。
  4. 没有找到其他方法。

所以答案似乎是没有可靠的 pre-C11 方法,似乎也没有有效的 pre-C99 方法。

【讨论】:

    【解决方案2】:

    获取 NULL 的字符串定义,然后根据需要进行完整的检查。这是一个非常简单的想法:

    #define XSTR(x) #x
    #define STR(x) XSTR(x)
    
    if (STR(NULL)[0] == '(') {
       ...
    }
    

    但我不知道您将如何处理可以从中产生的__null

    【讨论】:

    • @R..,我提出一个头脑简单的人是有原因的;
    • 这是否适用于任意级别的宏间接?例如#define NULL _NULL1, #define _NULL1 _NULL2, #define _NULL2 _NULL3, ..., #define _NULL999 0?我认为不是..
    • @R..:似乎已经确定了定义可以在理论上被检测到。是否确实需要稳健的解决方案?
    • @Oli:不,实际上我对应用程序可能无意中依赖于定义的方式更感兴趣。
    • @R..,因为您排除了可变参数函数(其中的差异可能导致 UB),我没有看到无意依赖的合理案例。如果有的话,我希望它现在会得到解决,或者至少是民间传说(特别是 (void*)0 在 C 实现中很常见,而在 C++ 中不符合标准)。
    【解决方案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");
    }
    

    如果您添加它,它会正确打印 "integer"(通常 NULL 具有 pinter 类型):

    # 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 的需要以简单的方式进行检查,需要做一些工作(简单的解析)。

    EDIT 2:正如评论正确指出的那样,还有typedef

    【讨论】:

    • @netcoder:反之亦然。将值为 0 的整数常量表达式强制转换为 void * 时仍然是空指针常量。但是将指针转换为整数类型永远不会产生整数常量表达式。
    • 显然,可能难以处理的情况是#define NULL __null,其中__null 是编译器的内在特性。
    • @R.. 我之前的评论是错误的。无论如何,你很幸运,没有以 v 开头的标准整数类型 :) 我认为你的想法可行,而且很容易。
    • @R.. 检查'v' 不需要工作:typedef void *GenericPointer;
    • 但是typedef 不只是一个等价类型,它是同一类型的另一个名称,所以转换为GenericPointer 转换为void*。跨度>
    【解决方案4】:

    这是一种晦涩难懂的方式:如果程序使用表达式&amp;*NULL,这将不会与具有整数类型的NULL 一起编译,但如果NULL 具有指针类型则会编译。

    C99 有针对这种特殊情况的措辞:

    如果 [&amp; 运算符的] 操作数是一元 * 的结果 运算符,该运算符和 &amp; 运算符都不会被评估和 结果好像两者都被省略了,除了对 运算符仍然适用,结果不是左值。

    不违反运算符的约束:&amp; 的操作数是一元运算符 * 的结果,而一元运算符 * 的操作数具有指针类型(因为我们假设 @987654330 @ 是这样定义的)。

    【讨论】:

    • 我认为问题在于寻找可以编译NULL 是指针还是整数类型的东西。 NULL+NULL 编译当且仅当 NULL 是整数类型。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-08-13
    • 1970-01-01
    • 1970-01-01
    • 2017-06-25
    • 1970-01-01
    • 2013-05-09
    • 1970-01-01
    相关资源
    最近更新 更多