Towards understanding void pointers

限于喜欢 提交于 2019-12-13 12:03:10

问题


In my answer I mention that dereferencing a void pointer is a bad idea. However, what happens when I do this?

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

Compilation:

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];
  ^

Here is an image from Wandbox for those who say it didn't happen:

and a Live demo in Ideone.

It will actually try to read what the memory that c points to has, and then fetch that result, but actually do nothing in the end? Or this line will simply have no effect (but then GCC wouldn't produce a warning).

I am thinking that since the compiler doesn't know anything about the data type, it won't be able to do much, without knowing the size of the type.

Why derefencing a void* does not produce an error, but just a warning?


If I try an assignment, I will get an error:

invalid use of void expression

but shouldn't the dereferencing alone produce an error?


回答1:


From 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.)

So an expression can have void type, you just can't do anything with the result of that expression (such as assign it to something). *c is a valid expression that does nothing.

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.

And array subscripting is defined in terms of pointer arithmetic. So normally, the expression &c[0] would not be allowed.

GCC allows pointer arithmetic on (and therefore subscripting arrays of) void types as an extension.




回答2:


The C standard explicitly states in 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)

With footnote 9 saying that

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.

So, GCC complies with the letter of the C standard. Your program is an invalid program. Only a diagnostics message is required - and the compiler is allowed to successfully translate your invalid program. As GCC has a non-standard extension for 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.

it decided that it might do something "sensible" with your invalid program and translated it successfully.


Notice that non-evaluated dereference of pointer-to-void is already required in sizeof, because:

void *foo;
sizeof *foo;

must match that of

sizeof (void);

They both evaluate to 1, so it is just easier allow the discarded dereference of pointers to void everywhere.


As Lundin says, if you want actual errors for constraint violations, use -std=c11 -pedantic-errors.




回答3:


The only reason this compiles is because you are using the wrong GCC options - you are not compiling this with a strictly conforming C compiler but with GCC extensions.

Compiled properly with -std=c11 -pedantic-errors, we get:

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 pointer de-referencing and arithmetic are gcc extensions. When such non-standard extensions are enabled, void* is treated as uint8_t* in terms of arithmetic, but you still can't de-reference it, because it is regarded as an incomplete type still.

As for what GCC does with this code in non-standard mode, with no optimizations enabled (Mingw/x86), nothing terribly exciting happens:

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



回答4:


in gcc, it's possible to perform pointer arithmetic on void * pointers (How void pointer arithmetic is happening in GCC)

And it's even possible to print sizeof(void) which is 1.

Your example issues a warning, but the line does nothing (like when you ignore a parameter by doing (void)a to avoid the "unused parameter" warning).

Try assigning something and gcc will complain

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

gives me 1 warning, and 1 error

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

Or even use a volatile char cast on it:

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

So you can dereference it, but you cannot use the dereferenced value (also tried reading/assigning it, no way: you get "void value not ignored as it ought to be")

EDIT: semi-valid example (lifted from memcpy: warning: dereferencing ‘void *’ pointer)

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

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

here you're dereferencing with an offset then you take the address again to get c+2 : that works because of gcc pointer arithmetic. Of course:

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

is the way to avoid the warning and is compliant to the standard.



来源:https://stackoverflow.com/questions/46241035/towards-understanding-void-pointers

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!