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


90

我一直在写类似的东西

char *x=NULL;

假设

 char *x=2;

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

但是,在《 GNU C编程指南》中,它指出int *my_int_ptr = 2;将整数值存储2my_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;

代替。


72
做什么int *x = whatever;和做什么之间有一个非常令人困惑的区别int *x; *x = whatever;int *x = whatever;实际上表现得很int *x; x = whatever;,不是*x = whatever;
user2357112支持Monica's

78
本教程似乎弄错了区分的错误。
user2357112支持Monica's

51
网上有那么多糟糕的教程!立即停止阅读。我们真的需要一个SO黑名单,在这里我们可以公开羞辱烂书……
Lundin

9
@MM在2017年并不会因此变得不那么糟糕。考虑到80年代以来编译器和计算机的发展,基本上就像我是医生并正在阅读18世纪写的医学书籍一样。
伦丁

13
我不认为这个教程有资格作为“ GNU C程序设计教程” ...
marcelm

Answers:


114

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

TL; DR是的,非常非常。


在指南上提出实际要求如下:

另一方面,如果仅使用单个初始分配,int *my_int_ptr = 2;则程序将尝试my_int_ptr使用值2 my_int_ptr填充指向的内存位置的内容。由于已填充垃圾,因此它可以是任何地址。[...]

那么,他们错的,你是对的。

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

int * my_int_ptr = 2;

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

现在,my_int_ptr作为指针类型,可以说,它指向保存在其中的值所指向的存储位置处的“类型” 值my_int_ptr。所以,你基本上是将值指针变量,存储位置不是值由指针指向。

所以,结论

 char *x=NULL;

将指针变量初始化xNULL,而不是的内存地址值

这是一样的

 char *x;
 x = NULL;    

扩张:

现在,严格遵循以下声明

 int * my_int_ptr = 2;

是非法的,因为它涉及到约束违反。要清楚一点

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

并且它们不是“兼容”类型,因此此初始化无效,因为它违反了第6.5.16.1/P1章中所述的简单分配规则, Lundin答案中

如果有人对初始化如何链接到简单的分配约束感兴趣C11,请参见第§6.7.9章,P11

标量的初始值设定项应为单个表达式,可以选择用大括号括起来。对象的初始值是表达式的初始值(转换后);采用与简单赋值相同的类型约束和转换,将标量的类型作为其声明类型的非限定版本。


@ Random832n他们错的。我在回答中引用了相关部分,否则请更正我。哦,强调有意。
Sourav Ghosh'4

