【问题标题】:Is it possible to initialize a C pointer to NULL?是否可以将 C 指针初始化为 NULL?
【发布时间】:2017-09-06 09:03:56
【问题描述】:

我一直在写类似的东西

char *x=NULL;

假设

 char *x=2;

将创建一个指向地址 2 的 char 指针。

但是,在The GNU C Programming Tutorial 中,它说int *my_int_ptr = 2; 在分配时将整数值2 存储到my_int_ptr 中的任何随机地址。

这似乎意味着我自己的char *x=NULL 正在将NULL 转换为char 的值分配给内存中的某个随机地址。

虽然

#include <stdlib.h>
#include <stdio.h>

int main()
{
    char *x=NULL;

    if (x==NULL)
        printf("is NULL\n");

    return EXIT_SUCCESS;
}

确实会打印

为空

当我编译和运行它时,我担心我依赖于未定义的行为,或者至少是未指定的行为,我应该写

char *x;
x=NULL;

改为。

【问题讨论】:

  • int *x = whatever;int *x; *x = whatever; 之间的区别非常令人困惑。 int *x = whatever; 实际上的行为类似于 int *x; x = whatever;,而不是 *x = whatever;
  • 本教程似乎把这种令人困惑的区别弄错了。
  • 网络上有那么多烂教程!立即停止阅读。我们真的需要一个这样的黑名单,我们可以公开羞辱蹩脚的书......
  • @MM 这并没有让它在 2017 年变得不那么糟糕。鉴于 80 年代以来编译器和计算机的发展,这基本上就像我是一名医生并阅读医学书籍一样在 18 世纪。
  • 我认为本教程不符合“The GNU C 编程教程”...

标签: c pointers initialization


【解决方案1】:

是否可以将 C 指针初始化为 NULL?

TL;DR是的,非常。


The actual claim made on the guide reads like

另一方面,如果您只使用单个初始赋值 int *my_int_ptr = 2;,程序将尝试用值 2 填充 my_int_ptr 指向的内存位置的内容。因为 @987654325 @ 是垃圾,可以是任意地址。 [...]

嗯,他们错了,你是对的。

