2017-09-15 163 views
8

在我的answer我提到,取消引用void指针是一个坏主意。但是,当我这样做时会发生什么?向理解空指针

#include <stdlib.h> 
int main (void) { 
void* c = malloc(4); 
*c; 
&c[0]; 
} 

编译:

gcc prog.c -Wall -Wextra 
prog.c: In function 'main': 
prog.c:4:2: warning: dereferencing 'void *' pointer 
    *c; 
    ^~ 
prog.c:5:4: warning: dereferencing 'void *' pointer 
    &c[0]; 
    ^
prog.c:5:2: warning: statement with no effect [-Wunused-value] 
    &c[0]; 
^

这里是Wandbox对于那些谁说这没有发生过的图像:

enter image description here

和Ideone一个现场演示

它实际上会尝试读取c指向的内存,然后获取该结果,但最终没有做任何事情?或者这条线不会有任何影响(但是GCC不会产生警告)。

我在想,由于编译器不知道有关数据类型的任何信息,因此在不知道该类型的大小的情况下,它将无法做得太多。

为什么取消拒绝void*不会产生错误,但只是一个警告?


如果我尝试的任务,我会得到一个错误:

invalid use of void expression

但不应独解引用产生一个错误?

+9

由于'void'是一个不完整的类型,无法完成,因此解除引用'void *'应该生成一个编译器诊断。 –

+2

某些编译器在指向时假定为1的空间大小(至少使用指针算术) –

+0

@JonathanLeffler是的这就是我所说的,我希望发生错误。让,让我们专注于GCC。 – gsamaras

回答

5

从C11 6.3.2.3“空白”:

The (nonexistent) value of a void expression (an expression that has type void) shall not be used in any way, and implicit or explicit conversions (except to void) shall not be applied to such an expression. If an expression of any other type is evaluated as a void expression, its value or designator is discarded. (A void expression is evaluated for its side effects.)

所以一个表达式可以有void类型,你不能对该表达式的结果做任何事情(比如把它赋值给某个东西)。 *c是一个无效的表达式。

6.2.5/19 “类型”

The void type comprises an empty set of values; it is an incomplete object type that cannot be completed.

6.5.6/2 “加法运算符”

For addition, either both operands shall have arithmetic type, or one operand shall be a pointer to a complete object type and the other shall have integer type.

AND阵列下标在指针运算来定义。所以通常情况下,表达式&c[0]将不被允许。

GCC允许将void类型的指针算术(作为下标数组)作为扩展。

2
在GCC

,有可能在void *指针(How void pointer arithmetic is happening in GCC

执行指针算术和它甚至可以打印sizeof(void)这是1

你的榜样发出警告,但该行不执行任何操作(例如通过执行(void)a来忽略参数以避免“未使用的参数”警告)。

尝试分配的东西和gcc会抱怨

void *c=malloc(4); 
    *c = 'a'; 

给了我1个警告,1个错误

test.c:9:3: warning: dereferencing 'void *' pointer 
    *c = 'a'; 
    ^~ 
test.c:9:3: error: invalid use of void expression 
    *c = 'a'; 
^

甚至使用一个volatile char投就可以了:

test.c:9:3: error: invalid use of void expression 
    (volatile char)*c; 

所以你可以解引用它,但你不能使用解除引用的值(也试过阅读/分配纳克它,没办法:你得到)

EDIT“不忽略,因为它应该是空值”:半有效的例子(从memcpy: warning: dereferencing ‘void *’ pointer解除)

void *c=malloc(4); 
    void *d=malloc(5); 

    memcpy(d, &c[2], 2); 

这里你提领与抵消然后你再次拿到地址得到c+2:这是因为gcc指针算术。当然:

memcpy(d, ((char*)c)+2, 2); 

是避免警告和符合标准的方法。

+2

如果说到正常的记忆,这应该只是被优化掉了。但是如果我们将其定义为“volatile”呢? –

+0

我认为@EugeneSh。评论是这里的本质,因为我已经知道这个任务(抱歉,问题已经更新)。因为我的代码什么都不做,所以实际的去疏忽不会发生?或者它确实存在,并且不知何故编译器能够处理这个低谷,尽管它不知道底层类型? – gsamaras

+0

@EugeneSh。也不工作(编辑后) –

12

C标准在5.1.1.3p1明确规定:

A conforming implementation shall produce at least one diagnostic message (identified in an implementation-defined manner) if a preprocessing translation unit or translation unit contains a violation of any syntax rule or constraint, even if the behavior is also explicitly specified as undefined or implementation-defined. Diagnostic messages need not be produced in other circumstances. 9)