“……是非法的,因为它涉及到约束的违反。……根据定义,整数文字2的类型为int。” 有问题。这听起来像因为2int,分配是一个问题。不仅如此。 NULL也可能是一个int,一个int 0。只是char *x = 0;定义明确而char *x = 2;并非如此。6.3.2.3指针3(顺便说一句:C未定义整数文字,只有字符串文字复合文字0整数常量
chux-恢复莫妮卡(Monica)

@chux你是非常正确的,但不是char *x = (void *)0;,要遵守吗?还是仅在其他表达式中产生值0
Sourav Ghosh'4

10
@SouravGhosh:带值的整数常量0是特殊的:它们与通常的规则分别隐式转换为null指针,以将常规整数表达式显式转换为指针类型。
史蒂夫·杰索普

1
1974 C参考手册描述的语言不允许使用声明来指定初始化表达式,并且缺少此类表达式使“声明镜像使用”更加实用。int *p = somePtrExpressionIMHO 的语法非常恐怖,因为它看起来像是在设置的值,*p但实际上是在设置的值p
supercat

53

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

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


1
我不知道整数到指针的转换是实现定义的。感谢您的信息。
taskinoor

1
@taskinoor请注意,只有在您强制转换时才有转换,如本答案所示。如果不进行强制转换,则代码不应编译。
伦丁

2
@taskinoor:是的,C语言中的各种转换非常令人困惑。这个问号包含有关转换的有趣信息:C:何时在指针类型之间进行强制转换不是未定义的行为?
sleske'4

17

为了阐明本教程为什么是错误的,int *my_int_ptr = 2;是“约束违例”,是不允许编译的代码,并且编译器在遇到该错误时必须给您诊断。

按照6.5.16.1简单分配:

约束条件

以下条件之一应成立:

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

在这种情况下,左操作数是不合格的指针。它在任何地方都没有提到允许正确的操作数是整数(算术类型)。因此,该代码违反了C标准。

除非您明确地将GCC定义为标准C编译器,否则GCC的运行状况会很差。如果将代码编译为-std=c11 -pedantic-errors,它将正确地给出诊断,就像必须做的那样。


4
因暗示-pedantic-errors而被赞扬。虽然我可能会使用相关的-Wpedantic。
fagricipni

2
关于您的声明的正确操作数不允许为整数的一个例外:第6.3.2.3节说:“值为0的整数常量表达式,或将此类强制转换为type的表达式void *称为空指针常量。” 请注意报价中倒数第二个要点。因此,这int* p = 0;是一种合法的书写方式int* p = NULL;。尽管后者更清晰,更常规。
戴维斯洛

1
这也使病理混淆变得int m = 1, n = 2 * 2, * p = 1 - 1, q = 2 - 1;合法。
戴维斯洛

@Davislor在此答案的标准引号中被子弹点5覆盖(同意之后的摘要可能应该提一下)
MM

1
@chux我相信格式正确的程序将需要intptr_t在右侧将显式转换为允许的类型之一。也就是说,void* a = (void*)(intptr_t)b;从点4开始是合法的,但(intptr_t)b既不是兼容的指针类型,也不是a void*,也不是空指针常量,void* a也不是算术类型也不是_Bool。该标准说转换是合法的,但不是隐式的。
戴维斯洛

15

int *my_int_ptr = 2

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

这是完全错误的。如果这是实际写的,那么请获得更好的书或教程。

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

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

编辑:撰写本文时,我不知道整数到指针的转换是实现定义的行为。有关详细信息,请参见@MM和@SouravGhosh给出的良好答案。


1
这是完全错误的,因为它是违反约束的行为,而不是出于其他任何原因。特别是,这是不正确的:“ int * my_int_ptr = 2定义了指向地址2的整数指针”。
伦丁

@伦丁:您的“不是出于任何其他原因”这句话本身就是错误和误导性的。如果解决了类型兼容性问题,那么您仍然会发现本教程的作者严重误解了指针初始化和赋值的工作方式。
Lightness Races in Orbit

14

有关C指针的许多困惑来自最初在编码样式方面做出的一个非常糟糕的选择,并由该语言的语法中一个非常糟糕的选择所证实。

int *x = NULL;是正确的C语言,但它很容易引起误解,我什至会说很荒谬,而且它阻碍了许多新手对语言的理解。这使人们认为以后我们可以做到*x = NULL;,这当然是不可能的。您会看到,变量的类型不是int,变量的名称不是*x*声明中的也不与协同工作=。它纯粹是声明性的。因此,更有意义的是:

int* x = NULL;尽管它不遵循原始的K&R编码样式,但它也是正确的C。它非常清楚地表明类型是int*,而指针变量是x,因此即使是未初始化的人也清楚地知道该值NULL是存储在x其中的,即指向的指针int

此外,它使导出规则变得更加容易:当星号远离变量名时,它就是一个声明,而与该名称相连的星号是指针取消引用。

因此,现在我们可以做得更多x = NULL;或更容易理解了,或者*x = 2;换句话说,它更容易为新手,看看如何variable = expression导致pointer-type variable = pointer-expressiondereferenced-pointer-variable = expression。(对于发起者,“表达”是指“右值”。)

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


int *x = NULL; is correct C, but it is very misleading, I would even say nonsensical,...我必须同意不同意。It makes one think....停止思考,先读一本C书,没有冒犯。
Sourav Ghosh

^^这对我来说是完全合理的。因此,我认为这是主观的。
Mike Nakis

5
@SouravGhosh作为一个观点,我认为应该设计C 来int* somePtr, someotherPtr声明两个指针,实际上,我曾经写过程序,int* somePtr但这导致了您所描述的错误。
fagricipni

1
因此,@ fagricipni我停止使用多变量声明语法。我一一声明我的变量。如果我真的希望它们在同一行上,请使用分号(而不是逗号)将它们分开。“如果一个地方不好,那就不要去那个地方。”
Mike Nakis

2
@fagricipni好吧,如果我可以从头开始设计linux,我会用create代替creat。:)关键是,它是如此,我们需要塑造自己以适应这一点。最终,一切都归结为个人选择。
Sourav Ghosh

6

我想添加一些与许多出色答案正交的东西。实际上,初始化NULL为绝不是坏习惯,并且如果该指针可能用于或可能不用于存储动态分配的内存块,则可能很方便。

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

由于根据ISO-IEC 9899标准, free当参数为时为nop NULL,因此上面的代码(或沿同一行更有意义的代码)是合法的。


5
在C中强制转换malloc的结果是多余的,除非C代码也应作为C ++编译。

您是对的,将void*根据需要进行转换。但是拥有可与C和C ++编译器一起使用的代码可能会有好处。
卡·花旗

1
@LucaCiti C和C ++是不同的语言。如果尝试使用为另一个编写的编译器来编译为一个编写的源文件,则只有错误等待着您。就像尝试编写可以使用Pascal工具进行编译的C代码一样。
Evil Dog Pie'Apr

1
好建议。我(试图)总是将指针常量初始化为某种东西。在现代C语言中,这通常可以是它们的最终值,并且可以是在medias中const声明的指针,但是即使指针需要可变(例如在循环或by中使用的指针),也可以将其设置为在以前使用过的地方捕获错误设置其实际价值。在大多数系统上,取消引用会在故障点导致段错误(尽管有例外),而未初始化的指针包含垃圾,对其进行写入会破坏任意内存。realloc()NULLNULL
戴维斯洛

1
另外,在调试器中很容易看到指针包含NULL,但是很难从有效指针中分辨出垃圾指针。因此NULL,从声明的那一刻起确保所有指针总是有效或便很有帮助。
戴维斯洛


1

这是对的。

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)才是正确的。指针不一定是整数。
chux-恢复莫妮卡

它不会“将指针x指向内存地址0”。它将指针值设置为未指定的无效值,可以通过将其与0或NULL比较进行测试。实际操作是实现定义的。这里没有任何答案可以回答实际问题。
罗恩侯爵
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.