简体   繁体   English

取消引用指向数组的 NULL 指针在 C 中是否有效?

[英]Is dereferencing a NULL pointer to array valid in C?

Is this behavior defined or not?这种行为是否已定义?

volatile long (*volatile ptr)[1] = (void*)NULL;
volatile long v = (long) *ptr;

printf("%ld\n", v);

It works because by dereferencing pointer to array we are receiving an array itself, then that array decaying to pointer to it's first element.它起作用是因为通过取消对数组的指针的引用,我们正在接收一个数组本身,然后该数组衰减为指向它的第一个元素的指针。

Updated demo: https://ideone.com/DqFF6T更新演示: https : //ideone.com/DqFF6T

Also, GCC even considers next code as a constant expression:此外,GCC 甚至将下一个代码视为常量表达式:

volatile long (*ptr2)[1] = (void*)NULL;
enum { this_is_constant_in_gcc = ((void*)ptr2 == (void*)*ptr2) };
printf("%d\n", this_is_constant_in_gcc);

Basically, dereferencing ptr2 at compile time;基本上,在编译时取消引用 ptr2;

This:这个:

long (*ptr)[1] = NULL;

Is declaring a pointer to an "array of 1 long " (more precisely, the type is long int (*)[1] ), with the initial value of NULL .声明一个指向“1 long数组”的指针(更准确地说,类型是long int (*)[1] ),初始值为NULL Everything fine, any pointer can be NULL .一切正常,任何指针都可以是NULL

Then, this:然后,这个:

long v = (long) *ptr;

Is dereferencing the NULL pointer, which is undefined behavior .正在取消引用NULL指针,这是未定义的行为 All bets are off, if your program does not crash, the following statement could print any value or do anything else really.所有的赌注都关了,如果你的程序没有崩溃,下面的语句可以打印任何值或真正做任何其他事情。

Let me make this clear one more time: undefined behavior means that anything can happen .让我再澄清一次:未定义的行为意味着任何事情都可能发生 There is no explanation as to why anything strange happens after invoking undefined behavior, nor there needs to be.没有解释为什么在调用未定义行为后会发生任何奇怪的事情,也不需要解释。 The compiler could very well emit 16-bit Real Mode x86 assembly, produce a binary that deletes your entire home folder, emit the Apollo 11 Guidance Computer assembly code, or whatever else.编译器可以很好地发出 16 位实模式 x86 程序集,生成一个删除整个主文件夹的二进制文件,发出 Apollo 11 指导计算机程序集代码,或其他任何东西。 It is not a bug.不是一个错误。 It's perfectly conforming to the standard.它完全符合标准。


The only reason your code seems to work is because GCC decides, purely out of coincidence , to do the following ( Godbolt link ):您的代码似乎可以工作的唯一原因是 GCC纯粹出于巧合决定执行以下操作( Godbolt 链接):

mov     QWORD PTR [rbp-8], 0    ; put NULL on the stack
mov     rax, QWORD PTR [rbp-8]
mov     QWORD PTR [rbp-16], rax ; move NULL to the variable v

Causing the NULL -dereference to never actually happen.导致NULL取消引用永远不会真正发生。 This is most probably a consequence of the undefined behavior in dereferencing ptr ¯\\_(ツ)_/¯这很可能是解引用ptr ¯\\_(ツ)_/¯ 中未定义行为的结果


Funnily enough, I previously said in a comment:有趣的是,我之前在评论中说:

dereferencing NULL is invalid and will basically always cause a segmentation fault.取消引用NULL是无效的,并且基本上总是会导致分段错误。

But of course, since it is undefined behavior that "basically always" is wrong.但是当然,因为“基本上总是”是错误的未定义行为。 I think this is the first time I ever see a null-pointer dereference not cause a SIGSEGV.我认为这是我第一次看到空指针取消引用不会导致 SIGSEGV。

Is this behavior defined or not?这种行为是否已定义?

Not.不是。

 long (*ptr)[1] = NULL; long v = (long) *ptr; printf("%ld\\n", v);

It works because by dereferencing pointer to array we are receiving an array itself, then that array decaying to pointer to it's first element.它起作用是因为通过取消对数组的指针的引用,我们正在接收一个数组本身,然后该数组衰减为指向它的第一个元素的指针。

No, you are confusing type with value.不,您将类型与价值混淆了。 It is true that the expression *ptr on the second line has type long[1] , but evaluating that expression produces undefined behavior regardless of the data type, and regardless of the automatic conversion that would be applied to the result if it were defined.确实,第二行中的表达式*ptr类型为long[1] ,但是无论数据类型如何,并且无论将应用于结果的自动转换如何,计算该表达式都会产生未定义的行为。

The relevant section of the spec is paragraph 6.5.2.3/4 :规范的相关部分是第 6.5.2.3/4 段