对于语句,(暂时忽略指向整数转换的指针是实现定义的行为这一事实

int * my_int_ptr = 2;

my_int_ptr 是一个变量(类型为指向int 的指针),它有自己的地址(类型:指向整数的指针地址),您将2 的值存储到 地址。

现在,my_int_ptr,作为指针类型,我们可以说,它指向内存位置的“type”值指向所持有的值在my_int_ptr。因此,您实际上是在为指针变量分配 of 的值,而不是指针指向的内存位置的值。

所以,总结一下

 char *x=NULL;

将指针变量x初始化为NULL,而不是指针指向的内存地址处的值

这与

相同
 char *x;
 x = NULL;    

扩展:

现在,严格遵守,像这样的陈述

 int * my_int_ptr = 2;

是非法的,因为它涉及违反约束。说清楚,

  • my_int_ptr 是一个指针变量,输入int *
  • 一个整数常量,2 的类型为 int,根据定义。

而且它们不是“兼容”类型,所以这个初始化是无效的,因为它违反了简单赋值的规则,在第 6.5.16.1/P1 章中提到,在Lundin's answer 中描述。

如果有人对初始化如何与简单的赋值约束相关联感兴趣,请引用 C11,第 6.7.9 章,P11

标量的初始值设定项应该是一个表达式,可选地用大括号括起来。这 对象的初始值是表达式的初始值(转换后); 同一类型 适用于简单赋值的约束和转换,采用标量的类型 成为其声明类型的非限定版本。

【讨论】:

  • @Random832n 他们错了。我在回答中引用了相关部分,否则请纠正我。哦,重点是故意的。
  • "... 是非法的,因为它涉及违反约束。... 整数文字,根据定义,2 具有 int 类型。"是有问题的。听起来因为2int,所以分配是个问题。但不仅如此。 NULL 也可以是 intint 0。只是char *x = 0; 定义明确而char *x = 2; 不是。 6.3.2.3 指针 3(顺便说一句:C 没有定义 整数文字,只有 字符串文字复合文字0 是 整数常量)
  • @chux 你说的很对,但是char *x = (void *)0; 不符合要求吗?还是仅与其他表达式一起产生值0
  • @SouravGhosh:值为0 的整数常量很特殊:它们隐式转换为空指针,与将一般整数表达式显式转换为指针类型的常规规则不同。
  • 1974 C 参考手册 中描述的语言不允许声明来指定初始化表达式,并且缺少此类表达式使得“声明镜像使用”更加实用。语法 int *p = somePtrExpression 恕我直言,相当可怕,因为它看起来像是在设置 *p 的值,但实际上是在设置 p 的值。
【解决方案2】:

教程错了。在 ISO C 中,int *my_int_ptr = 2; 是一个错误。在 GNU C 中,它的含义与 int *my_int_ptr = (int *)2; 相同。这会将整数 2 转换为内存地址,具体方式由编译器确定。

它不会尝试将任何内容存储在该地址所寻址的位置(如果有)。如果您继续写*my_int_ptr = 5;,那么它会尝试将数字5 存储在该地址所寻址的位置。

【讨论】:

  • 我不知道整数到指针的转换是实现定义的。感谢您提供信息。
  • @taskinoor 请注意,只有在您通过强制转换强制转换的情况下才会进行转换,如本答案所示。如果没有演员表,代码不应该编译。
  • @taskinoor:是的,C 中的各种转换非常混乱。这个 Q 有关于转化的有趣信息:C: When is casting between pointer types not undefined behavior?
【解决方案3】:

为了澄清教程错误的原因,int *my_int_ptr = 2; 是一个“违反约束”的代码,它是不允许编译的代码,编译器必须在遇到它时给你一个诊断。

按照 6.5.16.1 简单分配:

约束

应满足以下条件之一:

  • 左边的操作数有原子、合格或不合格的算术类型,右边有算术类型;
  • 左操作数具有与右操作数兼容的结构或联合类型的原子、限定或非限定版本;
  • 左操作数具有原子、限定或非限定指针类型,并且(考虑到左操作数在左值之后的类型 转换)两个操作数都是指向合格或不合格的指针 兼容类型的版本,左边指向的类型有 right 指向的类型的所有限定符;
  • 左操作数具有原子、限定或非限定指针类型,并且(考虑到左操作数在左值之后的类型 转换)一个操作数是指向对象类型的指针,而另一个 是指向 void 的合格或不合格版本的指针,并且 左侧指向的类型具有所指向类型的所有限定符 向右;
  • 左边的操作数是一个原子的、合格的或不合格的指针,右边是一个空指针常量;或
  • 左操作数的类型为 atomic、qualified 或 unqualified _Bool,右操作数是指针。

在这种情况下,左操作数是一个非限定指针。它没有提到允许右操作数是整数(算术类型)。所以代码违反了C标准。

众所周知,除非您明确告诉它是标准 C 编译器,否则 GCC 会表现不佳。如果您将代码编译为-std=c11 -pedantic-errors,它将正确地给出必须做的诊断。

【讨论】:

  • 赞成建议 -pedantic-errors。虽然我可能会使用相关的 -Wpedantic 。
  • 您的声明的一个例外是,右操作数不允许为整数:第 6.3.2.3 节说,“值为 0 的整数常量表达式,或此类转换为类型 @987654323 的表达式@,称为空指针常量。”请注意报价中倒数第二个要点。因此,int* p = 0; 是写int* p = NULL; 的合法方式。虽然后者更清晰、更传统。
  • 这使得病态混淆int m = 1, n = 2 * 2, * p = 1 - 1, q = 2 - 1;也合法。
  • @Davislor 在此答案的标准引用中被第 5 点所涵盖(同意之后的摘要可能应该提到它)
  • @chux 我相信一个格式良好的程序需要将intptr_t 显式转换为右侧允许的类型之一。也就是说,void* a = (void*)(intptr_t)b; 在第 4 点是合法的,但(intptr_t)b 既不是兼容的指针类型,也不是void*,也不是空指针常量,而且void* a 既不是算术类型也不是_Bool。标准说转换是合法的,但不是隐含的。
【解决方案4】:

int *my_int_ptr = 2

在分配时将整数值 2 存储到 my_int_ptr 中的任意随机地址。

这是完全错误的。如果这是真的写的,那么请找一本更好的书或教程。

int *my_int_ptr = 2 定义了一个指向地址 2 的整数指针。如果您尝试访问地址 2,很可能会发生崩溃。

*my_int_ptr = 2,即行中没有int,将值2存储到my_int_ptr指向的任何随机地址。话虽如此,您可以在定义指针时将 NULL 分配给它。 char *x=NULL; 是完全有效的 C。

编辑:在写这篇文章时,我不知道整数到指针的转换是实现定义的行为。有关详细信息,请参阅 @M.M 和 @SouravGhosh 的好答案。

【讨论】:

  • 这是完全错误的,因为它违反了约束,而不是出于任何其他原因。特别是,这是不正确的:“int *my_int_ptr = 2 定义了一个指向地址 2 的整数指针”。
  • @Lundin:您的短语“不是出于任何其他原因” 本身就是错误和误导性的。如果你解决了类型兼容性问题,你仍然会发现教程的作者严重歪曲了指针初始化和赋值的工作原理。
【解决方案5】:

很多关于 C 指针的困惑来自最初在编码风格方面做出的一个非常糟糕的选择,而语言语法中的一个非常糟糕的小选择也证实了这一点。

int *x = NULL; 是正确的C,但它很容易误导,我什至会说是荒谬的,它阻碍了许多新手对该语言的理解。这让人认为以后我们可以做*x = NULL;,这当然是不可能的。你看,变量的类型不是int,变量的名字也不是*x,声明中的*也没有与=协同作用。它纯粹是声明性的。所以,更有意义的是:

int* x = NULL; 也是正确的 C,尽管它不符合原始的 K&R 编码风格。它非常清楚类型是int*,而指针变量是x,因此即使对于外行来说,值NULL 被存储到x 中也很明显,这是一个指向int

此外,它更容易导出规则:当星号远离变量名时,它是一个声明,而附加到名称的星号是指针解引用。

所以,现在更容易理解的是,我们可以更进一步地执行x = NULL;*x = 2;,换句话说,它使新手更容易看到variable = expression 是如何导致pointer-type variable = pointer-expression 和@987654337 @。 (对于发起者,“表达式”是指“右值”。)

语言语法中的不幸选择是,当声明局部变量时,您可以说int i, *p;,它声明了一个整数和一个指向整数的指针,所以它让人相信*是一个有用的部分的名字。但事实并非如此,而且这种语法只是一个古怪的特例,为了方便而添加的,在我看来它不应该存在,因为它使我上面提出的规则无效。据我所知,这种语法在语言中的其他任何地方都没有意义,但即使它是有意义的,它也表明在 C 中定义指针类型的方式存在差异。在其他任何地方,在单变量声明中,在参数列表中,在结构成员等中,您可以将指针声明为type* pointer-variable 而不是type *pointer-variable;这是完全合法的,而且更有意义。

【讨论】:

  • int *x = NULL; is correct C, but it is very misleading, I would even say nonsensical,... 我必须同意不同意。 It makes one think....别想了,先读一本C书,无意冒犯。
  • ^^ 这对我来说非常有意义。所以,我想这是主观的。
  • @SouravGhosh 作为一个意见问题,我认为 C 应该被设计成 int* somePtr, someotherPtr 声明两个指针,事实上,我曾经写过 int* somePtr 但是这会导致您描述的错误。
  • @fagricipni 因此我停止使用多变量声明语法。我一一声明我的变量。如果我真的希望它们在同一行,我用分号而不是逗号分隔它们。 “如果一个地方不好,就不要去那个地方。”
  • @fagricipni 好吧,如果我可以从头开始设计 linux,我会使用 create 而不是 creat。 :) 关键是,它就是这样,我们需要塑造自己以适应它。归根结底,这一切都归结为个人选择,同意。
