要理解无效指针

在我的回答中,我提到解除引用void指针是一个坏主意。 但是,当我这样做时会发生什么?

 #include  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的图片,对于那些说它没有发生的人:

在此处输入图像描述

和Ideone中的现场演示

它实际上会尝试读取c指向的内存,然后获取该结果,但实际上什么都不做? 或者这条线根本没有效果(但GCC不会产生警告)。

我在想,既然编译器对数据类型一无所知,在不知道类型大小的情况下,它将无法做很多事情。

为什么derefencing void*不会产生错误,只是警告?


如果我尝试作业,我会收到错误:

无效使用void表达式

但不应该单独解除引用会产生错误吗?

从C11 6.3.2.3“无效”:

void表达式(具有void类型的表达式)的(不存在)值不得以任何方式使用,并且隐式或显式转换(void除外)不应应用于此类表达式。 如果将任何其他类型的表达式计算为void表达式,则会丢弃其值或指示符。 (评估void表达式的副作用。)

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

6.2.5 / 19“类型”

void类型包含一组空值; 它是一个不完整的对象类型,无法完成。

6.5.6 / 2“添加剂操作员”

另外,两个操作数都应具有算术类型,或者一个操作数应是指向完整对象类型的指针,另一个操作数应具有整数类型。

数组下标是根据指针算法定义的。 通常,不允许使用表达式&c[0]

GCC允许将指针算法(以及因此下标数组)的void类型作为扩展。

C标准在5.1.1.3p1中明确说明:

如果预处理转换单元或转换单元包含违反任何语法规则或约束的情况,则符合的实现应生成至少一个诊断消息 (以实现定义的方式标识),即使该行为也明确指定为未定义或实现 -定义。 在其他情况下不需要产生诊断消息。 9)

用脚注9说

目的是实施应确定每次违规的性质,并在可能的情况下进行本地化。 当然,只要仍然正确地翻译了有效的程序,实现就可以自由地产生任意数量的诊断。 它也可能成功翻译无效程序。

因此,GCC符合C标准的字母。 您的程序是无效的程序。 只需要一条诊断消息 – 允许编译器成功翻译您的无效程序。 由于GCC具有无效指针算法 的非标准扩展:

在GNU C中,指向void指针和指向函数的指针都支持加法和减法操作。 这是通过将void或函数的大小视为1

这样做的结果是在void和函数类型上也允许sizeof ,并返回1

如果使用这些扩展,选项-Wpointer-arith警告。

它决定它可以对你的无效程序做一些“合理的”并成功翻译它。


请注意, sizeof已经需要非求值取消引用指向void指针 ,因为:

 void *foo; sizeof *foo; 

必须匹配

 sizeof (void); 

它们都评估为1,所以更容易让丢弃的指针取消引用到处都无效。


正如Lundin所说,如果您想要实现约束违规的实际错误,请使用-std=c11 -pedantic-errors

这个编译的唯一原因是因为你使用了错误的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 / x86),没有什么令人兴奋的事情发生:

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

在gcc中,可以对void *指针执行指针运算( 如何在GCC中进行无效指针运算 )

甚至可以打印sizeof(void) ,即1。

您的示例发出警告,但该行不执行任何操作(例如,通过执行(void)a忽略参数以避免“unused参数”警告)。

尝试分配一些东西,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; 

所以你可以取消引用它,但是你不能使用解除引用的值(也尝试读取/分配它,没办法:你得到“空值不被忽略,因为它应该是”)

编辑:半有效的例子(取自memcpy:warning:dereferencing’void *’指针 )

  void *c=malloc(4); void *d=malloc(5); memcpy(d, &c[2], 2); 

在这里你用一个偏移量去引用然后你再次获取地址得到c+2 :这是因为gcc指针算法。 当然:

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

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