【问题标题】:Can I use NULL as substitution for the value of 0?我可以使用 NULL 代替 0 的值吗?
【发布时间】:2020-04-13 16:15:46
【问题描述】:

我可以使用NULL 指针代替0 的值吗?

或者这样做有什么问题吗?


例如:

int i = NULL;

替代:

int i = 0;

作为实验,我编译了以下代码:

#include <stdio.h>

int main(void)
{
    int i = NULL;
    printf("%d",i);

    return 0;
}

输出:

0

确实它给了我这个警告,这本身是完全正确的:

warning: initialization makes integer from pointer without a cast [-Wint-conversion] 

但结果还是一样的。


  • 我是否因此而陷入“未定义行为”?
  • 可以这样使用NULL吗?
  • 在算术表达式中使用NULL 作为数值有什么问题吗?
  • 在这种情况下,C++ 中的结果和行为是什么?

我已经阅读了What is the difference between NULL, '\0' and 0 的答案,了解NULL、\0 和0 之间的区别是什么,但我没有从那里得到简明的信息,如果这是完全允许的,也是正确的在赋值和其他算术运算中使用NULL 作为值。

【问题讨论】:

  • 评论不用于扩展讨论;这个对话是moved to chat。
  • 最好分别问两个问题,一个是针对 C 的,一个是针对 C++ 的。

标签: c++ c pointers null language-lawyer


【解决方案1】:

我可以使用 NULL 指针代替 0 的值吗?

不,这样做不安全。 NULL 是一个空指针常量,可以具有 int 类型,但更通常具有 void * 类型(在 C 中),否则不能直接分配给 int (在 C++ >= 11 中)。两种语言都允许将指针转换为整数,但它们不提供隐式执行此类转换(尽管某些编译器将其作为扩展提供)。此外,尽管将空指针转换为整数以产生值 0 是很常见的,但标准并不能保证这一点。如果您想要一个类型为 int 且值为 0 的常量,则拼写为 0。

  • 我可能会因此而进入未定义行为吗?

是的,在 NULL 扩展为类型为 void * 或任何其他不能直接分配给 int 的值的任何实现上。该标准没有定义您在此类实现上的分配行为,因此它的行为是未定义的。

  • 是否允许以这种方式使用 NULL 操作?

这是一种糟糕的风格,它会在某些系统和某些情况下崩溃。鉴于您似乎在使用 GCC,如果您使用 -Werror 选项进行编译,它将在您自己的示例中中断。

  • 在算术表达式中使用NULL作为数值有什么问题吗?

是的。根本不保证有一个数值。如果你的意思是0,那就写0,这不仅定义明确,而且更短更清晰。

  • 那么 C++ 中的结果如何?

C++ 语言在转换方面比 C 语言更严格,并且对 NULL 有不同的规则,但在那里,实现也可以提供扩展。同样,如果你的意思是 0,那么这就是你应该写的。

【讨论】:

  • 您应该指出“更典型的类型为 void *”仅适用于 C。void * 不是 C++ 的合法类型(因为您不能将 void* 分配给任何其他指针类型)。在 C++89 和 C++03 中,实际上 NULL 必须 是 int 类型,但在以后的版本中它可以是(并且通常是)nullptr_t。
  • 您也错了,将void* 转换为int 是未定义的行为。它不是;这是实现指定的行为。
  • @MartinBonnersupportsMonica,在那些 C 指定将指针转换为整数的上下文中,转换的结果确实是实现指定的,但这不是我要说的。它是将指针分配给具有未定义行为的整数类型的左值(无需通过强制转换显式转换)。该语言没有在那里定义自动转换。
  • @MartinBonnersupportsMonica,我进行了编辑以更包含 C++ 考虑因素。无论如何,中心主题同样适用于两种语言:如果您想要一个整数 0,则将其显式地写为适当类型的整数常量。
【解决方案2】:

NULL 是一些空指针常量。在 C 中,它可能是一个整数常量表达式,其值为 0 或这种表达式转换为 void*,后者更有可能。这意味着您不能假设将NULL 与零互换使用。例如,在这个代码示例中

char const* foo = "bar"; 
foo + 0;

用NULL 替换0 不能保证是一个有效的C 程序,因为两个指针之间的加法(更不用说不同的指针类型)没有定义。由于违反约束,它将导致发出诊断。 The operands for addition will not be valid.


