Towards understanding void pointers - c

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?

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.

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.

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

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.

Related

Address of a Variable assigned to integer varaiable.How?

address of a variable is assigned to integer variable without an error(just a warning) in C Language.how is this possible?? Can Anyone explain?
Code:
int main()
{
int a=9;
int *x=&a;
int y=&a;
printf("%d\n",y);
printf("%p",x);
return 0;
}
OUTPUT of the code:
test.c: In function 'main':
test.c:7:11: warning: initialization makes integer from pointer without a cast [-Wint-conversion]
int y=&a;
^
6422292
0061FF14
I am expecting an error in storing of address in integer variable.
how is this possible?? … I am expecting an error in storing of address in integer variable.
int y=&a; violates the constraints for simple assignment in C 2018 6.5.16.1 1, which list several combinations of types allowed for the right operands. The only combination in which the left operand has arithmetic type other than _Bool, as int does, says the right operand must also have arithmetic type, which &a does not:
… the left operand has atomic, qualified, or unqualified arithmetic type, and the right has arithmetic type…
C 2018 5.1.13 1 says what a conforming C implementation must do for this:
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,…
A warning is a diagnostic message. Therefore it satisfies the requirements of the C standard. This answers your question “how is this possible?”
Enable warnings in your compiler and elevate warnings to errors. With Clang, start with -Wmost -Werror. With GCC, start with -Wall -Werror. With MSVC, start with /W3 /WX.
how is this possible?
Because the compiler isn't obliged to do anything else but to give you a message of some form. See What must a C compiler do when it finds an error?
without an error(just a warning)
There is no such thing as "just a warning". A warning typically means: "here is a severe bug which you must fix to make your program behave as intended". It does not typically mean: "here is some minor cosmetic issue which you can ignore".
As for why your code isn't valid C, see "Pointer from integer/integer from pointer without a cast" issues.
As for what you should do to prevent lax compilers from generating an executable despite the code being invalid C, check out What compiler options are recommended for beginners learning C?

Casting an address of subroutine into void pointer