脚注9说

The intent is that an implementation should identify the nature of, and where possible localize, each violation. Of course, an implementation is free to produce any number of diagnostics as long as a valid program is still correctly translated. It may also successfully translate an invalid program.

所以,GCC与C标准的信的要求。你的程序是一个无效的程序。只需要诊断消息 - 并且编译器可以成功翻译无效的程序。作为GCC有非标准扩展void pointer arithmetic

In GNU C, addition and subtraction operations are supported on pointers to void and on pointers to functions. This is done by treating the size of a void or of a function as 1 .

A consequence of this is that sizeof is also allowed on void and on function types, and returns 1 .

The option -Wpointer-arith requests a warning if these extensions are used.

决定,它可能做一些“理智”与您的无效程序,并成功地把它翻译。


注意,不作价指针到空白已经要求sizeof,因为取消引用:

void *foo; 
sizeof *foo; 

必须匹配的

sizeof (void); 

他们都评价到1,所以它只是更容易允许丢弃指针的引用无效无处不在


由于伦丁说,如果你想实际的错误是否违反约束,使用-std=c11 -pedantic-errors

+0

OP未正确编译为标准C ,在这种情况下,您会收到有关算术中使用的void指针的错误。据我所知,问题只是关于非标准GCC。 – Lundin

+0

用'-std = c11 -pedantic-errors'正确编译会给出:'warning:dereferencing'void *'指针| 错误:算术中使用的类型'void *'的指针[-Wpointer-arith] | 警告:解除引用'void *'指针| 警告:无效果的声明[-Wunused-value] |'。 OP不像这样编译,所以标准所说的不适用。 – Lundin

+3

@Lundin一个兼容的编译器被允许成功编译代码。只是需要进行诊断。因此'âpedant'将足以达到合规性。 –

3

此编译的唯一原因是因为你使用了错误的GCC选项 - 你是不是一个严格符合C编译器但GCC编译扩展此。

-std=c11 -pedantic-errors正确的编译,我们得到:

warning: dereferencing 'void *' pointer| 
error: pointer of type 'void *' used in arithmetic [-Wpointer-arith]| 
warning: dereferencing 'void *' pointer| 
warning: statement with no effect [-Wunused-value]| 

void指针去引用和算术是GCC的扩展。当启用这种非标准扩展时,void*在算术上被视为uint8_t*,但仍不能解除引用,因为它仍被视为不完整类型。

至于与此代码在非标准模式做什么GCC,启用了没有优化(MinGW的/ 86),没有什么特别令人兴奋发生:

0x004014F0 push %rbp 
0x004014F1 mov %rsp,%rbp 
0x004014F4 sub $0x30,%rsp 
0x004014F8 callq 0x402310 <__main> 
0x004014FD mov $0x4,%ecx 
0x00401502 callq 0x402b90 <malloc> 
0x00401507 mov %rax,-0x8(%rbp) 
0x0040150B mov $0x0,%eax 
0x00401510 add $0x30,%rsp 
0x00401514 pop %rbp 
0x00401515 retq 
+0

检查我更新的问题。这不就是你说我应该使用的命令吗?也许这都是Wandbox的错? – gsamaras

+0

@gsamaras屏幕截图中的代码与问题中的代码不同。使用相同的代码,你应该得到在这个答案中发布的警告/错误。 – Lundin