对于 C++,情况有些不同。缺少从 void* 到其他对象类型的隐式转换意味着 NULL 在历史上在 C++ 代码中被定义为 0。在 C++03 中,您可能会侥幸逃脱。但从 C++11 开始,它可以合法地be defined as the nullptr keyword。现在再次产生错误,因为 std::nullptr_t 可能不会添加到指针类型中。

如果NULL 被定义为nullptr,那么即使你的实验也会失效。没有从 std::nullptr_t 到整数的转换。这就是为什么它被认为是更安全的空指针常量。

【讨论】:

  • 为了完整起见,0L 也是一个空指针常量,在两种语言中都可以用作NULL。
  • @jamesqf 标准规定值为 0 的整数常量是一个空指针常量。因此 0L 是一个空指针常量。
  • @eerorika:这正是世界所需要的,忽略现实的标准 :-) 因为如果我正确地记住了我的 80286 程序集,你甚至不能将远指针分配为单个操作,因此编译器编写者必须对其进行特殊处理。
  • @jamesqf Per the C FAQ,回复:使0 成为一个空指针常量:“显然是对所有现存写得不好且做出错误假设的 C 代码的一种安慰”
  • @jamesqf,任何值为 0 的整数常量都是空指针常量(在 C 中)与硬件指针实现无关。还要注意,标准 C 在任何情况下都不识别近指针和远指针之间的区别,但支持指针到指针的分配。它还支持(一些)指针比较,这对分段寻址格式(例如 286)提出了有趣的问题。
【解决方案3】:

我可以使用 NULL 指针代替 0 的值吗?

int i = NULL;

规则因语言及其版本而异。在某些情况下,您可以,而在其他情况下,您不能。无论如何,您不应该。如果你幸运的话,你的编译器会在你尝试它时发出警告,甚至更好的是,编译失败。

在 C++ 中,在 C++11 之前(引用自 C++03):

[lib.support.types]

NULL 是本国际标准中实现定义的 C++ 空指针常量。

将空指针常量用作整数几乎没有意义。不过……

[conv.ptr]

空指针常量是整数类型的整数常量表达式 (5.19) 右值,其计算结果为零。

因此,即使它是荒谬的,它在技术上也可以工作。由于这种技术性,您可能会遇到滥用NULL 的编写不佳的程序。

自 C++11 起(引用自最新草案):

[conv.ptr]

空指针常量是一个值为零的整数文字 ([lex.icon])或 std​::​nullptr_t 类型的纯右值。

std​::​nullptr_­t 不能转换为整数,因此使用 NULL 作为整数只能有条件地工作,具体取决于语言实现所做的选择。

附: nullptr 是 std​::​nullptr_­t 类型的纯右值。除非您需要在 pre-C++11 中编译您的程序,否则您应该始终使用 nullptr 而不是 NULL。


C 有点不同(引自 C11 草案 N1548):

6.3.2.3 语言/转换/其他操作数/指针

3 值为 0 的整型常量表达式,或这种类型转换为void * 的表达式称为空指针常量。 ...

因此,这种情况类似于 C++11 后的情况,即 NULL 的滥用根据语言实现的选择有条件地起作用。

