理解空指针

Towards understanding void pointers

在我的 中,我提到取消引用 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 的图片,供那些说它没有发生的人使用:

和 Ideone 中的 现场演示

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

我在想,由于编译器对数据类型一无所知,因此在不知道类型大小的情况下将无能为力。

为什么取消引用 void* 不会产生错误,而只会产生警告?


如果我尝试赋值,我会得到一个错误:

invalid use of void expression

但是单独取消引用不应该产生错误吗?

在 gcc 中,可以对 void * 指针 (How void pointer arithmetic is happening in GCC)

执行指针运算

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

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

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

所以你可以取消引用它,但是你不能使用取消引用的值(也试过reading/assigning,不行:你得到"void value not ignored as it ought to be")

编辑:半有效示例(摘自 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);

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

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 中已经需要 pointer-to-void 的非求值取消引用,因为:

void *foo;
sizeof *foo;

必须与

匹配
sizeof (void);

它们的计算结果都为 1,因此更容易允许丢弃指向 void 所有地方.

的指针的取消引用

正如 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    [=11=]x30,%rsp
0x004014F8  callq  0x402310 <__main>
0x004014FD  mov    [=11=]x4,%ecx
0x00401502  callq  0x402b90 <malloc>
0x00401507  mov    %rax,-0x8(%rbp)
0x0040150B  mov    [=11=]x0,%eax
0x00401510  add    [=11=]x30,%rsp
0x00401514  pop    %rbp
0x00401515  retq

来自 C11 6.3.2.3 "void":

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"Types"

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

6.5.6/2 "Additive operators"

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.

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

GCC 允许对 void 类型(并因此对数组下标)进行指针运算作为扩展。