问题
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 avoid
or of a function as1
.A consequence of this is that
sizeof
is also allowed onvoid
and on function types, and returns1
.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