【解决方案6】:

我想添加一些与许多优秀答案正交的东西。实际上,初始化为 NULL 远非坏习惯,如果该指针可能用于存储动态分配的内存块,也可能不用于存储动态分配的内存块,它可能会很方便。

int * p = NULL;
...
if (...) {
    p = (int*) malloc(...);
    ...
}
...
free(p);

由于根据ISO-IEC 9899 standard free 是一个 nop,当参数是 NULL 时,上面的代码(或类似的更有意义的代码)是合法的。

【讨论】:

  • 在 C 中强制转换 malloc 的结果是多余的,除非 C 代码也应该编译为 C++。
  • 你是对的,void* 是根据需要转换的。但是拥有与 C 和 C++ 编译器一起工作的代码可能会有好处。
  • @LucaCiti C 和 C++ 是不同的语言。如果您尝试使用为另一个而设计的编译器来编译为一个编写的源文件,那么只有等待您的错误。这就像尝试编写可以使用 Pascal 工具编译的 C 代码。
  • 好建议。我(尝试)总是将我的指针常量初始化为某些东西。在现代 C 中,这通常可以是它们的最终值,它们可以是在 in medias res 中声明的 const 指针,但即使指针需要是可变的(比如在循环中使用的指针或 @ 987654328@),将其设置为NULL 会捕获在设置为实际值之前使用它的错误。在大多数系统上,取消引用 NULL 会导致故障点出现段错误(尽管有例外),而未初始化的指针包含垃圾并且写入它会破坏任意内存。
  • 此外,在调试器中很容易看到指针包含NULL,但很难区分垃圾指针和有效指针。因此,从声明的那一刻起,确保所有指针始终有效或 NULL 是有帮助的。
【解决方案7】:

这是正确的。

int main()
{
    char * x = NULL;

    if (x==NULL)
        printf("is NULL\n");

    return EXIT_SUCCESS;
}

这个函数是正确的。它将地址 0 分配给 char 指针 x。也就是将指针x指向内存地址0。

替代方案:

int main()
{
    char* x = 0;

    if ( !x )
        printf(" x points to NULL\n");

    return EXIT_SUCCESS;
}

我猜你想要的是:

int main()
{
    char* x = NULL;
    x = alloc( sizeof( char ));
    *x = '2';

    if ( *x == '2' )
        printf(" x points to an address/location that contains a '2' \n");

    return EXIT_SUCCESS;
}

x is the street address of a house. *x examines the contents of that house.

【讨论】:

  • "它将 0 的地址分配给 char 指针 x。" --> 也许。 C 没有指定指针的,只有char* x = 0; if (x == 0) 为真。指针不一定是整数。
  • 它没有“将指针 x 指向内存地址 0”。它将指针值设置为 未指定 无效值,可以通过将其与 0 或 NULL 进行比较来测试。实际操作是实现定义的。这里没有什么可以回答实际问题。
【解决方案8】:

这是一个空指针

int * nullPtr = (void*) 0;

【讨论】:

  • 这回答了标题,但不是问题的主体。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-18
相关资源
最近更新 更多