I have some trouble understanding semantics of 5.1.1.3/1 Diagnostics subclause from N1570 C11 draft (emphasis mine):
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
I understand that the intent was to exclude (non-constraint) undefined behavior (thus that are no diagnostics on e.g. buffer overflow), but what about #error directive? As in 6.10.5/1 Error directive:
A preprocessing directive of the form
# error pp-tokensopt new-line
causes the implementation to produce a diagnostic message that includes the specified sequence of preprocessing tokens.
Does these both subclauses are not mutually exclusive?
For some other reference see also DR#176.
C99 went a step further than the suggested resolution for that DR. Instead of requiring a diagnostic, they require treating it as an error.
4. Conformance
4 The implementation shall not successfully translate a preprocessing translation unit
containing a #error preprocessing directive unless it is part of a group skipped by
conditional inclusion.
Now, strictly speaking, perhaps you're right that an implementation could choose to refuse to compile a program containing an #error directive without issuing a diagnostic, claiming that 5.1.1.3 allows it to ignore the semantics for #error. However, an implementation that goes to such lengths to being as useless as possible within the bounds set by the standard, would easily work around any attempt to require a diagnostic: the implementation could simply dump the complete preprocessor output (including anything following #error), and follow that by "there's an error in there somewhere". Because of that, it effectively doesn't matter whether the standard requires a diagnostic. There's no excuse for an implementation not to do so, and very little that the standard could to do force unwilling implementors.
As someone who served on the committee, our response to a question like this would often begin with a phrase along the lines of "A careful reading of the standard ...", which is not quite as glib as it sounds. the language used is very specific. Consider the first clause
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
This clause is explicitly referring to a "violation of any syntax rule or constraint". A well formed #error directive does not, therefore, trigger this clause. Thus it does not apply, and mutual-exclusion is moot.
Also note the final sentence, where it says that diagnostics " need not be produced ". The term "need not" does not imply "must not". It simply means the implementation has the option whether or not to issue a diagnostic for other conditions (for example, style concerns). But again this entire clause is irrelevant for #error
Your second quote simply states exactly what an implementation must do for a well-formed #error directive.
Related
I can understand that:
One of the origins of the UB is a performance increase (e.g. by removing never executed code, such as if (i+1 < i) { /* never_executed_code */ }; UPD: if i is a signed integer).
UB can be triggered at compile time because C does not clearly distinguish between compile time and run time. The "whole language is based on the (rather unhelpful) concept of an "abstract machine" (link).
However, I cannot understand yet why C preprocessor is a subject of undefined behavior? It is known that preprocessing directives are executed at compile time.
Consider C11, 6.10.3.3 The ## operator, 3:
If the result is not a valid preprocessing token, the behavior is undefined.
Why not make it a constraint? For example:
The result shall be a valid preprocessing token.
The same question goes for all the other "the behavior is undefined" in 6.10 Preprocessing directives.
Why is the C preprocessor a subject of undefined behavior?
When the C standard was created, there were some existing C preprocessors and there was some imaginary ideal C preprocessor in the minds of standardization committee members.
So there were these gray areas, where committee members weren't completely sure what would they want to do and/or existing C preprocessor implementations differed which each other in behavior.
So, these cases are not defined behavior. Because the C committee members are not completely sure what the behavior actually should be. So there is no requirement on what it should be.
One of the origins of the UB
Yes, one of.
UB may exist to ease up implementing the language. Like for example, in case of the preprocessor, the preprocessor writers don't have to care about what happens when an invalid preprocessor token is a result of ##.
Or UB may exist to reconcile existing implementations with different behaviors or as a point for extensions. So a preprocessor that segfaults in case of UB, a preprocessor that accepts and works in case of UB, and a preprocessor that formats your hard drive in case of UB, all can be standard conformant (but I wouldn't want to work on that one that formats your drive).
Suppose a file which is read in via include directive ends with the partial line:
#define foo bar
Depending upon the design of the preprocessor, it's possible that the partial token bar might be concatenated to whatever appears at the start of the line following the #include directive, or that whatever appears on that line will behave as though it were placed on the line with the #define directive, but with a whitespace separating it from the token bar, and it would hardly be inconceivable that a build script might rely upon such behaviors. It's also possible that implementations might behave as though a newline were inserted at the end of the included file, or might ignore the last partial line of such a file.
Any code which relied upon one of the former behaviors would clearly have been non-portable, but if code exploited such behavior to do something that would otherwise not be practical, such code would hardly be "erroneous", and the authors of the Standard would not have wanted to forbid an implementation that would process it usefully from continuing to do so.
When the Standard uses the phrase "non-portable or erroneous", that does not mean "non-portable, therefore erroneous". Prior to the publication of C89, C implementations defined many useful constructs, but none of them were defined by "the C Standard" since there wasn't one. If an implementation defined the behavior of some construct, some didn't, and the Standard left the construct as "Undefined", that would simply preserve the status quo where implementations that chose to define a useful behavior would do so, those that chose not to wouldn't, and programs that relied upon such behaviors would be "non-portable", working correctly on implementations that supported the behaviors, but not on those that didn't.
Without getting into specifics, my guess is, there exist several preprocessor implementations which have bugs, but the Standard doesn't want to declare them non-conforming, for compatibility reasons.
In human language: if you write a program which has X in it, preprocessor does weird stuff.
In standardese: the behavior of program with X is undefined.
If the standard says something like "The result shall be a valid preprocessing token", it might be unclear what "shall" means in this context.
The programmer shall write the program so this condition holds? If so, the wording with "undefined behavior" is clearer and more uniform (it appears in other places too)
The preprocessor shall make sure this condition holds? If so, this requires dedicated logic which checks the condition; may be impractical to implement.
Follow-up question for: If "shall / shall not" requirement is violated, then does it matter in which section (e.g. Semantics, Constraints) such requirement is located?.
ISO/IEC 9899:202x (E) working draft— December 11, 2020 N2596, 5.1.1.3 Diagnostics, 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.
Consequence: semantics violation does not require diagnostics.
Question: what is the (possible) rationale for "semantics violation does not require diagnostics"?
A possible rationale is given by Rice's theorem : non-trivial semantic properties of programs are undecidable
For example, division by zero is a semantics violation; and you cannot decide, by static analysis alone of the C source code, that it won't happen...
A standard cannot require total detection of such undefined behavior, even if of course some tools (e.g. Frama-C) are sometimes capable of detecting them.
See also the halting problem. You should not expect a C compiler to solve it!
The C99 rationale v5.10 gives this explanation:
5.1.1.3 Diagnostics
By mandating some form of diagnostic message for any program containing a syntax error or
constraint violation, the Standard performs two important services. First, it gives teeth to the
concept of erroneous program, since a conforming implementation must distinguish such a program from a valid one. Second, it severely constrains the nature of extensions permissible to
a conforming implementation.
The Standard says nothing about the nature of the diagnostic message, which could simply be
“syntax error”, with no hint of where the error occurs. (An implementation must, of course,
describe what translator output constitutes a diagnostic message, so that the user can recognize it as such.) The C89 Committee ultimately decided that any diagnostic activity beyond this level is
an issue of quality of implementation, and that market forces would encourage more useful
diagnostics. Nevertheless, the C89 Committee felt that at least some significant class of errors
must be diagnosed, and the class specified should be recognizable by all translators.
This happens because the grammar of the C language is context-sensitive and for all the languages that are defined with context-free or more complex grammars on the Chomsky hierarchy one must do a tradeoff between the semantics of the language and its power.
C designers chose to allow much power for the language and this is why the problem of undecidability is omnipresent in C.
There are languages like Coq that try to cut out the undecidable situations and they restrict the semantics of the recursive functions (they allow only sigma(primitive) recursivity).
The question of whether an implementation provides any useful diagnostics in any particular situation is a Quality of Implementation issue outside the Standard's jurisdiction. If an implementation were to unconditionally output "Warning: this program does not output any useful diagnostics" or even "Warning: water is wet", such output would fully satisfy all of the Standard's requirements with regard to diagnostics even if the implementation didn't output any other diagnostics.
Further, the authors of the Standard characterized as "Undefined Behavior" many actions which they expected would be processed in a meaningful and useful fashion by many if not most implementations. According to the published Rationale document, Undefined Behavior among other things "identifies areas of conforming language extension", since implementations are allowed to specify how they will behave in cases that are not defined by the Standard.
Having implementations issue warnings about constructs which were non-portable, but which they would process in a useful fashion would have been annoying.
Prior to the Standard, some implementations would usefully accept constructs like:
struct foo {
int *p;
char pad [4-sizeof (int*)];
int q,r;
};
for all sizes of pointer up to four bytes (8-byte pointers weren't a thing back then), rather than squawking if pointers were exactly four bytes, but some people on the Committee were opposed to the idea of accepting declarations for zero-sized arrays. Thus, a compromise was reached where compilers would squawk about such things, programmers would ignore the useless warnings, and the useful constructs would remain usable on implementations that supported them.
While there was a vague attempt to distinguish between constructs that should produce warnings that programmers could ignore, versus constructs that might be used so much that warnings would be annoying, the fact that issuance of useful diagnostics was a Quality of Implementation issue outside the Standard's jurisdiction meant there was no real need to worry too much about such distinctions.
Follow-up question for: clang: <string literal> + <expression returning int> leads to confusing warning: adding 'int' to a string does not append to the string.
Does "strictly conforming program" + no extensions mean "no diagnostics emitted"?
Reason: better understanding of the term "strictly conforming program".
An implementation may generate diagnostics even if a program is conforming.
Section 5.1.1.3p1 of the C standard regarding diagnostics states:
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)
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
The portion in bold in footnote 9 states that additional diagnostics may be produced.
Does "strictly conforming program" + no extensions == no diagnostics emitted?
No.
The only things for which the language specification requires diagnostics to be emitted are invalid syntax and constraint violations:
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.
(C2017, 5.1.1.3/1; emphasis added)
By definition, a strictly conforming program exhibits only valid syntax and does not contain any constraint violations, therefore the specification does not require a conforming implementation to emit any diagnostics when presented with such a program.
HOWEVER, the specification does not forbid implementations to emit diagnostics other than those that are required, and most implementations do, under some circumstances, emit diagnostics that are not required. The specification allows this, as clarified by footnote 9, which says, in part:
Of course, an
implementation is free to produce any number of diagnostics as long as
a valid program is still correctly translated.
Note also that "'strictly conforming program' + no extensions" is redundant. A program that makes use of any language extensions may conform, but it does not strictly conform:
A strictly conforming program shall use only those features of the
language and library specified in this International Standard. It
shall not produce output dependent on any unspecified, undefined,or
implementation-defined behavior, and shall not exceed any minimum
implementation limit.
(C2017 4/5; emphasis added)
Context: The C standard does not classify diagnostic messages as "warnings" or "errors".
Question: By treating certain "diagnostic messages" as "warnings" and by giving the ability to disable generation of warnings, certain compiler implementations allow to the end user to violate "shall" requirements of the C standard w/o generation of a diagnostic messages. Is this allowance a compiler bug / defect? If not, then how to correctly interpret this case? As a "compiler feature that allows to violate "shall" requirement w/o generation of a diagnostic message"?
Example:
#pragma warning( disable : 34 )
typedef int T[];
int main()
{
return sizeof(T);
}
$ cl t28.c /Za
<no diagnostic messages, the "shall" requirement [1] is silently violated>
[1] ISO/IEC 9899:1990:
The sizeof operator shall not be applied to an expression that has function type or an incomplete type.
UPD.
If /Za (Disable Language Extensions) is specified, then __STDC__ is defined with definition 1.
According to ANSI Conformance page (https://learn.microsoft.com/en-us/cpp/c-language/ansi-conformance?view=msvc-160):
Microsoft C conforms to the standard for the C language as set forth in the 9899:1990 edition of the ANSI C standard.
However, cl gives to the end user the ability to disable "shall requirement originated" warnings. Is it a compiler bug / defect or feature? Need to to correctly interpret this case.
C 2018 6.10.6 discusses the #pragma directive. Paragraph 1 says:
… causes the implementation to behave in an implementation-defined manner. The behavior might cause translation to fail or cause the translator or the resulting program to behave in a non-conforming manner…
That largely licenses the implementation to do anything it wants, as long as it documents it. If #pragma warning( disable : 34 ) is documented to disable the warning, and that is what it does, then that is conforming.
Note in particular that the #pragma “might … cause the translator … to behave in a non-conforming manner.” So, doing something that is otherwise non-conforming because a pragma told you to is conforming.
(I think the original text should say that the #pragma may cause the translator or program to behave in an otherwise non-conforming manner. Because, as currently written, behaving in this documented non-conforming manner is conforming, not non-conforming.)
"shall" (and "shall not") requirements in the standard come in two distinct kinds: restrictions on the program and restrictions on the implementation.
Restrictions on the implemention are things the implementation must (or must not) do -- these may have mandatory diagnostics associated with them.
Restrictions on the program are in fact freedoms for the implementation -- they are things that -- if the program does them -- cause undefined behavior, so the implementation can do anything with them and still be conforming.
The example you have above "The sizeof operator shall not be applied to an expression that "... is a restriction on the program. So a program that does that is not conforming and an implementation can do anything it wants (including treating it as an extension without any requirement for a flag or pragma) and still be conforming.
C standards talk about constraints, e. g. ISO/IEC 9899:201x defines the term
constraint
restriction, either syntactic or semantic, by which the
exposition of language elements is to be interpreted
and says in chapter Conformance
If a ‘‘shall’’ or ‘‘shall not’’ requirement that appears outside of a
constraint or runtime-constraint is violated, the behavior is
undefined.
In chapter Environment, Subsection Diagnostics it is said
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.
So, it is important to know what are the constraints in C, for example for compiler writers to judge when diagnostics are required, or for C programmers when diagnostics rather than just undefined behaviour can be expected.
Now, there are sections all over the standard document with the title Constraints, but I cannot find definitive wording as to what exactly the term constraint covers in the standard.
Are the constraints everything that appears in the sections titled Constraints?
Is every requirement that is stated outside of those sections not a constraint?
Is there a comprehensive description of constraint in the standard that I missed?
Are the constraints everything that appears in the sections titled Constraints?
In the sense of n1570 3.8 (a restriction imposed on programs which requires a conforming implementation to issue a compile-time diagnostic message when violated), I think yes.
Is every requirement that is stated outside of those sections not a constraint?
In the sense of 3.8, I think yes, but for a more circular reason: The standard's structure is fairly formal. Whenever applicable there seems to be an explicit Constraints section. Therefore I understand that by definition anything which is not in a Constraints section is not a constraint in the sense of 3.8.
There are a few "shall" clauses outside Constraints sections which appear completely compile-time enforceable, cf. below for a few examples. They are often in adjacent Semantics sections. I may be missing subtleties which prevent compile-time detection in the general case (so that a diagnosis cannot be made mandatory), or perhaps the standard is not completely consistent. But I would think that a compiler could simply translate a violating program, exactly because the requirements are not in a Constraints section.
Is there a comprehensive description of constraint in the standard that I missed?
I think 3.8 is all you get. I try to explore the term below and agree that the definition is unsatisfying.
I looked deeper into the standard to find that out. Here is my research.
The term constraint
Let's start with the basics. The definition of "constraint" in 3.8 which you quote is surprisingly hard to understand, at least without context ("restriction, either syntactic or semantic, by which the exposition of language elements is to be interpreted"). "Restriction" and "constraint" are synonyms, so that the rewording doesn't add much; and what is meant by "exposition of language elements"?? Exposition is a word with several meanings; let's take "writing or speech primarily intended to convey information" from Dictionary.com, and let's assume they mean the standard with that. Then it means basically that a constraint in this standard is a constraint of what is said in this standard. Wow, I wouldn't have guessed that.
Constraints as per 3.8
Pragmatically just examining the actual Constraints sections in the standard shows that they list compile time restrictions imposed on conforming programs. This makes sense because only compile-time constraints can be checked at compile time.
These additional restrictions are those which cannot be expressed in the C syntax.1
Constraints outside Constraints sections
Most uses of "shall" outside of Constraints sections impose restrictions on a conforming implementation. Example: "All objects with static storage duration shall be initialized (set to their
initial values) before program startup", a job of a conforming implementation.
There are a few "shall" clauses imposing restrictions on a program (not the implementation) outside of Constraints sections though. I would argue that most fall in the same category as the "runtime constraints [...] on a program when calling a library function" mentioned in 3.18. They seem to be run time constraints which are not generally detectable at compile time (so that diagnostics can not be mandatory).
Here are a few examples.
In 6.5/7 n1570 details the much-debated aliasing rules:
An object shall have its stored value accessed only
by an lvalue expression that has one of
the following types:
a type compatible with the effective type of the object
a qualified version of a type compatible
with the effective type of the object,
[...]
In 6.5.16.1, "Simple Assignment":
If the value being stored in an object is read from another object that overlaps in any way
the storage of the first object, then the overlap shall be exact[..]."
Other examples concern pointer arithmetic (6.5.6/8).
Shall clauses which could be in Constraints sections
But then there are other shall clauses whose violation should be detectable at compile time; I would not have blinked if they had appeared in the respective Constraints section.
6.6/6, "Cast operators in an integer constant
expression shall only convert arithmetic types to integer types" (under "Semantics"); what can you detect at compile time if you cannot detect types of constants and casts?
6.7/7, "If an identifier for an object is declared with no linkage, the type for the object shall be complete by the end of its declarator" (under "Semantics"). To me is seems to be a basic compiler task to detect whether a type is complete at some point in the code. But of course, I have never written a C compiler.
There are a few more examples. But as I said, I would think that an implementation is not required to diagnose violations. A violating program which manages to sneak past the compiler simply exposes undefined behavior.
1 For example, I understand that the syntax doesn't deal with types -- it only has generic "expressions". Therefore every operator has a Constraints section detailing the permissible types of its arguments. Example for shift operators: "Each of the operands shall have integer type." A program which is trying to shift the bits of a float is violating this constraint, and the implementation must issue a diagnostic.
The C committee addressed this issue in the response to Defect Report # 033. The question in that defect report was:
Is a conforming implementation required to diagnose all violations of ''shall'' and ''shall not'' statements in the standard, even if those statements occur outside of a section labeled Constraints?
The author of that defect report suggested a couple of possible alternative ways of interpreting the language of the standard. The second alternative he listed said (in part):
Syntax rules are those items listed in the Syntax sections of the standard. Constraints are those items listed in the Constraints sections of the standard.
Part of the committee's response was:
Suggested Interpretation #2 is the correct one.
I believe that covers your questions fairly completely, but just to state answers to your questions more directly:
Are the constraints everything that appears in the sections titled Constraints?
Is every requirement that is stated outside of those sections not a constraint?
A "constraint" is a requirement that is stated in a section explicitly marked "Constraints". Any requirement stated outside such a section is not a constraint.
Is there a comprehensive description of constraint in the standard that I missed?
At least as far as I know, the standard itself doesn't contain a more specific statement about what is or isn't a constraint, but the linked defect report does.
Are the constraints everything that appears in the sections titled Constraints?
It appears they are mostly (there are some cases which are not, fx: it's stated that "Incrementing is equivalent to adding 1" in one of the constraint sections).
Is every requirement that is stated outside of those sections not a constraint?
I haven't seen a "constraint" outside those sections.
Is there a comprehensive description of constraint in the standard that I missed?
Probably not, if there were an authoritative such it would be in the standard and probably be the "constraint" sections (and explicitly mentioned that these are all "constraints").
My interpretation is that the chapter 3 should be interpreted so that every use of the defined terms would have the meaning defined in that section. Especially everywhere the term "constraint" is used it should be understood according to your first quote.
Your second quote is no exception. It's noted in the definition of the term "constraint" that there is no requirement that the constraint is explicitely termed a constraint. This means that you have to determine if it's a "constraint" by checking if it's a such restriction.
However there seem to be quite a few examples of "shall" and "shall not" that could be taken to be such restrictions without explicitely termed as such. That would leave all the occurrences "shall" and "shall not" be mandating or prohibiting a certain behavior of the implementation - and if these are not fulfilled, then yes the behavior may be undefined (since you're using an implementation that doesn't conform to the standard).
It looks like all that fits the definition of "constraint" seem to occur under a "constraint" section, and everything in the "constraint" sections seem to be "constraints".
Are the constraints everything that appears in the sections titled Constraints?
Yes. Every syntactic and semantic restrictions mentioned in the standard are constraints.
For example, a constraint on Constant expressions (C11-6.6/3):
Constant expressions shall not contain assignment, increment, decrement, function-call, or comma operators, except when they are contained within a subexpression that is not evaluated.115)
Therefore, the constant expressions
3 = 5;
10++;
shows constraint violation.
Note that in this case shall requirement as well as constraint both are violated.
Is every requirement that is stated outside of those sections not a constraint?
For standard conforming C, yes. A shall requirement on integer constant expression (C11-6.6/6):
An integer constant expression117) shall have integer type [...]
For example, an integer constant expression is required for size of a non-variable length array. Therefore,
int arr[5+1.5];
violates the shall requirement. The type of expression 5+1.5 is not integer type. This shall requirement is out of constraint.
It should be noted that a shall requirement may be a constraint too.
In my work in requirements engineering, the words "constraint" and "requirement" have different scope. It is important, also for the standard, to define those explicitly. I searched the word "constraint" in the standard and it seems I may draw the following conclusion:
A constraint is a limitation of either the input (pre-condition) or the output (post-condition) of the behavior the section of the standard describes. For input it means the input must adhere to the constraint (e.g. argc shall be positive). For output it means it must satisfy the constraint for any following unit of the standard to have a well-defined input (its pre-condition).
A requirement is part of the specification of the behavior of the section of the standard. "Shall" is a positive description of what is required; "shall not" is generally a limitiation, but not a constraint - it may participate though in meeting a constraint on its output.
Constraints and requirements can be seen as "external interfaces" (the constraints) and "system behavior/processing" (the requirements).
Shall generally denotes a requirement (a phrase without "shall" is hence not a requirement). "Shall" used in a constraint is then either used to define the input or output (e.g. argc shall be positive) or specifies behavior concerning validating the constraint (e.g. "...shall give a diagnostic message").
Strictly speaking, "shall" used in specifying behavior of validating an input constraint should not be listed in the constraint section (should not be listed in the interface specification) but in a processing section (behavior section).
Note that there can be no validation of an output constraint as the output should adhere to the specification; only a next uit can check those constraints if they are in its input constraints.
This may be a personal view but it seems to fit the uses of these words in the standard.
constraint
restriction, either syntactic or semantic, by which the
exposition of language elements is to be interpreted
This means that every explicit restriction for program logic or syntax set by the c standart in any way is a constraint. This includes syntactic constraints (e.g. Blocks must be terminated with a ;) and semantic constraints (e.g. You shall not use a variable before initializing it), basicly everything that is either syntacticly (notation-wise) or semanticly (usage of correct notation-wise) not allowed or defined as not allowed (undefined behaviour).
Is every requirement that is stated outside of those sections not a
constraint?
I do think that all explicit requirements for the programming in the C language fall either under a syntactic or semantic constraint.
Is there a comprehensive description of constraint in the standard
that I missed?
Not to my knowledge.
The purpose of constraints in the Standard is to specify conditions where a conforming implementation would be required to issue a diagnostic, or to allow implementations could process a program in ways contrary to what would be required absent the constraint in cases where doing so might be might be useful than the otherwise-specified behavior. Although Strictly Conforming C Programs are not allowed to violate constraints (no program that violates a constraint is a Strictly Conforming C Program), no such restriction applies to programs that are intended to be Conforming but not Strictly Conforming.
The C Standard was written as a compromise among multiple overlapping factions, including
those who thought that it should discourage programmers from writing code that wouldn't work on all platforms interchangeably
those who thought it should allow programmers who were targeting known platforms to exploit features that were common to all of the platforms they'd need to support, even if they wouldn't be supportable on all platforms
those who thought that compilers should be allowed to diagnose constructs and actions which would be performed more often by accident than deliberate intent
those who thought that it should allow programmers to do things like perform address calculations which would appear erroneous, but which would, if performed precisely as specified, yield the address of the object the programmer was expecting.
In order to achieve a consensus among these groups, the Standard imposed limits on what could be done within Strictly Conforming C Programs, but also write the definition of Conforming C Program broadly enough that almost no useful programs would be branded non-conforming no matter how obscure the extensions upon which they rely. If a source-code construct would violate a diagnosable constraint, but an implementation's customers would find it useful anyhow, then the implementation could output a diagnostic which its customers could ignore (even an unconditional: "Warning: This implementation doesn't bother outputting diagnostics its author thinks are silly, other than this one" would suffice) and everybody could get on with life.