【讨论】:

    【解决方案4】:

    是的,但根据实施情况,您可能需要演员表。但是,是的,否则它是 100% 合法的。

    虽然它的风格真的非常非常糟糕(不用说?)。

    NULL 是,或者曾经是,实际上不是 C++,它是 C。然而,标准 确实,就像许多 C 的遗产一样,有两个子句([diff .nu​​ll] 和 [support.types.nullptr]) 在技术上使NULL C++。它是一个实现定义的空指针常量。因此,即使它的风格很糟糕,但从技术上讲,它与 C++ 一样。
    正如footnote 中所指出的,可能的实现可能是0 或0L,但不是 (void*)0。

    NULL 当然可以是nullptr(标准没有明确说明,但它几乎是0 或0L 之后剩下的唯一选择)。几乎从来没有这种情况,但这是一种合法的可能性。

    编译器向您显示的警告表明编译器实际上不兼容(除非您在 C 模式下编译)。因为,好吧,根据警告,它确实转换一个空指针(不是nullptr,它是nullptr_t,这将是不同的),所以显然NULL的定义是确实是(void*)0,它可能不是。

    无论哪种方式,您都有两种可能的合法(即编译器未损坏)情况。无论是(实际情况),NULL 类似于0 或0L,然后您将“零或一” 转换为整数,然后就可以开始了。

    或者NULL 确实是nullptr。在这种情况下,您有一个独特的值,它可以保证比较以及明确定义的转换从整数,但不幸的是没有到整数。但是,它确实有明确定义的到bool 的转换(导致false),bool 有明确定义的整数转换(导致0)。

    不幸的是,这是 两次 转换,因此它不在 [conv] 中指出的 “零或一” 范围内。因此,如果您的实现将 NULL 定义为 nullptr,那么您将必须添加显式强制转换才能使您的代码正确。

    【讨论】:

      【解决方案5】:

      来自 C 常见问题:

      问:如果NULL和0等价于空指针常量,我应该使用哪个?

      答:只有在指针上下文中NULL 和0 是等价的。 NULL 应该不在需要另一种 0 时使用,即使它可能有效,因为这样做会发送错误的风格信息。 (此外,ANSI 允许将 NULL 定义为 ((void *)0),这在非指针上下文中根本不起作用。)特别是,当需要 ASCII 空字符 (NUL) 时,不要使用 NULL。提供您自己的定义

      http://c-faq.com/null/nullor0.html

      【讨论】:

        【解决方案6】:

        免责声明:我不懂 C++。我的回答不适用于 C++ 的上下文

        '\0' 是一个值为零的int,与0 完全相同。

        for (int k = 10; k > '\0'; k--) /* void */;
        for (int k = 10; k > 0; k--) /* void */;
        

        在指针的上下文中,0 和 NULL 100% 等效:

        if (ptr) /* ... */;
        if (ptr != NULL) /* ... */;
        if (ptr != '\0') /* ... */;
        if (ptr != 0) /* ... */;
        

        都是 100% 等效的。


        注意ptr + NULL

        ptr + NULL 的上下文不是指针的上下文。 C语言中没有关于指针添加的定义;指针和整数可以相加(或相减)。在ptr + NULL 中,如果ptr 或NULL 是指针,则另一个必须是 整数,因此ptr + NULL 实际上是(int)ptr + NULL 或ptr + (int)NULL,具体取决于ptr 和 NULL 可以预期几种行为:一切正常,指针和整数之间的转换警告,编译失败,...

        【讨论】:

        • 我以前见过#define NULL (void *)0。你确定 NULL 和 plain 0 是 100% 等价的吗?
        • 在指针的上下文中,是的......我的回答中强调了条件,谢谢
        • @phuclv:我完全不了解 C++。我的答案(括号之间的那位除外)是关于 C
        • @phuclv ptr + NULL 没有在指针上下文中使用NULL
        • @JesperJuhl:在指针的上下文中它们是 100% 等效的。我不知道nullptr 是什么,但((void*)0) 和0(或'\0')在指针上下文中是等价的...if (ptr == '\0' /* or equivalent 0, NULL */)
        【解决方案7】:

        不,不再喜欢使用NULL(指针初始化的旧方式)。

        C++11 起:

        关键字nullptr 表示指针字面量。它是 std::nullptr_t 类型的纯右值。存在从nullptr 到任何指针类型的空指针值和任何指向成员类型的指针的隐式转换。任何空指针常量都存在类似的转换,包括std::nullptr_t 类型的值以及宏NULL。

        https://en.cppreference.com/w/cpp/language/nullptr

        其实std::nullptr_t就是空指针字面量nullptr的类型。它是一种独特的类型,它本身不是指针类型或指向成员类型的指针。

        #include <cstddef>
        #include <iostream>
        
        void f(int* pi)
        {
           std::cout << "Pointer to integer overload\n";
        }
        
        void f(double* pd)
        {
           std::cout << "Pointer to double overload\n";
        }
        
        void f(std::nullptr_t nullp)
        {
           std::cout << "null pointer overload\n";
        }
        
        int main()
        {
            int* pi; double* pd;
        
            f(pi);
            f(pd);
            f(nullptr);  // would be ambiguous without void f(nullptr_t)
            // f(0);  // ambiguous call: all three functions are candidates
            // f(NULL); // ambiguous if NULL is an integral null pointer constant 
                        // (as is the case in most implementations)
        }
        

        输出:

        Pointer to integer overload
        Pointer to double overload
        null pointer overload
        

        【讨论】:

        • 问题是关于将 NULL 分配给整数的 0。从这个意义上说,使用 nullptr 而不是 NULL 没有任何变化。
        • 顺便说一句,C++ 11 之后就没有 NULL 的概念了。作者可能对使用 constexpr 或定义旧方式初始化感到困惑。 en.cppreference.com/w/cpp/language/default_initialization
        猜你喜欢
        • 2019-08-10
        • 2017-11-20
        • 2012-08-11
        • 2022-01-23
        • 2015-08-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多