The unary * operator denotes indirection.一元 * 运算符表示间接。 If the operand points to a function, the result is a function designator;如果操作数指向一个函数,则结果是一个函数指示符; if it points to an object, the result is an lvalue designating the object.如果它指向一个对象,则结果是一个指定该对象的左值。 If the operand has type ''pointer to type'', the result has type ''type''.如果操作数的类型为“指向类型的指针”,则结果的类型为“类型”。 If an invalid value has been assigned to the pointer, the behavior of the unary * operator is undefined.如果为指针分配了无效值,则一元 * 运算符的行为未定义。

A footnote goes on to clarify that脚注继续澄清

[...] Among the invalid values for dereferencing a pointer by the unary * operator are a null pointer [...] [...] 一元 * 运算符取消引用指针的无效值包括空指针 [...]

It may "work" for you in an empirical sense, but from a language perspective, any output at all or none is a conforming result.从经验意义上讲,它可能对您“有效”,但从语言的角度来看,任何输出或根本没有输出都是符合要求的结果。

Update:更新:

It may be interesting to note that the answer would be different for explicitly taking the address of *ptr than it is for supposing that array decay will overcome the undefinedness of the dereference.有趣的是,明确获取*ptr的地址的答案与假设数组衰减将克服取消引用的不确定性的答案不同。 The standard provides that, as a special case, where the operand of the unary & operator is the result of a unary * operator, neither of those operators is evaluated.该标准规定,作为一种特殊情况,一元&运算符的操作数是一元*运算符的结果,这两个运算符都不会被计算。 Provided that all relevant constraints are satisfied, the result is as if they were both omitted altogether, except that it is never an lvalue.如果满足所有相关约束,结果就好像它们都被完全省略了,只是它永远不是左值。

Thus, this is ok:因此,这是可以的:

long (*ptr)[1] = NULL;
long v = (long) &*ptr;

printf("%ld\n", v);

On many implementations it will reliably print 0, but do note that C does not specify that it must be 0.在许多实现中,它会可靠地打印 0,但请注意,C 没有指定它必须为 0。

The key distinction here is that in this case, the * operation is not evaluated (per spec).这里的关键区别在于,在这种情况下,不评估*操作(根据规范)。 The * operation in the original code is is evaluated, notwithstanding the fact that if the pointer value were valid, the resulting array would be converted right back to a pointer (of a different type, but to the same location).*在原始代码中的操作进行评价,尽管事实上,如果该指针值是有效的,所得到的阵列将被转换右回指针(不同类型的,但相同的位置)。 That does suggest an obvious shortcut that implementations may take with the original code, and they may take it, if they wish, without regard to whether ptr 's value is valid because if it is in valid then they can do whatever they want.这并不意味着一个明显的捷径,实现可采取与原代码,他们可能会采取它,如果他们愿意的话,不考虑是否ptr的值是有效的,因为如果它有效的,那么他们可以为所欲为。

To just answer you´re provided questions:只是回答你提供的问题:

  1. Is dereferencing a NULL pointer to array valid in C?取消引用指向数组的 NULL 指针在 C 中是否有效?

No.不。

  1. Is this behavior defined or not?这种行为是否已定义?

It is classified as "undefined behavior", so it is not defined.它被归类为“未定义行为”,因此未定义。


Never mind of the case, that this trick with the array, maybe will work on some implementations and it fills absolutely no needs to do so (I imply you are asking out of curiousity), it is not valid per the C standard to dereference a NULL pointer in any way and will cause "Undefined Behavior".没关系,这个使用数组的技巧可能适用于某些实现,并且完全不需要这样做(我暗示您是出于好奇而询问),根据 C 标准取消引用 a 是无效任何方式的NULL指针都会导致“未定义行为”。


Anything can happen when you implement such statements into your program.当你在你的程序中实现这样的语句时,任何事情都可能发生。

Look at the answers on this question, which explain why:看看这个问题的答案,它解释了原因:

What EXACTLY is meant by "de-referencing a NULL pointer"? “取消引用空指针”究竟是什么意思?

One qoute from Adam Rosenfield´s answer :亚当·罗森菲尔德( Adam Rosenfield) 的回答中的一句话:

A null pointer is a pointer that does not point to any valid data (but it is not the only such pointer).空指针是不指向任何有效数据的指针(但它不是唯一的此类指针)。 The C standard says that it is undefined behavior to dereference a null pointer. C 标准说取消引用空指针是未定义的行为。 This means that absolutely anything could happen: the program could crash, it could continue working silently, or it could erase your hard drive (although that's rather unlikely).这意味着任何事情都可能发生:程序可能会崩溃,可能会继续静默运行,或者可能会擦除您的硬盘驱动器(尽管这不太可能)。

Is this behavior defined or not?这种行为是否已定义?

The behavior is undefined because you are applying * operator to a pointer that compares equal to null pointer constant.该行为未定义,因为您将*运算符应用于与空指针常量比较相等的指针。

The following stackoverflow thread tries to explain what undefined behavior is: Undefined, unspecified and implementation-defined behavior以下 stackoverflow 线程试图解释什么是未定义行为: 未定义、未指定和实现定义的行为

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM