MSVC: why "extern void x;" is "illegal use of type 'void'"?

Viewed 140

Why this code:

extern void x;

leads to:

$ cl t555.c /std:c11 /Za
t555.c(1): error C2182: 'x': illegal use of type 'void'

What is illegal here?

UPD. Use case:

$ cat t555a.c t555.p.S
#include <stdio.h>

extern void x;

int main(void)
{
    printf("%p\n", &x);
    return 0;
}

       .globl  x
x:
        .space 4

$ gcc t555a.c -std=c11 -pedantic -Wall -Wextra -c && as t555.p.S -o t555.p.o && gcc t555a.o t555.p.o && ./a.exe
t555a.c: In function ‘main’:
t555a.c:7:20: warning: taking address of expression of type ‘void’
    7 |     printf("%p\n", &x);
      |                    ^
0x1004010c0

$ clang t555a.c -std=c11 -pedantic -Wall -Wextra -c && as t555.p.S -o t555.p.o && clang t555a.o t555.p.o && ./a.exe
t555a.c:7:20: warning: ISO C forbids taking the address of an expression of type 'void' [-Wpedantic]
    printf("%p\n", &x);
                   ^~
1 warning generated.
00007FF76E051120
1 Answers

This is an interesting case. It does not appear to violate any constraints to declare an identifier x of type void with external linkage, but it is nearly unusable.

void “is an incomplete object type that cannot be completed” (C 2018 6.2.5 19). When an identifier for an object is declared with no linkage, the type must “be complete by the end of its declarator” (6.7 7). But the same is not true for identifiers with external linkage; we can declare extern int a[]; extern struct foo b; and define a and b later, even in another translation unit.

If x is not used, I do not see that it violates any constraint. If the program attempted to use it, then 6.9 5 would apply:

… If an identifier declared with external linkage is used in an expression (other than as part of the operand of a sizeof or _Alignof operator whose result is an integer constant), somewhere in the entire program there shall be exactly one external definition for the identifier; otherwise, there shall be no more than one.

But we cannot define x in C code because it has an incomplete type, and its type cannot be completed. As long as it is not defined, we cannot use x in an expression other than as the operand of sizeof or _Alignof. Neither can we use it with sizeof or _Alignof, because those operators require a complete type.

We could imagine that x is defined outside of C and linked with this C code. So some assembly module might provide a definition for x that is unknown to the C code. Of course, the C code cannot use the value of the object without having a definition for the type. But it could use the address of x. For example, it could serve as a sentinel or other token for pointer values. E.g., we could pass a list of lists of pointers to another routine as a list of pointers where the sublists were separated by &x and the end of the whole list was marked by a null pointer. (So two sublists (&a, &b, &c) and (&d, &e, &f) would be passed as (void *[]) { &a, &b, &c, &x, &d, &e, &f, NULL };.)

However, compiling printf("%p\n", &x); with Clang and using -pedantic produces the error message “ISO C forbids taking the address of an expression of type 'void'”. I do not see a reason for that in the C standard. It is not a constraint of the & operator (in 6.5.3.2). 6.5.3.2 3 says “… the result is a pointer to the object or function designated by its operand,” so we might say there is no object x, so &x cannot produce a pointer to any object or function. But that is straining the meaning a bit.

I think Clang may be wrong on this point. 6.7 4 says “All declarations in the same scope that refer to the same object or function shall specify compatible types.” But I see no prohibition on declaring extern void x; in one translation unit and int x = 0; in another translation unit. Then the first translation unit ought to be able to take the address of the object x, and it even ought to be able to convert &x to char * and read and write its bytes.

On the one hand, Microsoft may have concluded there is no way to use this x and so are issuing a diagnostic for it. However, while a compiler is free to issue additional diagnostic messages, it ought to accept a conforming program. That is, the diagnostic may be a warning but may not be an error that prevents compilation.

Related