Is it okay to cast function location with void pointer though function pointers size is not always the same as opaque pointer size?
I already did search about opaque pointers , and casting function pointers . I found out function pointers and normal pointers are not the same on some systems.
void (*fptr)(void) = (void *) 0x00000009; // is that legal???
I feel I should do this
void (*fptr)(void)= void(*)(void) 0x00000009;
It did work fine , though I expected some errors or at least warnings
I'm using keil arm compiler
No, the problem is that you cannot go between void* and function pointers, they are not compatible types. void* is the generic pointer type for object pointers only.
In the second case you have a minor syntax error, should be (void(*)(void)). Fixing that, we have:
void (*fptr)(void) = (void *) 0x00000009; // NOT OK
void (*fptr)(void) = (void(*)(void)) 0x00000009; // PERHAPS OK (but likely not on ARM)
Regarding the former, it is simply not valid C (but might be supported as a non-standard compiler extension). Specifically, it violates the rules of simple assignment, C17 6.5.16.1. Both operands of = must be compatible pointer types.
Regarding the latter, the relevant part is C17 6.3.2.3/5
An integer may be converted to any pointer type. Except as previously specified, the
result is implementation-defined, might not be correctly aligned, might not point to an
entity of the referenced type, and might be a trap representation.
Some special cases exist for null pointers, but that does not apply in your case.
So you can probably cast the value 9 to a function pointer - you have to check the Keil compiler manual regarding implementation-defined behavior. What meaningful purpose the resulting function pointer will have from there on, I have no idea.
Because the address 9 is certainly not an aligned address on ARM. Rather, it is 1 byte past where I'd expect to find the address of the non-maskable interrupt ISR or some such. So what are you actually trying to do here? Grab an address of a ISR from the vector table?
void (*fptr)(void) = (void *) 0x00000009;
is not legal according to standard C as such.
If the pointer on the left is integer constant expression with value 0, or such expression cast to (void *) is a null pointer constant, that can be assigned to a function pointer:
void (*fptr)(void) = 0;
This only applies to the null pointer constant. It does not even apply to a variable of type void * that contains a null pointer. The constraints (C11 6.5.16.1) for simple assignments include
the left operand is an atomic, qualified, or unqualified pointer, and the right is a null pointer constant; or
In strictest sense the standard does not provide a mechanism to convert a pointer-to-void to a pointer to function at all! Not even with a cast. However it is available on most common platforms as a documented common extension C11 J.5.7 Function pointer casts:
A pointer to an object or to void may be cast to a pointer to a function, allowing data to be invoked as a function (6.5.4).
A pointer to a function may be cast to a pointer to an object or to void, allowing a function to be inspected or modified (for example, by a debugger) (6.5.4).
but it is not required at all by the C standard - indeed it is possible to use C in a platform where the code can be executed only from memory that cannot be accessed as data at all.
The first expression is perfectly legal in C, as the void * pointer type is assignment and parameter passing compatible with any other pointer type, and you can run into trouble, if pointers are different size than integers. It's not good programming style, and there's apparently no reason to assign to a function pointer the integer literal 9. I cannot guess what are you doing so for.
Despite of that, there are some few (well, very few) cases in history that that thing has been done (e.g. to give the special values SIG_DFL and SIG_IGN to the signal(2) system call, one can assume nobody will ever use those values to call the function dereferenced by the pointer, indeed, you can use some integers, different than zero, in the page zero virtual addresses of a process, to avoid dereferencing the pointers (so you cannot call the functions, or you'll get a segmentation violation immediately), while using different than zero values to assume several values apart of the NULL pointer itself)
But the second expression is not legal. It's not valid for an expression to start with a type identifier, so the subexpression to the right of the = sign is invalid. To do a correct assignment, with a valid cast, you had to write:
void (*ptr)(void) = (void (*)(void)) 0x9; /* wth to write so many zeros? */
(enclosing the whole type mark in parenthesis) then, you can call the function as:
(*ptr)();
or simply as:
ptr();
Just writing
void(*ptr)(void) = 9;
is also legal, while the integer to pointer conversion is signalled by almost every compiler with a warning. You'll get an executable from there.
If the integer is 0, then the compiler will shut up, as 0 is converted automatically to the NULL pointer.
EDIT
To illustrate the simple use I mentioned above in the first paragraph, from the file <sys/signal.h> of FreeBSD 12.0:
File /usr/include/sys/signal.h
139 #define SIG_DFL ((__sighandler_t *)0)
140 #define SIG_IGN ((__sighandler_t *)1)
141 #define SIG_ERR ((__sighandler_t *)-1)
142 /* #define SIG_CATCH ((__sighandler_t *)2) See signalvar.h */
143 #define SIG_HOLD ((__sighandler_t *)3)
all those definitions are precisely of the type mentioned in the question, an integer value cast to a pointer to function, in order to permit special values to represent non executable/non callback values. The type __sighandler_t is defined as:
161 typedef void __sighandler_t(int);
below.
From CLANG:
$ cc -std=c17 -c pru.c
$ cat pru.c
void (*ptr)(void) = (void *)0x9;
you get even no warning at all.
Without the cast:
$ cc -std=c11 pru.c
pru.c:1:8: warning: incompatible integer to pointer conversion
initializing 'void (*)(void)' with an expression of type 'int'
[-Wint-conversion]
void (*ptr)(void) = 0x9;
^ ~~~
1 warning generated.
(Only a warning, not an error)
With a zero literal:
$ cc -std=c11 -c pru.c
$ cat pru.c
void (*ptr)(void) = 0x0;
even no warning at all.

Is this behavior of clang standard compliant?

This is going to be a long, language lawyerish question, so I'd like to quickly state why I find it relevant. I am working on a project where strict standard compliance is crucial (writing a language that compiles to C). The example I am going to give seems like a standard violation on the part of clang, and so, if this is the case, I'd like to confirm it.
gcc says that a conditional with a pointer to a restrict qualified pointer can not co-inhabit a conditional statement with a void pointer. On the other hand, clang compiles such things fine. Here is an example program:
#include <stdlib.h>
int main(void){
int* restrict* A = malloc(8);
A ? A : malloc(8);
return 0;
}
For gcc, the options -std=c11 and -pedantic may be included or not in any combination, likewise for clang and the options -std=c11 and -Weverything. In any case, clang compiles with no errors, and gcc gives the following:
tem-2.c: In function ‘main’:
tem-2.c:7:2: error: invalid use of ‘restrict’
A ? A : malloc(8);
^
The c11 standard says the following with regard to conditional statements, emphasis added:
6.5.15 Conditional operator
...
One of the following shall hold for the second and third operands:
— both operands have arithmetic type;
— both operands have the same structure or union type;
— both operands have void type;
— both operands are pointers to qualified or unqualified versions of compatible types;
— one operand is a pointer and the other is a null pointer constant; or
— one operand is a pointer to an object type and the other is a pointer to a qualified or unqualified version of void.
...
If both the second and third operands are pointers or one is a null pointer constant and the
other is a pointer, the result type is a pointer to a type qualified with all the type qualifiers
of the types referenced by both operands. Furthermore, if both operands are pointers to
compatible types or to differently qualified versions of compatible types, the result type is
a pointer to an appropriately qualified version of the composite type; if one operand is a
null pointer constant, the result has the type of the other operand; otherwise, one operand
is a pointer to void or a qualified version of void, in which case the result type is a
pointer to an appropriately qualified version of void.
...
The way I see it, the first bold portion above says that the two types can go together, and the second bold portion defines the result to be a pointer to a restrict qualified version of void. However, as the following states, this type can not exist, and so the expression is correctly identified as erroneous by gcc:
6.7.3 Type qualifiers, paragraph 2
Types other than pointer types whose referenced type is an object type shall not be restrict-qualified.
Now, the problem is that a "shall not" condition is violated by this example program, and so is required to produce an error, by the following:
5.1.1.3 Diagnostics, paragraph 1
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.
It seems clang is not standard compliant by treating an erroneous type silently. That makes me wonder what else clang does silently.
I am using gcc version 5.4.0 and clang version 3.8.0, on an x86-64 Ubuntu machine.
Yes it looks like a bug.
Your question more briefly: can void be restrict qualified? Since void is clearly not a pointer type, the answer is no. Because this violates a constraint, the compiler should give a diagnostic.
I was able to trick clang to confess its sins by using a _Generic expression
puts(_Generic(A ? A : malloc(8), void* : "void*"));
and clang tells me
static.c:24:18: error: controlling expression type 'restrict void *' not compatible with any generic association type
puts(_Generic(A ? A : malloc(8), void* : "void*"));
which shows that clang here really tries to match a nonsense type restrict void*.
Please file them a bug report.
While a compiler could satisfy all obligations surrounding restrict by ignoring the qualifier altogether, a compiler which wants to keep track of what it is or is not allowed to do needs to keep track of which pointers hold copies of restrict pointers. Given something like:
int *foo;
int *bar;
int wow(int *restrict p)
{
foo = p;
...
*p = 123;
*foo = 456;
*p++;
*bar = 890;
return *p;
}
since foo is derived from p, a compiler must allow for accesses made via
foo to alias accesses via p. A compiler need not make such allowances
for accesses made via bar, since that is known not to hold an address derived from p.
The rules surrounding restrict get murky in cases where a pointer may or
may not be derived from another. A compiler would certainly be allowed to
simply ignore a restrict qualifier in cases where it can't track all of
the pointers derived from a pointer; I'm not sure if any such cases would
invoke UB even if nothing ever modifies the storage identified by the
pointer. If a syntactic construct is structurally guaranteed to invoke
UB, having a compiler squawk may be more useful than having it act in an
arbitrary fashion (though having a compiler simply ignore any restrict
qualifiers it can't fully handle might be more useful yet).

Sizeof a function that returns void in C [duplicate]

What would this statement yield?
void *p = malloc(sizeof(void));
Edit: An extension to the question.
If sizeof(void) yields 1 in GCC compiler, then 1 byte of memory is allocated and the pointer p points to that byte and would p++ be incremented to 0x2346? Suppose p was 0x2345. I am talking about p and not *p.
The type void has no size; that would be a compilation error. For the same reason you can't do something like:
void n;
EDIT.
To my surprise, doing sizeof(void) actually does compile in GNU C:
$ echo 'int main() { printf("%d", sizeof(void)); }' | gcc -xc -w - && ./a.out
1
However, in C++ it does not:
$ echo 'int main() { printf("%d", sizeof(void)); }' | gcc -xc++ -w - && ./a.out
<stdin>: In function 'int main()':
<stdin>:1: error: invalid application of 'sizeof' to a void type
<stdin>:1: error: 'printf' was not declared in this scope
If you are using GCC and you are not using compilation flags that remove compiler specific extensions, then sizeof(void) is 1. GCC has a nonstandard extension that does that.
In general, void is a incomplete type, and you cannot use sizeof for incomplete types.
Although void may stand in place for a type, it cannot actually hold a value. Therefore, it has no size in memory. Getting the size of a void isn’t defined.
A void pointer is simply a language construct meaning a pointer to untyped memory.
void has no size. In both C and C++, the expression sizeof (void) is invalid.
In C, quoting N1570 6.5.3.4 paragraph 1:
The sizeof operator shall not be applied to an expression that
has function type or an incomplete type, to the parenthesized name of
such a type, or to an expression that designates a bit-field member.
(N1570 is a draft of the 2011 ISO C standard.)
void is an incomplete type. This paragraph is a constraint, meaning that any conforming C compiler must diagnose any violation of it. (The diagnostic message may be a non-fatal warning.)
The C++ 11 standard has very similar wording. Both editions were published after this question was asked, but the rules go back to the 1989 ANSI C standard and the earliest C++ standards. In fact, the rule that void is an incomplete type to which sizeof may not be applied goes back exactly as far as the introduction of void into the language.
gcc has an extension that treats sizeof (void) as 1. gcc is not a conforming C compiler by default, so in its default mode it doesn't warn about sizeof (void). Extensions like this are permitted even for fully conforming C compilers, but the diagnostic is still required.
Taking the size of void is a GCC extension.
sizeof() cannot be applied to incomplete types. And void is incomplete type that cannot be completed.
In C, sizeof(void) == 1 in GCC, but this appears to depend on your compiler.
In C++, I get:
In function 'int main()':
Line 2: error: invalid application of 'sizeof' to a void type
compilation terminated due to -Wfatal-errors.
To the 2nd part of the question: Note that sizeof(void *)!= sizeof(void).
On a 32-bit arch, sizeof(void *) is 4 bytes, so p++, would be set accordingly.The amount by which a pointer is incremented is dependent on the data it is pointing to. So, it will be increased by 1 byte.
while sizeof(void) perhaps makes no sense in itself, it is important when you're doing any pointer math.
eg.
void *p;
while(...)
p++;
If sizeof(void) is considered 1 then this will work.
If sizeof(void) is considered 0 then you hit an infinite loop.
Most C++ compilers choosed to raise a compile error when trying to get sizeof(void).
When compiling C, gcc is not conforming and chose to define sizeof(void) as 1. It may look strange, but has a rationale. When you do pointer arithmetic adding or removing one unit means adding or removing the object pointed to size. Thus defining sizeof(void) as 1 helps defining void* as a pointer to byte (untyped memory address). Otherwise you would have surprising behaviors using pointer arithmetic like p+1 == p when p is void*. Such pointer arithmetic on void pointers is not allowed in c++ but works fine with when compiling C with gcc.
The standard recommended way would be to use char* for that kind of purpose (pointer to byte).
Another similar difference between C and C++ when using sizeof occurs when you defined an empty struct like:
struct Empty {
} empty;
Using gcc as my C compiler sizeof(empty) returns 0.
Using g++ the same code will return 1.
I'm not sure what states both C and C++ standards on this point, but I believe defining the size of some empty structs/objects helps with reference management to avoid that two references to differing consecutive objects, the first one being empty, get the same address. If reference are implemented using hidden pointers as it is often done, ensuring different address will help comparing them.
But this is merely avoiding a surprising behavior (corner case comparison of references) by introduction another one (empty objects, even PODs consume at least 1 byte memory).

Casting NULL to void in C++

I ran across a puzzling notation:
if(ptr != (void)(NULL)) {
//some action
}
So it expanded to
if(ptr != (void)((void *)0)) {
//some action
}
which seems at least strange.
Is there really any rationale behind that or is it simply pointless, or even wrong? It compiled fine, though (on linux / gcc, don't remember version).
-EDIT-
I checked that code today, and here's new info:
First of all, the casting was used in a macro, and it expanded to
return (void)((void*)0);
within a function returning void.
The code compiles using gcc (GCC) 4.1.2 20080704 on (Red Hat 4.1.2-50).
So, is this statement equivalent to
return void;
which is equivalent to
return;
or is there something more to that?
It likely should be a cast to a void pointer which is the same as just "pointer" (i.e. a pointer to any type):
if (ptr != (void *)(NULL)) {
...
}
Although the cast is unnecessary:
if (ptr != NULL) {
...
}
Your code doesn't even compile on my system (Mac with GCC 4.2.1), the error message GCC is reporting is void value not ignored as it ought to be. That's expected and because void means no value/type at all, and you can't compare that to anything else.
Here's what the C99 standard is saying:
§6.3.2.2 void
1 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.)
§6.5.9 Equality operators
Constraints
2 One of the following shall hold:
both operands have arithmetic type;
both operands are pointers to qualified or unqualified versions of compatible types;
one operand is a pointer to an object or incomplete type and the other is a pointer to a qualified or unqualified version of void; or
one operand is a pointer and the other is a null pointer constant.
§6.3.2.2 already forbids the usage of void, and the constraints in §6.5.9 would also be violated.
So if a compiler would allow ptr != (void)(NULL) then it would violate the C99 standard. It might simply be a bug or misfeature of an older GCC version.
I only found a draft for C89, and here the void section is almost the same as in C99. But the section about the equality operators leaves me scratching my head. It seems to allow comparing to void, but this might be a mistake in the draft or a wording problem:
§3.2.2.2 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 occurs in a context
where a void expression is required, its value or designator is
discarded. (A void expression is evaluated for its side effects.)
§3.3.9 Equality operators
…
Constraints
One of the following shall hold:
both operands have arithmetic type;
both operands are pointers to qualified or unqualified versions of compatible types;
one operand is a pointer to an object or incomplete type and the other is a qualified or unqualified version of void ; or
one operand is a pointer and the other is a null pointer constant.
I have checked the assembly for this case.
Having such a function:
void x() {
return (void)(NULL);
}
Running preprocessor as: gcc x.c -E -o x.E -Wall -Wextra results in:
void x() {
return (void)(((void *)0));
}
Which seems rather bizzare. Anyway, after compiling it with gcc x.c -o x -Wall -Wextra, the assembly is (from objdump -d x):
0804841d <x>:
804841d: 55 push %ebp
804841e: 89 e5 mov %esp,%ebp
8048420: 90 nop
8048421: 5d pop %ebp
8048422: c3 ret
It's exactly the same as assembly for:
void x() {
return;
}
So these two expressions are equivalent.
I am using gcc version 4.8.2 (2013), for 64-bit linux, so it is not a bug of an old gcc version.
Adding -pedantic to gcc, however, makes it yield a warning:
warning: ISO C forbids ‘return’ with expression, in function returning void [-Wpedantic]
return (void)(NULL);
This happens for all of the dialects: c89, c99, gnu89, gnu99.

Resources