问题
The C11 standard ISO/IEC 9899:2011 (E) states the following constraints for simple assignments in §6.5.16.1/1:
One of the following shall hold:
- the left operand has atomic, qualified, or unqualified arithmetic type, and the right has arithmetic type;
- the left operand has an atomic, qualified, or unqualified version of a structure or union type compatible with the type of the right;
- the left operand has atomic, qualified, or unqualified pointer type, and (considering the type the left operand would have after lvalue conversion) both operands are pointers to qualified or unqualified versions of compatible types, and the type pointed to by the left has all the qualifiers of the type pointed to by the right;
- the left operand has atomic, qualified, or unqualified pointer type, and (considering the type the left operand would have after lvalue conversion) one operand is a pointer to an object type, and the other is a pointer to a qualified or unqualified version of void, and the type pointed to by the left has all the qualifiers of the type pointed to by the right;
- the left operand is an atomic, qualified, or unqualified pointer, and the right is a null pointer constant; or
- the left operand has type atomic, qualified, or unqualified
_Bool
, and the right is a pointer.
I am interested in the case in which both sides are pointers to incompatible types different from void
. If I understand correctly, this should at the very least invoke UB, as it violates this constraint. One example for incompatible types should be (according to §6.2.7 and §6.7.2) int
and double
.
Therefore the following program should be in violation:
int main(void) {
int a = 17;
double* p;
p = &a;
(void)p;
}
Both gcc and clang warn about "-Wincompatible-pointer-types", but do not abort compilation (compilation with -std=c11 -Wall -Wextra -pedantic
).
Similarly, the following program only leads to a "-Wint-conversion" warning, while compiling just fine.
int main(void) {
int a;
double* p;
p = a;
(void)p;
}
Coming from C++, I expected that either of those test cases would require a cast to compile. Is there any reason why either of the programs would be standards-legal? Or, are there at least significant historic reasons for supporting this code style even when disabling the entertaining GNU C extensions by explicitly using -std=c11
instead of -std=gnu11
?
回答1:
The compiler flag (both gcc and clang) to request checks for strict standards conformance and to refuse to compile nonconformant code is -pedantic-errors
:
$ gcc -std=c11 -pedantic-errors x.c
x.c: In function ‘main’:
x.c:3:15: error: initialization from incompatible pointer type [-Wincompatible-pointer-types]
double* p = &a;
^
Clang:
$ clang -std=c11 -pedantic-errors x.c
x.c:3:11: error: incompatible pointer types initializing 'double *' with an
expression of type 'int *' [-Werror,-Wincompatible-pointer-types]
double* p = &a;
^ ~~
1 error generated.
A significant proportion (to say the least) of typical C code in the wild is nonconformant, so -pedantic-errors
would cause most C programs and libraries to fail to compile.
回答2:
Is there any reason why either of the programs would be standards-legal?
These programs are not "standards-legal". They contain constraint violations and you already quoted the right text from the standard.
The compilers conform to the standard by producing a diagnostic for constraint violation. The standard does not require compilation to abort in the case of a constraint violation or other erroneous program.
It doesn't say in as many words, but the only reasonable conclusion is that any executable generated as a result of a program containing a constraint violation has completely undefined behaviour. (I have seen people try to argue otherwise though).
Speculation follows: C (and C++) are used for many purposes; sometimes people want "high level assembler" for their machine and don't care about portability or standards. Presumably the compiler vendors set the defaults to what they think their target audience would prefer.
回答3:
Your code example and your citation of the standard does not match. The example is initialization and 6.5.16 talks about assignment.
Confusingly the matching-type requirement is in a constraint section 6.5.16 for assignment, but "only" in the semantics section (6.7.9) for for initialization. So the compilers have the "right" not to issue a diagnostic for initialization.
In C, constraint violations only require "diagnostics", the compiler may well continue compilation, but there is no guarantee that the resulting executable is valid.
On my platform, a Debian testing, both compilers give me a diagnostic without any option, so I guess your installation must be quite old and obsolete.
回答4:
No, Yes
It's really very simple.
No, not standards legal. Yes, significant historical reasons.
C did not originally even have casts. As a system programming language, using it as an ultra-powerful glorified assembler was not only reasonable, it was best-practice and really "only-practice", back in the day.
A key piece of information should shine a light on things: it really is not the compiler's job to either implement or enforce the specification. The specification is, actually, literally, only a suggestion. The compiler's actual job is to compile all the C code that was ever written, including pre-C11, pre-C99, pre-C89, and even pre-K&R. This is why there are so many optional restrictions. Projects with modern code styles turn on strictly conforming modes.
There is the way C is defined in standards, and, there is the way C is used in practice. So you can see, the compiler simply can't refuse to build the code.
Over decades, developers have been shifting to portable, strictly conforming code, but when C first appeared, it was used a bit like a really amazingly powerful assembler, and it was kind of open season on address arithmetic and type punning. Programs in those days were mostly written for one architecture at a time.
来源:https://stackoverflow.com/questions/36828742/technical-legality-of-incompatible-pointer-assignments