要理解无效指针

时间:2017-09-15 13:42:06

标签: c pointers gcc void

在我的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不会产生警告)。

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

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

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

  

无效使用void表达式

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

4 个答案:

答案 0 :(得分:12)

C标准明确指出5.1.1.3p1

  

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

脚注9说

  

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

因此,GCC遵守C标准的字母。您的程序是无效的程序。只需要一条诊断消息 - 允许编译器成功翻译您的无效程序。由于GCC具有void pointer arithmetic非标准扩展名:

  

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

     

这样做的结果是sizeofvoid以及函数类型也允许1,并返回-Wpointer-arith

     

如果使用这些扩展,选项sizeof会发出警告。

它决定它可以做一些事情&#34;明智的&#34;使用您的无效程序并将其成功翻译。

请注意void *foo; sizeof *foo; 中已经需要取消引用指向空格指针,因为:

sizeof (void);

必须与

相匹配
-std=c11 -pedantic-errors

它们都评估为1,因此更容易允许丢弃指向void 到处的指针。

正如Lundin所说,如果您想要违反约束条件的实际错误,请使用 Caused by: com.microsoft.sqlserver.jdbc.SQLServerException: The server principal "testuser" is not able to access the database "A_Source_Db" under the current security context.

答案 1 :(得分:5)

来自C11 6.3.2.3&#34; void&#34;:

  

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

因此,表达式可以具有void类型,您只能对该表达式的结果执行任何操作(例如将其指定给某些内容)。 var employees = JSON.parse(localStorage["employees"]); 是一个无效的表达式。

6.2.5 / 19&#34;类型&#34;

  

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

6.5.6 / 2&#34;添加剂操作员&#34;

  

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

数组下标是根据指针算法定义的。通常情况下,不允许使用表达式*c

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

答案 2 :(得分: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 / x86),没有什么可怕的事情发生:

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

答案 3 :(得分: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;

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

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

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