On 04/06/2026 13:35, David Brown wrote:
[...]
"(a*a) + (b*b) has too many parentheses", on the other hand, is a
purely subjective opinion.
So, you're arguing 'more than needed' is a completely different thing
from 'too many'.
[...]
I don't have the patience for such nonsense any more:
* The () in '(a * b) + c' are generally unnecessary
* The () in 'a << (b + c)' are advisable
* The () in '(a << b) + c)' are necessary if the intent is to have
what might be the more intuitive meaning.
[...]
On 2026-06-04 15:18, Bart wrote:
[...]
* The () in '(a << b) + c)' are necessary if the intent is to have
what might be the more intuitive meaning.
I've already written in some former post about _unnecessarily_ mixing different types in expressions.
If you stay in such subexpressions with the same types you'll notice
that the parentheses are unnecessary; the C-language's precedences
have been sensibly chosen (in this case[*]).
[*] And even if you add some of ^ | & it's still no problem, unless
you have also any of the comparison operators in your expressions.
Janis
[...]
On 04/06/2026 13:35, David Brown wrote:
On 04/06/2026 13:40, Bart wrote:
On 04/06/2026 10:34, Tim Rentsch wrote:
Bart <bc@freeuk.com> writes:
My point was that it could be objective, at least for too many. So >>>>> (a*a) + (b*b) would be commonly agreed to have too many, [...]
Apparently you misunderstand what is meant by the word objective.
An objective statement is one that is independent of personal
assessment, even collective personal assessment.
I don't know of any infix PL syntax where 'a*a + b*b', as a
standalone expression, doesn't mean '(a*a) + (b*b)'.
Google agrees with me (in that 2*2+3*3 shows 13), and so does my
Casio calculator.
It's not my personal opinion!
You are - again - moving the goalposts.
It is an objective fact that "a * a + b * b" means "(a * a) + (b * b)"
in normal mathematics (at least in the countries I am familiar with),
and also in most mainstream programming languages.
It is an objective fact, therefore, that "(a*a) + (b*b)" has more
parentheses than needed in the context of most programming languages.
"(a*a) + (b*b) has too many parentheses", on the other hand, is a
purely subjective opinion.
So, you're arguing 'more than needed' is a completely different thing
from 'too many'.
Sigh...
If you wrote, for example, that "a << b + c" is ambiguous in C, then you
It is technically unambiguous in C.
It can be ambiguous in the mind of
somebody who would have to double-check the precedence levels, or where
the C context is missing.
The discssion seems to about what exactly is 'too many'.
Apparently you can constuct a valid C source file where 99.9% of the
text consists of () characters, but if someone - or even a million
people - say that it is too many, then that is just their subjective opinion.
I don't have the patience for such nonsense any more:
* The () in '(a * b) + c' are generally unnecessary
* The () in 'a << (b + c)' are advisable
* The () in '(a << b) + c)' are necessary if the intent is to have
what might be the more intuitive meaning.
If this not 100% C-specific, than () are needed for both the last two examples, but not the first.
You all know this.
(Note that C has its own problems in this area:
a = b/*p; // divide b by dereferenced pointer p
Here, /* also happens to start a block comment.)
Here you are objectively wrong. C does not have a "problem" with
this. The parsing rules of the language are clear - often called
"maximum munch". The character sequence "/*" is the start of a
comment, it is not two separate operators.
This is where it falls down. It's very clearly a 'gotcha', and
consequence of poorly thought-out design.
That the behaviour is deterministic doesn't change that.
It's silly to
worry about something that 999 people out of a 1000 (and the actual
numbers are undoubtedly much higher) are able to navigate without
difficulty. Yet the examples you give insist on focusing on the few
hopeless individuals.
Are you saying that whoever wrote code like this:
crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b & 0xF)]; >>>
is needlessly worrying about the 99.9+% of the readership who you
claim will know C syntax rules precisely? That is, they would find
this version just as clear without any extra cognitive effort:
crcu32 = crcu32 >> 4 ^ s_crc32[crcu32 & 0xF ^ b & 0xF];
?
Tim did not write that.
What was the 'something' in "It's silly to worry about something that ..."?
I assume it's people being unable to understand that second example.
Yet I seee parenthese being used in such cases a LOT more than 0.1% of
the time. 50% or more would be my guess.
That example was not on the list of examples you gave recently.
It was posted several times.
(https://github.com/richgel999/miniz/blob/master/miniz.c line 81, second
hit for '>>')
On 04/06/2026 11:51, Dan Cross wrote:
In article <10vqftg$2d72$1@dont-email.me>, Bart <bc@freeuk.com> wrote:
On 03/06/2026 23:30, Waldek Hebisch wrote:
Bart <bc@freeuk.com> wrote:
Personally I don't have much use for CSTs for a normal compiler, but >>>>> they might be useful for source-to-source translators, or programs that >>>>> do source refactoring, where you want to preserve extras such as
parentheses even if they're not strictly needed.
(Injecting the right parentheses for examples like `(a + b) * c' which >>>>> would have an AST like '(* (+ a b) c)' is surpringly tricky. Easier to >>>>> just follow the original source!
You probably mean some more complicated example. This one is
easy:
(10) -> parse("(a + b) * c")
(10) (* (+ a b) c)
(11) -> unparse(parse("(a + b) * c"))
(11) "(a+b)*c"
(12) -> parse("a + b * c")
(12) (+ a (* b c))
(13) -> unparse(parse("a + b * c"))
(13) "a+b*c"
You just need to track priorities of subexpressions to produce the
above: '+' has lower priority than '*' so subexpression needs
parentheses, '*' has higher priority, so there is no need for
parentheses.
I seem to remember one problem was with minus, for example original expr is:
a - (b - c)
The AST is (- a (- b c)), but a simplistic approach would generate, from >>> either that or (- (- a b) c), the same output:
a - b - c
No parentheses because the the two "-" have the same precedence. The
example might have been 'a + (b - c)'; same thing.
It just seemed more trouble than it was worth.
What? I don't understand what you're saying at all.
This is about turning AST (which has stripped parentheses) back into the >original source text.
I was responding to 'You just need to track priorities of
subexpressions', with an example where the two operators had the same >priorities.
It would extra work to generate the parentheses needed to make the
output correct. For my application, I decided not to be bother.
On 04/06/2026 15:18, Bart wrote:
It is an objective fact, therefore, that "(a*a) + (b*b)" has more
parentheses than needed in the context of most programming languages.
"(a*a) + (b*b) has too many parentheses", on the other hand, is a
purely subjective opinion.
So, you're arguing 'more than needed' is a completely different thing
from 'too many'.
Of course they are different things - albeit related things, rather
than /completely/ different. One is a question of fact, the other a question of opinion, and they do not always coincide.
It is a fact that "a << (b + c)" has more parentheses than needed. But
I think we are both of the opinion that it does not have "too many" parentheses - it has an appropriate number of parentheses.
Sigh...
It is technically unambiguous in C.
If you wrote, for example, that "a << b + c" is ambiguous in C, then you >>
There is no "technically" about it. It is unambiguous in C.
It can be ambiguous in the mind of somebody who would have to double-
check the precedence levels, or where the C context is missing.
I would not use the word "ambiguous" there - "unclear" would be more appropriate in the situation when someone does not know the C precedence levels.
No, it's an attempt to get you to understand the difference between "objective" and "subjective" - fact and opinion. I don't understand why you are having such a problem here.
* The () in '(a << b) + c)' are necessary if the intent is to have
what might be the more intuitive meaning.
The parentheses in "(a << b) + c" are necessary if the intent is to
shift "a" by "b", and then add "c" to the result. That is fact, not opinion. Any discussion of "intuitive" is necessarily subjective.
a = b/*p; // divide b by dereferenced pointer p
This is where it falls down. It's very clearly a 'gotcha', and
consequence of poorly thought-out design.
It is neither a "gotcha", not a consequence of poor design. It does not "fall down". It is simply a minor consequence of the choice of operator syntax. Such an expression would occur rarely in code, and to be a "gotcha" it would need to be realistic for someone to write it, without spaces, and for their code to compile and be used without the mistake
being noticed. Do you think that is in any way realistic? I do not.
And to be "poor design", it needs to be something that is likely to
cause problems
What was the 'something' in "It's silly to worry about something
that ..."?
My mind-reading skills are not that well developed.
On 04/06/2026 15:18, Bart wrote:
(Note that C has its own problems in this area:
a = b/*p; // divide b by dereferenced pointer p
Here, /* also happens to start a block comment.)
Here you are objectively wrong. C does not have a "problem" with
this. The parsing rules of the language are clear - often called
"maximum munch". The character sequence "/*" is the start of a
comment, it is not two separate operators.
This is where it falls down. It's very clearly a 'gotcha', and
consequence of poorly thought-out design.
It is neither a "gotcha", not a consequence of poor design.
David Brown <david.brown@hesbynett.no> writes:
On 04/06/2026 15:18, Bart wrote:
(Note that C has its own problems in this area:
a = b/*p; // divide b by dereferenced pointer p
Here, /* also happens to start a block comment.)
Here you are objectively wrong. C does not have a "problem" with
this. The parsing rules of the language are clear - often called
"maximum munch". The character sequence "/*" is the start of a
comment, it is not two separate operators.
This is where it falls down. It's very clearly a 'gotcha', and
consequence of poorly thought-out design.
It is neither a "gotcha", not a consequence of poor design.
Indeed, and in the early days, the compiler itself would never
have seen '/*' - the preprocessor (cpp) would have removed it
from the source before the source reached the first
pass of the compiler (c0).
cross@spitfire.i.gajendra.net (Dan Cross) writes:
In article <86ik81cfk5.fsf_-_@linuxsc.com>,
Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:
Janis Papanagnou <janis_papanagnou+ng@hotmail.com> writes:
On 2026-06-01 00:54, Keith Thompson wrote:
[...]
Yes, a compiler can reduce (a + b) * 0 to just 0. But it's not
required to do so, and (INT_MAX + 1) * 0 still has undefined
behavior. Undefined behavior is determined by the rules of the
abstract machine *without* any adjustments permitted by the as-if
rule.
This is something I really don't get in the actual C-logic...
Using constants that can be determined at compile time is UB here,
despite the '* 0' mathematically indicating an IMO clear semantics,
but using variables is only UB possibly at runtime? [...]
There's an important distinction to make here. Consider this
program:
#include <limits.h>
int
foo(){
int zero = (INT_MAX+1)*0;
return zero;
}
int
main(){
return 0;
}
This program does not transgress the bounds of undefined behavior.
To clarify, the comments in my posting were meant to be read as
saying the given text is the entire program, and that it is strictly >conforming with respect to conforming hosted implementations.
(Incidentally, given the rules for freestanding implementations, I'm
not sure that it is even possible for any program to be strictly
conforming with respect to conforming freestanding implementations.
In any case my statements were meant only in the context of hosted >implementations.)
[snip]
Perhaps you mean that this is irrelevant because `foo` is not
invoked, but I see no reason why that need be the case in e.g.
a freestanding environment.
I explained the context of my previous statements above. Sorry for
not saying that in the original message.
In a hosted environment, I don't
think anything explicitly prevents `foo` from being called after
`main` returns (though I can't imagine that would happen in real
life; it would be weird if it did).
The semantics described in the ISO C standard don't admit that
possibility.
Whether foo() has external linkage or internal
linkage doesn't change that.
Only those actions initiated by
statements in main() are ever elaborated.
But I'm not sure what _you_ mean by "transgress the bounds of
undefined behavior" here.
It's a grammatical fine point. I think for present purposes it's
okay to gloss over the distinction, and say this statement may be
read as saying "the program does not have undefined behavior".
Even more than that, the program is strictly conforming, and must be
accepted by a conforming implementation.
See above.
Now let's change the program slightly:
#include <limits.h>
int
foo(){
static int zero = (INT_MAX+1)*0;
return zero;
}
int
main(){
return 0;
}
This program does transgress the bounds of undefined behavior. The
reason for the difference is that in the first program the semantics
of foo() is to evaluate the expression to be stored in 'zero' only
at runtime, whereas in the second program the semantics of foo() is
to evaluate the expression to be stored in 'zero' before program
startup (informally, "at compile time"). What matters is not
whether the offending expression /might/ be evaluated "at compile
time", but whether the offending expression /must/ be evaluated "at
compile time". Only in the second case is undefined behavior
inevitable (and thus it does not occur in the first program).
Fine point: strictly speaking, I believe the C standard allows even
the second program to complete translation phase 8 successfully, and
for any offending behavior to occur only when we actually try to run
the program. To say that another way, there is no requirement that
possible nasal demons be made manifest at any point before an actual
attempted execution. On the other hand, because that possibility is
there lurking in the background, there is no requirement that the
program be accepted, and could be rejected by a conforming compiler.
Indeed. Further, I believe that the same is true for the first
program, as well.
It isn't. In the first program the offending expression is never
evaluated, because foo() is never called.
On 04/06/2026 17:18, Scott Lurndal wrote:
David Brown <david.brown@hesbynett.no> writes:
On 04/06/2026 15:18, Bart wrote:
(Note that C has its own problems in this area:
a = b/*p; // divide b by dereferenced pointer p
Here, /* also happens to start a block comment.)
Here you are objectively wrong. C does not have a "problem" with
this. The parsing rules of the language are clear - often called
"maximum munch". The character sequence "/*" is the start of a
comment, it is not two separate operators.
This is where it falls down. It's very clearly a 'gotcha', and
consequence of poorly thought-out design.
It is neither a "gotcha", not a consequence of poor design.
Indeed, and in the early days, the compiler itself would never
have seen '/*' - the preprocessor (cpp) would have removed it
from the source before the source reached the first
pass of the compiler (c0).
How does that not make it bad design?
The proprocessor would strip everything from the /* until the next
matching */, so a chunk of your program goes missing.
Indeed, and in the early days, the compiler itself would never
have seen '/*' - the preprocessor (cpp) would have removed it
from the source before the source reached the first
pass of the compiler (c0).
On 04/06/2026 15:27, David Brown wrote:
On 04/06/2026 15:18, Bart wrote:
It is an objective fact, therefore, that "(a*a) + (b*b)" has more
parentheses than needed in the context of most programming languages.
"(a*a) + (b*b) has too many parentheses", on the other hand, is a
purely subjective opinion.
So, you're arguing 'more than needed' is a completely different thing
from 'too many'.
Of course they are different things - albeit related things, rather
than /completely/ different. One is a question of fact, the other a
question of opinion, and they do not always coincide.
It is a fact that "a << (b + c)" has more parentheses than needed.
But I think we are both of the opinion that it does not have "too
many" parentheses - it has an appropriate number of parentheses.
So saying 'too many' of something will be a subjective opinion?
OK, so let's try compiling this bit of C:
void F(int, int);
int main() {
F(1, 2, 3);
}
8 out of 9 compilers reported 'Too many arguments'.
[...]
I think we'll leave it here.
[...]
* The () in '(a << b) + c)' are necessary if the intent is to have
what might be the more intuitive meaning.
The parentheses in "(a << b) + c" are necessary if the intent is to
shift "a" by "b", and then add "c" to the result. That is fact, not
opinion. Any discussion of "intuitive" is necessarily subjective.
Intuitive because here << performs the same scaling function as multiply:
a << b is the same as a * 2**b
a * b is the same as a << log2(b) when b is a power of two
(or thereabouts!)
The point is: they naturally belong together.
Given 'a * 8 + b' or 'a << 3 + b', it is desirable to freely convert one
to the other without having to restructure the parentheses.
[...]
Indeed, and in the early days, the compiler itself would never
have seen '/*' - the preprocessor (cpp) would have removed it
from the source before the source reached the first
pass of the compiler (c0).
On 04/06/2026 15:27, David Brown wrote:
On 04/06/2026 15:18, Bart wrote:
It is an objective fact, therefore, that "(a*a) + (b*b)" has more
parentheses than needed in the context of most programming languages.
"(a*a) + (b*b) has too many parentheses", on the other hand, is a
purely subjective opinion.
So, you're arguing 'more than needed' is a completely different thing
from 'too many'.
Of course they are different things - albeit related things, rather
than /completely/ different. One is a question of fact, the other a
question of opinion, and they do not always coincide.
It is a fact that "a << (b + c)" has more parentheses than needed.
But I think we are both of the opinion that it does not have "too
many" parentheses - it has an appropriate number of parentheses.
So saying 'too many' of something will be a subjective opinion? OK, so
let's try compiling this bit of C:
void F(int, int);
int main() {
F(1, 2, 3);
}
8 out of 9 compilers reported 'Too many arguments'.
According to you, that's only their subjective opinion, not an objective fact?
My mind-reading skills are not that well developed.
It didn't stop you giving an opinion about what you thought he meant!
Bart <bc@freeuk.com> writes:
On 04/06/2026 17:18, Scott Lurndal wrote:
David Brown <david.brown@hesbynett.no> writes:
On 04/06/2026 15:18, Bart wrote:
(Note that C has its own problems in this area:
a = b/*p; // divide b by dereferenced pointer p >>>>>>>
Here, /* also happens to start a block comment.)
Here you are objectively wrong. C does not have a "problem" with >>>>>> this. The parsing rules of the language are clear - often called
"maximum munch". The character sequence "/*" is the start of a
comment, it is not two separate operators.
This is where it falls down. It's very clearly a 'gotcha', and
consequence of poorly thought-out design.
It is neither a "gotcha", not a consequence of poor design.
Indeed, and in the early days, the compiler itself would never
have seen '/*' - the preprocessor (cpp) would have removed it
from the source before the source reached the first
pass of the compiler (c0).
How does that not make it bad design?
The proprocessor would strip everything from the /* until the next
matching */, so a chunk of your program goes missing.
Whatcha talkin' 'bout willis?
On 04/06/2026 13:35, David Brown wrote:
On 04/06/2026 13:40, Bart wrote:
On 04/06/2026 10:34, Tim Rentsch wrote:You are - again - moving the goalposts.
Bart <bc@freeuk.com> writes:
My point was that it could be objective, at least for too many. So >>>>> (a*a) + (b*b) would be commonly agreed to have too many, [...]
Apparently you misunderstand what is meant by the word objective.
An objective statement is one that is independent of personal
assessment, even collective personal assessment.
I don't know of any infix PL syntax where 'a*a + b*b', as a
standalone expression, doesn't mean '(a*a) + (b*b)'.
Google agrees with me (in that 2*2+3*3 shows 13), and so does my
Casio calculator.
It's not my personal opinion!
It is an objective fact that "a * a + b * b" means "(a * a) + (b *
b)" in normal mathematics (at least in the countries I am familiar
with), and also in most mainstream programming languages.
It is an objective fact, therefore, that "(a*a) + (b*b)" has more
parentheses than needed in the context of most programming
languages.
"(a*a) + (b*b) has too many parentheses", on the other hand, is a
purely subjective opinion.
So, you're arguing 'more than needed' is a completely different thing
from 'too many'.
Sigh...
If you wrote, for example, that "a << b + c" is ambiguous in C, then
you
It is technically unambiguous in C. It can be ambiguous in the mind of somebody who would have to double-check the precedence levels, or
where the C context is missing.
The discssion seems to about what exactly is 'too many'.
Apparently you can constuct a valid C source file where 99.9% of the
text consists of () characters, but if someone - or even a million
people - say that it is too many, then that is just their subjective
opinion.
I don't have the patience for such nonsense any more:
* The () in '(a * b) + c' are generally unnecessary
* The () in 'a << (b + c)' are advisable
* The () in '(a << b) + c)' are necessary if the intent is to have
what might be the more intuitive meaning.
On 2026-06-04 18:18, Scott Lurndal wrote:
Indeed, and in the early days, the compiler itself would never
have seen '/*' - the preprocessor (cpp) would have removed it
from the source before the source reached the first
pass of the compiler (c0).
Curious; was the comment-handling at some point in history removed
from the Cpp-processing? - If so, when was that? And I assume the
semantics are still the same; is that correct?
On 2026-06-04 18:18, Scott Lurndal wrote:
Indeed, and in the early days, the compiler itself would never
have seen '/*' - the preprocessor (cpp) would have removed it
from the source before the source reached the first
pass of the compiler (c0).
Curious; was the comment-handling at some point in history removed
from the Cpp-processing? - If so, when was that? And I assume the
semantics are still the same; is that correct?
On 04/06/2026 19:47, Janis Papanagnou wrote:
On 2026-06-04 18:18, Scott Lurndal wrote:
Indeed, and in the early days, the compiler itself would never
have seen '/*' - the preprocessor (cpp) would have removed it
from the source before the source reached the first
pass of the compiler (c0).
Curious; was the comment-handling at some point in history removed
from the Cpp-processing? - If so, when was that? And I assume the
semantics are still the same; is that correct?
No, at least since the standardisation of the C language (including K&R "standard"), "preprocessing" has been an integral part of the C language
and conversion of comments to space characters is done in phase 3 of the translation. But the C standards do not give an explicit distinction between "preprocessing" and "compiling" - just different translation
phases. (They do not define a "compiler" at all.) It is not uncommon
for implementations to separate translation into two or more programs, especially in the good old days when hosts had much less memory, but logically they are all one implementation. Distinguishing "the compiler itself" is somewhat artificial.
On 04/06/2026 17:46, Bart wrote:
On 04/06/2026 15:27, David Brown wrote:
On 04/06/2026 15:18, Bart wrote:
It is an objective fact, therefore, that "(a*a) + (b*b)" has more
parentheses than needed in the context of most programming languages. >>>>>
"(a*a) + (b*b) has too many parentheses", on the other hand, is a
purely subjective opinion.
So, you're arguing 'more than needed' is a completely different
thing from 'too many'.
Of course they are different things - albeit related things, rather
than /completely/ different. One is a question of fact, the other a
question of opinion, and they do not always coincide.
It is a fact that "a << (b + c)" has more parentheses than needed.
But I think we are both of the opinion that it does not have "too
many" parentheses - it has an appropriate number of parentheses.
So saying 'too many' of something will be a subjective opinion? OK, so
let's try compiling this bit of C:
void F(int, int);
int main() {
F(1, 2, 3);
}
8 out of 9 compilers reported 'Too many arguments'.
According to you, that's only their subjective opinion, not an
objective fact?
Again - /please/ stop trying to guess what people say or put words in
their mouths. I can't remember ever seeing you do so accurately.
It is an objective fact, therefore, that "(a*a) + (b*b)" has more parentheses than needed in the context of most programming languages.
"(a*a) + (b*b) has too many parentheses", on the other hand, is a purely subjective opinion. Even if it is true that this is "commonly agreed
to" (and AFAIK you have no basis for that claim), that would still be a subjective opinion - no matter how common that opinion is.
"Too many parentheses" is subjective, because they affect the ease of reading the code as a human reader.
On Thu, 04 Jun 2026 16:18:07 +0000, Scott Lurndal wrote:
[snip]
Indeed, and in the early days, the compiler itself would never
have seen '/*' - the preprocessor (cpp) would have removed it
from the source before the source reached the first
pass of the compiler (c0).
So, I've looked through "The C Programming Language" (the K&R C)
and the paper "A Tour Through the Portable C Compiler" (S. C.
Johnson, circa 1974), and neither document states that the
preprocessor strips comments. In fact, the mentions of the
preprocessor are exclusively about the #operation operators,
and not about C comments.
In article <10vsh43$b3is$1@dont-email.me>,
Lew Pitcher <lew.pitcher@digitalfreehold.ca> wrote:
On Thu, 04 Jun 2026 16:18:07 +0000, Scott Lurndal wrote:
[snip]
Indeed, and in the early days, the compiler itself would never
have seen '/*' - the preprocessor (cpp) would have removed it
from the source before the source reached the first
pass of the compiler (c0).
So, I've looked through "The C Programming Language" (the K&R C)
and the paper "A Tour Through the Portable C Compiler" (S. C.
Johnson, circa 1974), and neither document states that the
preprocessor strips comments. In fact, the mentions of the
preprocessor are exclusively about the #operation operators,
and not about C comments.
The PDP-11 compiler from 5th Edition research Unix removes
comments in `cc.c`. The 1972 compilers from Dennis Ritchie's
web page remove them in the compiler proper, as they predated
the preprocessor: >https://www.nokia.com/bell-labs/about/dennis-m-ritchie/primevalC.html
On 2026-06-04 18:18, Scott Lurndal wrote:
Indeed, and in the early days, the compiler itself would never
have seen '/*' - the preprocessor (cpp) would have removed it
from the source before the source reached the first
pass of the compiler (c0).
Curious; was the comment-handling at some point in history removed
from the Cpp-processing? - If so, when was that? And I assume the
semantics are still the same; is that correct?
On 04/06/2026 17:47, Scott Lurndal wrote:
Bart <bc@freeuk.com> writes:
On 04/06/2026 17:18, Scott Lurndal wrote:
David Brown <david.brown@hesbynett.no> writes:
On 04/06/2026 15:18, Bart wrote:
(Note that C has its own problems in this area:
a = b/*p; // divide b by dereferenced pointer p >>>>>>>>
Here, /* also happens to start a block comment.)
Here you are objectively wrong. C does not have a "problem" with >>>>>>> this. The parsing rules of the language are clear - often called >>>>>>> "maximum munch". The character sequence "/*" is the start of a >>>>>>> comment, it is not two separate operators.
This is where it falls down. It's very clearly a 'gotcha', and
consequence of poorly thought-out design.
It is neither a "gotcha", not a consequence of poor design.
Indeed, and in the early days, the compiler itself would never
have seen '/*' - the preprocessor (cpp) would have removed it
from the source before the source reached the first
pass of the compiler (c0).
How does that not make it bad design?
The proprocessor would strip everything from the /* until the next
matching */, so a chunk of your program goes missing.
Whatcha talkin' 'bout willis?
What were /you/ talking about? What was your point?
In article <865x3yd21n.fsf@linuxsc.com>,[...]
Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:
cross@spitfire.i.gajendra.net (Dan Cross) writes:
In article <86ik81cfk5.fsf_-_@linuxsc.com>,
Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:
There's an important distinction to make here. Consider this
program:
#include <limits.h>
int
foo(){
int zero = (INT_MAX+1)*0;
return zero;
}
int
main(){
return 0;
}
This program does not transgress the bounds of undefined behavior.
To clarify, the comments in my posting were meant to be read as
saying the given text is the entire program, and that it is strictly >>conforming with respect to conforming hosted implementations. >>(Incidentally, given the rules for freestanding implementations, I'm
not sure that it is even possible for any program to be strictly
conforming with respect to conforming freestanding implementations.
In any case my statements were meant only in the context of hosted >>implementations.)
Ok.
[snip]
Perhaps you mean that this is irrelevant because `foo` is not
invoked, but I see no reason why that need be the case in e.g.
a freestanding environment.
I explained the context of my previous statements above. Sorry for
not saying that in the original message.
In a hosted environment, I don't
think anything explicitly prevents `foo` from being called after
`main` returns (though I can't imagine that would happen in real
life; it would be weird if it did).
The semantics described in the ISO C standard don't admit that
possibility.
Could you please point to where it says this, in the C standard?
I cannot find anything that says that arbitrary code cannot run
after `main()` returns, and I don't see how that could possibly
be true.
Whether foo() has external linkage or internal
linkage doesn't change that.
I disagree. There's no possible way for the implementation to
know whether a function with external linkage will be ultimately
invoked or not; consider a system that supports loadable shared
modules. Nothing prevents even this simple program from being
compiled as a shared module, dynamically loaded, the loading
program explicitly searching for and finding the symbol
corresponding to the `foo` function, and invoking it.
Hence, the compiler _must_ treat with UB as written, which is
why `ubsan` inserts trapping code in `foo`.
In your example, `foo` clearly exhibits UB; I think your
argument is whether that has a realized effect or not, since the
UB is not invoked. I'm saying that in general a compiler cannot
possibly know that when it compiles `foo`, and is free to assume
the worst.
cross@spitfire.i.gajendra.net (Dan Cross) writes:
In article <10vsh43$b3is$1@dont-email.me>,
Lew Pitcher <lew.pitcher@digitalfreehold.ca> wrote:
On Thu, 04 Jun 2026 16:18:07 +0000, Scott Lurndal wrote:
[snip]
Indeed, and in the early days, the compiler itself would never
have seen '/*' - the preprocessor (cpp) would have removed it
from the source before the source reached the first
pass of the compiler (c0).
So, I've looked through "The C Programming Language" (the K&R C)
and the paper "A Tour Through the Portable C Compiler" (S. C.
Johnson, circa 1974), and neither document states that the
preprocessor strips comments. In fact, the mentions of the
preprocessor are exclusively about the #operation operators,
and not about C comments.
The PDP-11 compiler from 5th Edition research Unix removes
comments in `cc.c`. The 1972 compilers from Dennis Ritchie's
web page remove them in the compiler proper, as they predated
the preprocessor: >>https://www.nokia.com/bell-labs/about/dennis-m-ritchie/primevalC.html
The v6 cpp.c processes the comments
and deletes them if the 'passcom' (-C) flag is not set.
[snip]
In article <sglUR.17897$pxGb.10844@fx07.iad>,
Scott Lurndal <slp53@pacbell.net> wrote:
cross@spitfire.i.gajendra.net (Dan Cross) writes:
In article <10vsh43$b3is$1@dont-email.me>,
Lew Pitcher <lew.pitcher@digitalfreehold.ca> wrote:
On Thu, 04 Jun 2026 16:18:07 +0000, Scott Lurndal wrote:
[snip]
Indeed, and in the early days, the compiler itself would never
have seen '/*' - the preprocessor (cpp) would have removed it
from the source before the source reached the first
pass of the compiler (c0).
So, I've looked through "The C Programming Language" (the K&R C)
and the paper "A Tour Through the Portable C Compiler" (S. C.
Johnson, circa 1974), and neither document states that the
preprocessor strips comments. In fact, the mentions of the
preprocessor are exclusively about the #operation operators,
and not about C comments.
The PDP-11 compiler from 5th Edition research Unix removes
comments in `cc.c`. The 1972 compilers from Dennis Ritchie's
web page remove them in the compiler proper, as they predated
the preprocessor: >>>https://www.nokia.com/bell-labs/about/dennis-m-ritchie/primevalC.html
The v6 cpp.c processes the comments
and deletes them if the 'passcom' (-C) flag is not set.
[snip]
You sure? That looks like V7 code to me.
On 04/06/2026 19:54, David Brown wrote:[...]
Again - /please/ stop trying to guess what people say or put words
in their mouths. I can't remember ever seeing you do so accurately.
This is what you actually said:
It is an objective fact, therefore, that "(a*a) + (b*b)" has more
parentheses than needed in the context of most programming languages.
"(a*a) + (b*b) has too many parentheses", on the other hand, is a purely
subjective opinion. Even if it is true that this is "commonly agreed
to" (and AFAIK you have no basis for that claim), that would still be a
subjective opinion - no matter how common that opinion is.
You're saying that:
* "more than needed" is objective
* "too many" is subjective
"Too many parentheses" is subjective, because they affect the ease
of reading the code as a human reader.
And 'more than needed' isn't that?!
No, this is just getting ludicrous and suggests not wanting to tackle
the real subject: should people write '(a << b) & c' or 'a << b & c'?
Tim Rentsch I'm sure will prefer the latter because 99.9% of C
programmers are machines, according to him.
Presumably, the same 99.9% will not use indentation, and will write
their programs all on one line anyway, because it is still after all completely unambiguous according to the C standard!
One advantage of having a single program do the whole thing, is that
error messages can mention the actual text of the line where a problem
was detected, without any pre-processing applied.
Bart <bc@freeuk.com> writes:
On 04/06/2026 17:47, Scott Lurndal wrote:
Bart <bc@freeuk.com> writes:
On 04/06/2026 17:18, Scott Lurndal wrote:
David Brown <david.brown@hesbynett.no> writes:
On 04/06/2026 15:18, Bart wrote:
(Note that C has its own problems in this area:
a = b/*p; // divide b by dereferenced pointer p >>>>>>>>>
Here, /* also happens to start a block comment.)
Here you are objectively wrong. C does not have a "problem" with >>>>>>>> this. The parsing rules of the language are clear - often called >>>>>>>> "maximum munch". The character sequence "/*" is the start of a >>>>>>>> comment, it is not two separate operators.
This is where it falls down. It's very clearly a 'gotcha', and
consequence of poorly thought-out design.
It is neither a "gotcha", not a consequence of poor design.
Indeed, and in the early days, the compiler itself would never
have seen '/*' - the preprocessor (cpp) would have removed it
from the source before the source reached the first
pass of the compiler (c0).
How does that not make it bad design?
The proprocessor would strip everything from the /* until the next
matching */, so a chunk of your program goes missing.
Whatcha talkin' 'bout willis?
What were /you/ talking about? What was your point?
Your inaccurate characterization that a chunk of the program
went "missing". Nothing meaningful is missing (and the comment
remains in the original source file).
So what do you mean, exactly, when you claim that the output of
the preprocessor causes a chunk of the program (which doesn't
include whitespace or comments) is missing?
Bart <bc@freeuk.com> writes:
On 04/06/2026 19:54, David Brown wrote:[...]
Again - /please/ stop trying to guess what people say or put words
in their mouths. I can't remember ever seeing you do so accurately.
This is what you actually said:
It is an objective fact, therefore, that "(a*a) + (b*b)" has more
parentheses than needed in the context of most programming languages.
"(a*a) + (b*b) has too many parentheses", on the other hand, is a purely >>> subjective opinion. Even if it is true that this is "commonly agreed
to" (and AFAIK you have no basis for that claim), that would still be a
subjective opinion - no matter how common that opinion is.
You're saying that:
* "more than needed" is objective
* "too many" is subjective
Stop it. He's not saying that.
You're taking phrases out of context and making false claims that the
full statement was far more general than it actually was.
Nobody said or implied that "too many" is always subjective.
"Too many parentheses" is subjective, because they affect the ease
of reading the code as a human reader.
And 'more than needed' isn't that?!
More than needed *for what*? Without that context, we can't tell
whether "more than needed" is subjective or objective.
You know all this.
[...]
No, this is just getting ludicrous and suggests not wanting to tackle
the real subject: should people write '(a << b) & c' or 'a << b & c'?
Oh, is that the real subject?
I presume you prefer `(a << b) & c` to `a << b & c`.
So do I.
Others might or might not have different opinions. If that was the
"real subject", we've wasted a lot of time debating the difference
between subjectivity and objectivity.
Tim Rentsch I'm sure will prefer the latter because 99.9% of C
programmers are machines, according to him.
Tim didn't say or imply that.
Presumably, the same 99.9% will not use indentation, and will write
their programs all on one line anyway, because it is still after all
completely unambiguous according to the C standard!
Of course not, because 99.9% of C programmers are not idiots..
Your record of guessing incorrectly what other people think is
unbroken. I suggest you stop trying.
On 04/06/2026 21:34, Scott Lurndal wrote:
Bart <bc@freeuk.com> writes:
On 04/06/2026 17:47, Scott Lurndal wrote:
Bart <bc@freeuk.com> writes:
On 04/06/2026 17:18, Scott Lurndal wrote:
David Brown <david.brown@hesbynett.no> writes:
On 04/06/2026 15:18, Bart wrote:
(Note that C has its own problems in this area:
a = b/*p; // divide b by dereferenced pointer p >>>>>>>>>>
Here, /* also happens to start a block comment.)
Here you are objectively wrong. C does not have a "problem" with >>>>>>>>> this. The parsing rules of the language are clear - often called >>>>>>>>> "maximum munch". The character sequence "/*" is the start of a >>>>>>>>> comment, it is not two separate operators.
This is where it falls down. It's very clearly a 'gotcha', and >>>>>>>> consequence of poorly thought-out design.
It is neither a "gotcha", not a consequence of poor design.
Indeed, and in the early days, the compiler itself would never
have seen '/*' - the preprocessor (cpp) would have removed it
from the source before the source reached the first
pass of the compiler (c0).
How does that not make it bad design?
The proprocessor would strip everything from the /* until the next
matching */, so a chunk of your program goes missing.
Whatcha talkin' 'bout willis?
What were /you/ talking about? What was your point?
Your inaccurate characterization that a chunk of the program
went "missing". Nothing meaningful is missing (and the comment
remains in the original source file).
So what do you mean, exactly, when you claim that the output of
the preprocessor causes a chunk of the program (which doesn't
include whitespace or comments) is missing?
This is the example I gave elsewhere:
---------------------------
There are actually other issues associated with /**/ comments; here
someone forgot to terminate the first comment:
puts("one"); /* comment 1
puts("two"); /* commmet 2 */
puts("three"); /* comment 3 */
---------------------------
After preprocessing you're left with this:
puts("one");
puts("three");
That middle puts call is missing, and it's meant to be part of the program.
And 'more than needed' isn't that?!
There are actually other issues associated with /**/ comments; here
someone forgot to terminate the first comment:
puts("one"); /* comment 1
puts("two"); /* commmet 2 */
puts("three"); /* comment 3 */
Jesus, the subthread has been going long enough.
On 04/06/2026 22:06, Keith Thompson wrote:
Bart <bc@freeuk.com> writes:
On 04/06/2026 19:54, David Brown wrote:[...]
Stop it. He's not saying that.Again - /please/ stop trying to guess what people say or put words
in their mouths. I can't remember ever seeing you do so accurately.
This is what you actually said:
It is an objective fact, therefore, that "(a*a) + (b*b)" has more
parentheses than needed in the context of most programming languages.
"(a*a) + (b*b) has too many parentheses", on the other hand, is a purely >>>> subjective opinion. Even if it is true that this is "commonly agreed
to" (and AFAIK you have no basis for that claim), that would still be a >>>> subjective opinion - no matter how common that opinion is.
You're saying that:
* "more than needed" is objective
* "too many" is subjective
That is EXACTLY what he's saying: "It is an OBJECTIVE fact .. has more
... than needed", and:
"has too many ... is ... purely subjective".
You're taking phrases out of context and making false claims that the
full statement was far more general than it actually was.
And this is exactly what other people are doing.
So I used TOO MANY instead of MORE THAN NEEDED to describe the exact
same phenomenon.
(1) Why are you all making such a big fucking deal of this?
(2) Why are you all sticking up for each other?
(3) Why don't you this discuss the fucking subject instead of going
down these pointless rabbit holes?
It is abourt how many brackets are too many, more than needed,
superfluous to requirements, etc etc etc.
Presumably, the same 99.9% will not use indentation, and will write
their programs all on one line anyway, because it is still after all
completely unambiguous according to the C standard!
Of course not, because 99.9% of C programmers are not idiots..
Your record of guessing incorrectly what other people think is
unbroken. I suggest you stop trying.
This is what Tim said:
"If someone really can't learn the rules of expression syntax for the language they are using, they should be advised to try a different
language, or perhaps give up programming altogether. It's silly to
worry about something that 999 people out of a 1000 (and the actual
numbers are undoubtedly much higher) are able to navigate without difficulty."
It sounds to me very much as though he expects 99.9% to know all C's precedences by heart and to never need to use superfluous brackets (or
'more than needed if 'superfluous' is still to subjective).
But of course, I am wrong and he is right, and you will defend his
view (a subjective one) to the death.
Bart <bc@freeuk.com> writes:
On 04/06/2026 22:06, Keith Thompson wrote:
Bart <bc@freeuk.com> writes:
On 04/06/2026 19:54, David Brown wrote:[...]
Stop it. He's not saying that.Again - /please/ stop trying to guess what people say or put wordsThis is what you actually said:
in their mouths. I can't remember ever seeing you do so accurately. >>>>
It is an objective fact, therefore, that "(a*a) + (b*b)" has more
parentheses than needed in the context of most programming languages. >>>>>
"(a*a) + (b*b) has too many parentheses", on the other hand, is a purely >>>>> subjective opinion. Even if it is true that this is "commonly agreed >>>>> to" (and AFAIK you have no basis for that claim), that would still be a >>>>> subjective opinion - no matter how common that opinion is.
You're saying that:
* "more than needed" is objective
* "too many" is subjective
That is EXACTLY what he's saying: "It is an OBJECTIVE fact .. has more
... than needed", and:
"has too many ... is ... purely subjective".
You're taking phrases out of context and making false claims that the
full statement was far more general than it actually was.
And this is exactly what other people are doing.
Taken literally, your statement implies that you admit that that's
what you're doing. Is that what you meant? If so, I suggest you
*stop* making such false claims. If not, what did you actually mean?
So I used TOO MANY instead of MORE THAN NEEDED to describe the exact
same phenomenon.
That's not the problem. There is an actual meaningful distinction
here, between what's needed by the compiler and what's useful to
improve clarity for human readers. I have found some of what you've
written to be unclear about that distinction.
Can we agree that the question of whether parentheses in a C
expression are necessary to the compiler can be answered objectively?
Can we agree that the question of whether extra parentheses are
helpful to a human reader is at least partly subjective, and
varies from case to case? Is there really anything else that we fundamentally disagree about?
(1) Why are you all making such a big fucking deal of this?
Why are you?
Why are you?
In article <865x3yd21n.fsf@linuxsc.com>,[...]
Tim Rentsch <tr.17687@z991.linuxsc.com> wrote: >>>cross@spitfire.i.gajendra.net (Dan Cross) writes:
In article <86ik81cfk5.fsf_-_@linuxsc.com>,
Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:
There's an important distinction to make here. Consider this
program:
#include <limits.h>
int
foo(){
int zero = (INT_MAX+1)*0;
return zero;
}
int
main(){
return 0;
}
This program does not transgress the bounds of undefined behavior.
To clarify, the comments in my posting were meant to be read as
saying the given text is the entire program, and that it is strictly >>>conforming with respect to conforming hosted implementations. >>>(Incidentally, given the rules for freestanding implementations, I'm
not sure that it is even possible for any program to be strictly >>>conforming with respect to conforming freestanding implementations.
In any case my statements were meant only in the context of hosted >>>implementations.)
Ok.
[snip]
Perhaps you mean that this is irrelevant because `foo` is not
invoked, but I see no reason why that need be the case in e.g.
a freestanding environment.
I explained the context of my previous statements above. Sorry for
not saying that in the original message.
In a hosted environment, I don't
think anything explicitly prevents `foo` from being called after
`main` returns (though I can't imagine that would happen in real
life; it would be weird if it did).
The semantics described in the ISO C standard don't admit that >>>possibility.
Could you please point to where it says this, in the C standard?
I cannot find anything that says that arbitrary code cannot run
after `main()` returns, and I don't see how that could possibly
be true.
N3220 5.1.2.4, Program semantics.
It defines the *observable behavior* of a program, which consists of
accesses to volatile objects, data written to files, and I/O dynamics of >interactive devices.
If the usual "Hello, world" program prints "Hello, world" followed
by "Goodbye", the implementation is non-conforming. If it formats
my hard drive after printing "Goodbye", it's non-conforming and
dangerous.
Whether foo() has external linkage or internal
linkage doesn't change that.
I disagree. There's no possible way for the implementation to
know whether a function with external linkage will be ultimately
invoked or not; consider a system that supports loadable shared
modules. Nothing prevents even this simple program from being
compiled as a shared module, dynamically loaded, the loading
program explicitly searching for and finding the symbol
corresponding to the `foo` function, and invoking it.
Remember that linking is translation phase 8. The compiler is not
the entire implementation.
Hence, the compiler _must_ treat with UB as written, which is
why `ubsan` inserts trapping code in `foo`.
I don't know what "_must_ treat with UB" means.
foo() has undefined behavior if it's called, so replacing its
body with trapping code is valid. But (I'm reasonably sure that)
an implementation cannot reject a program just because it can't
prove that it has no undefined behavior during execution. It can
reject it if it can prove that it *always* has undefined behavior
during execution.
In your example, `foo` clearly exhibits UB; I think your
argument is whether that has a realized effect or not, since the
UB is not invoked. I'm saying that in general a compiler cannot
possibly know that when it compiles `foo`, and is free to assume
the worst.
foo() exhibits UB if and only if it's called during execution.
Yes, a compiler can't know whether foo() will be called.
An implementation, particularly a linker, might know, but is not
required to. No, it is not free to assume the worst.
I certainly wouldn't want a compiler to reject `1/time(NULL)`
because it can't prove that time(NULL) won't be zero, or reject
`argc+1` because it can't prove that argc < INT_MAX. Code whose
behavior would be undefined if it were executed has no behavior
(and therefore no UB) if it's not executed.
James Kuyper <jameskuyper@alumni.caltech.edu> writes:
[...]
One advantage of having a single program do the whole thing, is that
error messages can mention the actual text of the line where a problem
was detected, without any pre-processing applied.
Typical preprocessors emit directives that tell the compiler about
the current file name and line number, precisely so that diagnostic
messages can refer to the original text.
For example:
$ cat hello.c
#include <stdio.h>
int main(void) {
printf("Hello world!\n");
}
$ gcc -E hello.c | tail
extern int __uflow (FILE *);
extern int __overflow (FILE *, int);
# 983 "/usr/include/stdio.h" 3 4
# 2 "hello.c" 2
# 2 "hello.c"
int main(void) {
printf("Hello world!\n");
}
$
The line `# 2 "hello.c"` is, according to the C standard, a
"non-directive", which is a kind of directive. Executing a
non-directive has undefined behavior, but gcc apparently treats it
very much like a #line directive.
It doesn't really matter whether the preprocessor is a separate program
or not.
cross@spitfire.i.gajendra.net (Dan Cross) writes:
In article <sglUR.17897$pxGb.10844@fx07.iad>,
Scott Lurndal <slp53@pacbell.net> wrote:
cross@spitfire.i.gajendra.net (Dan Cross) writes:
In article <10vsh43$b3is$1@dont-email.me>,
Lew Pitcher <lew.pitcher@digitalfreehold.ca> wrote:
On Thu, 04 Jun 2026 16:18:07 +0000, Scott Lurndal wrote:
[snip]
Indeed, and in the early days, the compiler itself would never
have seen '/*' - the preprocessor (cpp) would have removed it
from the source before the source reached the first
pass of the compiler (c0).
So, I've looked through "The C Programming Language" (the K&R C)
and the paper "A Tour Through the Portable C Compiler" (S. C. >>>>>Johnson, circa 1974), and neither document states that the >>>>>preprocessor strips comments. In fact, the mentions of the >>>>>preprocessor are exclusively about the #operation operators,
and not about C comments.
The PDP-11 compiler from 5th Edition research Unix removes
comments in `cc.c`. The 1972 compilers from Dennis Ritchie's
web page remove them in the compiler proper, as they predated
the preprocessor: >>>>https://www.nokia.com/bell-labs/about/dennis-m-ritchie/primevalC.html
The v6 cpp.c processes the comments
and deletes them if the 'passcom' (-C) flag is not set.
[snip]
You sure? That looks like V7 code to me.
Yes, it is. I didn't have a machine readable version of the
v6 compiler handy. Dug it out and here's the v6 version.
getch()
{
register int c, lastst;
while ((c=getc1())=='/' && !instring)
{
if ((c=getc1())!='*')
{
pushback(c);
return('/');
}
if (!skipcom)
{putc('/',fout); putc('*', fout);}
lastst=0;
while ( (c = getc1()) != '\0')
{
if (lastst && c=='/')
{
if (!skipcom)
putc('/', fout);
break;
}
if (c=='\n' || !skipcom)
putc(c, fout);
lastst = (c=='*');
}
if (c=='\0')break;
}
return(c);
}
In article <8xlUR.17899$pxGb.16870@fx07.iad>,
Scott Lurndal <slp53@pacbell.net> wrote:
cross@spitfire.i.gajendra.net (Dan Cross) writes:
In article <sglUR.17897$pxGb.10844@fx07.iad>,
Scott Lurndal <slp53@pacbell.net> wrote:
cross@spitfire.i.gajendra.net (Dan Cross) writes:
In article <10vsh43$b3is$1@dont-email.me>,
Lew Pitcher <lew.pitcher@digitalfreehold.ca> wrote:
On Thu, 04 Jun 2026 16:18:07 +0000, Scott Lurndal wrote:
[snip]
Indeed, and in the early days, the compiler itself would never
have seen '/*' - the preprocessor (cpp) would have removed it
from the source before the source reached the first
pass of the compiler (c0).
So, I've looked through "The C Programming Language" (the K&R C) >>>>>>and the paper "A Tour Through the Portable C Compiler" (S. C. >>>>>>Johnson, circa 1974), and neither document states that the >>>>>>preprocessor strips comments. In fact, the mentions of the >>>>>>preprocessor are exclusively about the #operation operators,
and not about C comments.
The PDP-11 compiler from 5th Edition research Unix removes
comments in `cc.c`. The 1972 compilers from Dennis Ritchie's
web page remove them in the compiler proper, as they predated
the preprocessor: >>>>>https://www.nokia.com/bell-labs/about/dennis-m-ritchie/primevalC.html
The v6 cpp.c processes the comments
and deletes them if the 'passcom' (-C) flag is not set.
[snip]
You sure? That looks like V7 code to me.
Yes, it is. I didn't have a machine readable version of the
v6 compiler handy. Dug it out and here's the v6 version.
getch()
{
register int c, lastst;
while ((c=getc1())=='/' && !instring)
{
if ((c=getc1())!='*')
{
pushback(c);
return('/');
}
if (!skipcom)
{putc('/',fout); putc('*', fout);}
lastst=0;
while ( (c = getc1()) != '\0')
{
if (lastst && c=='/')
{
if (!skipcom)
putc('/', fout);
break;
}
if (c=='\n' || !skipcom)
putc(c, fout);
lastst = (c=='*');
}
if (c=='\0')break;
}
return(c);
}
Yeah, that's from `cc.c`, right?
On 2026-06-04 23:47, Bart wrote:
Jesus, the subthread has been going long enough.
I'd dare to say that there's an extremely high chance
that *everyone* in this group is agreeing with you on
this statement! - I suggest pinning it at the wall. :-)
Janis
On 05/06/2026 00:09, Keith Thompson wrote:
Bart <bc@freeuk.com> writes:
On 04/06/2026 22:06, Keith Thompson wrote:
Bart <bc@freeuk.com> writes:
On 04/06/2026 19:54, David Brown wrote:[...]
Stop it. He's not saying that.Again - /please/ stop trying to guess what people say or put words >>>>>> in their mouths. I can't remember ever seeing you do so accurately. >>>>>This is what you actually said:
It is an objective fact, therefore, that "(a*a) + (b*b)" has more
parentheses than needed in the context of most programming languages. >>>>>>
"(a*a) + (b*b) has too many parentheses", on the other hand, is a >>>>>> purely
subjective opinion. Even if it is true that this is "commonly agreed >>>>>> to" (and AFAIK you have no basis for that claim), that would still >>>>>> be a
subjective opinion - no matter how common that opinion is.
You're saying that:
* "more than needed" is objective
* "too many" is subjective
That is EXACTLY what he's saying: "It is an OBJECTIVE fact .. has more
... than needed", and:
"has too many ... is ... purely subjective".
You're taking phrases out of context and making false claims that the
full statement was far more general than it actually was.
And this is exactly what other people are doing.
Taken literally, your statement implies that you admit that that's
what you're doing. Is that what you meant? If so, I suggest you
*stop* making such false claims. If not, what did you actually mean?
So I used TOO MANY instead of MORE THAN NEEDED to describe the exact
same phenomenon.
That's not the problem. There is an actual meaningful distinction
here, between what's needed by the compiler and what's useful to
improve clarity for human readers. I have found some of what you've
written to be unclear about that distinction.
Can we agree that the question of whether parentheses in a C
expression are necessary to the compiler can be answered objectively?
Can we agree that the question of whether extra parentheses are
helpful to a human reader is at least partly subjective, and
varies from case to case? Is there really anything else that we
fundamentally disagree about?
(1) Why are you all making such a big fucking deal of this?
Why are you?
I didn't start this business of something being subjective or objective,
or suggesting than one turn of phrase to discuss the same thing was subjective and the other objective (implying that a subjective opinion
had less worth). TR started that and several people backed him up.
Myself I wouldn't even use those terms. My point was that some overuses
of () for commonly known precedences are more overkill than others.
If that's subjective then so be it; it is not some fundamental law of
the universe. I would just call it common sense.
Why are you?
Since you ask, I was defending my point of view then got sidetracked by
this subjective/objective nonsense. I notice that TR has disappeared
from this subthread.
In article <10vsnl7$lkmu$1@kst.eternal-september.org>,
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
cross@spitfire.i.gajendra.net (Dan Cross) writes:
In article <865x3yd21n.fsf@linuxsc.com>,[...]
Tim Rentsch <tr.17687@z991.linuxsc.com> wrote: >>>>cross@spitfire.i.gajendra.net (Dan Cross) writes:
In article <86ik81cfk5.fsf_-_@linuxsc.com>,
Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:
There's an important distinction to make here. Consider this
program:
#include <limits.h>
int
foo(){
int zero = (INT_MAX+1)*0;
return zero;
}
int
main(){
return 0;
}
This program does not transgress the bounds of undefined behavior.
To clarify, the comments in my posting were meant to be read as
saying the given text is the entire program, and that it is strictly >>>>conforming with respect to conforming hosted implementations. >>>>(Incidentally, given the rules for freestanding implementations, I'm >>>>not sure that it is even possible for any program to be strictly >>>>conforming with respect to conforming freestanding implementations.
In any case my statements were meant only in the context of hosted >>>>implementations.)
Ok.
[snip]
Perhaps you mean that this is irrelevant because `foo` is not
invoked, but I see no reason why that need be the case in e.g.
a freestanding environment.
I explained the context of my previous statements above. Sorry for
not saying that in the original message.
In a hosted environment, I don't
think anything explicitly prevents `foo` from being called after
`main` returns (though I can't imagine that would happen in real
life; it would be weird if it did).
The semantics described in the ISO C standard don't admit that >>>>possibility.
Could you please point to where it says this, in the C standard?
I cannot find anything that says that arbitrary code cannot run
after `main()` returns, and I don't see how that could possibly
be true.
N3220 5.1.2.4, Program semantics.
It defines the *observable behavior* of a program, which consists of >>accesses to volatile objects, data written to files, and I/O dynamics of >>interactive devices.
Yes, but it does so for strictly-conforming programs with no UB.
To understand conformance, we have to jump over to section 4,
which explicitly says that, 'Undefined behavior is otherwise
indicated in this document by the words "undefined behavior" or
by the omission of any explicit definition of behavior.' As it
does not say that a program with an instance of undefined
behavior in an integer constant expression that is not executed
must otherwise behave in any given manner, what the program does
is undefined. A constaint violation mandates a diagnostic, but
beyond that, the standard is (AFAICT) silent.
Undefined Behavior, in turn, is not defined as specific only to
execution: the standard simply says that it is "behavior, upon
use of a *nonportable or erroneous program construct*..." for
which there are no requirements, and there are examples of
things that are explicitly UB at translation time, such as
improperly terminated lexemes and so forth.
Furthermore, the expression above is obviously an integer
constant expression as defined by sec 6.6 para 8. Section 6.6,
para 4, reads in part, "Each constant expression shall evaluate
to a constant that is in the range of representable values for
its type." The expression, `(INT_MAX+1)*0` violates this
constraint, and so therefore a diagnostic is mandated as per
sec 5.1.1.3 para 1. That it appears in code that is not
obviously called from `main` doesn't change that.
Morever, sec 6.6 para 17 says that, "the semantic rules for
evaluation of a constant expression are the same as for
nonconstant expressions." This brings us back to 5.1.2.4,
though I submit that para (4) is a stronger argument for what
you and Tim are saying, as it reads in part, "An actual
implementation is not required to evaluate part of an expression
if it can deduce that its value is not used and that no needed
side effects are produced (including any caused by calling a
function or through volatile access to an object)." I interpret
this to mean that, if the implementation can determine that
there is no way that `foo` can be called, it does not _have_ to
evaluate the above expression. However, it must satisfy the
range constraint from section 6.6, so it likely will, and in any
event, the standard does not say that it, "shall not" evaluate
it, or when.
Once the compiler does that, if it does, and observes UB, the
standard is silent on what requirements it imposes, which means
the behavior is undefined. I see no reason it couldn't arrange
to invoke `foo` at that point.
So no, I do not see how execution according to the rules of the
abstract machine is not guaranteed, here. I certainly see no
way in which this can be regarded as a strictly conforming
program.
If the usual "Hello, world" program prints "Hello, world" followed
by "Goodbye", the implementation is non-conforming. If it formats
my hard drive after printing "Goodbye", it's non-conforming and
dangerous.
Two separate things. My point earlier was that code can
obviously run after `main` terminates. Moreoever, I can't
imagine what would _prevent_ a runtime system that invokes
`main` from doing something like printing, "PROGRAM STOPPED"
after `main` returned. C imposes no requirements here.
Whether `foo` could be invoked after, I think, is undefined.
Whether foo() has external linkage or internal
linkage doesn't change that.
I disagree. There's no possible way for the implementation to
know whether a function with external linkage will be ultimately
invoked or not; consider a system that supports loadable shared
modules. Nothing prevents even this simple program from being
compiled as a shared module, dynamically loaded, the loading
program explicitly searching for and finding the symbol
corresponding to the `foo` function, and invoking it.
Remember that linking is translation phase 8. The compiler is not
the entire implementation.
Exactly my point. The compiler cannot know how `foo` might be
used, or how the translated object might be exercised. There's
I don't see how it could possibly know that, given that `foo`
has external linkage.
Hence, the compiler _must_ treat with UB as written, which is
why `ubsan` inserts trapping code in `foo`.
I don't know what "_must_ treat with UB" means.
foo() has undefined behavior if it's called, so replacing its
body with trapping code is valid. But (I'm reasonably sure that)
an implementation cannot reject a program just because it can't
prove that it has no undefined behavior during execution. It can
reject it if it can prove that it *always* has undefined behavior
during execution.
What I'm saying is that, `foo` has undefined behavior _period_.
That's manifest in an integer constant expression, whether it is
executed at runtime or not. I believe that the standard forces
the expression to be evaluated at translation time, via the
"shall" mandate when checking the constraint on the range in sec
6.6 para 4. Further, that evaluation must happen in accordance
with the rules of the abstract machine, as per 5.1.2.4 para 17.
The diagnostic is mandated, as is the translation-time
evaluation. The expression is itself manifestly exhibits UB,
and so therefore the result of the rest of the translation is
undefined.
In article <10vspuu$lkmu$3@kst.eternal-september.org>,
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
James Kuyper <jameskuyper@alumni.caltech.edu> writes:
[...]
One advantage of having a single program do the whole thing, is that
error messages can mention the actual text of the line where a problem
was detected, without any pre-processing applied.
Typical preprocessors emit directives that tell the compiler about
the current file name and line number, precisely so that diagnostic >>messages can refer to the original text.
For example:
$ cat hello.c
#include <stdio.h>
int main(void) {
printf("Hello world!\n");
}
$ gcc -E hello.c | tail
extern int __uflow (FILE *);
extern int __overflow (FILE *, int);
# 983 "/usr/include/stdio.h" 3 4
# 2 "hello.c" 2
# 2 "hello.c"
int main(void) {
printf("Hello world!\n");
}
$
The line `# 2 "hello.c"` is, according to the C standard, a >>"non-directive", which is a kind of directive. Executing a
non-directive has undefined behavior, but gcc apparently treats it
very much like a #line directive.
It doesn't really matter whether the preprocessor is a separate program
or not.
In fairness to Kuyper, however, the *text* from the original
source file is lost. E.g.,
term% cat n.c
#include <stdio.h>
#define FOO "hi"; // Note trailing `;`
int
main(void)
{
printf("%s\n", FOO);
return 0;
}
term% clang -fkeep-system-includes -E n.c
# 1 "n.c"
# 1 "<built-in>" 1
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "n.c" 2
#include <stdio.h> /* clang -E -fkeep-system-includes */
# 1 "n.c"
# 2 "n.c" 2
int
main(void)
{
printf("%s\n", "hi";);
return 0;
}
term%
In this example, the preprocessor macro `FOO` has been lost, and
only its expansion remains. The compiler has no information to
give a useful diagnostic.
On 04/06/2026 22:06, Keith Thompson wrote:
Bart <bc@freeuk.com> writes:
[snip]
Tim Rentsch I'm sure will prefer the latter because 99.9% of C
programmers are machines, according to him.
Tim didn't say or imply that.
So what was his 99.9% all about? Nobody has a clue, except they are
certain that what I think it is is wrong!
Presumably, the same 99.9% will not use indentation, and will write
their programs all on one line anyway, because it is still after all
completely unambiguous according to the C standard!
Of course not, because 99.9% of C programmers are not idiots..
Your record of guessing incorrectly what other people think is
unbroken. I suggest you stop trying.
This is what Tim said:
"If someone really can't learn the rules of expression syntax for the >language they are using, they should be advised to try a different
language, or perhaps give up programming altogether. It's silly to
worry about something that 999 people out of a 1000 (and the actual
numbers are undoubtedly much higher) are able to navigate without >difficulty."
It sounds to me very much as though he expects 99.9% to know all C's >precedences by heart and to never need to use superfluous brackets (or
'more than needed if 'superfluous' is still to subjective).
But of course, I am wrong and he is right, and you will defend his view
(a subjective one) to the death.
On 04/06/2026 21:34, Scott Lurndal wrote:
Bart <bc@freeuk.com> writes:
On 04/06/2026 17:47, Scott Lurndal wrote:
Bart <bc@freeuk.com> writes:
On 04/06/2026 17:18, Scott Lurndal wrote:
David Brown <david.brown@hesbynett.no> writes:
On 04/06/2026 15:18, Bart wrote:
(Note that C has its own problems in this area:
a = b/*p; // divide b by dereferenced pointer p >>>>>>>>>>
Here, /* also happens to start a block comment.)
Here you are objectively wrong. C does not have a "problem" with >>>>>>>>> this. The parsing rules of the language are clear - often called >>>>>>>>> "maximum munch". The character sequence "/*" is the start of a >>>>>>>>> comment, it is not two separate operators.
This is where it falls down. It's very clearly a 'gotcha', and >>>>>>>> consequence of poorly thought-out design.
It is neither a "gotcha", not a consequence of poor design.
Indeed, and in the early days, the compiler itself would never
have seen '/*' - the preprocessor (cpp) would have removed it
from the source before the source reached the first
pass of the compiler (c0).
How does that not make it bad design?
The proprocessor would strip everything from the /* until the next
matching */, so a chunk of your program goes missing.
Whatcha talkin' 'bout willis?
What were /you/ talking about? What was your point?
Your inaccurate characterization that a chunk of the program
went "missing". Nothing meaningful is missing (and the comment
remains in the original source file).
So what do you mean, exactly, when you claim that the output of
the preprocessor causes a chunk of the program (which doesn't
include whitespace or comments) is missing?
This is the example I gave elsewhere:
---------------------------
There are actually other issues associated with /**/ comments; here
someone forgot to terminate the first comment:
puts("one"); /* comment 1
puts("two"); /* commmet 2 */
puts("three"); /* comment 3 */
---------------------------
After preprocessing you're left with this:
puts("one");
puts("three");
That middle puts call is missing, and it's meant to be part of the program.
This can also be a consequence of an inadvertent /* sequence such as in
'a = b/*p;'.
In article <10vspuu$lkmu$3@kst.eternal-september.org>,
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
James Kuyper <jameskuyper@alumni.caltech.edu> writes:
[...]
One advantage of having a single program do the whole thing, is that
error messages can mention the actual text of the line where a problem >>>> was detected, without any pre-processing applied.
Typical preprocessors emit directives that tell the compiler about
the current file name and line number, precisely so that diagnostic >>>messages can refer to the original text.
In fairness to Kuyper, however, the *text* from the original
source file is lost. E.g.,
term% cat n.c
#include <stdio.h>
#define FOO "hi"; // Note trailing `;`
int
main(void)
{
printf("%s\n", FOO);
return 0;
}
term% clang -fkeep-system-includes -E n.c
# 1 "n.c"
# 1 "<built-in>" 1
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "n.c" 2
#include <stdio.h> /* clang -E -fkeep-system-includes */
# 1 "n.c"
# 2 "n.c" 2
int
main(void)
{
printf("%s\n", "hi";);
return 0;
}
term%
In this example, the preprocessor macro `FOO` has been lost, and
only its expansion remains. The compiler has no information to
give a useful diagnostic.
Ah, but it does, as long as the original file is still there.
$ gcc -c n.c
n.c: In function ‘main’:
n.c:2:17: error: expected ‘)’ before ‘;’ token
2 | #define FOO "hi"; // Note trailing `;`
| ^
n.c:6:20: note: in expansion of macro ‘FOO’
6 | printf("%s\n", FOO);
| ^~~
n.c:6:11: note: to match this ‘(’
6 | printf("%s\n", FOO);
| ^
$
The output of `gcc -E` doesn't include the name FOO, but it does include
the line `# 3 "n.c"`, and that's enough information for the compiler to
open the original source file and copy information from it into an error >message.
(This is perhaps straying slightly off-topic, since the standard
only requires a diagnostic, but it's still interesting to see how
actual compilers do things.)
$ cat n.c
#include <stdio.h>
#define FOO "hi"; // Note trailing `;`
int
main(void)
{
printf("%s\n", FOO);
return 0;
}
$ gcc -E n.c >| n-preprocessed.c
$ grep FOO n-preprocessed.c
$ tail n-preprocessed.c
# 2 "n.c" 2
# 3 "n.c"
int
main(void)
{
printf("%s\n", "hi";);
return 0;
}
$ gcc -c n-preprocessed.c
n.c: In function ‘main’:
n.c:6:24: error: expected ‘)’ before ‘;’ token
6 | printf("%s\n", FOO);
| ~ ^
| )
$
And if I rename n.c before compiling n-preprocessed.c, the error
messages doesn't include that line of code.
[snip]
getch()
{
register int c, lastst;
while ((c=getc1())=='/' && !instring)
{
if ((c=getc1())!='*')
{
pushback(c);
return('/');
}
if (!skipcom)
{putc('/',fout); putc('*', fout);}
lastst=0;
while ( (c = getc1()) != '\0')
{
if (lastst && c=='/')
{
if (!skipcom)
putc('/', fout);
break;
}
if (c=='\n' || !skipcom)
putc(c, fout);
lastst = (c=='*');
}
if (c=='\0')break;
}
return(c);
}
Yeah, that's from `cc.c`, right?
No, it's from cpp.c
$ ls /work/reference/collegetapes/sltape/v6cc/
c0.c c00.c c01.c c02.c c03.c c04.c c05.c c1.h
c10.c c11.c c12.c c13.c c2.h c20.c c21.c cc.c cpp.c
On 04/06/2026 19:54, David Brown wrote:
On 04/06/2026 17:46, Bart wrote:
On 04/06/2026 15:27, David Brown wrote:
On 04/06/2026 15:18, Bart wrote:
It is an objective fact, therefore, that "(a*a) + (b*b)" has more >>>>>> parentheses than needed in the context of most programming languages. >>>>>>
"(a*a) + (b*b) has too many parentheses", on the other hand, is a >>>>>> purely subjective opinion.
So, you're arguing 'more than needed' is a completely different
thing from 'too many'.
Of course they are different things - albeit related things, rather
than /completely/ different. One is a question of fact, the other a >>>> question of opinion, and they do not always coincide.
It is a fact that "a << (b + c)" has more parentheses than needed.
But I think we are both of the opinion that it does not have "too
many" parentheses - it has an appropriate number of parentheses.
So saying 'too many' of something will be a subjective opinion? OK,
so let's try compiling this bit of C:
void F(int, int);
int main() {
F(1, 2, 3);
}
8 out of 9 compilers reported 'Too many arguments'.
According to you, that's only their subjective opinion, not an
objective fact?
Again - /please/ stop trying to guess what people say or put words in
their mouths. I can't remember ever seeing you do so accurately.
This is what you actually said:
It is an objective fact, therefore, that "(a*a) + (b*b)" has more parentheses than needed in the context of most programming languages.
"(a*a) + (b*b) has too many parentheses", on the other hand, is a purely subjective opinion. Even if it is true that this is "commonly agreed
to" (and AFAIK you have no basis for that claim), that would still be a subjective opinion - no matter how common that opinion is.
You're saying that:
* "more than needed" is objective
* "too many" is subjective
Even though both are about exactly the same thing: superfluous but
harmless parentheses in an expression.
So you are picking on my choice of words, apparently in order to win
some stupid argument on the internet. Even though the same "too many"
phrase used elsewhere can be objective, according to you.
This looks like a pattern: people here seem to have remarkable trouble debating with me on actual ideas and resort instead to find hidden significance in the some choice of words I'd happen to use.
"Too many parentheses" is subjective, because they affect the ease of
reading the code as a human reader.
And 'more than needed' isn't that?!
Tim Rentsch I'm sure will prefer the latter because 99.9% of C
programmers are machines, according to him.
Presumably, the same 99.9% will not use indentation, and will write
their programs all on one line anyway, because it is still after all completely unambiguous according to the C standard!
In article <10vsrpo$men2$2@dont-email.me>, Bart <bc@freeuk.com> wrote:
On 04/06/2026 22:06, Keith Thompson wrote:
Bart <bc@freeuk.com> writes:
[snip]
Tim Rentsch I'm sure will prefer the latter because 99.9% of C
programmers are machines, according to him.
Tim didn't say or imply that.
So what was his 99.9% all about? Nobody has a clue, except they are
certain that what I think it is is wrong!
Have you thought about, I don't know, maybe asking him?
On Thu, 04 Jun 2026 21:04:50 +0200, David Brown wrote:
On 04/06/2026 19:47, Janis Papanagnou wrote:
On 2026-06-04 18:18, Scott Lurndal wrote:
Indeed, and in the early days, the compiler itself would never
have seen '/*' - the preprocessor (cpp) would have removed it
from the source before the source reached the first
pass of the compiler (c0).
Curious; was the comment-handling at some point in history removed
from the Cpp-processing? - If so, when was that? And I assume the
semantics are still the same; is that correct?
No, at least since the standardisation of the C language (including K&R
"standard"), "preprocessing" has been an integral part of the C language
and conversion of comments to space characters is done in phase 3 of the
translation. But the C standards do not give an explicit distinction
between "preprocessing" and "compiling" - just different translation
phases. (They do not define a "compiler" at all.) It is not uncommon
for implementations to separate translation into two or more programs,
especially in the good old days when hosts had much less memory, but
logically they are all one implementation. Distinguishing "the compiler
itself" is somewhat artificial.
In historic Unix (Version 7 and before), the preprocessor was implemented
as a separate program ("cpp") from the compiler ("cc"). The compiler itself had no facility to handle preprocessor directives, and was, itself, often divided into two separate programs ("cc0" and "cc1"). All three phases ("cpp", "cc0" and "cc1") were managed by a program ("cc"), although the program for each phase could be invoked independently through manual execution.
What differs from today is that the preprocessor was an optional component, made available for a programmer's convenience.
[...][...]
[ ... (INT_MAX+1)*0 ]
Furthermore, the expression above is obviously an integer
constant expression as defined by sec 6.6 para 8. Section 6.6,
para 4, reads in part, "Each constant expression shall evaluate
to a constant that is in the range of representable values for
its type." The expression, `(INT_MAX+1)*0` violates this
constraint, and so therefore a diagnostic is mandated as per
sec 5.1.1.3 para 1. That it appears in code that is not
obviously called from `main` doesn't change that.
[...]
cross@spitfire.i.gajendra.net (Dan Cross) writes:
In article <10vsrpo$men2$2@dont-email.me>, Bart <bc@freeuk.com> wrote:
On 04/06/2026 22:06, Keith Thompson wrote:
Bart <bc@freeuk.com> writes:
[snip]
Tim Rentsch I'm sure will prefer the latter because 99.9% of C
programmers are machines, according to him.
Tim didn't say or imply that.
So what was his 99.9% all about? Nobody has a clue, except they are
certain that what I think it is is wrong!
Have you thought about, I don't know, maybe asking him?
At the risk of saying what may be obvious to everyone, Bart has
shown that he has no interest in having a serious, constructive,
useful, or productive conversation with anyone. His questions
are all rhetorical; he hasn't asked me a straight question
because he isn't really interested in what I would say. In
short, Bart isn't looking for an answer, he's looking for an
argument. My recommendation is just stop responding to him
altogether. My response to him upthread was a sincere effort to
provide a neutral and helpful answer to his question. Maybe my
remarks were helpful to other people, and if they were that's
good. Any further efforts to interact with Bart are not just a
waste of time but actually counterproductive. What Bart needs is
not help with understanding C but a good therapist. In any case
I'm confident that whatever Bart's needs may be, no one responding
to his postings here is in a position to provide them. Please
consider these remarks before responding to him further.
On 04/06/2026 21:29, Bart wrote:
You're saying that:
How can this be /so/ difficult for you?
* "more than needed" is objective
No, I said that "(a*a) + (b*b)" has more parentheses than needed in the context of most programming languages" is objective.
* "too many" is subjective
No, I said that "(a*a) + (b*b) has too many parentheses" is subjective.
BC:Sadly the idea of writing in a way that is "most easily understood"
has resulted in a race to the bottom, where writers are more and
more encouraged to take the view that (some) readers are pretty
much arbitrarily stupid, with the result that expressions become
littered with scads of unnecessary parentheses that actually
detract from ease of reading. Good writing is always a balance
between too much and too little.
Actual examples of too many parentheses?
The point of my comment is that either too many or too few is a
subjective judgment, not an objective one.
My point was that it could be objective, at least for too many.
Tim Rentsch I'm sure will prefer the latter because 99.9% of C
programmers are machines, according to him.
Please give a reference for him saying that. (I'll save you the bother,
he has not made any remarks remotely like this in c.l.c. since I have
been here.)
Don't presume - you make a fool out of yourself every time you do.
Presumably, the same 99.9% will not use indentation, and will write
their programs all on one line anyway, because it is still after all
completely unambiguous according to the C standard!
James Kuyper <jameskuyper@alumni.caltech.edu> writes:
[...]
One advantage of having a single program do the whole thing, is
that error messages can mention the actual text of the line where
a problem was detected, without any pre-processing applied.
Typical preprocessors emit directives that tell the compiler
about the current file name and line number, precisely so that
diagnostic messages can refer to the original text.
For example:
$ cat hello.c
#include <stdio.h>
int main(void) {
printf("Hello world!\n");
}
$ gcc -E hello.c | tail
extern int __uflow (FILE *);
extern int __overflow (FILE *, int);
# 983 "/usr/include/stdio.h" 3 4
# 2 "hello.c" 2
# 2 "hello.c"
int main(void) {
printf("Hello world!\n");
}
$
The line `# 2 "hello.c"` is, according to the C standard, a
"non-directive", which is a kind of directive. Executing a
non-directive has undefined behavior,
On 6/4/2026 4:44 PM, Bart wrote:
On 05/06/2026 00:09, Keith Thompson wrote:
Bart <bc@freeuk.com> writes:
On 04/06/2026 22:06, Keith Thompson wrote:
Bart <bc@freeuk.com> writes:
On 04/06/2026 19:54, David Brown wrote:[...]
Stop it. He's not saying that.Again - /please/ stop trying to guess what people say or put words >>>>>>> in their mouths. I can't remember ever seeing you do so accurately. >>>>>>This is what you actually said:
It is an objective fact, therefore, that "(a*a) + (b*b)" has more >>>>>>> parentheses than needed in the context of most programming languages. >>>>>>>
"(a*a) + (b*b) has too many parentheses", on the other hand, is a >>>>>>> purely
subjective opinion. Even if it is true that this is "commonly agreed >>>>>>> to" (and AFAIK you have no basis for that claim), that would still >>>>>>> be a
subjective opinion - no matter how common that opinion is.
You're saying that:
* "more than needed" is objective
* "too many" is subjective
That is EXACTLY what he's saying: "It is an OBJECTIVE fact .. has more >>>> ... than needed", and:
"has too many ... is ... purely subjective".
You're taking phrases out of context and making false claims that the >>>>> full statement was far more general than it actually was.
And this is exactly what other people are doing.
Taken literally, your statement implies that you admit that that's
what you're doing. Is that what you meant? If so, I suggest you
*stop* making such false claims. If not, what did you actually mean?
So I used TOO MANY instead of MORE THAN NEEDED to describe the exact
same phenomenon.
That's not the problem. There is an actual meaningful distinction
here, between what's needed by the compiler and what's useful to
improve clarity for human readers. I have found some of what you've
written to be unclear about that distinction.
Can we agree that the question of whether parentheses in a C
expression are necessary to the compiler can be answered objectively?
Can we agree that the question of whether extra parentheses are
helpful to a human reader is at least partly subjective, and
varies from case to case? Is there really anything else that we
fundamentally disagree about?
(1) Why are you all making such a big fucking deal of this?
Why are you?
I didn't start this business of something being subjective or objective,
or suggesting than one turn of phrase to discuss the same thing was
subjective and the other objective (implying that a subjective opinion
had less worth). TR started that and several people backed him up.
Myself I wouldn't even use those terms. My point was that some overuses
of () for commonly known precedences are more overkill than others.
If that's subjective then so be it; it is not some fundamental law of
the universe. I would just call it common sense.
Why are you?
Since you ask, I was defending my point of view then got sidetracked by
this subjective/objective nonsense. I notice that TR has disappeared
from this subthread.
Wrt the number of ()'s? Might as well go to sleep with the following
song playing in the background:
(The Fate of Ophelia - Taylor Swift (Lyrics) Charlie Puth ft. Selena
Gomez, the weekd, ariana grande)
Tim Rentsch <tr.17687@z991.linuxsc.com> writes:
Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
Note that in a context that requires a constant expression, overflow is
a constraint violation. For example, a case label like:
case (INT_MAX + 1) * 0:
must be diagnosed at compile time.
gcc disagrees with you.
What makes you think so?
[...]
But taking a closer look at the standard, I'm not 100% sure that the
language requires a diagnostic, though I think that's the intent.
The relevant constraint is:
Each constant expression shall evaluate to a constant that is
in the range of representable values for its type.
If I squint really hard, I can argue that the entire expression
has to be a constant expression, but it doesn't say that its
subexpressions are constant expressions -- and *if* INT_MAX +
1 evaluates to INT_MIN in the current implementation, then
(INT_MAX + 1) * 0 evaluates to 0 and therefore satisfies the
constraint.
But INT_MAX + 1 could legally trap, for example, and I don't
believe it was intended that a given expression can be a constant
expression or not depending on the vagaries of the behavior of an
instance of UB.
On 05/06/2026 08:29, David Brown wrote:
On 04/06/2026 21:29, Bart wrote:
You're saying that:
How can this be /so/ difficult for you?
* "more than needed" is objective
No, I said that "(a*a) + (b*b)" has more parentheses than needed in
the context of most programming languages" is objective.
* "too many" is subjective
No, I said that "(a*a) + (b*b) has too many parentheses" is subjective.
If anyone is interested (which I doubt; bart-bashing is much more fun),
this is the original context:
TR:
Sadly the idea of writing in a way that is "most easily understood"
has resulted in a race to the bottom, where writers are more and
more encouraged to take the view that (some) readers are pretty
much arbitrarily stupid, with the result that expressions become
littered with scads of unnecessary parentheses that actually
detract from ease of reading. Good writing is always a balance
between too much and too little.
BC:
Actual examples of too many parentheses?
TR:
The point of my comment is that either too many or too few is a
subjective judgment, not an objective one.
Here it is clear that 'too many' was just a paraphrase of 'unnecessary'.
Here is my followup to TR:
BC:
My point was that it could be objective, at least for too many.
For an infix syntax where * has higher priority than +, then it is a
fact that the () in (a*a) + (b*b) are not necessary.
So, assume a minimum number of () needed to properly parse an expression according to intent. Then:
(1) TOO FEW: necessarily has to be subjective. It suggests a desire for
more () than the minimum, but the exact number will vary.
(2) TOO MANY, MORE THAN NEEDED, ETC: These can objective if refering to
any number of extra () above the mininum. This is the point I made
above, the one I defended.
(3) TOO MANY, MORE THAN NEEDED, ETC: These can also be used in a
judgemental manner, and there are subjective. This is where a certain
number of extra () are accepted for readability etc, but the exact level will vary.
If this is the point people have been trying to make, then they've been doing it incredibly badly, and been unnecessarily unpleasant and insulting.
My own view is that C syntax has too much of (3), but necessarily so
because of the choices made in its operator levels.
Tim Rentsch I'm sure will prefer the latter because 99.9% of C
programmers are machines, according to him.
Please give a reference for him saying that. (I'll save you the
bother, he has not made any remarks remotely like this in c.l.c. since
I have been here.)
Find out what was the subject of the 99.9% (even if that was an exaggeration). Then we'll talk.
No, he didn't use the word 'machines'; I paraphrased to suggest
supernormal people who know everything and never make mistakes.
You're going to argue about this now?
Don't presume - you make a fool out of yourself every time you do.
Presumably, the same 99.9% will not use indentation, and will write
their programs all on one line anyway, because it is still after all
completely unambiguous according to the C standard!
And you proceed to do exactly the same; Bart must be wrong, but you
don't about what!
In article <1BoUR.3$lmCb.1@fx22.iad>, Scott Lurndal <slp53@pacbell.net> wrote: >>cross@spitfire.i.gajendra.net (Dan Cross) writes:<snip>
[snip]
Yeah, that's from `cc.c`, right?
No, it's from cpp.c
$ ls /work/reference/collegetapes/sltape/v6cc/
c0.c c00.c c01.c c02.c c03.c c04.c c05.c c1.h
c10.c c11.c c12.c c13.c c2.h c20.c c21.c cc.c cpp.c
Oh interesting. I don't have a `cpp.c` in my v6 archive.
I wonder what else I'm missing.
value = valp;}
On 05/06/2026 13:39, Bart wrote:
"a << (b + c)" has "more than needed" - that is objective.
"a << (b + c)" does not have "too many" in an objective sense, because
I cannot speak for the intentions of others, but it has certainly been
very frustrating trying to get you to understand the distinction between objective facts and subjective opinions,
Actual examples of too many parentheses?
The point of my comment is that either too many or too few is a
subjective judgment, not an objective one.
Sadly the idea of writing in a way that is "most easily understood"
has resulted in a race to the bottom, where writers are more and
more encouraged to take the view that (some) readers are pretty
much arbitrarily stupid, with the result that expressions become
littered with scads of unnecessary parentheses that actually
detract from ease of reading. Good writing is always a balance
between too much and too little.
On 2026-06-05 01:49, Dan Cross wrote:
[...][...]
[ ... (INT_MAX+1)*0 ]
Furthermore, the expression above is obviously an integer
constant expression as defined by sec 6.6 para 8. Section 6.6,
para 4, reads in part, "Each constant expression shall evaluate
to a constant that is in the range of representable values for
its type." The expression, `(INT_MAX+1)*0` violates this
constraint, and so therefore a diagnostic is mandated as per
sec 5.1.1.3 para 1. That it appears in code that is not
obviously called from `main` doesn't change that.
I'm curious about that "violation"; a violation would require
(at least) two sorts of logical preconditions. - The first is
that all *sequentially* (literally) evaluated sub-expression
values are representable as value - INT_MAX+1 certainly can't
be represented in generated code that conforms to the abstract
*mathematical* value - but is that necessary if _the whole_
expression is (mathematically) just 0 (because of the final
factor). And the second (related) is whether the order of the
sub-expression evaluation is relevant; if we'd assume the
expression evaluation to be considered from right to left then
it would be irrelevant what's inside the parenthesis.
From the standard quotes I cannot really recognize that these
preconditions, how to determine UB/errors/violations, would be
necessary.
I'm no native speaker and I fear my question as formulated was
hard to understand. It's basically the question of the standard
implying (INT_MAX+1)*0 to be analyzed sequentially as written
or whether it could as well analyze it from right to left and
thus recognizing no problem, since from the mathematical view -
but also practically - a concrete representable value of a here
irrelevant sub-expression isn't necessary. Or another try of a
(paraphrased) formulation; for the determination of constraint
violations does the expression have strict (sort of) sequencing
points _after each term_ (and each left-to-right sub-expression
has to be well-defined) or can it be valued/analyzed as a whole
not putting any preconditions about evaluation order etc. when
determining the overall value?
PS: One yet non-considered question that was part of my original
post was: "Is there any rationale from the _software designer_'s perspective?"
Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:[...]
The line `# 2 "hello.c"` is, according to the C standard, a
"non-directive", which is a kind of directive. Executing a
non-directive has undefined behavior,
Since it is gcc that is generating the non-directives, for
internal purposes, and gcc that is consuming them, it hardly
seems worth worrying about whether their behavior is defined
or not.
On 05/06/2026 08:29, David Brown wrote:[...]
On 04/06/2026 21:29, Bart wrote:
TR:
BC:Sadly the idea of writing in a way that is "most easily understood"
has resulted in a race to the bottom, where writers are more and
more encouraged to take the view that (some) readers are pretty
much arbitrarily stupid, with the result that expressions become
littered with scads of unnecessary parentheses that actually
detract from ease of reading. Good writing is always a balance
between too much and too little.
TR:Actual examples of too many parentheses?
The point of my comment is that either too many or too few is a
subjective judgment, not an objective one.
Here it is clear that 'too many' was just a paraphrase of
'unnecessary'.
Tim Rentsch I'm sure will prefer the latter because 99.9% of C
programmers are machines, according to him.
Please give a reference for him saying that. (I'll save you the
bother, he has not made any remarks remotely like this in
c.l.c. since I have been here.)
Find out what was the subject of the 99.9% (even if that was an exaggeration). Then we'll talk.
No, he didn't use the word 'machines'; I paraphrased to suggest
supernormal people who know everything and never make mistakes.
You're going to argue about this now?
Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
Tim Rentsch <tr.17687@z991.linuxsc.com> writes:
Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
Note that in a context that requires a constant expression, overflow is >>>> a constraint violation. For example, a case label like:
case (INT_MAX + 1) * 0:
must be diagnosed at compile time.
gcc disagrees with you.
What makes you think so?
[...]
I'm skipping this and proceeding on to the original question.
I see no basis for this belief. My conclusions are based on what
the C standard actually says, rather than guesses about some
unstated "intentions". I think you would do well to reach your
conclusions based more on the actual text of the C standard, and
less on your interpretation of what the text was "intended" to
mean.
Tim Rentsch <tr.17687@z991.linuxsc.com> writes:
Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
[...]
The line `# 2 "hello.c"` is, according to the C standard, a
"non-directive", which is a kind of directive. Executing a
non-directive has undefined behavior,
Since it is gcc that is generating the non-directives, for
internal purposes, and gcc that is consuming them, it hardly
seems worth worrying about whether their behavior is defined
or not.
I wasn't worried. I just mentioned in in passing.
You quoted most of the article, but snipped relevant context in
the middle of a sentence.
Bart <bc@freeuk.com> writes:
On 05/06/2026 08:29, David Brown wrote:[...]
On 04/06/2026 21:29, Bart wrote:
TR:
BC:Sadly the idea of writing in a way that is "most easily understood"
has resulted in a race to the bottom, where writers are more and
more encouraged to take the view that (some) readers are pretty
much arbitrarily stupid, with the result that expressions become
littered with scads of unnecessary parentheses that actually
detract from ease of reading. Good writing is always a balance
between too much and too little.
TR:Actual examples of too many parentheses?
The point of my comment is that either too many or too few is a
subjective judgment, not an objective one.
Here it is clear that 'too many' was just a paraphrase of
'unnecessary'.
No, it is clear that "too many" and "unnecessary" have two different meanings.
The idea that "too many" and "unnecessary" mean the same thing
is your own invention.
[...]
Tim Rentsch I'm sure will prefer the latter because 99.9% of C
programmers are machines, according to him.
Please give a reference for him saying that. (I'll save you the
bother, he has not made any remarks remotely like this in
c.l.c. since I have been here.)
Find out what was the subject of the 99.9% (even if that was an
exaggeration). Then we'll talk.
Only Tim can clarify that point, and he's made it clear that he's
not interested in doing so. Please don't complain to the rest of
us about that.
No, he didn't use the word 'machines'; I paraphrased to suggest
supernormal people who know everything and never make mistakes.
You're going to argue about this now?
Bart, when you make ridiculous and/or false statements, people are going
to argue with you. When you double down on such statements, people are
going to continue to argue with you.
Your use of the word "machines" was ridiculous and false.
Chris M. Thomasson <chris.m.thomasson.1@gmail.com> wrote:
On 6/4/2026 4:44 PM, Bart wrote:
On 05/06/2026 00:09, Keith Thompson wrote:
Bart <bc@freeuk.com> writes:
On 04/06/2026 22:06, Keith Thompson wrote:
Bart <bc@freeuk.com> writes:
On 04/06/2026 19:54, David Brown wrote:[...]
Stop it. He's not saying that.Again - /please/ stop trying to guess what people say or put words >>>>>>>> in their mouths. I can't remember ever seeing you do so accurately. >>>>>>>This is what you actually said:
It is an objective fact, therefore, that "(a*a) + (b*b)" has more >>>>>>>> parentheses than needed in the context of most programming languages. >>>>>>>>
"(a*a) + (b*b) has too many parentheses", on the other hand, is a >>>>>>>> purely
subjective opinion. Even if it is true that this is "commonly agreed >>>>>>>> to" (and AFAIK you have no basis for that claim), that would still >>>>>>>> be a
subjective opinion - no matter how common that opinion is.
You're saying that:
* "more than needed" is objective
* "too many" is subjective
That is EXACTLY what he's saying: "It is an OBJECTIVE fact .. has more >>>>> ... than needed", and:
"has too many ... is ... purely subjective".
You're taking phrases out of context and making false claims that the >>>>>> full statement was far more general than it actually was.
And this is exactly what other people are doing.
Taken literally, your statement implies that you admit that that's
what you're doing. Is that what you meant? If so, I suggest you
*stop* making such false claims. If not, what did you actually mean? >>>>
So I used TOO MANY instead of MORE THAN NEEDED to describe the exact >>>>> same phenomenon.
That's not the problem. There is an actual meaningful distinction
here, between what's needed by the compiler and what's useful to
improve clarity for human readers. I have found some of what you've
written to be unclear about that distinction.
Can we agree that the question of whether parentheses in a C
expression are necessary to the compiler can be answered objectively?
Can we agree that the question of whether extra parentheses are
helpful to a human reader is at least partly subjective, and
varies from case to case? Is there really anything else that we
fundamentally disagree about?
(1) Why are you all making such a big fucking deal of this?
Why are you?
I didn't start this business of something being subjective or objective, >>> or suggesting than one turn of phrase to discuss the same thing was
subjective and the other objective (implying that a subjective opinion
had less worth). TR started that and several people backed him up.
Myself I wouldn't even use those terms. My point was that some overuses
of () for commonly known precedences are more overkill than others.
If that's subjective then so be it; it is not some fundamental law of
the universe. I would just call it common sense.
> Why are you?
Since you ask, I was defending my point of view then got sidetracked by
this subjective/objective nonsense. I notice that TR has disappeared
from this subthread.
Wrt the number of ()'s? Might as well go to sleep with the following
song playing in the background:
(The Fate of Ophelia - Taylor Swift (Lyrics) Charlie Puth ft. Selena
Gomez, the weekd, ariana grande)
AFAICS outer parentheses there are excessive, inner ones look OK.
In article <10vsnl7$lkmu$1@kst.eternal-september.org>,
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote: >>>cross@spitfire.i.gajendra.net (Dan Cross) writes:
In article <865x3yd21n.fsf@linuxsc.com>,[...]
Tim Rentsch <tr.17687@z991.linuxsc.com> wrote: >>>>>cross@spitfire.i.gajendra.net (Dan Cross) writes:
In article <86ik81cfk5.fsf_-_@linuxsc.com>,
Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:
To clarify, the comments in my posting were meant to be read as >>>>>saying the given text is the entire program, and that it is strictly >>>>>conforming with respect to conforming hosted implementations. >>>>>(Incidentally, given the rules for freestanding implementations, I'm >>>>>not sure that it is even possible for any program to be strictly >>>>>conforming with respect to conforming freestanding implementations. >>>>>In any case my statements were meant only in the context of hosted >>>>>implementations.)There's an important distinction to make here. Consider this
program:
#include <limits.h>
int
foo(){
int zero = (INT_MAX+1)*0;
return zero;
}
int
main(){
return 0;
}
This program does not transgress the bounds of undefined behavior. >>>>>
Ok.
[snip]
Perhaps you mean that this is irrelevant because `foo` is not
invoked, but I see no reason why that need be the case in e.g.
a freestanding environment.
I explained the context of my previous statements above. Sorry for >>>>>not saying that in the original message.
In a hosted environment, I don't
think anything explicitly prevents `foo` from being called after
`main` returns (though I can't imagine that would happen in real
life; it would be weird if it did).
The semantics described in the ISO C standard don't admit that >>>>>possibility.
Could you please point to where it says this, in the C standard?
I cannot find anything that says that arbitrary code cannot run
after `main()` returns, and I don't see how that could possibly
be true.
N3220 5.1.2.4, Program semantics.
It defines the *observable behavior* of a program, which consists of >>>accesses to volatile objects, data written to files, and I/O dynamics of >>>interactive devices.
Yes, but it does so for strictly-conforming programs with no UB.
It does so for programs in general, not just strictly conforming
ones. If a program has undefined behavior, all bets are off,
but for example a program that evaluates `printf("%d\n", INT_MAX)`
is not strictly conforming, but it's fully subject to 5.1.2.4.
To understand conformance, we have to jump over to section 4,
which explicitly says that, 'Undefined behavior is otherwise
indicated in this document by the words "undefined behavior" or
by the omission of any explicit definition of behavior.' As it
does not say that a program with an instance of undefined
behavior in an integer constant expression that is not executed
must otherwise behave in any given manner, what the program does
is undefined. A constaint violation mandates a diagnostic, but
beyond that, the standard is (AFAICT) silent.
I don't think an integer constant expression can have undefined
behavior. INT_MAX+1 and 1/0 are not constant expressions, because
neither "evaluate(s) to a constant that is in the range of
representable values for its type".
I claim that an expression that looks like a constant expression
*isn't* a constant-expression if it doesn't appear in a context
that requires a constant-expression.
The program in question, quoted above, has:
int zero = (INT_MAX+1)*0;
`(INT_MAX+1)*0` is not a constant expression, not because of the
overflow, but because a constant expression is not required in
that context. "constant-expression" is defined by a production in
the grammar (it reduces to "conditional-expression"). Even in
int n = 42;
42 is not a a constant expression, because the grammar doesn't
call for a constant expression in that context -- even though it
looks like one. Similarly, in `a + b * c`, `a + b` looks like an
additive expression, but it isn't one. (Not a perfect analogy.)
Undefined Behavior, in turn, is not defined as specific only to
execution: the standard simply says that it is "behavior, upon
use of a *nonportable or erroneous program construct*..." for
which there are no requirements, and there are examples of
things that are explicitly UB at translation time, such as
improperly terminated lexemes and so forth.
Yes, there are constructs that are explicitly UB at translation time.
(I think that's unfortunate, and there are efforts to clear up some
such cases in C2y.)
Signed integer overflow is not one of those constructs.
Any undefined behavior from evaluating INT_MAX+1 happens during
execution (barring constraint violations).
Furthermore, the expression above is obviously an integer
constant expression as defined by sec 6.6 para 8. Section 6.6,
para 4, reads in part, "Each constant expression shall evaluate
to a constant that is in the range of representable values for
its type." The expression, `(INT_MAX+1)*0` violates this
constraint, and so therefore a diagnostic is mandated as per
sec 5.1.1.3 para 1. That it appears in code that is not
obviously called from `main` doesn't change that.
It satisfies the requirements for an integer constant expression in
6.6p8, but it violates the constraint in 6.6p4. (I presume that an
"integer constant expression" must be a "constant expression".)
But since "constant-expression" is a grammatical production,
it doesn't have to satisfy that constraint, and no diagnostic
is required. (A warning is certainly permitted.)
Similarly, this:
int n = INT_MAX + 1;
at block scope doesn't require a diagnostic, though of course it
has undefined behavior -- but at file scope, the initializer is a
constant expression, so that would be a constraint violation.
Morever, sec 6.6 para 17 says that, "the semantic rules for
evaluation of a constant expression are the same as for
nonconstant expressions." This brings us back to 5.1.2.4,
though I submit that para (4) is a stronger argument for what
you and Tim are saying, as it reads in part, "An actual
implementation is not required to evaluate part of an expression
if it can deduce that its value is not used and that no needed
side effects are produced (including any caused by calling a
function or through volatile access to an object)." I interpret
this to mean that, if the implementation can determine that
there is no way that `foo` can be called, it does not _have_ to
evaluate the above expression. However, it must satisfy the
range constraint from section 6.6, so it likely will, and in any
event, the standard does not say that it, "shall not" evaluate
it, or when.
Overflow in a constant expression is not undefined behavior. It's a >constraint violation. But that doesn't apply here, because the
initializer is not a constant expression. (Sorry if I'm repeating
myself.)
Once the compiler does that, if it does, and observes UB, the
standard is silent on what requirements it imposes, which means
the behavior is undefined. I see no reason it couldn't arrange
to invoke `foo` at that point.
Any UB in the program would occur during execution,
and in fact
it *won't* occur during execution because foo() isn't called.
A compiler can't generate code with arbitrary behavior just because
it can't prove that there will be no UB. If it could, every signed
or floating-point arithmetic operation with unknown operand values
would grant the same permission.
So no, I do not see how execution according to the rules of the
abstract machine is not guaranteed, here. I certainly see no
way in which this can be regarded as a strictly conforming
program.
foo()'s behavior would be undefined if it were called. It *isn't*
called, so there's no actual UB. The program does not violate any
of the other requirements for strict conformance.
If the usual "Hello, world" program prints "Hello, world" followed
by "Goodbye", the implementation is non-conforming. If it formats
my hard drive after printing "Goodbye", it's non-conforming and >>>dangerous.
Two separate things. My point earlier was that code can
obviously run after `main` terminates. Moreoever, I can't
imagine what would _prevent_ a runtime system that invokes
`main` from doing something like printing, "PROGRAM STOPPED"
after `main` returned. C imposes no requirements here.
Yes, it does. An OS can print "PROGRAM STOPPED", but not as part
of the execution of the program. On my system, a shell prompt is
printed after a program terminates, but not by the program. If I
execute a "hello, world" program with its output redirected to a file
(on a system that supports that), the resulting file cannot contain
"PROGRAM STOPPED". The requirements in 5.1.2.4 specify both what
the execution of a program must do and what it must not do.
Whether foo() has external linkage or internal
linkage doesn't change that.
I disagree. There's no possible way for the implementation to
know whether a function with external linkage will be ultimately
invoked or not; consider a system that supports loadable shared
modules. Nothing prevents even this simple program from being
compiled as a shared module, dynamically loaded, the loading
program explicitly searching for and finding the symbol
corresponding to the `foo` function, and invoking it.
Remember that linking is translation phase 8. The compiler is not
the entire implementation.
Exactly my point. The compiler cannot know how `foo` might be
used, or how the translated object might be exercised. There's
I don't see how it could possibly know that, given that `foo`
has external linkage.
We were presented with a complete translation unit that included a
function definition for "main". It's a complete program. There's no
valid way for some other program to call foo. If OS provided such
a mechanism, it would be outside the scope of C.
Hence, the compiler _must_ treat with UB as written, which is
why `ubsan` inserts trapping code in `foo`.
I don't know what "_must_ treat with UB" means.
foo() has undefined behavior if it's called, so replacing its
body with trapping code is valid. But (I'm reasonably sure that)
an implementation cannot reject a program just because it can't
prove that it has no undefined behavior during execution. It can
reject it if it can prove that it *always* has undefined behavior
during execution.
What I'm saying is that, `foo` has undefined behavior _period_.
That's manifest in an integer constant expression, whether it is
executed at runtime or not. I believe that the standard forces
the expression to be evaluated at translation time, via the
"shall" mandate when checking the constraint on the range in sec
6.6 para 4. Further, that evaluation must happen in accordance
with the rules of the abstract machine, as per 5.1.2.4 para 17.
The diagnostic is mandated, as is the translation-time
evaluation. The expression is itself manifestly exhibits UB,
and so therefore the result of the rest of the translation is
undefined.
foo is a function. foo does not have undefined behavior; it has no
behavior at all. A *call* to foo during execution has undefined
behavior. (`foo;` is a statement-expression that does nothing;
it does not have undefined behavior.)
[SNIP]
I think the question of whether the initializer is a
constant-expression or not has caused some not entirely relevant
confusion.
Here's another example that avoids that issue.
#include <limits.h>
int foo(void) {
int zero;
zero = INT_MAX;
zero ++;
zero *= 0;
return zero;
}
int main(void) {
return 0;
}
Given my grammatical argument above, I would say that this program
has no constant expressions.
Whether that argument is correct or
not, it certainly has no constant expressions that violate any
constraint or that have undefined behavior. Evaluating `zero ++`
(which doesn't even pretend to be a constant expression) would have
run-time undefined behavior -- *if* foo() were ever called.
And given this translation unit, I don't think there's any way to
construct a multi-TU program that calls foo, so a compiler *can*
determine that foo is never called (but there's no requirement to
do so, or to make any use of that information).
Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
[snip]
But taking a closer look at the standard, I'm not 100% sure that the
language requires a diagnostic, though I think that's the intent.
The relevant constraint is:
Each constant expression shall evaluate to a constant that is
in the range of representable values for its type.
If I squint really hard, I can argue that the entire expression
has to be a constant expression, but it doesn't say that its
subexpressions are constant expressions -- and *if* INT_MAX +
1 evaluates to INT_MIN in the current implementation, then
(INT_MAX + 1) * 0 evaluates to 0 and therefore satisfies the
constraint.
My reasoning is as follows.
To determine if the constraint is satisfied, the compiler must
first evaluate the expression (INT_MAX + 1) * 0.
To evaluate the expression (INT_MAX + 1) * 0, the compiler must
first evaluate the sub-expression (INT_MAX + 1).
Because the expression (INT_MAX + 1) overflows, the behavior is
undefined, and the compiler is free to decide that the value of
the sub-expression (INT_MAX + 1) is, let's say, 12.
The compiler next evaluates the overall expression as 12*0, which
is 0 (an int).
This result of the overall expression satisfies the constraint,
and so the compiler is not obliged to generate a diagnostic.
[snip]
I see no basis for this belief. My conclusions are based on what
the C standard actually says, rather than guesses about some
unstated "intentions". I think you would do well to reach your
conclusions based more on the actual text of the C standard, and
less on your interpretation of what the text was "intended" to
mean.
On 05/06/2026 08:53, Tim Rentsch wrote:
cross@spitfire.i.gajendra.net (Dan Cross) writes:
In article <10vsrpo$men2$2@dont-email.me>, Bart <bc@freeuk.com> wrote:
On 04/06/2026 22:06, Keith Thompson wrote:
Bart <bc@freeuk.com> writes:
[snip]
Tim Rentsch I'm sure will prefer the latter because 99.9% of C
programmers are machines, according to him.
Tim didn't say or imply that.
So what was his 99.9% all about? Nobody has a clue, except they are
certain that what I think it is is wrong!
Have you thought about, I don't know, maybe asking him?
Asking him straight questions is usually futile. You can probably guess
this from the response below.
Notice he hasn't tried to enlighten anyone about that 99.9%.
That may just have been a throwaway line like when I say 'nobody likes
X', but I would still dispute that, if it's about what I think it is,
it's anything like a super-majority.
At the risk of saying what may be obvious to everyone, Bart has
shown that he has no interest in having a serious, constructive,
useful, or productive conversation with anyone. His questions
are all rhetorical; he hasn't asked me a straight question
because he isn't really interested in what I would say. In
short, Bart isn't looking for an answer, he's looking for an
argument. My recommendation is just stop responding to him
altogether. My response to him upthread was a sincere effort to
provide a neutral and helpful answer to his question. Maybe my
remarks were helpful to other people, and if they were that's
good. Any further efforts to interact with Bart are not just a
waste of time but actually counterproductive. What Bart needs is
not help with understanding C but a good therapist. In any case
I'm confident that whatever Bart's needs may be, no one responding
to his postings here is in a position to provide them. Please
consider these remarks before responding to him further.
I didn't read Bart's posting. Unfortunately it seems
true that any continued interaction with his comments
is counterproductive.
cross@spitfire.i.gajendra.net (Dan Cross) writes:
In article <1BoUR.3$lmCb.1@fx22.iad>, Scott Lurndal <slp53@pacbell.net> wrote:<snip>
cross@spitfire.i.gajendra.net (Dan Cross) writes:
[snip]
Yeah, that's from `cc.c`, right?
No, it's from cpp.c
$ ls /work/reference/collegetapes/sltape/v6cc/
c0.c c00.c c01.c c02.c c03.c c04.c c05.c c1.h
c10.c c11.c c12.c c13.c c2.h c20.c c21.c cc.c cpp.c
Oh interesting. I don't have a `cpp.c` in my v6 archive.
I wonder what else I'm missing.
[snip]
In article <10vu703$11s5q$1@dont-email.me>, Bart <bc@freeuk.com> wrote:
On 05/06/2026 08:53, Tim Rentsch wrote:[...]
[...][...]
Generally speaking, AFAIK, none of the regular posters here are
qualified mental health professionals; as such, we should all
avoid from making armchair psychological diagnoses, the
occasionally midly offcolor joke aside ("that's crazy!").
In article <10vt7b9$pi3s$1@kst.eternal-september.org>,
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
cross@spitfire.i.gajendra.net (Dan Cross) writes:
In article <10vsnl7$lkmu$1@kst.eternal-september.org>,
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote: >>>>cross@spitfire.i.gajendra.net (Dan Cross) writes:
In article <865x3yd21n.fsf@linuxsc.com>,[...]
Tim Rentsch <tr.17687@z991.linuxsc.com> wrote: >>>>>>cross@spitfire.i.gajendra.net (Dan Cross) writes:
In article <86ik81cfk5.fsf_-_@linuxsc.com>,
Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:
To clarify, the comments in my posting were meant to be read as >>>>>>saying the given text is the entire program, and that it is strictly >>>>>>conforming with respect to conforming hosted implementations. >>>>>>(Incidentally, given the rules for freestanding implementations, I'm >>>>>>not sure that it is even possible for any program to be strictly >>>>>>conforming with respect to conforming freestanding implementations. >>>>>>In any case my statements were meant only in the context of hosted >>>>>>implementations.)There's an important distinction to make here. Consider this
program:
#include <limits.h>
int
foo(){
int zero = (INT_MAX+1)*0;
return zero;
}
int
main(){
return 0;
}
This program does not transgress the bounds of undefined behavior. >>>>>>
Ok.
[snip]
Perhaps you mean that this is irrelevant because `foo` is not
invoked, but I see no reason why that need be the case in e.g.
a freestanding environment.
I explained the context of my previous statements above. Sorry for >>>>>>not saying that in the original message.
In a hosted environment, I don't
think anything explicitly prevents `foo` from being called after >>>>>>> `main` returns (though I can't imagine that would happen in real >>>>>>> life; it would be weird if it did).
The semantics described in the ISO C standard don't admit that >>>>>>possibility.
Could you please point to where it says this, in the C standard?
I cannot find anything that says that arbitrary code cannot run
after `main()` returns, and I don't see how that could possibly
be true.
N3220 5.1.2.4, Program semantics.
It defines the *observable behavior* of a program, which consists of >>>>accesses to volatile objects, data written to files, and I/O dynamics of >>>>interactive devices.
Yes, but it does so for strictly-conforming programs with no UB.
It does so for programs in general, not just strictly conforming
ones. If a program has undefined behavior, all bets are off,
but for example a program that evaluates `printf("%d\n", INT_MAX)`
is not strictly conforming, but it's fully subject to 5.1.2.4.
To understand conformance, we have to jump over to section 4,
which explicitly says that, 'Undefined behavior is otherwise
indicated in this document by the words "undefined behavior" or
by the omission of any explicit definition of behavior.' As it
does not say that a program with an instance of undefined
behavior in an integer constant expression that is not executed
must otherwise behave in any given manner, what the program does
is undefined. A constaint violation mandates a diagnostic, but
beyond that, the standard is (AFAICT) silent.
I don't think an integer constant expression can have undefined
behavior. INT_MAX+1 and 1/0 are not constant expressions, because
neither "evaluate(s) to a constant that is in the range of
representable values for its type".
I claim that an expression that looks like a constant expression
*isn't* a constant-expression if it doesn't appear in a context
that requires a constant-expression.
That's a bold claim, but I think I see why you're saying that.
The program in question, quoted above, has:
int zero = (INT_MAX+1)*0;
`(INT_MAX+1)*0` is not a constant expression, not because of the
overflow, but because a constant expression is not required in
that context. "constant-expression" is defined by a production in
the grammar (it reduces to "conditional-expression"). Even in
int n = 42;
42 is not a a constant expression, because the grammar doesn't
call for a constant expression in that context -- even though it
looks like one. Similarly, in `a + b * c`, `a + b` looks like an
additive expression, but it isn't one. (Not a perfect analogy.)
Right; I see what you mean. In this case, the
`assignment-expression` production applies, not
`constant-expression`.
Undefined Behavior, in turn, is not defined as specific only to
execution: the standard simply says that it is "behavior, upon
use of a *nonportable or erroneous program construct*..." for
which there are no requirements, and there are examples of
things that are explicitly UB at translation time, such as
improperly terminated lexemes and so forth.
Yes, there are constructs that are explicitly UB at translation time.
(I think that's unfortunate, and there are efforts to clear up some
such cases in C2y.)
It's unclear to me how it could be any other way. If UB was
_only_ an issue at runtime, then how could a compiler take
advantage of it to perform optimizations during translation?
We know that compilers do this.
Signed integer overflow is not one of those constructs.
This I'm not sure I agree with. It the compiler detects signed
integer overflow in (perhaps not relevant in _this_ example) an
integer constant expression, I still don't see anthing that
makes that anything other than UB. It's a constaint violation,
sure, but nothing says it is not also UB.
Any undefined behavior from evaluating INT_MAX+1 happens during
execution (barring constraint violations).
I'm not sure the standard says that. The standard says this
happens during _evaluation_, and that evaluation must be
performed in accordance with the rules of the abstract syntax
machine. But it doesn't precisely specify _when_ evaluation
takes place, and in particular, there are places in the standard
that explicitly mention evaluation during translation. I still
don't see anything that prohibits a compiler from evaluating
that expression at compile time (indeed, it clearly does, as it
generates a diagnostic about the overflow).
I suppose that changes the matter: does the language merely
leave that unspecified, in which case, this program is not
strictly conforming, or does it say that it _cannot_ make any translation-time decisions about it? I cannot find a satisfying
argument for the latter.
Furthermore, the expression above is obviously an integer
constant expression as defined by sec 6.6 para 8. Section 6.6,
para 4, reads in part, "Each constant expression shall evaluate
to a constant that is in the range of representable values for
its type." The expression, `(INT_MAX+1)*0` violates this
constraint, and so therefore a diagnostic is mandated as per
sec 5.1.1.3 para 1. That it appears in code that is not
obviously called from `main` doesn't change that.
It satisfies the requirements for an integer constant expression in
6.6p8, but it violates the constraint in 6.6p4. (I presume that an >>"integer constant expression" must be a "constant expression".)
But since "constant-expression" is a grammatical production,
it doesn't have to satisfy that constraint, and no diagnostic
is required. (A warning is certainly permitted.)
Fair point. It's grammatical position makes it an
assignment-expression. I clearly misinterpreted that before.
Similarly, this:
int n = INT_MAX + 1;
at block scope doesn't require a diagnostic, though of course it
has undefined behavior -- but at file scope, the initializer is a
constant expression, so that would be a constraint violation.
Right. The semantics of this are defined in sec 6.7.11 para 5.
Morever, sec 6.6 para 17 says that, "the semantic rules for
evaluation of a constant expression are the same as for
nonconstant expressions." This brings us back to 5.1.2.4,
though I submit that para (4) is a stronger argument for what
you and Tim are saying, as it reads in part, "An actual
implementation is not required to evaluate part of an expression
if it can deduce that its value is not used and that no needed
side effects are produced (including any caused by calling a
function or through volatile access to an object)." I interpret
this to mean that, if the implementation can determine that
there is no way that `foo` can be called, it does not _have_ to
evaluate the above expression. However, it must satisfy the
range constraint from section 6.6, so it likely will, and in any
event, the standard does not say that it, "shall not" evaluate
it, or when.
Overflow in a constant expression is not undefined behavior. It's a >>constraint violation. But that doesn't apply here, because the
initializer is not a constant expression. (Sorry if I'm repeating
myself.)
Where does it say that UB and constraint violations are mutually
exclusive? I don't see any such statement in the standard. Am
I missing it?
The standard says that if a constraint is violated, a diagnostic
must be emitted, regardless of whether or not the constraint
violation is the result of something that is UB not; that is, if
a constraint violation occurs due to something that is UB, the
implementation must still emit a diagnostic: UB is not an escape
hatch from that requirement.
It also says, 'If a "shall" or "shall not" requirement that
appears outside of a constraint or runtime-constraint is
violated, the behavior is undefined. Undefined behavior is
otherwise indicated in this document by the words "undefined
behavior" or by the omission of any explicit definition of
behavior.' However, that does not preclude such behavior being
undefined; it just means that the words "shall" and "shall not"
in a constraint violation do not a priori describe behavior vis
definition.
Once the compiler does that, if it does, and observes UB, the
standard is silent on what requirements it imposes, which means
the behavior is undefined. I see no reason it couldn't arrange
to invoke `foo` at that point.
Any UB in the program would occur during execution,
I suppose; but it's not clear to me that UB is tied _only_ to
execution time.
The standard is explicit that there _are_ things that are
evaluated at translation time, like the initializer for an
object with storage class `constexpr`. It is not clear me that
a compiler is otherwise _prohibited_ from evaluating an
expression during translation; indeed, one could imagine it
doing so to perform constant folding, and I do not believe there
exists any normative text defining it as such.
I realize this is an extreme interpretation, and not one that is
not widely shared. Personally, I think it's rather silly.
However, I that is _a_ danger of the informality of the C
specification; it does not define the semantics of the abstract
machine in the formally precise way that, say, the SML spec
defines that language's semantics. Rather, it informally
specifies them in prose, and that prose is ambiguous.
Probably much good would be done if C's semantics _were_
rigorously defined, but they are not. Thus, they are open to
radical interpretation, and as extreme as those may be, I do not
see how the normative text of the standard explicitly
_prohibits_ them.
and in fact
it *won't* occur during execution because foo() isn't called.
A compiler can't generate code with arbitrary behavior just because
it can't prove that there will be no UB. If it could, every signed
or floating-point arithmetic operation with unknown operand values
would grant the same permission.
But that's not the situation here. The situation is that the
compiler can prove that something _is_ UB.
Regardless, I think you highlighted an actual problem with the
spec; I don't think that behavior is _explicitly_ prohibited,
therefore, it is likely undefined, but at a minimum unspecified,
whether it actually could happen. If the argument against that
is that this renders the language essentially unusuable, then
my response is, "yeah, well, welcome to programming in C in the
2020s." Most compilers would never be that extreme, but I see
no evidence that it would not be an invalid reading of the
literal text of the standard if they did.
So no, I do not see how execution according to the rules of the
abstract machine is not guaranteed, here. I certainly see no
way in which this can be regarded as a strictly conforming
program.
foo()'s behavior would be undefined if it were called. It *isn't*
called, so there's no actual UB. The program does not violate any
of the other requirements for strict conformance.
I understand _what_ you're saying: despite the expression itself
manifesting undefined behavior, in this case it's not UB because
`foo` is never executed. What I'm saying is that I don't see
anything in the standard that restricts UB to _only_ executed
code. A reputable compiler obviously instruments `foo` with
code to trap into ubsan; if it's not UB, since it's not
executed, then why do so? Granted, that's not evidence of
anything other than the behavior of those compilers, but still.
It is clearly the _intent_ that this be a strictly conforming
program. The C standard, as an imprecise, informal document,
cannot guarantee it.
If the usual "Hello, world" program prints "Hello, world" followed
by "Goodbye", the implementation is non-conforming. If it formats
my hard drive after printing "Goodbye", it's non-conforming and >>>>dangerous.
Two separate things. My point earlier was that code can
obviously run after `main` terminates. Moreoever, I can't
imagine what would _prevent_ a runtime system that invokes
`main` from doing something like printing, "PROGRAM STOPPED"
after `main` returned. C imposes no requirements here.
Yes, it does. An OS can print "PROGRAM STOPPED", but not as part
of the execution of the program. On my system, a shell prompt is
printed after a program terminates, but not by the program. If I
execute a "hello, world" program with its output redirected to a file
(on a system that supports that), the resulting file cannot contain >>"PROGRAM STOPPED". The requirements in 5.1.2.4 specify both what
the execution of a program must do and what it must not do.
Files are a separate case. There's no guarantee that the
standard output refers to a file; it may well refer to an
"interactive device", the semantics of which are (necessarily)
unspecified.
Here's an example: consider an interactive user who uses a
screen reader device. Suppose that user makes use of an
implementation that includes runtime support for that device,
and that precedes invocation of `main` with a command sequence
causing the screen reader to (perhaps) change intonation; and
suceeds return from main by outputing another command sequence
that resets to the original state.
I do not see how C could prohibit that, assuming that the
implementation takes care to detect whether standard output
really refers to the screen reader, and does emit the control
sequences if output is redirected to a file. Another user who
runs that same program without a screen reader may see the
standard text printed on the screen, without the control
sequence sandwich.
I don't think a conforming implementation can prohibit that kind
of thing.
Whether foo() has external linkage or internal
linkage doesn't change that.
I disagree. There's no possible way for the implementation to
know whether a function with external linkage will be ultimately
invoked or not; consider a system that supports loadable shared
modules. Nothing prevents even this simple program from being
compiled as a shared module, dynamically loaded, the loading
program explicitly searching for and finding the symbol
corresponding to the `foo` function, and invoking it.
Remember that linking is translation phase 8. The compiler is not
the entire implementation.
Exactly my point. The compiler cannot know how `foo` might be
used, or how the translated object might be exercised. There's
I don't see how it could possibly know that, given that `foo`
has external linkage.
We were presented with a complete translation unit that included a
function definition for "main". It's a complete program. There's no
valid way for some other program to call foo. If OS provided such
a mechanism, it would be outside the scope of C.
Given an excessively pedantic and literal reading of the text of
the standard, I don't think an implementation is explicitly
prohibited from evaluating the initializer at translation time,
deducing that the behavior is undefined, and blaming it on the
program, at which point, all bets are off.
Hence, the compiler _must_ treat with UB as written, which is
why `ubsan` inserts trapping code in `foo`.
I don't know what "_must_ treat with UB" means.
foo() has undefined behavior if it's called, so replacing its
body with trapping code is valid. But (I'm reasonably sure that)
an implementation cannot reject a program just because it can't
prove that it has no undefined behavior during execution. It can >>>>reject it if it can prove that it *always* has undefined behavior >>>>during execution.
What I'm saying is that, `foo` has undefined behavior _period_.
That's manifest in an integer constant expression, whether it is
executed at runtime or not. I believe that the standard forces
the expression to be evaluated at translation time, via the
"shall" mandate when checking the constraint on the range in sec
6.6 para 4. Further, that evaluation must happen in accordance
with the rules of the abstract machine, as per 5.1.2.4 para 17.
The diagnostic is mandated, as is the translation-time
evaluation. The expression is itself manifestly exhibits UB,
and so therefore the result of the rest of the translation is
undefined.
foo is a function. foo does not have undefined behavior; it has no >>behavior at all. A *call* to foo during execution has undefined
behavior. (`foo;` is a statement-expression that does nothing;
it does not have undefined behavior.)
The _evaluation_ of that expression in `foo` has undefined
behavior. The standard does not say that it _cannot_ be
evaluated at translation time.
[SNIP]
I think the question of whether the initializer is a
constant-expression or not has caused some not entirely relevant
confusion.
Here's another example that avoids that issue.
#include <limits.h>
int foo(void) {
int zero;
zero = INT_MAX;
zero ++;
zero *= 0;
return zero;
}
int main(void) {
return 0;
}
Given my grammatical argument above, I would say that this program
has no constant expressions.
Agreed, if by "constant expressions" you mean those mandated to
use the `constant-expression` grammatical production.
Whether that argument is correct or
not, it certainly has no constant expressions that violate any
constraint or that have undefined behavior. Evaluating `zero ++`
(which doesn't even pretend to be a constant expression) would have >>run-time undefined behavior -- *if* foo() were ever called.
Let me turn this around in two ways: suppose that the
translation unit _only_ included `foo`. Could the compiler
deduce that the behavior of `foo`, if called, is undefined? If
not, why not?
Second, suppose that `foo` _were_ called, could the compiler
replace this with a program that was the equivalent of,
`int main(void) {printf("check your nose"); abort();}`? If so
why? If not, why not?
And given this translation unit, I don't think there's any way to
construct a multi-TU program that calls foo, so a compiler *can*
determine that foo is never called (but there's no requirement to
do so, or to make any use of that information).
This is the crux of my point, as well. There's not requirement
for the translator to _not_ evaluate the expression and become
privy to UB.
Would it be stupid if a compiler did that? Yes. Do existing
compilers do so? No, not that I'm aware of. Would some dweeb
nerd compiler douche who thinks this would make a compiler
benchmark some microfraction of a percent faster take advantage
of that? I absolutely think so, yes.
The text of the standard explicitly carves this out; or, rather,[...]
it attempts to. If the result of an expression is not
representable in the target type, _regardless of whether that's
due to UB or not_, a diagnostic is required.
In article <DHAUR.47540$0o1c.29921@fx08.iad>,
Scott Lurndal <slp53@pacbell.net> wrote:
cross@spitfire.i.gajendra.net (Dan Cross) writes:
In article <1BoUR.3$lmCb.1@fx22.iad>, Scott Lurndal <slp53@pacbell.net> wrote:<snip>
cross@spitfire.i.gajendra.net (Dan Cross) writes:
[snip]
Yeah, that's from `cc.c`, right?
No, it's from cpp.c
$ ls /work/reference/collegetapes/sltape/v6cc/
c0.c c00.c c01.c c02.c c03.c c04.c c05.c c1.h
c10.c c11.c c12.c c13.c c2.h c20.c c21.c cc.c cpp.c
Oh interesting. I don't have a `cpp.c` in my v6 archive.
I wonder what else I'm missing.
[snip]
Thanks! This is an artifact definitely worth preserving. As
far as I know, it's not in any of the extant V6 archives. I'll
shoot you an email, if that's ok.
- Dan C.
In article <DHAUR.47540$0o1c.29921@fx08.iad>,
Scott Lurndal <slp53@pacbell.net> wrote:
cross@spitfire.i.gajendra.net (Dan Cross) writes:
In article <1BoUR.3$lmCb.1@fx22.iad>, Scott Lurndal <slp53@pacbell.net> wrote:<snip>
cross@spitfire.i.gajendra.net (Dan Cross) writes:
[snip]
Yeah, that's from `cc.c`, right?
No, it's from cpp.c
$ ls /work/reference/collegetapes/sltape/v6cc/
c0.c c00.c c01.c c02.c c03.c c04.c c05.c c1.h
c10.c c11.c c12.c c13.c c2.h c20.c c21.c cc.c cpp.c
Oh interesting. I don't have a `cpp.c` in my v6 archive.
I wonder what else I'm missing.
[snip]
Thanks! This is an artifact definitely worth preserving. As
far as I know, it's not in any of the extant V6 archives. I'll
shoot you an email, if that's ok.
I claim that an expression that looks like a constant expression
*isn't* a constant-expression if it doesn't appear in a context
that requires a constant-expression.
On 31/05/2026 19:11, Bart wrote:...
Actual examples of too many parentheses?
Any source code written in LISP :-)
(And for too few parentheses, any source code in Forth.)
PS: One yet non-considered question that was part of my original
post was: "Is there any rationale from the _software designer_'s perspective?"
Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
I claim that an expression that looks like a constant expression
*isn't* a constant-expression if it doesn't appear in a context
that requires a constant-expression.
Right. This question came up years ago in a Defect Report. The
response from the Committee was basically the same as what you
said: the 6.6 constraints for constant expressions apply only in
situations where the C standard expressly requires a constant
expression. (I don't have the DR in front of me; I'm summarizing
based on memory, but am confident the actual wording is consistent
with what I just said.)
Tim Rentsch <tr.17687@z991.linuxsc.com> writes:
Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
I claim that an expression that looks like a constant expression
*isn't* a constant-expression if it doesn't appear in a context
that requires a constant-expression.
Right. This question came up years ago in a Defect Report. The
response from the Committee was basically the same as what you
said: the 6.6 constraints for constant expressions apply only in
situations where the C standard expressly requires a constant
expression. (I don't have the DR in front of me; I'm summarizing
based on memory, but am confident the actual wording is consistent
with what I just said.)
C99 DR 261 looks similar to what you're talking about.
https://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_261.htm
The Committee Response section says:
In general, the interpretation of an expression for constantness
is context sensitive. For any expression which contains only
constants:
- If the syntax or context only permits a constant expression, the
constraints of 6.6#3 and 6.6#4 shall apply.
- Otherwise, if the expression meets the requirements of 6.6
(including any form accepted in accordance with 6.6#10), it is a
constant expression.
- Otherwise it is not a constant expression.
That's close to what I claimed, but the second bullet point differs.
My claim was that, given:
n = 2+2;
2+2 is not a constant expression because the grammar doesn't require
a constant expression in that context. The Committee's opinion
(at least at the time) was that it is a constant expression because
it meets the requirements of 6.6.
But I *think* it's a distinction without a difference. [...]
Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:[...]
That's close to what I claimed, but the second bullet point differs.
My claim was that, given:
n = 2+2;
2+2 is not a constant expression because the grammar doesn't require
a constant expression in that context. The Committee's opinion
(at least at the time) was that it is a constant expression because
it meets the requirements of 6.6.
But I *think* it's a distinction without a difference. [...]
Right. The key point is that the constraints need to be satisfied
only in situations where the C standard expressly requires a
constant expression. Whether a given expression is called a
"constant expression" doesn't matter; all that does matter is
whether the constraints need to be satisfied.
cross@spitfire.i.gajendra.net (Dan Cross) writes:
In article <865x3yd21n.fsf@linuxsc.com>,
Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:
cross@spitfire.i.gajendra.net (Dan Cross) writes:
In article <86ik81cfk5.fsf_-_@linuxsc.com>,
Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:
[...]
There's an important distinction to make here. Consider this
program:
#include <limits.h>
int
foo(){
int zero = (INT_MAX+1)*0;
return zero;
}
int
main(){
return 0;
}
This program does not transgress the bounds of undefined behavior.
To clarify, the comments in my posting were meant to be read as
saying the given text is the entire program, and that it is strictly
conforming with respect to conforming hosted implementations.
(Incidentally, given the rules for freestanding implementations, I'm
not sure that it is even possible for any program to be strictly
conforming with respect to conforming freestanding implementations.
In any case my statements were meant only in the context of hosted
implementations.)
foo() has undefined behavior if it's called, so replacing its
body with trapping code is valid.
But (I'm reasonably sure that)
an implementation cannot reject a program just because it can't
prove that it has no undefined behavior during execution. [...]
In your example, `foo` clearly exhibits UB; I think your
argument is whether that has a realized effect or not, since the
UB is not invoked. I'm saying that in general a compiler cannot
possibly know that when it compiles `foo`, and is free to assume
the worst.
foo() exhibits UB if and only if it's called during execution.
[...]
The text of the standard explicitly carves this out; or, rather,[...]
it attempts to. If the result of an expression is not
representable in the target type, _regardless of whether that's
due to UB or not_, a diagnostic is required.
How would an expression (appearing in a context that requires an
integer constant expression) not "evaluate to a constant that is in
the range of representable values for its type" other than by UB?
I can't think of an example, but I'd be interested in seeing one.
Note in particular that UINT_MAX+1U is well defined, not an overflow.
In article <1100gbk$1lt8i$2@kst.eternal-september.org>,
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
cross@spitfire.i.gajendra.net (Dan Cross) writes:
[...]
The text of the standard explicitly carves this out; or, rather,[...]
it attempts to. If the result of an expression is not
representable in the target type, _regardless of whether that's
due to UB or not_, a diagnostic is required.
How would an expression (appearing in a context that requires an
integer constant expression) not "evaluate to a constant that is in
the range of representable values for its type" other than by UB?
It wouldn't. But because it's UB, it could evaluate to
anything, including something that didn't violate the
constraint.
I can't think of an example, but I'd be interested in seeing one.
In terms of a practical, working compiler? I doubt that one
exists.
and in fact
it *won't* occur during execution because foo() isn't called.
A compiler can't generate code with arbitrary behavior just because
it can't prove that there will be no UB. If it could, every signed
or floating-point arithmetic operation with unknown operand values
would grant the same permission.
But that's not the situation here. The situation is that the
compiler can prove that something _is_ UB.
In the program quoted at the top of this post, the UB occurs in
a function foo() that's never called. A compiler can replace the
body of foo() with a trap, and it can certainly warn about the UB,
but I don't believe it can reject the entire program. A clever
compiler could prove that the UB never occurs.
A naive compiler that performs no optimizations would generate
code for foo() that attempts to compute (INT_MAX+1)*0 step by
step, without recognizing the overflow, and that code would never
be executed.
foo()'s behavior would be undefined if it were called. It *isn't* >>>called, so there's no actual UB. The program does not violate any
of the other requirements for strict conformance.
I understand _what_ you're saying: despite the expression itself
manifesting undefined behavior, in this case it's not UB because
`foo` is never executed. What I'm saying is that I don't see
anything in the standard that restricts UB to _only_ executed
code. A reputable compiler obviously instruments `foo` with
code to trap into ubsan; if it's not UB, since it's not
executed, then why do so? Granted, that's not evidence of
anything other than the behavior of those compilers, but still.
Probably the compiler generated the trap code because it didn't
(yet?) know whether foo is ever called. If it were clever enough
to prove that foo is never called, it could generate no code for
it at all.
The note on the definition of undefined behavior is a bit vague.
It permits terminating a translation in response to UB, but that
doesn't address exactly when it can do so. I believe it can do so
only when it can prove that the UB always occurs, but that's not
clearly stated.
However, the behavior of the program as a whole is clearly defined.
It returns a status of 0 from main and does nothing else.
A conforming implementation *must* generate code that implements
that behavior.
Another argument (subject to interpretation of wording): Undefined
behavior is "behavior, **upon use** of a nonportable or erroneous
program construct or of erroneous data, for which this document
imposes no requirements". The overflowing expression within foo()
is never *used*, so there is no undefined behavior.
To put it another way, undefined behavior is behavior. Something
that never occurs is not behavior.
It is clearly the _intent_ that this be a strictly conforming
program. The C standard, as an imprecise, informal document,
cannot guarantee it.
If the usual "Hello, world" program prints "Hello, world" followed
by "Goodbye", the implementation is non-conforming. If it formats
my hard drive after printing "Goodbye", it's non-conforming and >>>>>dangerous.
Two separate things. My point earlier was that code can
obviously run after `main` terminates. Moreoever, I can't
imagine what would _prevent_ a runtime system that invokes
`main` from doing something like printing, "PROGRAM STOPPED"
after `main` returned. C imposes no requirements here.
Yes, it does. An OS can print "PROGRAM STOPPED", but not as part
of the execution of the program. On my system, a shell prompt is
printed after a program terminates, but not by the program. If I
execute a "hello, world" program with its output redirected to a file
(on a system that supports that), the resulting file cannot contain >>>"PROGRAM STOPPED". The requirements in 5.1.2.4 specify both what
the execution of a program must do and what it must not do.
Files are a separate case. There's no guarantee that the
standard output refers to a file; it may well refer to an
"interactive device", the semantics of which are (necessarily)
unspecified.
The requirements for "observable behavior" cover both files and
interactive devices.
Here's an example: consider an interactive user who uses a
screen reader device. Suppose that user makes use of an
implementation that includes runtime support for that device,
and that precedes invocation of `main` with a command sequence
causing the screen reader to (perhaps) change intonation; and
suceeds return from main by outputing another command sequence
that resets to the original state.
I do not see how C could prohibit that, assuming that the
implementation takes care to detect whether standard output
really refers to the screen reader, and does emit the control
sequences if output is redirected to a file. Another user who
runs that same program without a screen reader may see the
standard text printed on the screen, without the control
sequence sandwich.
I don't think a conforming implementation can prohibit that kind
of thing.
I agree. printf("hello, world\n") must write that string to standard
output, which may be a file or an interactive device. Just what
that means is unspecified or implementation-defined. It might be
printed in EBCDIC or incised into clay tablets. Closing stdout,
which occurs when main() terminates, might involve firing the tablet
or emitting control sequences for a screen reader.
[snip for size]
Given an excessively pedantic and literal reading of the text of
the standard, I don't think an implementation is explicitly
prohibited from evaluating the initializer at translation time,
deducing that the behavior is undefined, and blaming it on the
program, at which point, all bets are off.
An implementation can certainly evaluate the initializer at
translation time, deduce that the behavior would be undefined
*if the initializer were evaluated*, and blame it on the program.
That doesn't mean it can reject a strictly conforming program.
foo is a function. foo does not have undefined behavior; it has no >>>behavior at all. A *call* to foo during execution has undefined >>>behavior. (`foo;` is a statement-expression that does nothing;
it does not have undefined behavior.)
The _evaluation_ of that expression in `foo` has undefined
behavior. The standard does not say that it _cannot_ be
evaluated at translation time.
If a compiler sees a subexpression INT_MAX+1 it can attempt to
evaluate it at compile time. But it can't just blindly add the
values if overflow would cause a fatal trap, crashing the compiler.
That would be a serious compiler bug. The behavior *of the compiler*
is not undefined.
[SNIP]
I think the question of whether the initializer is a
constant-expression or not has caused some not entirely relevant >>>confusion.
Here's another example that avoids that issue.
#include <limits.h>
int foo(void) {
int zero;
zero = INT_MAX;
zero ++;
zero *= 0;
return zero;
}
int main(void) {
return 0;
}
Given my grammatical argument above, I would say that this program
has no constant expressions.
Agreed, if by "constant expressions" you mean those mandated to
use the `constant-expression` grammatical production.
Yes, that's what I mean by it.
Whether that argument is correct or
not, it certainly has no constant expressions that violate any
constraint or that have undefined behavior. Evaluating `zero ++`
(which doesn't even pretend to be a constant expression) would have >>>run-time undefined behavior -- *if* foo() were ever called.
Let me turn this around in two ways: suppose that the
translation unit _only_ included `foo`. Could the compiler
deduce that the behavior of `foo`, if called, is undefined? If
not, why not?
Certainly.
Second, suppose that `foo` _were_ called, could the compiler
replace this with a program that was the equivalent of,
`int main(void) {printf("check your nose"); abort();}`? If so
why? If not, why not?
Yes, if foo were called in every possible execution of the program,
the program's behavior would be undefined. The compiler could also
reject it.
And given this translation unit, I don't think there's any way to >>>construct a multi-TU program that calls foo, so a compiler *can* >>>determine that foo is never called (but there's no requirement to
do so, or to make any use of that information).
This is the crux of my point, as well. There's not requirement
for the translator to _not_ evaluate the expression and become
privy to UB.
I believe there is. The program is strictly conforming, which means,
among other things, that it does not produce output depending on any >undefined behavior. There is no undefined behavior because foo() is
never called.
A *strictly conforming program* shall use only those features of the
language and library specified in this document. It shall not
produce output dependent on any unspecified, undefined, or
implementation- defined behavior, and shall not exceed any minimum
implementation limit.
...
A *conforming hosted implementation* shall accept any strictly
conforming program.
An implementation that rejects the program quoted at the top of this
article is non-conforming.
Would it be stupid if a compiler did that? Yes. Do existing
compilers do so? No, not that I'm aware of. Would some dweeb
nerd compiler douche who thinks this would make a compiler
benchmark some microfraction of a percent faster take advantage
of that? I absolutely think so, yes.
And I'd submit a bug report.
In article <1100gbk$1lt8i$2@kst.eternal-september.org>,
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote: >>>cross@spitfire.i.gajendra.net (Dan Cross) writes:
[...]
The text of the standard explicitly carves this out; or, rather,[...]
it attempts to. If the result of an expression is not
representable in the target type, _regardless of whether that's
due to UB or not_, a diagnostic is required.
How would an expression (appearing in a context that requires an
integer constant expression) not "evaluate to a constant that is in
the range of representable values for its type" other than by UB?
It wouldn't. But because it's UB, it could evaluate to
anything, including something that didn't violate the
constraint.
I can't think of an example, but I'd be interested in seeing one.
In terms of a practical, working compiler? I doubt that one
exists.
I actually meant in terms of the standard, not of any particular
compiler.
I can't think of an example, but maybe someone else can.
[...]
Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:[...]
But (I'm reasonably sure that)
an implementation cannot reject a program just because it can't
prove that it has no undefined behavior during execution. [...]
Right.
In article <86bjdpayv0.fsf@linuxsc.com>,
Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:
Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
[snip]
But taking a closer look at the standard, I'm not 100% sure that the
language requires a diagnostic, though I think that's the intent.
The relevant constraint is:
Each constant expression shall evaluate to a constant that is
in the range of representable values for its type.
If I squint really hard, I can argue that the entire expression
has to be a constant expression, but it doesn't say that its
subexpressions are constant expressions -- and *if* INT_MAX +
1 evaluates to INT_MIN in the current implementation, then
(INT_MAX + 1) * 0 evaluates to 0 and therefore satisfies the
constraint.
My reasoning is as follows.
To determine if the constraint is satisfied, the compiler must
first evaluate the expression (INT_MAX + 1) * 0.
To evaluate the expression (INT_MAX + 1) * 0, the compiler must
first evaluate the sub-expression (INT_MAX + 1).
Because the expression (INT_MAX + 1) overflows, the behavior is
undefined, and the compiler is free to decide that the value of
the sub-expression (INT_MAX + 1) is, let's say, 12.
The compiler next evaluates the overall expression as 12*0, which
is 0 (an int).
This result of the overall expression satisfies the constraint,
and so the compiler is not obliged to generate a diagnostic.
The text of the standard explicitly carves this out; or, rather,
it attempts to. If the result of an expression is not
representable in the target type, _regardless of whether that's
due to UB or not_, a diagnostic is required.
But as it happens, I think I can see how your interpretation may
be valid: if, as a result of UB, the expression evaluates to "0"
(or 12 or something simiilar) that _is_ representable, then
there _is no constraint violation_ and so no diagnostic is
required.
I do not believe that that is the intent. But it _is_
conformant with the text of the standard.
This is a problem with the C standard: it is insufficiently
precise, as the semantics of the language are not formally
defined.
[snip]
I see no basis for this belief. My conclusions are based on what
the C standard actually says, rather than guesses about some
unstated "intentions". I think you would do well to reach your
conclusions based more on the actual text of the C standard, and
less on your interpretation of what the text was "intended" to
mean.
The same could be said to you, as well. There exists a reading
of the standard by which your `foo`-containing program is not
strictly conforming .
But that way lies madness; C is not a
formally specified language. Given that as an objective fact,
we must accept intent, consistency, and other "soft" aspects
when considering its definition.
cross@spitfire.i.gajendra.net (Dan Cross) writes:
[snip]
But as it happens, I think I can see how your interpretation may
be valid: if, as a result of UB, the expression evaluates to "0"
(or 12 or something simiilar) that _is_ representable, then
there _is no constraint violation_ and so no diagnostic is
required.
Right. In fact the reasoning doesn't have to be so elaborate.
Just by looking at the types of the operands, a compiler can
determine the result is type int. Then, just by noticing the
multiplication by 0, a compiler could decide that the result is
zero, because the compiler is free to assume that there was no
undefined behavior in the left-hand side expression. Whether the
left-hand size expression has undefined behavior doesn't even have
to be checked to decide that (INT_MAX+1)*0 is 0, and so it can
satisfy the constraints of an integer constant expression.
I do not believe that that is the intent. But it _is_
conformant with the text of the standard.
I think your intuition is leading you astray. The people who
wrote the C standard have gone to great lengths to say (write)
what they mean and mean what they say (write). I don't see any
evidence to suggest that this property doesn't apply in the
situation being discussed.
This is a problem with the C standard: it is insufficiently
precise, as the semantics of the language are not formally
defined.
On the contrary, the C standard is quite precise:
when a program
construct is encountered that the Standard identifies as having
undefined behavior, the Standard IMPOSES NO REQUIREMENTS on what
behavior may result. That rule may not be what someone wants, but
there isn't any question about what is allowed, which is anything
at all.
[snip]
I see no basis for this belief. My conclusions are based on what
the C standard actually says, rather than guesses about some
unstated "intentions". I think you would do well to reach your
conclusions based more on the actual text of the C standard, and
less on your interpretation of what the text was "intended" to
mean.
The same could be said to you, as well. There exists a reading
of the standard by which your `foo`-containing program is not
strictly conforming .
The difference is my reading is based on what the C standard
actually says, and not on any guesses about "intent" or whether
the result "makes sense". When reading the C standard it's
important to develop the habit of reading the text as neutrally as
one can, and not inject any subconscious ideas about what it ought
to be saying.
But that way lies madness; C is not a
formally specified language. Given that as an objective fact,
we must accept intent, consistency, and other "soft" aspects
when considering its definition.
The C standard is not written in formal mathematical language, but
it is written in formal English.
Certainly there are places in
the standard where what is said does a poor job of conveying what
is expected. But the particular case of (INT_MAX+1)*0 is not one
of them.
Tim Rentsch <tr.17687@z991.linuxsc.com> writes:
Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
Tim Rentsch <tr.17687@z991.linuxsc.com> writes:
Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
Note that in a context that requires a constant expression, overflow is >>>>> a constraint violation. For example, a case label like:
case (INT_MAX + 1) * 0:
must be diagnosed at compile time.
gcc disagrees with you.
What makes you think so?
[...]
I'm skipping this and proceeding on to the original question.
Why?
You made a statement, "gcc disagrees with you". I demonstrated,
in text that you snipped, that gcc does in fact agree with me.
You were wrong.
I don't know the basis of your error, so I asked.
Or maybe I'm missing something, and you had a valid point that I
didn't understand.
You're not required to answer my question, which I think was
an extremely reasonable one, but quoting it and then explicitly
refusing to answer it is pointlessly rude.
I'd like to know whether you still think you were right. If so,
I'd like to see your explanation. If not, an admission that you
made a mistake would be appreciated. But I expect neither from you.
[SNIP]
I see no basis for this belief. My conclusions are based on what
the C standard actually says, rather than guesses about some
unstated "intentions". I think you would do well to reach your
conclusions based more on the actual text of the C standard, and
less on your interpretation of what the text was "intended" to
mean.
The actual text of the standard implies that 42 is not an expression.
I rely on the obvious intent to conclude that it is.
Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
Tim Rentsch <tr.17687@z991.linuxsc.com> writes:
Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
Tim Rentsch <tr.17687@z991.linuxsc.com> writes:
Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
Note that in a context that requires a constant expression, overflow is >>>>>> a constraint violation. For example, a case label like:
case (INT_MAX + 1) * 0:
must be diagnosed at compile time.
gcc disagrees with you.
What makes you think so?
[...]
I'm skipping this and proceeding on to the original question.
Why?
gcc is not authoritative.
I didn't want to get into an argument
about whether gcc is conforming, or which version of gcc was used,
or any similar distractions.
The C standard /is/ authoritative,
and I thought it would save time to cut to the chase.
[snip]
I'd like to know whether you still think you were right. If so,
I'd like to see your explanation. If not, an admission that you
made a mistake would be appreciated. But I expect neither from you.
I'd like to know why you ignored my explanation, based directly on
text from the C standard, about why an implementation is allowed to
process the code in question, without giving a diagnostic, and
still be conforming. An explanation that Dan Cross agreed with,
even if he may not like the consequences.
In investigating this question, I have run compilations using
multiple versions of gcc, on two different platforms. I have looked >carefully through the gcc man page. I have also run compilations
using multiple versions of clang, on two different platforms. After
doing all that, I ran compilations using godbolt, so I could check
the latest, or maybe almost latest, versions of gcc and clang. All
the different versions of gcc and clang that I have tried support my >hypothesis that gcc (and now also clang) interpret the C standard so
as to conclude that conforming to the C standard need not require a >diagnostic for situations like the code under discussion..
I'd like to ask you to do two things. First, read through the
reasoning given in my previous post, try to assess whether that
reasoning is sound, and post the results of yours contemplations.
Second, look again at the question of whether gcc (and also clang,
if you're up to it) support the hypothesis that a conforming
implementation need not give a diagnostic for code like that under >discussion. See if you can find a way of framing the question that
supports my statement, rather than simply looking for one that
supports your preconceived ideas. Post the results of your
investigations, both what other experiments you tried, and what your >assessment is of the results you got.
Do these two things and I will endeavor to explain my views on the
questions you have raised here, if such explanations are still
needed after your further examinations and comments.
[SNIP]
I see no basis for this belief. My conclusions are based on what
the C standard actually says, rather than guesses about some
unstated "intentions". I think you would do well to reach your
conclusions based more on the actual text of the C standard, and
less on your interpretation of what the text was "intended" to
mean.
The actual text of the standard implies that 42 is not an expression.
I rely on the obvious intent to conclude that it is.
Now it is you who is changing the subject. Besides not being on
point to the question being considered, it's a silly argument, and I
would hope you are smart enough to realize that. However, if you do
what I have asked in the previous paragraph, I can try to explain
why I think your views on this unrelated matter are wrongheaded.
My example is this:
constexpr int A = ~0U;
The type of the rhs is `int` and the value is not representable
In article <1100g0e$1lt8i$1@kst.eternal-september.org>,[...]
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
A naive compiler that performs no optimizations would generate
code for foo() that attempts to compute (INT_MAX+1)*0 step by
step, without recognizing the overflow, and that code would never
be executed.
Sure. But a far more sophisticated translator (and I would
argue a nefarious one) could emulate that code, decide it was
UB, and immediately fail translation with an error.
It returns a status of 0 from main and does nothing else.
A conforming implementation *must* generate code that implements
that behavior.
I have yet to find or be shown a way in which the standard
actually guarantees that.
There was, once, a view that was almost universally shared that
UB was meant for things that could not be precisely described
because hardware was too varied. We're well past that; now it's
a vehicle for compiler writers to make benchmarks faster, but is
(generally) hostile to programmers. A lot of hay is made about
it in this group, but at the core, it's just (ironically) not
well-defined.
I agree. printf("hello, world\n") must write that string to standard
output, which may be a file or an interactive device. Just what
that means is unspecified or implementation-defined. It might be
printed in EBCDIC or incised into clay tablets. Closing stdout,
which occurs when main() terminates, might involve firing the tablet
or emitting control sequences for a screen reader.
Exactly. It could also emit the string, "GOODBYE WORLD."
This presupposes that the program is strictly conforming, but
in the limit, the standard can be interpreted in such a way that
if any statement in the program is proveably UB (as this one is)
then the program cannot said to be strictly conforming.
Ok, so in that case, would we say that "`foo` has undefined
behavior?" The qualification, "...if called" seems superfluous,
and I don't see anything in the standard that explicitly
disagrees.
UB can time-travel, however. Because it's undefined, the
compiler is free to assume that it never executes, or that it
always executes.
So any program that produces no output at all is strictly
conforming? Then what about this?
#include <limits.h>
int
zero(void)
{
return (INT_MAX + 1) * 0;
}
int
main(void)
{
(void)zero();
return 0;
}
This program produces no output, yet clearly executes a function
that contains an expression that induces undefined behavior when
evaluated. I suppose an argument could be made that it _might_
generate output due to UB, as UB imposes no requirements Not to
do so, so perhaps the _absence_ of output depends on UB.
In my ideal world, C would be rigorously defined with a precise
operational semantics. That would be accompanied by an
explanatory document that presented those semantics in lay
terms in prose, similar to the standard now, for those who did
not want to drive Coq or something similar. But at least we'd
have something definitive to define the language, so that when
there was apparent ambiguity, we had some objective metric by
which to judge. The C standard, as written, is nowhere close as
precise as it should be.
I do not think that this will ever happen: not only would it be
very difficult to produce (as you noted elsethread), I think the
compiler writers would rebel if they felt that their UB hands
were tied by a formal specification.
Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
Tim Rentsch <tr.17687@z991.linuxsc.com> writes:
Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
Tim Rentsch <tr.17687@z991.linuxsc.com> writes:
Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
Note that in a context that requires a constant expression, overflow is >>>>>> a constraint violation. For example, a case label like:
case (INT_MAX + 1) * 0:
must be diagnosed at compile time.
gcc disagrees with you.
What makes you think so?
[...]
I'm skipping this and proceeding on to the original question.
Why?
gcc is not authoritative. I didn't want to get into an argument
about whether gcc is conforming, or which version of gcc was used,
or any similar distractions. The C standard /is/ authoritative,
and I thought it would save time to cut to the chase.
You made a statement, "gcc disagrees with you". I demonstrated,
in text that you snipped, that gcc does in fact agree with me.
No, you didn't.
You were wrong.
No, I wasn't. Your testing was faulty.
I don't know the basis of your error, so I asked.
Or maybe I'm missing something, and you had a valid point that I
didn't understand.
I'm offended that you think I have an obligation to remedy your
habit of lazy thinking, especially when as here the answer was
staring you right in the face, and you simply ignored it.
You're not required to answer my question, which I think was
an extremely reasonable one, but quoting it and then explicitly
refusing to answer it is pointlessly rude.
I wasn't refusing to answer. What I was doing was trying to
answer the original question, and answer it in a way that wouldn't
get lost in pointless bickering. Silly me.
I'd like to know whether you still think you were right. If so,
I'd like to see your explanation. If not, an admission that you
made a mistake would be appreciated. But I expect neither from you.
I'd like to know why you ignored my explanation, based directly on
text from the C standard, about why an implementation is allowed to
process the code in question, without giving a diagnostic, and
still be conforming. An explanation that Dan Cross agreed with,
even if he may not like the consequences.
In investigating this question, I have run compilations using
multiple versions of gcc, on two different platforms. I have looked carefully through the gcc man page. I have also run compilations
using multiple versions of clang, on two different platforms. After
doing all that, I ran compilations using godbolt, so I could check
the latest, or maybe almost latest, versions of gcc and clang. All
the different versions of gcc and clang that I have tried support my hypothesis that gcc (and now also clang) interpret the C standard so
as to conclude that conforming to the C standard need not require a diagnostic for situations like the code under discussion..
I'd like to ask you to do two things. First, read through the
reasoning given in my previous post, try to assess whether that
reasoning is sound, and post the results of yours contemplations.
Second, look again at the question of whether gcc (and also clang,
if you're up to it) support the hypothesis that a conforming
implementation need not give a diagnostic for code like that under discussion. See if you can find a way of framing the question that
supports my statement, rather than simply looking for one that
supports your preconceived ideas. Post the results of your
investigations, both what other experiments you tried, and what your assessment is of the results you got.
Do these two things and I will endeavor to explain my views on the
questions you have raised here, if such explanations are still
needed after your further examinations and comments.
[SNIP]
I see no basis for this belief. My conclusions are based on what
the C standard actually says, rather than guesses about some
unstated "intentions". I think you would do well to reach your
conclusions based more on the actual text of the C standard, and
less on your interpretation of what the text was "intended" to
mean.
The actual text of the standard implies that 42 is not an expression.
I rely on the obvious intent to conclude that it is.
Now it is you who is changing the subject. Besides not being on
point to the question being considered, it's a silly argument, and I
would hope you are smart enough to realize that. However, if you do
what I have asked in the previous paragraph, I can try to explain
why I think your views on this unrelated matter are wrongheaded.
The actual text of the standard implies that 42 is not an expression.
I rely on the obvious intent to conclude that it is.
In article <1100g0e$1lt8i$1@kst.eternal-september.org>,[...]
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
A naive compiler that performs no optimizations would generate
code for foo() that attempts to compute (INT_MAX+1)*0 step by
step, without recognizing the overflow, and that code would never
be executed.
Sure. But a far more sophisticated translator (and I would
argue a nefarious one) could emulate that code, decide it was
UB, and immediately fail translation with an error.
I disagree. That's not a sensible interpretation of what the
standard says.
A call to a foo() would have undefined behavior if it occurred.
There
is no call to foo().
Similarly:
int a = ..., b = ...;
int c;
if (b != 0) {
c = a / b;
}
else {
c = 0;
}
A division by zero would have undefined behavior if it occurred,
but it never occurs. A compiler cannot reject the above code
because of UB that never happens.
[...]
It returns a status of 0 from main and does nothing else.
A conforming implementation *must* generate code that implements
that behavior.
I have yet to find or be shown a way in which the standard
actually guarantees that.
How does the standard guarantee *anything*?
This strictly conforming program:
int main(void) { return 0; }
when executed returns a status of 0 from main and does nothing else.
Adding an uncalled function to the same source file doesn't change
that.
[...]
There was, once, a view that was almost universally shared that
UB was meant for things that could not be precisely described
because hardware was too varied. We're well past that; now it's
a vehicle for compiler writers to make benchmarks faster, but is
(generally) hostile to programmers. A lot of hay is made about
it in this group, but at the core, it's just (ironically) not
well-defined.
The standard does say what UB is meant for. It says what UB
*is*, and what constructs lead to it (by omission in some cases).
Any optimization tricks played by compiler implementers must be
based on that specification.
[...]
I agree. printf("hello, world\n") must write that string to standard
output, which may be a file or an interactive device. Just what
that means is unspecified or implementation-defined. It might be
printed in EBCDIC or incised into clay tablets. Closing stdout,
which occurs when main() terminates, might involve firing the tablet
or emitting control sequences for a screen reader.
Exactly. It could also emit the string, "GOODBYE WORLD."
No, it couldn't. It must emit "hello, world\n" in some form.
It must emit the character 'h' as represented in the execution
character set, followed by 'e', and so on.
[...]
This presupposes that the program is strictly conforming, but
in the limit, the standard can be interpreted in such a way that
if any statement in the program is proveably UB (as this one is)
then the program cannot said to be strictly conforming.
It's not UB if it's never called. Behavior that doesn't happen is
not behavior.
I did not presuppose that the program is strictly conforming.
I read the source code and determined that it meets the standard's
definition of a strictly conforming program.
[...]
Ok, so in that case, would we say that "`foo` has undefined
behavior?" The qualification, "...if called" seems superfluous,
and I don't see anything in the standard that explicitly
disagrees.
The qualification "if called" is the whole point.
[...]
UB can time-travel, however. Because it's undefined, the
compiler is free to assume that it never executes, or that it
always executes.
"UB can time-travel" is perhaps an oversimplification.
An example is
a bug that occurred in the Linux kernel, something like:
void func(int *ptr) {
do_something_with(*ptr);
if (ptr != NULL) {
blah();
}
}
The compiler, on seeing the expression `*ptr`, assumed that `ptr` is
not null, and elided the test on the following line.
But even assuming that's valid, a compiler absolutely cannot assume that
an instance UB always executes when, according to the semantics of the >program, it provably never executes.
[...]
So any program that produces no output at all is strictly
conforming? Then what about this?
#include <limits.h>
int
zero(void)
{
return (INT_MAX + 1) * 0;
}
int
main(void)
{
(void)zero();
return 0;
}
That's an interesting point. A more terse example:
#include <limits.h>
int main(void) {
int unused = INT_MAX + 1;
}
This program produces no output, yet clearly executes a function
that contains an expression that induces undefined behavior when
evaluated. I suppose an argument could be made that it _might_
generate output due to UB, as UB imposes no requirements Not to
do so, so perhaps the _absence_ of output depends on UB.
The program clearly has undefined behavior when executed, but no
output depends on that undefined behavior. In my humble opinion,
this demonstrates a flaw in the standard's definition of "strictly
conforming program". (As a programmer: Don't do that.)
[...]
In my ideal world, C would be rigorously defined with a precise
operational semantics. That would be accompanied by an
explanatory document that presented those semantics in lay
terms in prose, similar to the standard now, for those who did
not want to drive Coq or something similar. But at least we'd
have something definitive to define the language, so that when
there was apparent ambiguity, we had some objective metric by
which to judge. The C standard, as written, is nowhere close as
precise as it should be.
I do not think that this will ever happen: not only would it be
very difficult to produce (as you noted elsethread), I think the
compiler writers would rebel if they felt that their UB hands
were tied by a formal specification.
"There are only two kinds of languages: the ones people complain
about and the ones nobody uses."
In article <1100g0e$1lt8i$1@kst.eternal-september.org>,
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
and in fact
it *won't* occur during execution because foo() isn't called.
A compiler can't generate code with arbitrary behavior just because
it can't prove that there will be no UB. If it could, every signed
or floating-point arithmetic operation with unknown operand values >>>>would grant the same permission.
But that's not the situation here. The situation is that the
compiler can prove that something _is_ UB.
In the program quoted at the top of this post, the UB occurs in
a function foo() that's never called. A compiler can replace the
body of foo() with a trap, and it can certainly warn about the UB,
but I don't believe it can reject the entire program. A clever
compiler could prove that the UB never occurs.
So there are two things that are at play here.
First, this notion that UB is _only_ a runtime matter. The text
of the standard contradicting that aside, if a translator can
detect that the behavior of a construct is provably undefined if
executed, then it seems axiomatic that UB is clearly something
that plays a role at translation time, as well.
Indeed, I would go so far as to suggest that _most_ instances of
UB are detected and used (by the translator) during translation.
So to say that, "this program doesn't have UB because the
statement that contains UB is never executed" doesn't make a lot
of sense to me. It would be closer to being correct if one said
"this program is unaffected by UB since the expression that has
UB is never evaluated when the program executes": again, in this
case (as, I suspect, in most cases) the UB simply _is_: the
expression `INT_MAX + 1` does not become well-defined just
because it is never executed.
Second, there's this notion that the standard is just
underspecified with respect to these matters, specifically, it
does not _prohibit_ a translation from implementing an emulator
for the abstract machine that evaluates code at translation
time. Indeed, I suspect that _most_ compilers do something
largely analogous to that; that's how they detect UB so that
they can take advantage of it when optimizing. But if that's
the case, then nothing prohibits them from relieving themselves
of their obligation to follow the standard once they observe
that some bit of code has UB.
A naive compiler that performs no optimizations would generate
code for foo() that attempts to compute (INT_MAX+1)*0 step by
step, without recognizing the overflow, and that code would never
be executed.
Sure. But a far more sophisticated translator (and I would
argue a nefarious one) could emulate that code, decide it was
UB, and immediately fail translation with an error.
Is it? I am unable to locate where the standard _actually says
that it is_. That is my whole point.
And yet the standard does not say that. That is an
interpretation; I assume it is universally shared, but if we
want to limit ourselves to what the standard _actually says_ it
is woefully underspecified in this regard.
There was, once, a view that was almost universally shared that
UB was meant for things that could not be precisely described
because hardware was too varied.
This is circular reasoning. You're saying that something that
is provably UB in this program cannot prevent that program from
being strictly confirming because the program is strictly
confirming.
This presupposes that the program is strictly conforming, but
in the limit, the standard can be interpreted in such a way that
if any statement in the program is proveably UB (as this one is)
then the program cannot said to be strictly conforming.
In my ideal world, C would be rigorously defined with a precise
operational semantics. That would be accompanied by an
explanatory document that presented those semantics in lay
terms in prose, similar to the standard now, for those who did
not want to drive Coq or something similar. But at least we'd
have something definitive to define the language, so that when
there was apparent ambiguity, we had some objective metric by
which to judge. The C standard, as written, is nowhere close as
precise as it should be.
I do not think that this will ever happen: not only would it be
very difficult to produce (as you noted elsethread), I think the
compiler writers would rebel if they felt that their UB hands
were tied by a formal specification.
In article <11075os$3fm4u$1@kst.eternal-september.org>,
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
cross@spitfire.i.gajendra.net (Dan Cross) writes:
In article <1100g0e$1lt8i$1@kst.eternal-september.org>,[...]
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
A naive compiler that performs no optimizations would generate
code for foo() that attempts to compute (INT_MAX+1)*0 step by
step, without recognizing the overflow, and that code would never
be executed.
Sure. But a far more sophisticated translator (and I would
argue a nefarious one) could emulate that code, decide it was
UB, and immediately fail translation with an error.
I disagree. That's not a sensible interpretation of what the
standard says.
I agree it's not sensible. But sadly, the standard does not
seem to explicitly prohibit it, either. This is the point: we
necessarily rely on a "reasonable interpretation" of the
standard to be able to usefully write C code. An adversarial
interpretation is not sensible, but it appears that such is
possible given the standard as written. This is a danger with a
language that is not formally specified.
In article <865x3yd21n.fsf@linuxsc.com>,
Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:
cross@spitfire.i.gajendra.net (Dan Cross) writes:
In article <86ik81cfk5.fsf_-_@linuxsc.com>,
Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:
Janis Papanagnou <janis_papanagnou+ng@hotmail.com> writes:
On 2026-06-01 00:54, Keith Thompson wrote:
[...]
Yes, a compiler can reduce (a + b) * 0 to just 0. But it's not
required to do so, and (INT_MAX + 1) * 0 still has undefined
behavior. Undefined behavior is determined by the rules of the
abstract machine *without* any adjustments permitted by the as-if
rule.
This is something I really don't get in the actual C-logic...
Using constants that can be determined at compile time is UB here,
despite the '* 0' mathematically indicating an IMO clear semantics,
but using variables is only UB possibly at runtime? [...]
There's an important distinction to make here. Consider this
program:
#include <limits.h>
int
foo(){
int zero = (INT_MAX+1)*0;
return zero;
}
int
main(){
return 0;
}
This program does not transgress the bounds of undefined behavior.
To clarify, the comments in my posting were meant to be read as
saying the given text is the entire program, and that it is strictly
conforming with respect to conforming hosted implementations.
(Incidentally, given the rules for freestanding implementations, I'm
not sure that it is even possible for any program to be strictly
conforming with respect to conforming freestanding implementations.
In any case my statements were meant only in the context of hosted
implementations.)
Ok.
[snip]
Perhaps you mean that this is irrelevant because `foo` is not
invoked, but I see no reason why that need be the case in e.g.
a freestanding environment.
I explained the context of my previous statements above. Sorry for
not saying that in the original message.
In a hosted environment, I don't
think anything explicitly prevents `foo` from being called after
`main` returns (though I can't imagine that would happen in real
life; it would be weird if it did).
The semantics described in the ISO C standard don't admit that
possibility.
Could you please point to where it says this, in the C standard?
I cannot find anything that says that arbitrary code cannot run
after `main()` returns, and I don't see how that could possibly
be true.
In article <86y0gp82pd.fsf@linuxsc.com>,
Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:
I'd like to know why you ignored my explanation, based directly on
text from the C standard, about why an implementation is allowed to
process the code in question, without giving a diagnostic, and
still be conforming. An explanation that Dan Cross agreed with,
even if he may not like the consequences.
I am mystified as to why you are bringing my name into this, and
why you think "I may not like the consequences", or even what
that means. In any event, you are evidently laboring under some
assumption about what I think about this matter that is probably
incorrect.
But as it happens, I think I can see how your interpretation may
be valid: if, as a result of UB, the expression evaluates to "0"
(or 12 or something simiilar) that _is_ representable, then
there _is no constraint violation_ and so no diagnostic is
required.
I do not believe that that is the intent. But it _is_
conformant with the text of the standard.
In article <11075os$3fm4u$1@kst.eternal-september.org>,
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote: >>>cross@spitfire.i.gajendra.net (Dan Cross) writes:
In article <1100g0e$1lt8i$1@kst.eternal-september.org>,[...]
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
A naive compiler that performs no optimizations would generate
code for foo() that attempts to compute (INT_MAX+1)*0 step by
step, without recognizing the overflow, and that code would never
be executed.
Sure. But a far more sophisticated translator (and I would
argue a nefarious one) could emulate that code, decide it was
UB, and immediately fail translation with an error.
I disagree. That's not a sensible interpretation of what the
standard says.
I agree it's not sensible. But sadly, the standard does not
seem to explicitly prohibit it, either. This is the point: we
necessarily rely on a "reasonable interpretation" of the
standard to be able to usefully write C code. An adversarial
interpretation is not sensible, but it appears that such is
possible given the standard as written. This is a danger with a
language that is not formally specified.
I started to compose a followup, but I found that I was mostly
repeating things I've already written.
I see no semantic difference between code in a function that's never
called and code that simply isn't in the program. Neither allows
an implementation to reject a strictly conforming program -- and
yes, the program we've been discussing is as strictly conforming as
`int main(void){}`.
There's nothing special about functions as units of a program
subject to undefined behavior. These two programs are semantically >equivalent:
void foo(void) { do_something(); }
int main(void) { foo(); }
and
int main(void) { do_something(); }
A simpler demonstration program might be:
#include <limits.h>
int main(void) {
return 0;
INT_MAX+1;
}
I assert that it is strictly conforming.
The permission for UB to result in terminating a translation
isn't even in normative text. It's in a non-normative note,
which in principle means that it should be derivable from the
normative text of the standard. (I'm not entirely sure it can be.)
It certainly doesn't override the requirement that a conforming
hosted implementation shall accept any strictly conforming program.
cross@spitfire.i.gajendra.net (Dan Cross) writes:
In article <86y0gp82pd.fsf@linuxsc.com>,
Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:
[...]
I'd like to know why you ignored my explanation, based directly on
text from the C standard, about why an implementation is allowed to
process the code in question, without giving a diagnostic, and
still be conforming. An explanation that Dan Cross agreed with,
even if he may not like the consequences.
I am mystified as to why you are bringing my name into this, and
why you think "I may not like the consequences", or even what
that means. In any event, you are evidently laboring under some
assumption about what I think about this matter that is probably
incorrect.
In a response to another posting of mine, you wrote this:
But as it happens, I think I can see how your interpretation may
be valid: if, as a result of UB, the expression evaluates to "0"
(or 12 or something simiilar) that _is_ representable, then
there _is no constraint violation_ and so no diagnostic is
required.
I do not believe that that is the intent. But it _is_
conformant with the text of the standard.
I based my statement that begins "An explanation that Dan Cross
agreed with, ..." on those two paragraphs.
cross@spitfire.i.gajendra.net (Dan Cross) writes:
[snip]
I cannot find anything that says that arbitrary code cannot run
after `main()` returns, and I don't see how that could possibly
be true.
The logic here is backwards. The C standard is prescriptive: it
says what _does_ happen, not what _doesn't_ happen.
If one wants
to establish that some "action" takes place, it is necessary to
find a passage, or passages, in the C standard that, if all are
taken together, shows that the "action" occurs, or at least that it
can occur.
The C standard doesn't need to say that, for example, a
function x() other than main(), whose name is never referenced,
will never be called. If someone wants to establish that x() could
be called, there needs to be a chain of reasoning going through the
semantic descriptions given in the C standard, to show that a call
to x() could occur.
If there is no such chain of reasoning, naming
the pertinent passages in the C standard, to establish a possible
call, then there is no possible call. In other words the burden of
proof for a claim that some action could occur rests on whoever is
making the claim; there is no need to look for something in the C
standard that says something cannot occur.
[...]
I've discussed this particular glitch before, but it's been a while.
N3220 6.5.1 says:
An *expression* is a sequence of operators and operands that
specifies computation of a value, or that designates an object
or a function, or that generates side effects, or that performs
a combination thereof.
I believe the wording is unchanged from C90 up to the latest C202y
draft. Since the word "expression" is in italics, this is the
standard's definition of the word.
This is a flawed definition. The terms "operator" and "operand"
are defined in 6.4.6:
*punctuator: one of
[ ] ( )
[snip]
A punctuator is a symbol that has independent syntactic and semantic
significance. Depending on context, it may specify an operation to
be performed (which in turn may yield a value or a function
designator, produce a side effect, or some combination thereof) in
which case it is known as an *operator* (other forms of operator also
exist in some contexts). An *operand* is an entity on which an
operator acts.
Consider this expression statement:
42;
Is `42` an expression? Clearly it's intended to be, but there is no operator, and therefore there is no operand, so it doesn't meet the standard's definition of the word "expression".
[...]
The fact that the standard's definition of "expression" is flawed is
not much of a problem in practice. Virtually everyone, implementers
and programmers, assumes the obvious intent. Nobody believes that
`42` isn't an expression. But it is my strongly held opinion that
the wording should be improved in a future edition of the standard.
I think it should say something to the effect that the meaning
of the term "expression" is defined by the grammar. The current
wording that claims to be the definition of the term could, with
a few tweaks, still be turned into a valid normative statement
*about* expressions.
I have a similar issue with the standard's definition of "value":
"precise meaning of the contents of an object when interpreted as
having a specific type". It's obvious that the result of evaluating
a non-void expression (such as the infamous `42`) is a "value",
but the definition implies that a "value" can only be the meaning
of the contents of an object. Nobody is actually misled by the
current definition, but it should be improved.
On 2026-06-08 23:05, Keith Thompson wrote:
[...]
I've discussed this particular glitch before, but it's been a while.
N3220 6.5.1 says:
An *expression* is a sequence of operators and operands that
specifies computation of a value, or that designates an object
or a function, or that generates side effects, or that performs
a combination thereof.
I believe the wording is unchanged from C90 up to the latest C202y
draft. Since the word "expression" is in italics, this is the
standard's definition of the word.
This is a flawed definition. The terms "operator" and "operand"
are defined in 6.4.6:
*punctuator: one of
[ ] ( )
[snip]
A punctuator is a symbol that has independent syntactic and semantic
significance. Depending on context, it may specify an operation to >> be performed (which in turn may yield a value or a function
designator, produce a side effect, or some combination thereof) in >> which case it is known as an *operator* (other forms of operator >> also
exist in some contexts). An *operand* is an entity on which an
operator acts.
Consider this expression statement:
42;
Is `42` an expression? Clearly it's intended to be, but there is no
operator, and therefore there is no operand, so it doesn't meet the
standard's definition of the word "expression".
Above you used the term "expression statement", and then compare the
"42" to an "expression".
I know from my earlier C-days that '42;' is a valid statement, and so
the term "expression statement" makes sense to me.
I know from various languages' syntax definitions that a number like
'42' is a sensible form for an expression (and no operators required).
It's also depending on the context. Where expressions may be written
(and where not) depends on the concrete language; syntactically and
also semantically.
Usually I'd expect above "expression-statement" to serve some purpose, semantically. I don't recall that in "C" such an expression-statement
would serve any purpose. (Or that they'd show any observable behavior,
if that term fits the C-parlance better?)
Or do these stand-alone values (the "expression-statement") have some practically useful semantics?
In other languages such stand-alone values serve a purpose; e.g. they
may determine the result value of a block that can then be used in an
outer context; but in "C" such constructs are obviously not possible.
What purpose serve such stand-alone numbers in places where statements
are expected?
[...]
Unfortunately, the C standard is simply not a precise, formal
document. This is well-known, and it's hardly C's fault: indeed
most of the applications of formalized descriptions of PL
semantics to practical programming languages postdates C's
invention; Dana Scott didn't introduce the term, "operational
semantics" until 1970, and it didn't start to make a serious
impact on languages until later.
[...]
On 09/06/2026 14:17, Janis Papanagnou wrote:
On 2026-06-08 23:05, Keith Thompson wrote:
[...]
I've discussed this particular glitch before, but it's been a while.
N3220 6.5.1 says:
An *expression* is a sequence of operators and operands that
specifies computation of a value, or that designates an object
or a function, or that generates side effects, or that performs >>> a combination thereof.
I believe the wording is unchanged from C90 up to the latest C202y
draft. Since the word "expression" is in italics, this is the
standard's definition of the word.
This is a flawed definition. The terms "operator" and "operand"
are defined in 6.4.6:
*punctuator: one of
[ ] ( )
[snip]
A punctuator is a symbol that has independent syntactic and
semantic
significance. Depending on context, it may specify an operation to >>> be performed (which in turn may yield a value or a function
designator, produce a side effect, or some combination thereof) in >>> which case it is known as an *operator* (other forms of operator >>> also
exist in some contexts). An *operand* is an entity on which an
operator acts.
Consider this expression statement:
42;
Is `42` an expression? Clearly it's intended to be, but there is no
operator, and therefore there is no operand, so it doesn't meet the
standard's definition of the word "expression".
Above you used the term "expression statement", and then compare the
"42" to an "expression".
I know from my earlier C-days that '42;' is a valid statement, and so
the term "expression statement" makes sense to me.
I know from various languages' syntax definitions that a number like
'42' is a sensible form for an expression (and no operators required).
It's also depending on the context. Where expressions may be written
(and where not) depends on the concrete language; syntactically and
also semantically.
Usually I'd expect above "expression-statement" to serve some purpose,
semantically. I don't recall that in "C" such an expression-statement
would serve any purpose. (Or that they'd show any observable behavior,
if that term fits the C-parlance better?)
Or do these stand-alone values (the "expression-statement") have some
practically useful semantics?
In other languages such stand-alone values serve a purpose; e.g. they
may determine the result value of a block that can then be used in an
outer context; but in "C" such constructs are obviously not possible.
What purpose serve such stand-alone numbers in places where statements
are expected?
I think it is just difficult for the syntax to ban certain expressons
and not others. How would you express that in the grammar?
If you ramp up the warnings, then you'll get messages like 'statement
with no effect' or 'computed value not used', since sometimes there are side-effects that are needed:
f() + g();
f() and g() both do something, but nothing is done with their sum.
[...]
On 2026-06-09 15:53, Bart wrote:
On 09/06/2026 14:17, Janis Papanagnou wrote:
On 2026-06-08 23:05, Keith Thompson wrote:
[...]
I've discussed this particular glitch before, but it's been a while.
N3220 6.5.1 says:
An *expression* is a sequence of operators and operands that
specifies computation of a value, or that designates an object >>>> or a function, or that generates side effects, or that performs >>>> a combination thereof.
I believe the wording is unchanged from C90 up to the latest C202y
draft. Since the word "expression" is in italics, this is the
standard's definition of the word.
This is a flawed definition. The terms "operator" and "operand"
are defined in 6.4.6:
*punctuator: one of
[ ] ( )
[snip]
A punctuator is a symbol that has independent syntactic and
semantic
significance. Depending on context, it may specify an operation to
be performed (which in turn may yield a value or a function
designator, produce a side effect, or some combination thereof) in
which case it is known as an *operator* (other forms of
operator also
exist in some contexts). An *operand* is an entity on which an >>>> operator acts.
Consider this expression statement:
42;
Is `42` an expression? Clearly it's intended to be, but there is no
operator, and therefore there is no operand, so it doesn't meet the
standard's definition of the word "expression".
Above you used the term "expression statement", and then compare the
"42" to an "expression".
I know from my earlier C-days that '42;' is a valid statement, and so
the term "expression statement" makes sense to me.
I know from various languages' syntax definitions that a number like
'42' is a sensible form for an expression (and no operators required).
It's also depending on the context. Where expressions may be written
(and where not) depends on the concrete language; syntactically and
also semantically.
Usually I'd expect above "expression-statement" to serve some purpose,
semantically. I don't recall that in "C" such an expression-statement
would serve any purpose. (Or that they'd show any observable behavior,
if that term fits the C-parlance better?)
Or do these stand-alone values (the "expression-statement") have some
practically useful semantics?
In other languages such stand-alone values serve a purpose; e.g. they
may determine the result value of a block that can then be used in an
outer context; but in "C" such constructs are obviously not possible.
What purpose serve such stand-alone numbers in places where statements
are expected?
I think it is just difficult for the syntax to ban certain expressons
and not others. How would you express that in the grammar?
Well, I'd do that as it's done in other languages.
Define _statements_ and define _expressions_.
And defined expressions
in contexts where a sensible operational semantics can be defined (as
in mathematical formulas, actual function parameter lists, etc.), but
not in places where statements are expected.
If you ramp up the warnings, then you'll get messages like 'statement
with no effect' or 'computed value not used', since sometimes there
are side-effects that are needed:
f() + g();
f() and g() both do something, but nothing is done with their sum.
Right. And I wouldn't allow a mathematical formula where the results
are calculated but not used, here an expression, as a statement.
But your example may indeed lead to the actual answer to my question;
when writing just
f();
There's no distinction of procedures and functions in "C". One cannot
tell whether that f() is a "procedure" (i.e. a function with no return
value, or one with return value but the call just relying on the side effects). In "C" any value of f() just gets discarded in this context.
That of course doesn't mean that it could be handled by the compilers
and sensibly defined by the language, depending on how f() is actually defined. After all, 'f();' is not the same case as '42;'.
But okay, we're talking about "C" here - so own design preferences are
anyway irrelevant here.
Janis
[...]
f() + g();
f() and g() both do something, but nothing is done with their sum.
On 6/9/26 15:53, Bart wrote:
f() + g();
f() and g() both do something, but nothing is done with their sum.
I've just one question : why did you waste your life time
with a lot of non-sense questions ?
In article <1107rk3$3ldg4$1@kst.eternal-september.org>,[...]
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
The permission for UB to result in terminating a translation
isn't even in normative text. It's in a non-normative note,
which in principle means that it should be derivable from the
normative text of the standard. (I'm not entirely sure it can be.)
That specific instance is not, no; that's in a note as you point
out. I believe deriving it from the normative text is based on
UB imposing no requirement at all on the implementation.
It certainly doesn't override the requirement that a conforming
hosted implementation shall accept any strictly conforming program.
...assuming the program is strictly conforming.
I have arrived at the same place you are with your "42 is not an--
expression" example. The wording of the standard could be
improved to avoid things like this.
Actually, no, a reference to a function is not necessary. A[...]
couple of years ago, a well-publicized issue in a C++ compiler a
couple of years ago was something along the lines of this:
```
#include <stdio.h>
void foo(void);
int
main(void)
{
for (;;);
}
void
foo(void)
{
printf("never called\n");
}
```
The result of which, when run, was to print the text "never
called" and exit. That compiler was conformant with the text
of the standard.
Above you used the term "expression statement", and then compare the
"42" to an "expression".
I know from my earlier C-days that '42;' is a valid statement, and so
the term "expression statement" makes sense to me.
Dan Cross <cross@spitfire.i.gajendra.net> wrote:...
In article <1100g0e$1lt8i$1@kst.eternal-september.org>,
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
In the program quoted at the top of this post, the UB occurs in
a function foo() that's never called. A compiler can replace the
body of foo() with a trap, and it can certainly warn about the UB,
but I don't believe it can reject the entire program. A clever
compiler could prove that the UB never occurs.
So there are two things that are at play here.
First, this notion that UB is _only_ a runtime matter. The text
of the standard contradicting that aside, if a translator can
detect that the behavior of a construct is provably undefined if
executed, then it seems axiomatic that UB is clearly something
that plays a role at translation time, as well.
"42" is an expression of type "int", and so is 'printf("Hello\n")'.[...]
How (and why) would a language distinguish between them and allow one
but not the other?
The committee has decided otherwise. The committee's resolution to DR
109 said:
"A conforming implementation must not fail to translate a strictly
conforming program simply because some possible execution of that
program would result in undefined behavior. Because foo might never be called, the example given must be successfully translated by a
conforming implementation."
David Brown <david.brown@hesbynett.no> writes:
[...]
"42" is an expression of type "int", and so is 'printf("Hello\n")'.[...]
How (and why) would a language distinguish between them and allow one
but not the other?
Ada, Pascal, and similar languages do exactly this, for what many
people consider to be good reasons.
In both languages, functions and procedures are distinct. Functions
return values; procedures do not. An expression cannot be turned
into a statement just by adding a semicolon. A function call is
an expression. A procedure call is a statement, not an expression.
An assignment is a statement, not an expression.
For I/O, the equivalent of printf is a procedure. In C,
printf("Hello, world\n") returns a negative result to denote an
error (and that value is often ignored). In Ada, an error in the
equivalent Put_Line("Hello, world") raises an exception, which
can't easily be ignored.
Both approaches are valid.
On 10/06/2026 00:34, Keith Thompson wrote:
David Brown <david.brown@hesbynett.no> writes:
[...]
"42" is an expression of type "int", and so is 'printf("Hello\n")'.[...]
How (and why) would a language distinguish between them and allow one
but not the other?
Ada, Pascal, and similar languages do exactly this, for what many
people consider to be good reasons.
I don't know enough about Ada to be sure, but Pascal does not do this -
see below.
In both languages, functions and procedures are distinct. Functions
return values; procedures do not. An expression cannot be turned
into a statement just by adding a semicolon. A function call is
an expression. A procedure call is a statement, not an expression.
An assignment is a statement, not an expression.
Sure. But the key factor there is that "printf", or its equivalent
(such as "writeln", if I remember my Pascal correctly - it's been a
while) are /procedures/. A "print" function in Pascal that returned the number of characters printed would be a function, used in an expression,
not a procedure used in a statement.
The rough equivalent of the distinction between Pascal procedures and functions is that procedures are like C functions that have "void"
return type. It's fine (and not at all a bad idea) for a language to distinguish between void and non-void like this. What cannot easily be done in a clear and consistent way is to distinguish between two
expressions of type "int" (or any other general non-void type).
In C, an expression statement "expr;" causes the expression to be
evaluated as a void expression for its side effects (§6.8.4p2).
You
can, arguably, say that C also requires all statements to be of "void"
type, just like Pascal - but the cast-to-void is done implicitly to
treat "expr;" as "(void) expr;".
For I/O, the equivalent of printf is a procedure. In C,
printf("Hello, world\n") returns a negative result to denote an
error (and that value is often ignored). In Ada, an error in the
equivalent Put_Line("Hello, world") raises an exception, which
can't easily be ignored.
Both approaches are valid.
Indeed they are.
It is also fine for a language to distinguish between "pure" functions
and functions/procedures with side-effects and/or functions/procedures
with observable behaviour. (A "pure procedure" would not do anything.)
As far as I remember, Pascal does not make that distinction.
On 10/06/2026 00:34, Keith Thompson wrote:
David Brown <david.brown@hesbynett.no> writes:
[...]
"42" is an expression of type "int", and so is 'printf("Hello\n")'.[...]
How (and why) would a language distinguish between them and allow one
but not the other?
Ada, Pascal, and similar languages do exactly this, for what many
people consider to be good reasons.
I don't know enough about Ada to be sure, but Pascal does not do this
- see below.
In both languages, functions and procedures are distinct. Functions
return values; procedures do not. An expression cannot be turned
into a statement just by adding a semicolon. A function call is
an expression. A procedure call is a statement, not an expression.
An assignment is a statement, not an expression.
Sure. But the key factor there is that "printf", or its equivalent
(such as "writeln", if I remember my Pascal correctly - it's been a
while) are /procedures/. A "print" function in Pascal that returned
the number of characters printed would be a function, used in an
expression, not a procedure used in a statement.
The rough equivalent of the distinction between Pascal procedures and functions is that procedures are like C functions that have "void"
return type. It's fine (and not at all a bad idea) for a language to distinguish between void and non-void like this. What cannot easily
be done in a clear and consistent way is to distinguish between two expressions of type "int" (or any other general non-void type).
On 10/06/2026 08:04, David Brown wrote:
On 10/06/2026 00:34, Keith Thompson wrote:
David Brown <david.brown@hesbynett.no> writes:
[...]
"42" is an expression of type "int", and so is 'printf("Hello\n")'.[...]
How (and why) would a language distinguish between them and allow one
but not the other?
Ada, Pascal, and similar languages do exactly this, for what many
people consider to be good reasons.
I don't know enough about Ada to be sure, but Pascal does not do this
- see below.
In both languages, functions and procedures are distinct. Functions
return values; procedures do not. An expression cannot be turned
into a statement just by adding a semicolon. A function call is
an expression. A procedure call is a statement, not an expression.
An assignment is a statement, not an expression.
Sure. But the key factor there is that "printf", or its equivalent
(such as "writeln", if I remember my Pascal correctly - it's been a
while) are /procedures/. A "print" function in Pascal that returned
the number of characters printed would be a function, used in an
expression, not a procedure used in a statement.
The rough equivalent of the distinction between Pascal procedures and
functions is that procedures are like C functions that have "void"
return type. It's fine (and not at all a bad idea) for a language to
distinguish between void and non-void like this. What cannot easily
be done in a clear and consistent way is to distinguish between two
expressions of type "int" (or any other general non-void type).
In C, an expression statement "expr;" causes the expression to be
evaluated as a void expression for its side effects (§6.8.4p2).
In C201x draft. 6.8.4p2 is about selection statements.
You can, arguably, say that C also requires all statements to be of
"void" type, just like Pascal - but the cast-to-void is done
implicitly to treat "expr;" as "(void) expr;".
That's not quite the same thing. If I write:
int a;
a;
then gcc -Wall will report a warning. But write it as (void)a, then it doesn't.
While this is awkward to express in a language's grammar, it can choose
to list the kinds of expressions that /are/ allowed to be statements,
rather than leave it to the whim of an implemenation. (The ones that
aren't allowed would be a much bigger, unlimited set.)
For example:
E(...); // function call
++E; // increment
E = E; // assigment (and compound assignment)
E is any expression term. Here, the call/increment/assignment is the top-level AST mode.
(I do this in my stuff, and there I can override the restriction using 'eval': eval a + b, which turns it into an allowed form.
Mainly this is for convenience of testing, but it was also used to
ensure an expression ended up in the primary register for subsequent
inline assembly.)
For I/O, the equivalent of printf is a procedure. In C,
printf("Hello, world\n") returns a negative result to denote an
error (and that value is often ignored). In Ada, an error in the
equivalent Put_Line("Hello, world") raises an exception, which
can't easily be ignored.
Both approaches are valid.
Indeed they are.
Distinguishing between function and procedure is incredibly rare in
modern languages. There the preoccupation seems to be to unify
everything: everything is a function, even if-statements and loops.
Every function is a closure, etc. I do not consider that useful.
It is also fine for a language to distinguish between "pure" functions
and functions/procedures with side-effects and/or functions/procedures
with observable behaviour. (A "pure procedure" would not do
anything.) As far as I remember, Pascal does not make that distinction.
This goes the other way and is a better idea!
David Brown <david.brown@hesbynett.no> writes:
On 10/06/2026 00:34, Keith Thompson wrote:
David Brown <david.brown@hesbynett.no> writes:
[...]
"42" is an expression of type "int", and so is 'printf("Hello\n")'.[...]
How (and why) would a language distinguish between them and allow one
but not the other?
Ada, Pascal, and similar languages do exactly this, for what many
people consider to be good reasons.
I don't know enough about Ada to be sure, but Pascal does not do this
- see below.
You seem to disagree with me, but then you describe most of what
I wrote. I'm not sure where you disagree, or where our signals
got crossed.
Ada and Pascal don't have expression statements. The Pascal
(writeln(...)) and Ada (Put_Line(...)) constructs most similar
to C's printf("Hello\n") are procedure calls. 42 can't made into
a statement by adding a semicolon. Neither can any function call.
But a procedure call can. That's how and why Pascal and Ada allow
one but not the other. (And both languages deliberately make it
awkward to ignore the value returned by a function.)
In both languages, functions and procedures are distinct. Functions
return values; procedures do not. An expression cannot be turned
into a statement just by adding a semicolon. A function call is
an expression. A procedure call is a statement, not an expression.
An assignment is a statement, not an expression.
Sure. But the key factor there is that "printf", or its equivalent
(such as "writeln", if I remember my Pascal correctly - it's been a
while) are /procedures/. A "print" function in Pascal that returned
the number of characters printed would be a function, used in an
expression, not a procedure used in a statement.
Right, and a Pascal function that prints its argument and returns an
integer value could not be used by itself as a statement.
The rough equivalent of the distinction between Pascal procedures and
functions is that procedures are like C functions that have "void"
return type. It's fine (and not at all a bad idea) for a language to
distinguish between void and non-void like this. What cannot easily
be done in a clear and consistent way is to distinguish between two
expressions of type "int" (or any other general non-void type).
Right. Which is why the I/O and similar subroutines that you'd want to
use as statements are procedures, not functions.
James Kuyper <jameskuyper@alumni.caltech.edu> writes:
[...]
The committee has decided otherwise. The committee's resolution to DR
109 said:
"A conforming implementation must not fail to translate a strictly
conforming program simply because some possible execution of that
program would result in undefined behavior. Because foo might never be
called, the example given must be successfully translated by a
conforming implementation."
https://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_109.html
[...]
[...]
Actually, no, a reference to a function is not necessary. A[...]
couple of years ago, a well-publicized issue in a C++ compiler a
couple of years ago was something along the lines of this:
```
#include <stdio.h>
void foo(void);
int
main(void)
{
for (;;);
}
void
foo(void)
{
printf("never called\n");
}
```
The result of which, when run, was to print the text "never
called" and exit. That compiler was conformant with the text
of the standard.
That doesn't make sense to me. Do you have a citation to this incident,
and is it relevant to C?
There is a special rule in C about implementations being allowed
to assume that an infinite loop terminates (N3220 6.8.6.1p4),
but (a) it wouldn't apply to this case, and (b) even if it did,
it wouldn't imply that an implicit call to foo would be permitted.
I can imagine an argument that the program has undefined behavior
and therefore it could print "never called" or "nasal demons",
but I'd have to see the argument.
In article <110a34q$b2kq$2@kst.eternal-september.org>,
[snip]
Here's a C version with the same behavior:
```
term% cat weird.c
#include <stdio.h>
int
main(void)
{
for (unsigned int k = 0; k != 1; k += 2)
;
return 0;
}
void
hello(void)
{
printf("Hello, World!\n");
}
term% clang --version
clang version 22.1.6
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
term% clang -Wall -pedantic -O1 -std=c23 -o weird weird.c
term% ./weird
Hello, World!
term%
```
In C, an expression statement "expr;" causes the expression to be[...]
evaluated as a void expression for its side effects (§6.8.4p2). You
can, arguably, say that C also requires all statements to be of "void"
type, just like Pascal - but the cast-to-void is done implicitly to
treat "expr;" as "(void) expr;".
In article <110a34q$b2kq$2@kst.eternal-september.org>,
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
cross@spitfire.i.gajendra.net (Dan Cross) writes:
[...]
Actually, no, a reference to a function is not necessary. A[...]
couple of years ago, a well-publicized issue in a C++ compiler a
couple of years ago was something along the lines of this:
```
#include <stdio.h>
void foo(void);
int
main(void)
{
for (;;);
}
void
foo(void)
{
printf("never called\n");
}
```
The result of which, when run, was to print the text "never
called" and exit. That compiler was conformant with the text
of the standard.
That doesn't make sense to me. Do you have a citation to this incident,
Yes: https://godbolt.org/z/d1WP4KP99
There was such an outcry when this was discovered that the C++
standard was modified to add a note explicitly allowing,
"trivial infinite loops, which cannot be removed or reordered." https://eel.is/c++draft/intro.progress
That change is commit 29fcc1c1fab7277d96bbd2ccd37b0c14dfd75a0e (https://github.com/cplusplus/draft/commit/29fcc1c1fab7277d96bbd2ccd37b0c14dfd75a0e)
in response to P2809: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p2809r3.html
and is it relevant to C?
Here's a C version with the same behavior:
```
term% cat weird.c
#include <stdio.h>
int
main(void)
{
for (unsigned int k = 0; k != 1; k += 2)
;
return 0;
}
void
hello(void)
{
printf("Hello, World!\n");
}
term% clang --version
clang version 22.1.6
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
term% clang -Wall -pedantic -O1 -std=c23 -o weird weird.c
term% ./weird
Hello, World!
term%
```
There is a special rule in C about implementations being allowed
to assume that an infinite loop terminates (N3220 6.8.6.1p4),
The program above meets the criteria in sec 6.8.6.1 para 4 that
allows an implementation to assume that the loop terminates.
Godbolt link: https://godbolt.org/z/q46o5cYGM
but (a) it wouldn't apply to this case, and (b) even if it did,
it wouldn't imply that an implicit call to foo would be permitted.
I can imagine an argument that the program has undefined behavior
and therefore it could print "never called" or "nasal demons",
but I'd have to see the argument.
Regehr aluded to this with his taxonomy of undefined functions.
For a function that is always undefined (a "Type 3" function), a
compiler is under no obligation to even produce a return
instruction for it, and the behavior of a call to such a
function is totally undefined. Nothing stops it from cascading
into whatever the linker happens to put after it.
Therefore, given UB, it is not necessary to have a reference to
some function in a program's source text in order for it to be
executed.
Replying to myself here, but...this is another example of weird[SNIP]
behavior:
```
term% cat boo.c
#include <limits.h>
int
monstartup(void)
{
return INT_MAX + 1;
}
int
main(void)
{
return 0;
}
(I admit that I am cheating a bit, but I claim that this program
is strictly conforming.)
In article <86tsrc8d0b.fsf@linuxsc.com>,
Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:
[...]
The C standard doesn't need to say that, for example, a
function x() other than main(), whose name is never referenced,
will never be called. If someone wants to establish that x() could
be called, there needs to be a chain of reasoning going through the
semantic descriptions given in the C standard, to show that a call
to x() could occur.
Actually, no, a reference to a function is not necessary. A
couple of years ago, a well-publicized issue in a C++ compiler a
couple of years ago was something along the lines of this:
[...]
cross@spitfire.i.gajendra.net (Dan Cross) writes:
In article <86tsrc8d0b.fsf@linuxsc.com>,
Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:
[...]
The C standard doesn't need to say that, for example, a
function x() other than main(), whose name is never referenced,
will never be called. If someone wants to establish that x() could
be called, there needs to be a chain of reasoning going through the
semantic descriptions given in the C standard, to show that a call
to x() could occur.
Actually, no, a reference to a function is not necessary. A
couple of years ago, a well-publicized issue in a C++ compiler a
couple of years ago was something along the lines of this:
[...]
This is comp.lang.c. My comments were only about C, and not
about C++. But of course you already knew that.
I see you did not read the other messages in the (sub)thread,
but ok, here it is again, in C:
```
term% cat what.c
#include <stdio.h>
int main(void) { for (unsigned int k = 0; k != 1; k += 2); return 0; }
void hello(void) { printf("Hello, World!\n"); }
term% clang --version | sed 1q
clang version 22.1.6
term% clang -Wall -pedantic -pedantic-errors -O1 -std=c23 -o what what.c what.c:2:58: warning: for loop has empty body [-Wempty-body]
2 | int main(void) { for (unsigned int k = 0; k != 1; k += 2); return 0; }
| ^ what.c:2:58: note: put the semicolon on a separate line to silence this warning
1 warning generated.
term% ./what
Hello, World!
term%
```
[...]
Replying to myself here, but...this is another example of weird[SNIP]
behavior:
```
term% cat boo.c
#include <limits.h>
int
monstartup(void)
{
return INT_MAX + 1;
}
int
main(void)
{
return 0;
}
(I admit that I am cheating a bit, but I claim that this program
is strictly conforming.)
I agree that the program is strictly conforming.
I don't know the details, but I think "monstartup" is a special name,
and that the program would behave as expected if a different name
were used. Since "monstartup" is not reserved, an implementation
that visibly treats it specially is not conforming.
Right. ("for (;;);" in the original program does not.)
Note that the C++ special rule applies only when the condition is
equivalent to a constant `true` and the body of the loop is empty.
An implementation can "assume" that any other loop will eventually
finish.
The rule in C is (6.8.6.1p4):
An iteration statement may be assumed by the implementation
to terminate if its controlling expression is not a constant
expression, and none of the following operations are performed
in its body, controlling expression or (in the case of a for
statement) its expression-3
— input/output operations
— accessing a volatile object
— synchronization or atomic operations.
`for (;;)` is treated as having a constant controlling expression.
This covers more cases than the C++ rule.
I dislike it for most of the same reasonss. It should be phrased
in terms of the permitted behavior of a program, not what an
implementation is allowed to "assume".
In addition to that, I dislike the whole idea. I think it's
intended to enable optimizations, but it means that for this
contrived program:
#include <stdio.h>
int main(void) {
bool keep_going = true;
while (keep_going) {
keep_going = true;
}
puts("never reached");
}
the implementation is allowed to "assume" that the loop eventually terminates. It's not clear what permissions the implementation is being given if the assumption is violated. I think the program could legally
print "never reached", but if violating the assumption implies undefined behavior it could do anything.
A programmer could easily write a program similar to the above
and think that the meaning is perfectly clear, have it behave very differently because of one obscure subclause in the standard.
David Brown <david.brown@hesbynett.no> writes:
[...]
In C, an expression statement "expr;" causes the expression to be[...]
evaluated as a void expression for its side effects (§6.8.4p2). You
can, arguably, say that C also requires all statements to be of "void"
type, just like Pascal - but the cast-to-void is done implicitly to
treat "expr;" as "(void) expr;".
In an expression statement, the expression is "evaluated as a void
expression for its side effects". I think that's equivalent to
convert (not casting!) it to void, but the standard doesn't describe
it that way.
6.3.2.2: "If an expression of any other type [other than void]
is evaluated as a void expression, its value or designator is
discarded."
But statements have no type.
On 10/06/2026 23:47, Keith Thompson wrote:
Right. ("for (;;);" in the original program does not.)
Note that the C++ special rule applies only when the condition is
equivalent to a constant `true` and the body of the loop is empty.
An implementation can "assume" that any other loop will eventually
finish.
The rule in C is (6.8.6.1p4):
An iteration statement may be assumed by the implementation
to terminate if its controlling expression is not a constant
expression, and none of the following operations are performed
in its body, controlling expression or (in the case of a for
statement) its expression-3
— input/output operations
— accessing a volatile object
— synchronization or atomic operations.
`for (;;)` is treated as having a constant controlling expression.
This covers more cases than the C++ rule.
I dislike it for most of the same reasonss. It should be phrased
in terms of the permitted behavior of a program, not what an
implementation is allowed to "assume".
In addition to that, I dislike the whole idea. I think it's
intended to enable optimizations, but it means that for this
contrived program:
#include <stdio.h>
int main(void) {
bool keep_going = true;
while (keep_going) {
keep_going = true;
}
puts("never reached");
}
the implementation is allowed to "assume" that the loop eventually
terminates. It's not clear what permissions the implementation is being
given if the assumption is violated. I think the program could legally
print "never reached", but if violating the assumption implies undefined
behavior it could do anything.
A programmer could easily write a program similar to the above
and think that the meaning is perfectly clear, have it behave very
differently because of one obscure subclause in the standard.
The idea of all this is given in a footnote in the C standards - "This
is intended to allow compiler transformations such as removal of empty
loops even when termination cannot be proven."
The loop might originally have contained source code, but become empty >through pre-processing, or from other compiler transformations (such as
the compiler seeing that the "keep_going" variable is not volatile and
its value is never used, so assignments to it can be elided, or moving
other things outside the loop body).
A programmer /could/ write the "keep_going" loop you gave, and
mistakenly believe it to be infinite. But is it likely? In my
experience, infinite loops are generally very clearly written - either
as "for (;;)" loops or "while (true)" loops - or they are the result of
bugs in the code that accidentally run forever. If the loop is
accidentally infinite, the programmer will already be expecting it to
run the code after the loop.
Equally, I don't think it is likely that compilers will often be able to
use this rule to improve code generation - it would only help in a
situation where the loop's controlling expression is too complicated for
the compiler to be sure that it will terminate, but where the loop body
ends up effectively empty. I doubt if that turns up often in real code >either.
So while I agree that this kind of thing can lead to curiosities and >behaviour that seems counter-intuitive, and is popular with the "modern >compilers are evil" crowd, I really do not see it as an issue in
practice. There are many other mistakes programmers can make, or UB
that they hit accidentally - this is a drop in the ocean IMHO.
[...]
I see you did not read the other messages in the (sub)thread,
but ok, here it is again, in C:
```
term% cat what.c
#include <stdio.h>
int main(void) { for (unsigned int k = 0; k != 1; k += 2); return 0; }
void hello(void) { printf("Hello, World!\n"); }
term% clang --version | sed 1q
clang version 22.1.6
term% clang -Wall -pedantic -pedantic-errors -O1 -std=c23 -o what what.c
what.c:2:58: warning: for loop has empty body [-Wempty-body]
2 | int main(void) { for (unsigned int k = 0; k != 1; k += 2); return 0; }
| ^
what.c:2:58: note: put the semicolon on a separate line to silence this warning
1 warning generated.
term% ./what
Hello, World!
term%
```
I see the same behavior.
The following largely repeats what I've written previously in
this thread.
Apparently the authors of clang decided that this statement in N3220 >6.8.6.p4:
An iteration statement may be assumed by the implementation to
terminate if its controlling expression is not a constant
expression, ...
means that a program that violates that assumption has undefined
behavior. I intensely dislike both the rule and the way it's stated,
but I agree that the conclusion that the behavior is undefined is
a reasonable one.
Of course since the behavior is undefined, *anything* could happen.
I don't know what happened inside clang (or the minds of its
maintainers) that caused it to generate code that executes a
statement in the body of a function that's never called, but that's
just one of the infinitely many allowed behaviors. A quick look at the >generated code indicates that there's no x86-64 "retq" instruction
for either main() or hello(), and apparently control falls through
from the end of main() to the body of hello(). That seems weird.
0$, it applies the rules of sec 6.8.6 para 4, assumes thatthe loop must terminate, and therefore can be removed, and
It might just be a bug (but not one that, as far as I can tell,
violates the C standard).
A function whose body contains a construct that would have undefined
behavior if the function were called (not the case here) does not
cause undefined behavior if there are no calls to the function.
In article <110dm6p$17r3s$1@dont-email.me>,
David Brown <david.brown@hesbynett.no> wrote:
On 10/06/2026 23:47, Keith Thompson wrote:
Right. ("for (;;);" in the original program does not.)
Note that the C++ special rule applies only when the condition is
equivalent to a constant `true` and the body of the loop is empty.
An implementation can "assume" that any other loop will eventually
finish.
The rule in C is (6.8.6.1p4):
An iteration statement may be assumed by the implementation
to terminate if its controlling expression is not a constant
expression, and none of the following operations are performed
in its body, controlling expression or (in the case of a for
statement) its expression-3
— input/output operations
— accessing a volatile object
— synchronization or atomic operations.
`for (;;)` is treated as having a constant controlling expression.
This covers more cases than the C++ rule.
I dislike it for most of the same reasonss. It should be phrased
in terms of the permitted behavior of a program, not what an
implementation is allowed to "assume".
In addition to that, I dislike the whole idea. I think it's
intended to enable optimizations, but it means that for this
contrived program:
#include <stdio.h>
int main(void) {
bool keep_going = true;
while (keep_going) {
keep_going = true;
}
puts("never reached");
}
the implementation is allowed to "assume" that the loop eventually
terminates. It's not clear what permissions the implementation is being >>> given if the assumption is violated. I think the program could legally
print "never reached", but if violating the assumption implies undefined >>> behavior it could do anything.
A programmer could easily write a program similar to the above
and think that the meaning is perfectly clear, have it behave very
differently because of one obscure subclause in the standard.
The idea of all this is given in a footnote in the C standards - "This
is intended to allow compiler transformations such as removal of empty
loops even when termination cannot be proven."
The loop might originally have contained source code, but become empty
through pre-processing, or from other compiler transformations (such as
the compiler seeing that the "keep_going" variable is not volatile and
its value is never used, so assignments to it can be elided, or moving
other things outside the loop body).
I suspect the original intent is as you said, to support removal
of "dead" loops where the body has been optimized away, or
excised using conditional compilation. Something like,
#ifdef DEBUG
#define DOTHING true
#else
#define DOTHING false
#endif
...
for (int i = 0; i < n; i++) {
if (DOTHING) {
// Something complex here...
}
}
If `DEBUG` is not defined in the preprocessor, the compiler has
license to elide the entire loop as part of dead code
elimination.
A programmer /could/ write the "keep_going" loop you gave, and
mistakenly believe it to be infinite. But is it likely? In my
experience, infinite loops are generally very clearly written - either
as "for (;;)" loops or "while (true)" loops - or they are the result of
bugs in the code that accidentally run forever. If the loop is
accidentally infinite, the programmer will already be expecting it to
run the code after the loop.
Equally, I don't think it is likely that compilers will often be able to
use this rule to improve code generation - it would only help in a
situation where the loop's controlling expression is too complicated for
the compiler to be sure that it will terminate, but where the loop body
ends up effectively empty. I doubt if that turns up often in real code
either.
So while I agree that this kind of thing can lead to curiosities and
behaviour that seems counter-intuitive, and is popular with the "modern
compilers are evil" crowd, I really do not see it as an issue in
practice. There are many other mistakes programmers can make, or UB
that they hit accidentally - this is a drop in the ocean IMHO.
As I understand it, primarily by reading the C++ problem report,
which covers both C and C++ for background, the idea is to
guarantee forward progress for programs that make use of
threads: consider cooperatively-scheduled green threads; a
programmer who inadvertantly creates an infinite loop shouldn't
be able to starve all threads for access to the CPU.
Personally, I don't think C should be in the business of doing
such things. But it is what it is.
- Dan C.
[...]
I think biggest trouble is normal programmers. They already
struggle with current standard text. More formal presentation
could alienate even folks who now are able to explain standard
rules to other programmers.
On 2026-06-09 03:25, Waldek Hebisch wrote:
[...]
Interesting views. - Thanks.
I think biggest trouble is normal programmers. They already
struggle with current standard text. More formal presentation
could alienate even folks who now are able to explain standard
rules to other programmers.
I'm not sure what "normal programmers" are. From own experience
I can just say that there's a difference between what's "formal"
in a "lawyer's speeches and texts" sense and what's formal in a
mathematical sense. - The C-Standard as had been quoted here is
more of a lawyer's text, with its inherent property of not being
formally (in a mathematical sense) accurate (despite their tries;
in both areas, law and programming language, respectively). It's
thus not necessarily a problem if we'd have a more [mathematical]
formal standard. - Programmers, as I see it, need definite texts.
And rejection of the "lawyer's" sort of texts is not surprising.
That not necessarily affects their acceptance will of more formal >specifications.
On 10/06/2026 23:47, Keith Thompson wrote:
[...]
#include <stdio.h>
int main(void) {
bool keep_going = true;
while (keep_going) {
keep_going = true;
}
puts("never reached");
}
[...]
[...]
The loop might originally have contained source code, but become empty through pre-processing, or from other compiler transformations (such as
the compiler seeing that the "keep_going" variable is not volatile and
its value is never used, so assignments to it can be elided, or moving
other things outside the loop body).
A programmer /could/ write the "keep_going" loop you gave, and
mistakenly believe it to be infinite. But is it likely?
In my
experience, infinite loops are generally very clearly written - either
as "for (;;)" loops or "while (true)" loops - or they are the result of
bugs in the code that accidentally run forever. If the loop is accidentally infinite, the programmer will already be expecting it to
run the code after the loop.
[...]
So while I agree that this kind of thing can lead to curiosities and behaviour that seems counter-intuitive, and is popular with the "modern compilers are evil" crowd, I really do not see it as an issue in
practice. There are many other mistakes programmers can make, or UB
that they hit accidentally - this is a drop in the ocean IMHO.
[...]Here's a C version with the same behavior:
```
term% cat weird.c
#include <stdio.h>
int
main(void)
{
for (unsigned int k = 0; k != 1; k += 2)
;
return 0;
}
void
hello(void)
{
printf("Hello, World!\n");
}
term% clang --version
clang version 22.1.6
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
term% clang -Wall -pedantic -O1 -std=c23 -o weird weird.c
term% ./weird
Hello, World!
term%
```
[...]
In article <110eht5$1naub$5@dont-email.me>,
Janis Papanagnou <janis_papanagnou+ng@hotmail.com> wrote:
I think biggest trouble is normal programmers. They already
struggle with current standard text. More formal presentation
could alienate even folks who now are able to explain standard
rules to other programmers.
I'm not sure what "normal programmers" are. From own experience
I can just say that there's a difference between what's "formal"
in a "lawyer's speeches and texts" sense and what's formal in a
mathematical sense. - The C-Standard as had been quoted here is
more of a lawyer's text, with its inherent property of not being
formally (in a mathematical sense) accurate (despite their tries;
in both areas, law and programming language, respectively). It's
thus not necessarily a problem if we'd have a more [mathematical]
formal standard. - Programmers, as I see it, need definite texts.
And rejection of the "lawyer's" sort of texts is not surprising.
That not necessarily affects their acceptance will of more formal
specifications.
One hopes that a formal specification (that's a term of art, and
implies something that's mathematically precise) would be
accompanied by a commentary for more casual reading.
However,
the truly precise, formal specification would be considered
definitive.
I think the odds of this ever happening for C are slim to none,
but it would be useful.
On 2026-06-09 03:25, Waldek Hebisch wrote:
[...]
Interesting views. - Thanks.
I think biggest trouble is normal programmers. They already
struggle with current standard text. More formal presentation
could alienate even folks who now are able to explain standard
rules to other programmers.
I'm not sure what "normal programmers" are. From own experience
I can just say that there's a difference between what's "formal"
in a "lawyer's speeches and texts" sense and what's formal in a
mathematical sense. - The C-Standard as had been quoted here is
more of a lawyer's text, with its inherent property of not being
formally (in a mathematical sense) accurate (despite their tries;
in both areas, law and programming language, respectively). It's
thus not necessarily a problem if we'd have a more [mathematical]
formal standard. - Programmers, as I see it, need definite texts.
And rejection of the "lawyer's" sort of texts is not surprising.
That not necessarily affects their acceptance will of more formal specifications.
David Brown <david.brown@hesbynett.no> writes:
[...]
"42" is an expression of type "int", and so is 'printf("Hello\n")'.[...]
How (and why) would a language distinguish between them and allow one
but not the other?
Ada, Pascal, and similar languages do exactly this, for what many
people consider to be good reasons.
In both languages, functions and procedures are distinct. Functions
return values; procedures do not. An expression cannot be turned
into a statement just by adding a semicolon. A function call is
an expression. A procedure call is a statement, not an expression.
An assignment is a statement, not an expression.
For I/O, the equivalent of printf is a procedure. In C,
printf("Hello, world\n") returns a negative result to denote an
error (and that value is often ignored).
[...]
[...]
The rough equivalent of the distinction between Pascal procedures and functions is that procedures are like C functions that have "void"
return type. It's fine (and not at all a bad idea) for a language to distinguish between void and non-void like this. What cannot easily be done in a clear and consistent way is to distinguish between two
expressions of type "int" (or any other general non-void type).
[...]
It is also fine for a language to distinguish between "pure" functions
and functions/procedures with side-effects and/or functions/procedures
with observable behaviour. (A "pure procedure" would not do anything.)
As far as I remember, Pascal does not make that distinction.
Janis Papanagnou <janis_papanagnou+ng@hotmail.com> wrote:
On 2026-06-09 03:25, Waldek Hebisch wrote:
[...]
Interesting views. - Thanks.
I think biggest trouble is normal programmers. They already
struggle with current standard text. More formal presentation
could alienate even folks who now are able to explain standard
rules to other programmers.
I'm not sure what "normal programmers" are. From own experience
I can just say that there's a difference between what's "formal"
in a "lawyer's speeches and texts" sense and what's formal in a
mathematical sense. - The C-Standard as had been quoted here is
more of a lawyer's text, with its inherent property of not being
formally (in a mathematical sense) accurate (despite their tries;
in both areas, law and programming language, respectively). It's
thus not necessarily a problem if we'd have a more [mathematical]
formal standard. - Programmers, as I see it, need definite texts.
And rejection of the "lawyer's" sort of texts is not surprising.
That not necessarily affects their acceptance will of more formal
specifications.
You sniped most of what I wrote.
I certainly would prefer standard
that is less lawyerish and more mathematical, say written in similar
way to Pascal standard. But there is a _big_ gap between normal
mathematical text and a formal mathematical text (and let me note that
Pascal standard is less formal than normal mathematics).
Normal
mathematical text depends on human understanding to disambiguate
and bridge small inconsistencies. Formal one has parts which
are there only because authors were not able to avoid
ambiguity in simpler way. And once things are written in a way
that is well fit to formalizm they tend to be much less
understandable to uninitiated.
On 2026-06-10 00:34, Keith Thompson wrote:...
For I/O, the equivalent of printf is a procedure. In C,
printf("Hello, world\n") returns a negative result to denote an
error (and that value is often ignored).
Erm, I hope that above printf() call does not create an error, but
returns the number of characters in the printed text. ;-)
The idea of all this is given in a footnote in the C standards - "This
is intended to allow compiler transformations such as removal of empty
loops even when termination cannot be proven."
The loop might originally have contained source code, but become empty through pre-processing, or from other compiler transformations (such
as the compiler seeing that the "keep_going" variable is not volatile
and its value is never used, so assignments to it can be elided, or
moving other things outside the loop body).
A programmer /could/ write the "keep_going" loop you gave, and
mistakenly believe it to be infinite. But is it likely? In my
experience, infinite loops are generally very clearly written - either
as "for (;;)" loops or "while (true)" loops - or they are the result
of bugs in the code that accidentally run forever. If the loop is accidentally infinite, the programmer will already be expecting it to
run the code after the loop.
On 2026-06-11 14:12, Janis Papanagnou wrote:
On 2026-06-10 00:34, Keith Thompson wrote:...
For I/O, the equivalent of printf is a procedure. In C,
printf("Hello, world\n") returns a negative result to denote an
error (and that value is often ignored).
Erm, I hope that above printf() call does not create an error, but
returns the number of characters in the printed text. ;-)
Hope is nice. I hope, in particular, that you're aware that there are
not guarantees on that matter?
I suspect the original intent is as you said, to support removal
of "dead" loops where the body has been optimized away, or
excised using conditional compilation. Something like,
#ifdef DEBUG
#define DOTHING true
#else
#define DOTHING false
#endif
...
for (int i = 0; i < n; i++) {
if (DOTHING) {
// Something complex here...
}
}
If `DEBUG` is not defined in the preprocessor, the compiler has
license to elide the entire loop as part of dead code
elimination.
As I understand it, primarily by reading the C++ problem report,
which covers both C and C++ for background, the idea is to
guarantee forward progress for programs that make use of
threads: consider cooperatively-scheduled green threads; a
programmer who inadvertantly creates an infinite loop shouldn't
be able to starve all threads for access to the CPU.
Personally, I don't think C should be in the business of doing
such things. But it is what it is.
On 2026-06-11 21:13, James Kuyper wrote:
On 2026-06-11 14:12, Janis Papanagnou wrote:
On 2026-06-10 00:34, Keith Thompson wrote:...
For I/O, the equivalent of printf is a procedure. In C,
printf("Hello, world\n") returns a negative result to denote an
error (and that value is often ignored).
Erm, I hope that above printf() call does not create an error, but
returns the number of characters in the printed text. ;-)
Hope is nice. I hope, in particular, that you're aware that there are
not guarantees on that matter?
Oh, actually I indeed thought that printing a constant string would not >create any error that would then be indicated by printf's return value.
[...]
I suspect the original intent is as you said, to support removal
of "dead" loops where the body has been optimized away, or
excised using conditional compilation. Something like,
#ifdef DEBUG
#define DOTHING true
#else
#define DOTHING false
#endif
...
for (int i = 0; i < n; i++) {
if (DOTHING) {
// Something complex here...
}
}
If `DEBUG` is not defined in the preprocessor, the compiler has
license to elide the entire loop as part of dead code
elimination.
I think I see what you mean, but in this particular case the loop
can be proven to terminate unless `i` is modified in the body of
The manual page also notes for the cases where printf returns -1:
[snip error list]
[snip opengroup-links]
In article <110cre9$13aa9$1@kst.eternal-september.org>,
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
cross@spitfire.i.gajendra.net (Dan Cross) writes:
[...]
I see you did not read the other messages in the (sub)thread,
but ok, here it is again, in C:
```
term% cat what.c
#include <stdio.h>
int main(void) { for (unsigned int k = 0; k != 1; k += 2); return 0; }
void hello(void) { printf("Hello, World!\n"); }
term% clang --version | sed 1q
clang version 22.1.6
term% clang -Wall -pedantic -pedantic-errors -O1 -std=c23 -o what what.c >>> what.c:2:58: warning: for loop has empty body [-Wempty-body]
2 | int main(void) { for (unsigned int k = 0; k != 1; k += 2); return 0; }
| ^
what.c:2:58: note: put the semicolon on a separate line to silence this warning
1 warning generated.
term% ./what
Hello, World!
term%
```
I see the same behavior.
The following largely repeats what I've written previously in
this thread.
Apparently the authors of clang decided that this statement in N3220 >>6.8.6.p4:
An iteration statement may be assumed by the implementation to
terminate if its controlling expression is not a constant
expression, ...
means that a program that violates that assumption has undefined
behavior. I intensely dislike both the rule and the way it's stated,
but I agree that the conclusion that the behavior is undefined is
a reasonable one.
I think the behavior is technical "unspecified" in the sense of
the C standard, but yes, this is the important bit. The
controlling expresion is not constant, and the loop doesn't meet
any of the other criteria set forth in sec 6.8.6 para 4 for,
therefore, the translator may assume it terminates (it is
unspecified whether or not it does; either behavior is correct.
GCC, for example, appears not to make the same assumption).
In article <110cre9$13aa9$1@kst.eternal-september.org>,
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote: >>>cross@spitfire.i.gajendra.net (Dan Cross) writes:
[...]
I see you did not read the other messages in the (sub)thread,
but ok, here it is again, in C:
```
term% cat what.c
#include <stdio.h>
int main(void) { for (unsigned int k = 0; k != 1; k += 2); return 0; } >>>> void hello(void) { printf("Hello, World!\n"); }
term% clang --version | sed 1q
clang version 22.1.6
term% clang -Wall -pedantic -pedantic-errors -O1 -std=c23 -o what what.c >>>> what.c:2:58: warning: for loop has empty body [-Wempty-body]
2 | int main(void) { for (unsigned int k = 0; k != 1; k += 2); return 0; }
| ^
what.c:2:58: note: put the semicolon on a separate line to silence this warning
1 warning generated.
term% ./what
Hello, World!
term%
```
I see the same behavior.
The following largely repeats what I've written previously in
this thread.
Apparently the authors of clang decided that this statement in N3220 >>>6.8.6.p4:
An iteration statement may be assumed by the implementation to
terminate if its controlling expression is not a constant
expression, ...
means that a program that violates that assumption has undefined >>>behavior. I intensely dislike both the rule and the way it's stated,
but I agree that the conclusion that the behavior is undefined is
a reasonable one.
I think the behavior is technical "unspecified" in the sense of
the C standard, but yes, this is the important bit. The
controlling expresion is not constant, and the loop doesn't meet
any of the other criteria set forth in sec 6.8.6 para 4 for,
therefore, the translator may assume it terminates (it is
unspecified whether or not it does; either behavior is correct.
GCC, for example, appears not to make the same assumption).
Why do you think the behavior is unspecified rather that undefined?
Unspecified behavior is defined as: "behavior, that results from
the use of an unspecified value, or other behavior upon which
this document provides two or more possibilities and imposes
no further requirements on which is chosen in any instance". >(Implementation-defined behavior differs from unspecified behavior
in that the implementation must document how the choice is made.)
What are the "two more more possibilities" in this case?
On 2026-06-11 21:13, James Kuyper wrote:
On 2026-06-11 14:12, Janis Papanagnou wrote:
On 2026-06-10 00:34, Keith Thompson wrote:...
For I/O, the equivalent of printf is a procedure. In C,
printf("Hello, world\n") returns a negative result to denote an
error (and that value is often ignored).
Erm, I hope that above printf() call does not create an error, but
returns the number of characters in the printed text. ;-)
Hope is nice. I hope, in particular, that you're aware that there are
not guarantees on that matter?
Oh, actually I indeed thought that printing a constant string would not create any error that would then be indicated by printf's return value.
I'd indeed also expected that, say, printing a string value with a '%d' specifier would produce an error, but I saw that it doesn't; while the compiler creates just a warning, execution provides some random output
and a _non-negative_ string-length value as printf's return value. Not exactly what I'd expect from a language.
Concerning the "guarantees" that you're asking for I sadly have to say
that I meanwhile expect nothing sensible at all any more from "C". ;-)
But to be more serious again...
The man-page is very unspecific on that; 'man 3 printf' says:
"If an output error is encountered, a negative value is returned."
Now of course an error can occur with that simple 'printf' above, for example, by issuing an 'fclose (stdout);' before the 'printf (...);'
But what can I as a C-programmer derive from that; how would one act
on that. (That's just rhetorical.)
Obviously (because of that?) I've never seen anyone test such a call
by, say,
int rc = printf("Hello, world\n");
if (rc < 0) {
/* umm.. */
}
Are you - plural, all CLC audience - writing such code with 'printf()', honestly? - Same question with 'int rc = fclose (...);' - what can one
do about that, then? (Write a logfile entry, maybe? - and then?)
But yes, I'm aware of negative OS function or library function output.--
Our rules (back in my C/C++ days) suggested to catch any sensible and possible error indications to quickly localize any potential issues.
On 2026-06-11 21:13, James Kuyper wrote:
On 2026-06-11 14:12, Janis Papanagnou wrote:
On 2026-06-10 00:34, Keith Thompson wrote:...
For I/O, the equivalent of printf is a procedure. In C,
printf("Hello, world\n") returns a negative result to denote an
error (and that value is often ignored).
Erm, I hope that above printf() call does not create an error, but
returns the number of characters in the printed text. ;-)
Hope is nice. I hope, in particular, that you're aware that there are
not guarantees on that matter?
Oh, actually I indeed thought that printing a constant string would not create any error that would then be indicated by printf's return value.
I'd indeed also expected that, say, printing a string value with a '%d' specifier would produce an error, but I saw that it doesn't; while the compiler creates just a warning, execution provides some random output
and a _non-negative_ string-length value as printf's return value. Not exactly what I'd expect from a language.
Now of course an error can occur with that simple 'printf' above, for example, by issuing an 'fclose (stdout);' before the 'printf (...);'
But what can I as a C-programmer derive from that; how would one act
on that. (That's just rhetorical.)
Obviously (because of that?) I've never seen anyone test such a call
by, say,
int rc = printf("Hello, world\n");
if (rc < 0) {
/* umm.. */
}
Are you - plural, all CLC audience - writing such code with 'printf()', honestly? - Same question with 'int rc = fclose (...);' - what can one
do about that, then? (Write a logfile entry, maybe? - and then?)
Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:[...]
I think I see what you mean, but in this particular case the loop
can be proven to terminate unless `i` is modified in the body of
...unless 'i' or 'n' is modified in the body of
In article <110fgbi$1qf9f$1@kst.eternal-september.org>,
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
cross@spitfire.i.gajendra.net (Dan Cross) writes:
In article <110cre9$13aa9$1@kst.eternal-september.org>,
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote: >>>>cross@spitfire.i.gajendra.net (Dan Cross) writes:
[...]
I see you did not read the other messages in the (sub)thread,
but ok, here it is again, in C:
```
term% cat what.c
#include <stdio.h>
int main(void) { for (unsigned int k = 0; k != 1; k += 2); return 0; } >>>>> void hello(void) { printf("Hello, World!\n"); }
term% clang --version | sed 1q
clang version 22.1.6
term% clang -Wall -pedantic -pedantic-errors -O1 -std=c23 -o what what.c >>>>> what.c:2:58: warning: for loop has empty body [-Wempty-body]
2 | int main(void) { for (unsigned int k = 0; k != 1; k += 2); return 0; }
| ^
what.c:2:58: note: put the semicolon on a separate line to silence this warning
1 warning generated.
term% ./what
Hello, World!
term%
```
I see the same behavior.
The following largely repeats what I've written previously in
this thread.
Apparently the authors of clang decided that this statement in N3220 >>>>6.8.6.p4:
An iteration statement may be assumed by the implementation to
terminate if its controlling expression is not a constant
expression, ...
means that a program that violates that assumption has undefined >>>>behavior. I intensely dislike both the rule and the way it's stated, >>>>but I agree that the conclusion that the behavior is undefined is
a reasonable one.
I think the behavior is technical "unspecified" in the sense of
the C standard, but yes, this is the important bit. The
controlling expresion is not constant, and the loop doesn't meet
any of the other criteria set forth in sec 6.8.6 para 4 for,
therefore, the translator may assume it terminates (it is
unspecified whether or not it does; either behavior is correct.
GCC, for example, appears not to make the same assumption).
Why do you think the behavior is unspecified rather that undefined?
Unspecified behavior is defined as: "behavior, that results from
the use of an unspecified value, or other behavior upon which
this document provides two or more possibilities and imposes
no further requirements on which is chosen in any instance". >>(Implementation-defined behavior differs from unspecified behavior
in that the implementation must document how the choice is made.)
What are the "two more more possibilities" in this case?
The two choices are that the implementation may assume the loop
terminates, or it may not, but it doesn't say which. I don't
think that the language permits it to be UB. But I could be
wrong. It's a bit of a distinction without a difference as far
as the outcome is concerned.
In article <110fgbi$1qf9f$1@kst.eternal-september.org>,
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote: >>>cross@spitfire.i.gajendra.net (Dan Cross) writes:
In article <110cre9$13aa9$1@kst.eternal-september.org>,
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote: >>>>>cross@spitfire.i.gajendra.net (Dan Cross) writes:
[...]
I see you did not read the other messages in the (sub)thread,
but ok, here it is again, in C:
```
term% cat what.c
#include <stdio.h>
int main(void) { for (unsigned int k = 0; k != 1; k += 2); return 0; } >>>>>> void hello(void) { printf("Hello, World!\n"); }
term% clang --version | sed 1q
clang version 22.1.6
term% clang -Wall -pedantic -pedantic-errors -O1 -std=c23 -o what what.c >>>>>> what.c:2:58: warning: for loop has empty body [-Wempty-body]
2 | int main(void) { for (unsigned int k = 0; k != 1; k += 2); return 0; }
| ^ >>>>>> what.c:2:58: note: put the semicolon on a separate line to silence this warning
1 warning generated.
term% ./what
Hello, World!
term%
```
I see the same behavior.
The following largely repeats what I've written previously in
this thread.
Apparently the authors of clang decided that this statement in N3220 >>>>>6.8.6.p4:
An iteration statement may be assumed by the implementation to
terminate if its controlling expression is not a constant
expression, ...
means that a program that violates that assumption has undefined >>>>>behavior. I intensely dislike both the rule and the way it's stated, >>>>>but I agree that the conclusion that the behavior is undefined is
a reasonable one.
I think the behavior is technical "unspecified" in the sense of
the C standard, but yes, this is the important bit. The
controlling expresion is not constant, and the loop doesn't meet
any of the other criteria set forth in sec 6.8.6 para 4 for,
therefore, the translator may assume it terminates (it is
unspecified whether or not it does; either behavior is correct.
GCC, for example, appears not to make the same assumption).
Why do you think the behavior is unspecified rather that undefined?
Unspecified behavior is defined as: "behavior, that results from
the use of an unspecified value, or other behavior upon which
this document provides two or more possibilities and imposes
no further requirements on which is chosen in any instance". >>>(Implementation-defined behavior differs from unspecified behavior
in that the implementation must document how the choice is made.)
What are the "two more more possibilities" in this case?
The two choices are that the implementation may assume the loop
terminates, or it may not, but it doesn't say which. I don't
think that the language permits it to be UB. But I could be
wrong. It's a bit of a distinction without a difference as far
as the outcome is concerned.
No, those are not the two choices. An assumption made by an
implementation is not behavior ("external appearance or action").
An implementation might invoke some behavior as a result of some
assumption.
If a loop doesn't terminate and the implementation assumes that
it does, the standard says nothing about the resulting behavior.
It doesn't provide two or more options for the actual behavior.
That's classic UB.
We've seen cases here where the actual behavior is falling through
into a function that's never called. That's certainly not a
possibility provided by the standard.
[...]
I suspect the original intent is as you said, to support removal
of "dead" loops where the body has been optimized away, or
excised using conditional compilation. Something like,
#ifdef DEBUG
#define DOTHING true
#else
#define DOTHING false
#endif
...
for (int i = 0; i < n; i++) {
if (DOTHING) {
// Something complex here...
}
}
If `DEBUG` is not defined in the preprocessor, the compiler has
license to elide the entire loop as part of dead code
elimination.
I think I see what you mean, but in this particular case the loop
can be proven to terminate unless `i` is modified in the body of
the loop, and a compiler can elide the entire loop anyway.
[...]
As I understand it, primarily by reading the C++ problem report,
which covers both C and C++ for background, the idea is to
guarantee forward progress for programs that make use of
threads: consider cooperatively-scheduled green threads; a
programmer who inadvertantly creates an infinite loop shouldn't
be able to starve all threads for access to the CPU.
Personally, I don't think C should be in the business of doing
such things. But it is what it is.
I agree.
David Brown <david.brown@hesbynett.no> writes:
[...]
The idea of all this is given in a footnote in the C standards - "This
is intended to allow compiler transformations such as removal of empty
loops even when termination cannot be proven."
The loop might originally have contained source code, but become empty
through pre-processing, or from other compiler transformations (such
as the compiler seeing that the "keep_going" variable is not volatile
and its value is never used, so assignments to it can be elided, or
moving other things outside the loop body).
A programmer /could/ write the "keep_going" loop you gave, and
mistakenly believe it to be infinite. But is it likely? In my
experience, infinite loops are generally very clearly written - either
as "for (;;)" loops or "while (true)" loops - or they are the result
of bugs in the code that accidentally run forever. If the loop is
accidentally infinite, the programmer will already be expecting it to
run the code after the loop.
How about a loop that has a non-constant condition, but that is
not expected to terminate in normal usage?
while (! something_really_bad_happened()) {
sleep(1);
}
self_destruct();
A compiler could "assume" that the loop terminates, even if >something_really_bad never happens, and that assumption could result in
a call to self_destruct(). There are probably better ways to do that,
but it's straightforward code with seemingly obvious semantics that
an implementation is permitted to make unwarrated assumptions about.
[...]
On 2026-06-11 18:30, Waldek Hebisch wrote:
Janis Papanagnou <janis_papanagnou+ng@hotmail.com> wrote:
On 2026-06-09 03:25, Waldek Hebisch wrote:
[...]
Interesting views. - Thanks.
I think biggest trouble is normal programmers. They already
struggle with current standard text. More formal presentation
could alienate even folks who now are able to explain standard
rules to other programmers.
I'm not sure what "normal programmers" are. From own experience
I can just say that there's a difference between what's "formal"
in a "lawyer's speeches and texts" sense and what's formal in a
mathematical sense. - The C-Standard as had been quoted here is
more of a lawyer's text, with its inherent property of not being
formally (in a mathematical sense) accurate (despite their tries;
in both areas, law and programming language, respectively). It's
thus not necessarily a problem if we'd have a more [mathematical]
formal standard. - Programmers, as I see it, need definite texts.
And rejection of the "lawyer's" sort of texts is not surprising.
That not necessarily affects their acceptance will of more formal
specifications.
You sniped most of what I wrote.
Yes, because I acknowledged it by my above on-line remark already
(and I didn't want to waste space unnecessarily). (No offense!)
I intended to comment just on the one paragraph above, with its
assumption that it may be an inherent problem to programmers.
To elaborate only a bit more...
There's folks who have problems with "lawyer's speech" standards.
There's folks who have problems with formal mathematical standards.
But, as to my observation, there's *no* strict or natural hierarchy
that one would imply the other.
You said: "They already struggle with current standard text."
as if there would be a strict "one implies the other" fact; there
isn't one, or to be more cautious, "there isn't necessarily one".
(I used the wording "necessarily" already in my original comment.)
I certainly would prefer standard
that is less lawyerish and more mathematical, say written in similar
way to Pascal standard. But there is a _big_ gap between normal
mathematical text and a formal mathematical text (and let me note that
Pascal standard is less formal than normal mathematics).
I agree.
Normal
mathematical text depends on human understanding to disambiguate
and bridge small inconsistencies. Formal one has parts which
are there only because authors were not able to avoid
ambiguity in simpler way. And once things are written in a way
that is well fit to formalizm they tend to be much less
understandable to uninitiated.
(I'll leave that uncommented. - I've said all I intended to say.)
Janis
On 2026-06-11 08:56, David Brown wrote:
On 10/06/2026 23:47, Keith Thompson wrote:
[...]
#include <stdio.h>
int main(void) {
bool keep_going = true;
while (keep_going) {
keep_going = true;
}
puts("never reached");
}
[...]
[...]
The loop might originally have contained source code, but become empty
through pre-processing, or from other compiler transformations (such
as the compiler seeing that the "keep_going" variable is not volatile
and its value is never used, so assignments to it can be elided, or
moving other things outside the loop body).
A programmer /could/ write the "keep_going" loop you gave, and
mistakenly believe it to be infinite. But is it likely?
I think we should not make any assumptions about the "creativity" of a programmer ("C" or else). - Semantics should be well defined, and then
clear to the programmer.
In my experience, infinite loops are generally very clearly written -
either as "for (;;)" loops or "while (true)" loops - or they are the
result of bugs in the code that accidentally run forever. If the loop
is accidentally infinite, the programmer will already be expecting it
to run the code after the loop.
[...]
So while I agree that this kind of thing can lead to curiosities and
behaviour that seems counter-intuitive, and is popular with the
"modern compilers are evil" crowd, I really do not see it as an issue
in practice. There are many other mistakes programmers can make, or
UB that they hit accidentally - this is a drop in the ocean IMHO.
Languages shall be sensibly and clearly defined. For bad designs (or
bad standards) the language or standard should be blamed, and not the
critics badly and inappropriately despised as ''"modern compilers are
evil" crowd''. - Programmers are at the final end of the "food chain".
And there's a lot of horrible pits in the C-language where programmers
"made the mistake" to fall in; don't blame them, neither the ones who silently suffer nor the ones who shout out.
David Brown <david.brown@hesbynett.no> writes:
[...]
The idea of all this is given in a footnote in the C standards - "This
is intended to allow compiler transformations such as removal of empty
loops even when termination cannot be proven."
The loop might originally have contained source code, but become empty
through pre-processing, or from other compiler transformations (such
as the compiler seeing that the "keep_going" variable is not volatile
and its value is never used, so assignments to it can be elided, or
moving other things outside the loop body).
A programmer /could/ write the "keep_going" loop you gave, and
mistakenly believe it to be infinite. But is it likely? In my
experience, infinite loops are generally very clearly written - either
as "for (;;)" loops or "while (true)" loops - or they are the result
of bugs in the code that accidentally run forever. If the loop is
accidentally infinite, the programmer will already be expecting it to
run the code after the loop.
How about a loop that has a non-constant condition, but that is
not expected to terminate in normal usage?
while (! something_really_bad_happened()) {
sleep(1);
}
self_destruct();
A compiler could "assume" that the loop terminates, even if something_really_bad never happens, and that assumption could result in
a call to self_destruct(). There are probably better ways to do that,
but it's straightforward code with seemingly obvious semantics that
an implementation is permitted to make unwarrated assumptions about.
In article <110fgbi$1qf9f$1@kst.eternal-september.org>,
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
cross@spitfire.i.gajendra.net (Dan Cross) writes:
In article <110cre9$13aa9$1@kst.eternal-september.org>,
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
cross@spitfire.i.gajendra.net (Dan Cross) writes:
[...]
I see you did not read the other messages in the (sub)thread,
but ok, here it is again, in C:
```
term% cat what.c
#include <stdio.h>
int main(void) { for (unsigned int k = 0; k != 1; k += 2); return 0; } >>>>> void hello(void) { printf("Hello, World!\n"); }
term% clang --version | sed 1q
clang version 22.1.6
term% clang -Wall -pedantic -pedantic-errors -O1 -std=c23 -o what what.c >>>>> what.c:2:58: warning: for loop has empty body [-Wempty-body]
2 | int main(void) { for (unsigned int k = 0; k != 1; k += 2); return 0; }
| ^
what.c:2:58: note: put the semicolon on a separate line to silence this warning
1 warning generated.
term% ./what
Hello, World!
term%
```
I see the same behavior.
The following largely repeats what I've written previously in
this thread.
Apparently the authors of clang decided that this statement in N3220
6.8.6.p4:
An iteration statement may be assumed by the implementation to
terminate if its controlling expression is not a constant
expression, ...
means that a program that violates that assumption has undefined
behavior. I intensely dislike both the rule and the way it's stated,
but I agree that the conclusion that the behavior is undefined is
a reasonable one.
I think the behavior is technical "unspecified" in the sense of
the C standard, but yes, this is the important bit. The
controlling expresion is not constant, and the loop doesn't meet
any of the other criteria set forth in sec 6.8.6 para 4 for,
therefore, the translator may assume it terminates (it is
unspecified whether or not it does; either behavior is correct.
GCC, for example, appears not to make the same assumption).
Why do you think the behavior is unspecified rather that undefined?
Unspecified behavior is defined as: "behavior, that results from
the use of an unspecified value, or other behavior upon which
this document provides two or more possibilities and imposes
no further requirements on which is chosen in any instance".
(Implementation-defined behavior differs from unspecified behavior
in that the implementation must document how the choice is made.)
What are the "two more more possibilities" in this case?
The two choices are that the implementation may assume the loop
terminates, or it may not, but it doesn't say which. I don't
think that the language permits it to be UB. But I could be
wrong. It's a bit of a distinction without a difference as far
as the outcome is concerned.
- Dan C.
On 2026-06-10 09:04, David Brown wrote:
[...]
The rough equivalent of the distinction between Pascal procedures and
functions is that procedures are like C functions that have "void"
return type. It's fine (and not at all a bad idea) for a language to
distinguish between void and non-void like this. What cannot easily
be done in a clear and consistent way is to distinguish between two
expressions of type "int" (or any other general non-void type).
Here I cannot follow you. - The C-compiler can analyze code to do optimizations and even (as so often stated) "assume" things about
the intent concerning UB and optimization but cannot value facts
about types and context? - If so, then it sounds rather arbitrary.
[...]
It is also fine for a language to distinguish between "pure" functions
and functions/procedures with side-effects and/or functions/procedures
with observable behaviour. (A "pure procedure" would not do anything.)
By "would not do anything" you probably mean that it would not have side-effects on/with relatively global entities in the program?
As far as I remember, Pascal does not make that distinction.
Pascal functions and procedures can affect and be affected by global entities. Predefined functions and procedures can have side effects
also unrelated to global entities in the program (e.g. print effect).
A procedure/function not affecting the global (or surrounding stack) environment could likely be identified. But here we're anyway talking
about the (clean!) return-interface of functions (as opposed to the procedures).
On 11/06/2026 17:34, Janis Papanagnou wrote:
On 2026-06-11 08:56, David Brown wrote:
On 10/06/2026 23:47, Keith Thompson wrote:
[...]
#include <stdio.h>
int main(void) {
bool keep_going = true;
while (keep_going) {
keep_going = true;
}
puts("never reached");
}
[...]
[...]
The loop might originally have contained source code, but become
empty through pre-processing, or from other compiler
transformations (such as the compiler seeing that the "keep_going"
variable is not volatile and its value is never used, so
assignments to it can be elided, or moving other things outside the
loop body).
A programmer /could/ write the "keep_going" loop you gave, and
mistakenly believe it to be infinite. But is it likely?
I think we should not make any assumptions about the "creativity" of
a programmer ("C" or else). - Semantics should be well defined, and
then clear to the programmer.
I think the semantics of this "loops can be assumed to terminate" are
clearly defined in the standard. I agree that the details might not
be known to all C programmers, but I think they are only relevant in a
very small number of cases.
David Brown <david.brown@hesbynett.no> writes:
On 11/06/2026 17:34, Janis Papanagnou wrote:
On 2026-06-11 08:56, David Brown wrote:
On 10/06/2026 23:47, Keith Thompson wrote:
[...]
#include <stdio.h>
int main(void) {
bool keep_going = true;
while (keep_going) {
keep_going = true;
}
puts("never reached");
}
[...]
[...]
The loop might originally have contained source code, but become
empty through pre-processing, or from other compiler
transformations (such as the compiler seeing that the "keep_going"
variable is not volatile and its value is never used, so
assignments to it can be elided, or moving other things outside the
loop body).
A programmer /could/ write the "keep_going" loop you gave, and
mistakenly believe it to be infinite. But is it likely?
I think we should not make any assumptions about the "creativity" of
a programmer ("C" or else). - Semantics should be well defined, and
then clear to the programmer.
I think the semantics of this "loops can be assumed to terminate" are
clearly defined in the standard. I agree that the details might not
be known to all C programmers, but I think they are only relevant in a
very small number of cases.
I disagree that the semantics are clearly defined. N3220 6.8.6.1p4
is specified in terms of what an implementation may "assume", not in
terms of the semantics of the program. One can conclude that this
means that the program has undefined behavior if the assumption is
violated, but that's not directly stated. I don't know how many C programmers know the standard well enough to reach that conclusion.
I'm not even 100% sure it's accurate.
The permission was added in C11 with little fanfare. It's not
mentioned in the list of major changes in the C11 Foreword.
The cases where it applies may be rarer than I had assumed, but
it at least has the potential to break existing code that was well
defined in C99.
The rationale is to provide more opportunities for optimization,
but it's not at all clear (at least to me) that it's particularly
successful. If cases where it can cause problems are rare, then
presumably cases where it's actually useful are rare. (That may
be an oversimplification.)
[snip]
As for my '"modern compilers are evil" crowd' comment, there are people
(not anyone involved in this discussion) who really do fall into that
camp. I've seen people who are experienced and respected developers
make all sorts of accusations to compiler developers, claiming they are
only interested in high scores on synthetic benchmarks and directly >insulting their motivations and integrity, blaming them for "breaking"
their code that relied on the effects of some kinds of UB. It is always >frustrating when you have code that works fine with one compiler
version, but using another compiler results in failure due to UB in your >code - especially if writing correct code gives inefficient results with
the first compiler. And it's fine to say you'd be happier if a
particular thing that is UB in C were not UB - but it is unreasonable to >blame compiler developers for implementing the language as it is defined.
I am not in any way saying that critics of aspects of C (the language,
the standards, or compiler implementations) should be dismissed or
despised - merely that the example of loop elimination leading to UB and >unexpected results is regularly used as "evidence" by those that hold >extreme positions about C, despite it being very unrealistic for the
issue to cause problems in real coding practice.
David Brown <david.brown@hesbynett.no> writes:
On 11/06/2026 17:34, Janis Papanagnou wrote:
On 2026-06-11 08:56, David Brown wrote:
On 10/06/2026 23:47, Keith Thompson wrote:
[...]
#include <stdio.h>
int main(void) {
bool keep_going = true;
while (keep_going) {
keep_going = true;
}
puts("never reached");
}
[...]
[...]
The loop might originally have contained source code, but become
empty through pre-processing, or from other compiler
transformations (such as the compiler seeing that the "keep_going"
variable is not volatile and its value is never used, so
assignments to it can be elided, or moving other things outside the
loop body).
A programmer /could/ write the "keep_going" loop you gave, and
mistakenly believe it to be infinite. But is it likely?
I think we should not make any assumptions about the "creativity" of
a programmer ("C" or else). - Semantics should be well defined, and
then clear to the programmer.
I think the semantics of this "loops can be assumed to terminate" are
clearly defined in the standard. I agree that the details might not
be known to all C programmers, but I think they are only relevant in a
very small number of cases.
I disagree that the semantics are clearly defined. N3220 6.8.6.1p4
is specified in terms of what an implementation may "assume", not in
terms of the semantics of the program. One can conclude that this
means that the program has undefined behavior if the assumption is
violated, but that's not directly stated. I don't know how many C >programmers know the standard well enough to reach that conclusion.
I'm not even 100% sure it's accurate.
The permission was added in C11 with little fanfare. It's not
mentioned in the list of major changes in the C11 Foreword.
The cases where it applies may be rarer than I had assumed, but
it at least has the potential to break existing code that was well
defined in C99.
The rationale is to provide more opportunities for optimization,
but it's not at all clear (at least to me) that it's particularly
successful. If cases where it can cause problems are rare, then
presumably cases where it's actually useful are rare. (That may
be an oversimplification.)
Here's my own vignette: I was chatting with a friend who works
on LLVM and clang some time ago. I said, "I don't want UB" and
he replied, "no, you really do." I asked him what he meant and
cross@spitfire.i.gajendra.net (Dan Cross) wrote or quoted:
Here's my own vignette: I was chatting with a friend who works
on LLVM and clang some time ago. I said, "I don't want UB" and
he replied, "no, you really do." I asked him what he meant and
Might like to have a look at the video
"Garbage In, Garbage Out, Arguing about Undefined Behavior
with Nasal Demons" (2016) by Chandler Carruth.
IIRC it essential takes the point of your friend, but maybe adds
some explanations. At 15' in, it discusses the suggestion to
"define all the behavior". It's for C++, but I think some of it
might apply to C as well. At 24' come some examples.
Janis Papanagnou <janis_papanagnou+ng@hotmail.com> wrote:
On 2026-06-11 18:30, Waldek Hebisch wrote:
Janis Papanagnou <janis_papanagnou+ng@hotmail.com> wrote:
On 2026-06-09 03:25, Waldek Hebisch wrote:
[...]
Interesting views. - Thanks.
I think biggest trouble is normal programmers. They already
struggle with current standard text. More formal presentation
could alienate even folks who now are able to explain standard
rules to other programmers.
I'm not sure what "normal programmers" are. From own experience
I can just say that there's a difference between what's "formal"
in a "lawyer's speeches and texts" sense and what's formal in a
mathematical sense. - The C-Standard as had been quoted here is
more of a lawyer's text, with its inherent property of not being
formally (in a mathematical sense) accurate (despite their tries;
in both areas, law and programming language, respectively). It's
thus not necessarily a problem if we'd have a more [mathematical]
formal standard. - Programmers, as I see it, need definite texts.
And rejection of the "lawyer's" sort of texts is not surprising.
That not necessarily affects their acceptance will of more formal
specifications.
You sniped most of what I wrote.
Yes, because I acknowledged it by my above on-line remark already
(and I didn't want to waste space unnecessarily). (No offense!)
I intended to comment just on the one paragraph above, with its
assumption that it may be an inherent problem to programmers.
But this paragraph was closely linked to the text above. Dan Cross
wanted formal semantics and my paragraph was responding to this.
I think that lawyerish style of current C standard is mostly inertia,
and making standard more mathematical would improve it. But giving
formal semantic in the standard would mean significantly bigger
change.
I think biggest trouble is normal programmers.
They already struggle with current standard text.
[...]
On 11/06/2026 20:29, Janis Papanagnou wrote:
[...]
I think this thread is getting difficult to follow - there is a lot of wandering and vagueness (mostly from me, I must admit). So I am not
sure if it is worth pursuing further.
[...]
In article <110ghmv$21vi3$1@dont-email.me>,
David Brown <david.brown@hesbynett.no> wrote:
[snip]
As for my '"modern compilers are evil" crowd' comment, there are people
(not anyone involved in this discussion) who really do fall into that
camp. I've seen people who are experienced and respected developers
make all sorts of accusations to compiler developers, claiming they are
only interested in high scores on synthetic benchmarks and directly
insulting their motivations and integrity, blaming them for "breaking"
their code that relied on the effects of some kinds of UB. It is always
frustrating when you have code that works fine with one compiler
version, but using another compiler results in failure due to UB in your
code - especially if writing correct code gives inefficient results with
the first compiler. And it's fine to say you'd be happier if a
particular thing that is UB in C were not UB - but it is unreasonable to
blame compiler developers for implementing the language as it is defined.
Eh...I think those people have a point.
Note, I don't think that "modern compilers are evil" (I mean,
wow, that's a strong word) and I certainly do not think it is
appropriate to malign the people who write them personally over
what one does with code.
But I _do_ think it is fair to say that UB is very easy to fall
into in C, that programs that have worked correctly (insofar as
their intended behavior as written) for years can suddenly fail
because latent UB is treated differently in a point revision of
a compiler, and that that (as you point out) can be incredibly
frustrating for the authors.
Regehr called out a dichotomy with UB: programmers using a
language hate it; compiler writers love it.
Here's my own vignette: I was chatting with a friend who works
on LLVM and clang some time ago. I said, "I don't want UB" and
he replied, "no, you really do." I asked him what he meant and
he responded that I wanted a compiler that is capable of
optimizing my program; "sure, but I still don't want UB." We
went on for a bit, and it became clear that he saw UB as _the_
vehicle for unlocking optimization.
I realized that we were not speaking the same language _at all_.
He and I both wanted a language where we could write programs
that yield efficient object code. He saw UB as essential for
that; but what I want is a language with well-defined semantics
that can be aggressively optimized.
That, I think, is the tension: there was a fundamental breakdown
in communication between the users of the language, and those
defining and implementing it. My subjective sense is that in
the past few years things are getting somewhat better, but it is
hard to evolve something as critical and widely used as C.
I am not in any way saying that critics of aspects of C (the language,
the standards, or compiler implementations) should be dismissed or
despised - merely that the example of loop elimination leading to UB and
unexpected results is regularly used as "evidence" by those that hold
extreme positions about C, despite it being very unrealistic for the
issue to cause problems in real coding practice.
The kernel I am working on has about 5 million lines of code.
That code has been evolving for 40 years; some of it predates
the ISO standards and even the ANSI standard. It has been
updated for newer compilers, sure, but in some places the
treatment is surface-level: using ISO-style function prototypes
and definition syntax, for example. But deep problems remain in
parts, and contraints on engineering resources couple with
economic and business pressures so that it's not going to get
cleaned up any time soon. I'm sure there is UB in it; in fact,
I know there is. But them's the breaks; and yet, customers are
using it in production. Because of this, upgrading toolchains
is laborious and complex, and takes a lot of time, and new
compilers are (rightly) viewed with suspicion. That is not a
great situation, but I don't think anyone is angry at the
compiler people over it.
And just as it's not acceptable to blame compiler writers for
implementating the language as it is defined, it's not really
acceptable to blame programmers either; some of the people who
put the UB there are (literally) dead, and there's just not
enough time in the day to go clean it all up. I wish there was
more compassion for that.
As said earlier, C is what it is. I suspect that it will
continue to make incremental improvements, but we're basically
stuck with what we have.
- Dan C.
On 13/06/2026 14:02, Dan Cross wrote:
In article <110ghmv$21vi3$1@dont-email.me>,
David Brown <david.brown@hesbynett.no> wrote:
[snip]Eh...I think those people have a point.
As for my '"modern compilers are evil" crowd' comment, there are people
(not anyone involved in this discussion) who really do fall into that
camp. I've seen people who are experienced and respected developers
make all sorts of accusations to compiler developers, claiming they are
only interested in high scores on synthetic benchmarks and directly
insulting their motivations and integrity, blaming them for "breaking"
their code that relied on the effects of some kinds of UB. It is always >>> frustrating when you have code that works fine with one compiler
version, but using another compiler results in failure due to UB in your >>> code - especially if writing correct code gives inefficient results with >>> the first compiler. And it's fine to say you'd be happier if a
particular thing that is UB in C were not UB - but it is unreasonable to >>> blame compiler developers for implementing the language as it is defined. >>
Note, I don't think that "modern compilers are evil" (I mean,
wow, that's a strong word) and I certainly do not think it is
appropriate to malign the people who write them personally over
what one does with code.
I think it is important for tools to be helpful, and it's fine to
complain if a tool is being directly unhelpful - or ask for improvements >when you think it could be better.
But I _do_ think it is fair to say that UB is very easy to fall
into in C, that programs that have worked correctly (insofar as
their intended behavior as written) for years can suddenly fail
because latent UB is treated differently in a point revision of
a compiler, and that that (as you point out) can be incredibly
frustrating for the authors.
It can certainly happen, yes. And I fully sympathise on these few
occasions when changes to the standard has meant that code that
previously had defined behaviour, now has different or undefined
behaviour. (However, I think that for some kinds of code, programmers
could be better at specifying exactly what standards their code
requires, and the standards they use when compiling code.)
But it is important to realise that if you write code with UB, it is
/your/ mistake - not the mistake of the compiler developers, or the
mistake of the standards authors. Compiler vendors can (and do!) try to >help programmers find their mistakes - experience shows, however, that
many programmers reach first for bug report forms or complaints in
forums before compiler tools like sanitisers or even enabling warnings
on their builds.
Programming in C is a cooperative effort - including the standards
authors, the compiler vendors, and the C programmers. Each group can
try to help the others, but each is ultimately responsible for their own >part.
Regehr called out a dichotomy with UB: programmers using a
language hate it; compiler writers love it.
I think Regehr has made some good points in his writings, but I do not
agree with him on everything.
As a programmer, I am a fan of the concept of UB. I am quite happy with
the idea that operations have a pre-condition, and that if there is no >"right answer" for a given input, I should not provide that input. I
prefer that signed integer arithmetic overflow is UB, and do not want it
to be wrapping or have some other semantics - to me, it is far clearer
that way. If I have UB in my code, it's a bug - no different from any
other bug I might make.
It is the case that in C, there are some kinds of UB that can be quite >subtle. However, you rarely need to risk meeting them. Yes, there are >pitfalls - don't go near them, and they don't matter.
However, it is unfortunately the case that sometimes avoiding UB can be >costly in performance terms. An example would be if you have need of >type-punning - perhaps you have a float in memory and you want to access
it as an uint32_t for some reason. Casting a float * to an uint32_t *
and using that new pointer is UB. Some compilers will nonetheless
generate the code you want after such a cast. Some compilers might not, >depending on details of the rest of the surrounding code, because it is
UB. A non-UB solution would be to use memcpy(), or a type-punning
union. For highly optimising compilers, that's fine - the code
generated by gcc or clang for a memcpy() here is likely to be as
efficient as you could get - directly reading the float from memory to
an integer register. For other compilers, however, you might get a call
to a memcpy() library function in an external DLL, taking orders of >magnitude more cycles. What is the poor programmer to do? Write code
that is portable and correct, but very slow with some implementations?
Write code that "cheats" and is efficient on some implementations but
might not give the desired results on others? Use pre-processor >monstrosities to detect different compilers and adapt accordingly? That
is what I see as the biggest issue resulting from compiler optimisation >based on UB. I don't know what the "best" answer here is.
Here's my own vignette: I was chatting with a friend who works
on LLVM and clang some time ago. I said, "I don't want UB" and
he replied, "no, you really do." I asked him what he meant and
he responded that I wanted a compiler that is capable of
optimizing my program; "sure, but I still don't want UB." We
went on for a bit, and it became clear that he saw UB as _the_
vehicle for unlocking optimization.
I realized that we were not speaking the same language _at all_.
He and I both wanted a language where we could write programs
that yield efficient object code. He saw UB as essential for
that; but what I want is a language with well-defined semantics
that can be aggressively optimized.
I too want a language with well-defined semantics that can be
aggressively optimised. But I do not see UB as a hinder to that.
I am happy knowing that I cannot divide by 0,
or find the square root of a negative number (in the real
domain).
I am happy knowing that I cannot add two ints if their sum
overflows the range of their type,
and that I cannot call a function with a different number or
type of parameters than its definition.
I have a great deal of difficulty seeing how things could be
any different, other than in a managed language with significant
overhead from run-time checks - and that goes against the
"aggressively optimised" requirement.
Having "well-defined semantics" does not mean the language should accept >anything that happens to fit the syntax and grammar rules, or that all >functions and operations should give a defined result for all inputs.
It means that the set of valid inputs is clearly defined, along with the >outputs and effects you get when the inputs are valid.
(There are plenty of points in the C standards where the wording could
make the semantics clearer, or where the range of input values could
easily have been larger - I am not suggesting C is as well-defined as it >could reasonably be.)
That, I think, is the tension: there was a fundamental breakdown
in communication between the users of the language, and those
defining and implementing it. My subjective sense is that in
the past few years things are getting somewhat better, but it is
hard to evolve something as critical and widely used as C.
Communication between the separate parties is always an issue, and it is >easy for it to be a one-way street with a language standards committee >dictating the rules with little attention to feedback, then compiler
vendors following these rules without listening to the users.
A challenge here, perhaps, is that users are a very diverse group. How
much should compiler vendors cater for those that put a lot of effort
into correctness and want top efficiency, or those that are less >knowledgable about the language but want to avoid the consequences of
their mistakes? What about those working with old code written for >different compilers with different unwritten rules? It is not easy to >please everyone.
I am not in any way saying that critics of aspects of C (the language,
the standards, or compiler implementations) should be dismissed or
despised - merely that the example of loop elimination leading to UB and >>> unexpected results is regularly used as "evidence" by those that hold
extreme positions about C, despite it being very unrealistic for the
issue to cause problems in real coding practice.
The kernel I am working on has about 5 million lines of code.
That code has been evolving for 40 years; some of it predates
the ISO standards and even the ANSI standard. It has been
updated for newer compilers, sure, but in some places the
treatment is surface-level: using ISO-style function prototypes
and definition syntax, for example. But deep problems remain in
parts, and contraints on engineering resources couple with
economic and business pressures so that it's not going to get
cleaned up any time soon. I'm sure there is UB in it; in fact,
I know there is. But them's the breaks; and yet, customers are
using it in production. Because of this, upgrading toolchains
is laborious and complex, and takes a lot of time, and new
compilers are (rightly) viewed with suspicion. That is not a
great situation, but I don't think anyone is angry at the
compiler people over it.
I think that is a good way to handle the situation. In my projects, I
do not normally upgrade or change toolchains. While I think the risk of
UB is small in my own code, small does not mean non-existent. And for
my work, generated code that behaves correctly in terms of C semantics
but has different execution times or code size might also be an issue -
so changes in toolchains mean a lot of extra testing and qualification.
In addition, for some microcontrollers the toolchains have relatively
small user bases and consequently higher risks of unknown bugs in the >toolchains themselves. Sometimes there are also implementation-specific >features that change between versions (though that is less of an issue
these days).
And just as it's not acceptable to blame compiler writers for
implementating the language as it is defined, it's not really
acceptable to blame programmers either; some of the people who
put the UB there are (literally) dead, and there's just not
enough time in the day to go clean it all up. I wish there was
more compassion for that.
Being dead does not resolve you of the responsibility - the person that >wrote the code with UB is the person who wrote the code with the UB,
just like any other bugs. That person wrote the code with the error.
It might not be fair to hold it against them - there are a great many >possible reasons why it was not their fault (typically management is
more at fault than the coders!). And placing blame is rarely a useful >exercise - usually it does not matter where the bugs came from, only
that they are there and need to be fixed or worked around.
As said earlier, C is what it is. I suspect that it will
continue to make incremental improvements, but we're basically
stuck with what we have.
Agreed.
I'm not a huge fan of Carruth.
In article <110k0mp$329k6$1@dont-email.me>,
David Brown <david.brown@hesbynett.no> wrote:
On 13/06/2026 14:02, Dan Cross wrote:
In article <110ghmv$21vi3$1@dont-email.me>,
David Brown <david.brown@hesbynett.no> wrote:
[snip]Eh...I think those people have a point.
As for my '"modern compilers are evil" crowd' comment, there are people >>>> (not anyone involved in this discussion) who really do fall into that
camp. I've seen people who are experienced and respected developers
make all sorts of accusations to compiler developers, claiming they are >>>> only interested in high scores on synthetic benchmarks and directly
insulting their motivations and integrity, blaming them for "breaking" >>>> their code that relied on the effects of some kinds of UB. It is always >>>> frustrating when you have code that works fine with one compiler
version, but using another compiler results in failure due to UB in your >>>> code - especially if writing correct code gives inefficient results with >>>> the first compiler. And it's fine to say you'd be happier if a
particular thing that is UB in C were not UB - but it is unreasonable to >>>> blame compiler developers for implementing the language as it is defined. >>>
Note, I don't think that "modern compilers are evil" (I mean,
wow, that's a strong word) and I certainly do not think it is
appropriate to malign the people who write them personally over
what one does with code.
I think it is important for tools to be helpful, and it's fine to
complain if a tool is being directly unhelpful - or ask for improvements
when you think it could be better.
Yes.
But I _do_ think it is fair to say that UB is very easy to fall
into in C, that programs that have worked correctly (insofar as
their intended behavior as written) for years can suddenly fail
because latent UB is treated differently in a point revision of
a compiler, and that that (as you point out) can be incredibly
frustrating for the authors.
It can certainly happen, yes. And I fully sympathise on these few
occasions when changes to the standard has meant that code that
previously had defined behaviour, now has different or undefined
behaviour. (However, I think that for some kinds of code, programmers
could be better at specifying exactly what standards their code
requires, and the standards they use when compiling code.)
But it is important to realise that if you write code with UB, it is
/your/ mistake - not the mistake of the compiler developers, or the
mistake of the standards authors. Compiler vendors can (and do!) try to
help programmers find their mistakes - experience shows, however, that
many programmers reach first for bug report forms or complaints in
forums before compiler tools like sanitisers or even enabling warnings
on their builds.
Programming in C is a cooperative effort - including the standards
authors, the compiler vendors, and the C programmers. Each group can
try to help the others, but each is ultimately responsible for their own
part.
Here's the problem that I have with this line of reasoning. C
is a language that has considerable history; there was a large
body of C code written before the first standard was ever
created, in 1988; C was a teenager. And it took many years for
decent quality ANSI C compilers to be ubiquitous. C could
legally drink by then.
"Undefined Behavior", in C, in the manner usually discussed in
this newsgroup, was introduced with the first standard. That
means that there is --- still --- a large body of software that
has "UB" that was put there before UB existed as a thing
programmers needed to worry about in C.
Even once it was a part of C, the concept was communicated
poorly.
Some people seem to delight in this, believing precision in
interpreting the standard in abstruse ways is an expression of
deep technical expertise; but it really is not.
Yes, UB is created by programmers. However, in large systems,
it may be that it was created inadvertantly; someone makes a
change that subtley invalidates some invariant that an unknown
caller far away in the code base (or in another one that relies
on the change via an indirect dependency) and now you've got UB;
locally, everything appears correct; but it's the combination
where the UB manifests.
Regehr called out a dichotomy with UB: programmers using a
language hate it; compiler writers love it.
I think Regehr has made some good points in his writings, but I do not
agree with him on everything.
As a programmer, I am a fan of the concept of UB. I am quite happy with
the idea that operations have a pre-condition, and that if there is no
"right answer" for a given input, I should not provide that input. I
prefer that signed integer arithmetic overflow is UB, and do not want it
to be wrapping or have some other semantics - to me, it is far clearer
that way. If I have UB in my code, it's a bug - no different from any
other bug I might make.
This example makes little sense to me. If you don't want
integer overflow, then don't overflow; the techniques for
avoiding it are pretty well known. But why is specifically
better that it is UB, rather than than trapping in debug
builds, or having IB semantics based on the underlying machine?
It seems to be that the burden on the programmer is the same.
It is the case that in C, there are some kinds of UB that can be quite
subtle. However, you rarely need to risk meeting them. Yes, there are
pitfalls - don't go near them, and they don't matter.
I disagree. I think almost all non-trivial programs have UB to
a greater or lesser extent, whether they intend to or not.
However, it is unfortunately the case that sometimes avoiding UB can be
costly in performance terms. An example would be if you have need of
type-punning - perhaps you have a float in memory and you want to access
it as an uint32_t for some reason. Casting a float * to an uint32_t *
and using that new pointer is UB. Some compilers will nonetheless
generate the code you want after such a cast. Some compilers might not,
depending on details of the rest of the surrounding code, because it is
UB. A non-UB solution would be to use memcpy(), or a type-punning
union. For highly optimising compilers, that's fine - the code
generated by gcc or clang for a memcpy() here is likely to be as
efficient as you could get - directly reading the float from memory to
an integer register. For other compilers, however, you might get a call
to a memcpy() library function in an external DLL, taking orders of
magnitude more cycles. What is the poor programmer to do? Write code
that is portable and correct, but very slow with some implementations?
Write code that "cheats" and is efficient on some implementations but
might not give the desired results on others? Use pre-processor
monstrosities to detect different compilers and adapt accordingly? That
is what I see as the biggest issue resulting from compiler optimisation
based on UB. I don't know what the "best" answer here is.
This is kind of my point. If you need a fast way to convery
Here's my own vignette: I was chatting with a friend who works
on LLVM and clang some time ago. I said, "I don't want UB" and
he replied, "no, you really do." I asked him what he meant and
he responded that I wanted a compiler that is capable of
optimizing my program; "sure, but I still don't want UB." We
went on for a bit, and it became clear that he saw UB as _the_
vehicle for unlocking optimization.
I realized that we were not speaking the same language _at all_.
He and I both wanted a language where we could write programs
that yield efficient object code. He saw UB as essential for
that; but what I want is a language with well-defined semantics
that can be aggressively optimized.
I too want a language with well-defined semantics that can be
aggressively optimised. But I do not see UB as a hinder to that.
UB is literally the opposite of well-defined.
I am happy knowing that I cannot divide by 0,
Yup. That should be a trap.
or find the square root of a negative number (in the real
domain).
Yup. That should be a trap.
I am happy knowing that I cannot add two ints if their sum
overflows the range of their type,
Yup. That should be a trap (if you want wrapping semantics, you
should request it explicitly).
and that I cannot call a function with a different number or
type of parameters than its definition.
Yup. That should be a compile-time error.
I have a great deal of difficulty seeing how things could be
any different, other than in a managed language with significant
overhead from run-time checks - and that goes against the
"aggressively optimised" requirement.
There are existence proofs of other languages that can, and do,
do these things, and do them well. I hate to keep beating this
drum, but I think Rust does well here: in safe Rust, UB is a
compile-time error; in *unsafe* Rust, there are tools to help
find where programmers violate the language's invariants.
Having "well-defined semantics" does not mean the language should accept
anything that happens to fit the syntax and grammar rules, or that all
functions and operations should give a defined result for all inputs.
I never said that it did.
It means that the set of valid inputs is clearly defined, along with the
outputs and effects you get when the inputs are valid.
So I was the one who said "well-defined semantics" and I had a
specific meaning in mind. Your definition is incomplete with
respect to that meaning: in addition to what you said, invalid
inputs should be rejected, either as a compile time error, or by
generating an exception or panic at runtime. If you want to
live dangerously and turn the runtime checks off for performance
reasons, then you get 2's complement behavior for integers or
whatever the machine does for the others.
(There are plenty of points in the C standards where the wording could
make the semantics clearer, or where the range of input values could
easily have been larger - I am not suggesting C is as well-defined as it
could reasonably be.)
It's not just that it's nowhere close to being as well-defined
as it should be, it's because the language as defined permits
behavior that varies far too widely, specifically because of UB.
Consider one of the examples you gave: signed integer overflow.
The standard doesn't say that you _can't_ add two numbers
together if you overflow, it just says that if you do, the
language imposes no requirements on the resulting behavior. It
may trap, it may elide the addition entirely, or it may do it
and let the result be whatever the underlying machine does.
That is, the _language_ does not say that it's a bug; it says
that it's not going to say anything about it at all.
This is one reason the committee is trying to reign some of this
in.
That, I think, is the tension: there was a fundamental breakdown
in communication between the users of the language, and those
defining and implementing it. My subjective sense is that in
the past few years things are getting somewhat better, but it is
hard to evolve something as critical and widely used as C.
Communication between the separate parties is always an issue, and it is
easy for it to be a one-way street with a language standards committee
dictating the rules with little attention to feedback, then compiler
vendors following these rules without listening to the users.
A challenge here, perhaps, is that users are a very diverse group. How
much should compiler vendors cater for those that put a lot of effort
into correctness and want top efficiency, or those that are less
knowledgable about the language but want to avoid the consequences of
their mistakes? What about those working with old code written for
different compilers with different unwritten rules? It is not easy to
please everyone.
I think that's simplistic; not many programmers actively want to
"avoid the consequences of their mistakes." Do you really
believe that they do? If so, why?
Conversely, there *is* this kind of machismo attitude among many
C programmers that it requires a superior intellect to truly
understand this language, and those who do not (or who make any
mistake in their understanding) are simply unworthy. I have
repeatedly observed this over many decades now, and when I see
it, I think that it is odious.
My experience is that most programmers are highly intelligent,
capable people. They are not wrong to want behavior they can
rely on, particularly when things are not obvious, as they
often are not. They also want a language that requires a less
lawyerly read of to understand its semantics; that could go the
way of formality (my preferred approach) or just clearer
exposition. Either would be preferable to the current state.
In fairness, I think the current members of the committee
recognize this.
I am not in any way saying that critics of aspects of C (the language, >>>> the standards, or compiler implementations) should be dismissed or
despised - merely that the example of loop elimination leading to UB and >>>> unexpected results is regularly used as "evidence" by those that hold
extreme positions about C, despite it being very unrealistic for the
issue to cause problems in real coding practice.
The kernel I am working on has about 5 million lines of code.
That code has been evolving for 40 years; some of it predates
the ISO standards and even the ANSI standard. It has been
updated for newer compilers, sure, but in some places the
treatment is surface-level: using ISO-style function prototypes
and definition syntax, for example. But deep problems remain in
parts, and contraints on engineering resources couple with
economic and business pressures so that it's not going to get
cleaned up any time soon. I'm sure there is UB in it; in fact,
I know there is. But them's the breaks; and yet, customers are
using it in production. Because of this, upgrading toolchains
is laborious and complex, and takes a lot of time, and new
compilers are (rightly) viewed with suspicion. That is not a
great situation, but I don't think anyone is angry at the
compiler people over it.
I think that is a good way to handle the situation. In my projects, I
do not normally upgrade or change toolchains. While I think the risk of
UB is small in my own code, small does not mean non-existent. And for
my work, generated code that behaves correctly in terms of C semantics
but has different execution times or code size might also be an issue -
so changes in toolchains mean a lot of extra testing and qualification.
Obviously in a production setting tools should be tested and
qualified. But the danger posed by UB adds unacceptable risk on
large projects, and the burden for updating a toolchain is too
high. That is as much an indictment of the language as of any
particular project.
As a counter example, there was the Harvey project, which was a
fork of Plan 9 where the Plan 9 C dialect was replaced with ISO
C; we accounted for this by having CI build with 6 seperate
compilers; this flushed out a lot of bugs.
I am surprised that more projects do not adopt canary CI builds
against newer toolchains.
In addition, for some microcontrollers the toolchains have relatively
small user bases and consequently higher risks of unknown bugs in the
toolchains themselves. Sometimes there are also implementation-specific
features that change between versions (though that is less of an issue
these days).
Fun fact: part of the reason Google got involved in clang and
LLVM development was because the vendor toolchain for a
particular microcontroller used in android phones was buggy and
would crash (that is, the compiler itself crashed). The
solution was not to live with it; it was to build a better
toolchain.
Google could afford to do that; I recognize not many
organizations can.
And just as it's not acceptable to blame compiler writers for
implementating the language as it is defined, it's not really
acceptable to blame programmers either; some of the people who
put the UB there are (literally) dead, and there's just not
enough time in the day to go clean it all up. I wish there was
more compassion for that.
Being dead does not resolve you of the responsibility - the person that
wrote the code with UB is the person who wrote the code with the UB,
just like any other bugs. That person wrote the code with the error.
See above. Those people may well have written the code before C
was standardized and before UB as we know it now existed. Also,
by definition UB is not an error.
It might not be fair to hold it against them - there are a great many
possible reasons why it was not their fault (typically management is
more at fault than the coders!). And placing blame is rarely a useful
exercise - usually it does not matter where the bugs came from, only
that they are there and need to be fixed or worked around.
Exactly. The footguns hiding in C code that has worked
perfectly for decades, dating back to before the standards
existed, are legion. Caveat emptor.
_Or_ the code may have been written with careful regard for the
standard, but something _else_ may have been changed that now
leads to exposure to UB. For example, perhaps code was written
that multiples two numbers, `a*b`; a known to be `unsigned int`
when written, but `b` is a signed int. But maybe that is hidden
behind a typedef; some time in the future, the typedef is
changed so that `a` is now `unsigned short`; perhaps someone
realized that the domain values never exceed 16 bits and by
changing the definition some critical structure now fits in a
single cache line. But also now the type promotion rules kick
so that `a*b` happens with the factors as `signed int` and in
there exist values of `a` and `b` where `a*b` overflows: UB.
The code had no UB; the change was elsewhere; no one saw this
because the tests all passed and everything looked ok; then
someone upgrades the compiler and now things break.
Who's fault is that?
And no, this is not contrived; this is exactly the sort of thing
that happens on large, long-lived projects.
As said earlier, C is what it is. I suspect that it will
continue to make incremental improvements, but we're basically
stuck with what we have.
Agreed.
...but be careful blaming the programmer.
cross@spitfire.i.gajendra.net (Dan Cross) wrote or quoted:
I'm not a huge fan of Carruth.
(Text after "| " below was generated by a chatbot asked to explain
narrow contracts and the reduction of efficiency by defining UB.)
(Let me guess: You are not a huge fan of chatbots either!
Ok, that was easy.)
Chandler talked about how narrow contracts allow optimizations.
| - Wide Contract: The function guarantees to handle all possible inputs
| gracefully, usually by returning an error code or throwing an
| exception. (e.g., "If the pointer is null, return ERR_NULL_PTR").
|
| - Narrow Contract: The function only guarantees correct behavior if
| the caller meets specific preconditions. If the preconditions are
| violated, the behavior is undefined.
|
| When is it appropriate to have a narrow contract? Always, when
| performance, memory footprint, or direct hardware control are
| paramount. In operating system kernels, embedded systems, real-time
| applications, and high-performance computing, the overhead of
| validating every pointer, checking every array bound, and verifying
| every integer range is unacceptable.
UB means precisely that I can choose trapping, or IB, or optimising on
the assumption it does not happen.
Making this UB is an admission of the blindingly obvious - there is no correct answer when signed integer overflow occurs. It tells
programmers that it is a mistake to let your arithmetic overflow, and
it allows tools to help programmers avoid these mistakes, and it
allows compilers to give programmers the most efficient results from
known good code rather than adding unnecessary run-time checks that
are never triggered.
I am happy knowing that I cannot divide by 0,Yup. That should be a trap.
For some programs, yes. For others, no.
I don't want to pay the price for checks, traps, and limited
re-arrangements and optimisations when I know my expressions don't
overflow. But I am also happy to be able to get a trap when I ask for
it.
David Brown <david.brown@hesbynett.no> writes:
[...]
UB means precisely that I can choose trapping, or IB, or optimising on
the assumption it does not happen.
No, it means that the implementation can make that choice (or allow you
to make that choice). A conforming compiler could generate code on the assumption that signed overflow never happens, and not give the
programmer any options.
[...]
Making this UB is an admission of the blindingly obvious - there is no
correct answer when signed integer overflow occurs. It tells
programmers that it is a mistake to let your arithmetic overflow, and
it allows tools to help programmers avoid these mistakes, and it
allows compilers to give programmers the most efficient results from
known good code rather than adding unnecessary run-time checks that
are never triggered.
Trapping or raising/throwing an exception on overflow would also be an admission of the blindingly obvious.
And a sufficiently clever compiler
can omit some (not all) checks in cases where it can be statically
proved that overflow doesn't occur, and/or hoist some checks out of
loops.
Of course those kinds of checks are not in the "spirit of C".
[...]
I am happy knowing that I cannot divide by 0,Yup. That should be a trap.
For some programs, yes. For others, no.
What's the difference between these programs?
[...]
I don't want to pay the price for checks, traps, and limited
re-arrangements and optimisations when I know my expressions don't
overflow. But I am also happy to be able to get a trap when I ask for
it.
I don't want to pay the price of checking for syntax errors when I know
my code is syntactically correct. But I never know that, because I'm fallible.
I admit that's not a very strong argument. There are real differences between compile-time and run-time checks.
On 15/06/2026 00:55, Keith Thompson wrote:<snip>
David Brown <david.brown@hesbynett.no> writes:
[...]
Making this UB is an admission of the blindingly obvious - there is no
correct answer when signed integer overflow occurs. It tells
programmers that it is a mistake to let your arithmetic overflow, and
it allows tools to help programmers avoid these mistakes, and it
allows compilers to give programmers the most efficient results from
known good code rather than adding unnecessary run-time checks that
are never triggered.
Trapping or raising/throwing an exception on overflow would also be an
admission of the blindingly obvious.
It is obvious - to me, anyway - that signed overflow is a mistake in the code. It is trying to do something that cannot be done. What is the single-digit sum of 5 and 8? There is no answer. The answer is not 3,
or 9. Putting your hand in the air and asking the teacher for help
might be appropriate sometimes, but it is not a correct answer.
Throwing some kind of exception or trap can definitely be helpful at
times. And I agree that it would make it obvious that there has been a problem detected. But throwing exceptions or traps can cause more
problems (the Ariane 5 failure was caused by the exception handler, not
the overflow fault). That does not mean it is better to ignore
overflows - it means there is no appropriate action that is suitable in every situation. I am far from convinced that there is even a
reasonable choice of default action that could be usefully made.
And a sufficiently clever compiler
can omit some (not all) checks in cases where it can be statically
proved that overflow doesn't occur, and/or hoist some checks out of
loops.
Sure - but in practice having strict overflow checks would significantly reduce optimisation and re-arrangement possibilities, as well as having
to include the checks themselves. You might allow non-strict checks in
some manner (thus allowing optimisations like "a + b - a" reducing to
just "b"), but I think that might be hard to specify and would reduce
the debugging help of the checks.
The correct way to handle the situation is to avoid it - be sure that
you are not dividing by zero in the first place. Identify and handle
the problem where it occurs - when this zero is created, or the circumstances leading to that point - rather than trying to do a
post-mortem after the failed division. And if you are doing that, then
what benefit is there in having trapping for division by zero? It
becomes just a waste of effort.
David Brown <david.brown@hesbynett.no> wrote:
On 15/06/2026 00:55, Keith Thompson wrote:<snip>
David Brown <david.brown@hesbynett.no> writes:
[...]
Making this UB is an admission of the blindingly obvious - there is no >>>> correct answer when signed integer overflow occurs. It tells
programmers that it is a mistake to let your arithmetic overflow, and
it allows tools to help programmers avoid these mistakes, and it
allows compilers to give programmers the most efficient results from
known good code rather than adding unnecessary run-time checks that
are never triggered.
Trapping or raising/throwing an exception on overflow would also be an
admission of the blindingly obvious.
It is obvious - to me, anyway - that signed overflow is a mistake in the
code. It is trying to do something that cannot be done. What is the
single-digit sum of 5 and 8? There is no answer. The answer is not 3,
or 9. Putting your hand in the air and asking the teacher for help
might be appropriate sometimes, but it is not a correct answer.
Throwing some kind of exception or trap can definitely be helpful at
times. And I agree that it would make it obvious that there has been a
problem detected. But throwing exceptions or traps can cause more
problems (the Ariane 5 failure was caused by the exception handler, not
the overflow fault). That does not mean it is better to ignore
overflows - it means there is no appropriate action that is suitable in
every situation. I am far from convinced that there is even a
reasonable choice of default action that could be usefully made.
And a sufficiently clever compiler
can omit some (not all) checks in cases where it can be statically
proved that overflow doesn't occur, and/or hoist some checks out of
loops.
Sure - but in practice having strict overflow checks would significantly
reduce optimisation and re-arrangement possibilities, as well as having
to include the checks themselves. You might allow non-strict checks in
some manner (thus allowing optimisations like "a + b - a" reducing to
just "b"), but I think that might be hard to specify and would reduce
the debugging help of the checks.
IMO resonable and easy definition is: computation either delivers mathematically correct result or traps, and it is not allowed to
trap in cases where naive bottom-up evaluation does not trap.
In more formal way optimization is not allowed to introduce
stronger precondition, but may weaken it.
<snip>
The correct way to handle the situation is to avoid it - be sure that
you are not dividing by zero in the first place. Identify and handle
the problem where it occurs - when this zero is created, or the
circumstances leading to that point - rather than trying to do a
post-mortem after the failed division. And if you are doing that, then
what benefit is there in having trapping for division by zero? It
becomes just a waste of effort.
What is value of certification required for some software? If
programmer did good job then program will work correctly.
Trap give assurance that programmer indeed correctly handled
tricky problem.
And once you know that computation works
according to math rules other forms of verification are easier.
You also seem to have bias to real time control: if you need
value just at given moment, then it is hard to do something
reasonable. But at least in some control areas there is
notion of "safe state", for example working heavy machine
is dangerous, stopped one usually is considerd safe. If
there is safe state, then anything not expected by program
should trigger transition to safe state.
In general computation, if you need correct value and have some
time there are options which may involve re-doing computation at
higher precistion, which may get rid of occasional overflows
and divisions by zero due to overflow. Division by zero may
be due to bad input data, traps allow indentification of
such data (doing it in other way may be computationaly quite
expensive).
ram@zedat.fu-berlin.de (Stefan Ram) writes:
cross@spitfire.i.gajendra.net (Dan Cross) wrote or quoted:
I'm not a huge fan of Carruth.
(Text after "| " below was generated by a chatbot asked to explain
narrow contracts and the reduction of efficiency by defining UB.)
(Let me guess: You are not a huge fan of chatbots either!
Ok, that was easy.)
Chandler talked about how narrow contracts allow optimizations.
| - Wide Contract: The function guarantees to handle all possible inputs
| gracefully, usually by returning an error code or throwing an
| exception. (e.g., "If the pointer is null, return ERR_NULL_PTR").
|
| - Narrow Contract: The function only guarantees correct behavior if
| the caller meets specific preconditions. If the preconditions are
| violated, the behavior is undefined.
|
| When is it appropriate to have a narrow contract? Always, when
| performance, memory footprint, or direct hardware control are
| paramount. In operating system kernels, embedded systems, real-time
| applications, and high-performance computing, the overhead of
| validating every pointer, checking every array bound, and verifying
| every integer range is unacceptable.
I have a recollection that a version of IBM's MVS operating
system did, indeed, validate input and output arguments to kernel
functions.
Indeed, google says it was called MVS/SP and later MVS/XA (extended addressing).
On 15/06/2026 12:43, Waldek Hebisch wrote:
David Brown <david.brown@hesbynett.no> wrote:
On 15/06/2026 00:55, Keith Thompson wrote:<snip>
David Brown <david.brown@hesbynett.no> writes:
[...]
Making this UB is an admission of the blindingly obvious - there is no >>>>> correct answer when signed integer overflow occurs. It tells
programmers that it is a mistake to let your arithmetic overflow, and >>>>> it allows tools to help programmers avoid these mistakes, and it
allows compilers to give programmers the most efficient results from >>>>> known good code rather than adding unnecessary run-time checks that
are never triggered.
Trapping or raising/throwing an exception on overflow would also be an >>>> admission of the blindingly obvious.
It is obvious - to me, anyway - that signed overflow is a mistake in the >>> code. It is trying to do something that cannot be done. What is the
single-digit sum of 5 and 8? There is no answer. The answer is not 3,
or 9. Putting your hand in the air and asking the teacher for help
might be appropriate sometimes, but it is not a correct answer.
Throwing some kind of exception or trap can definitely be helpful at
times. And I agree that it would make it obvious that there has been a
problem detected. But throwing exceptions or traps can cause more
problems (the Ariane 5 failure was caused by the exception handler, not
the overflow fault). That does not mean it is better to ignore
overflows - it means there is no appropriate action that is suitable in
every situation. I am far from convinced that there is even a
reasonable choice of default action that could be usefully made.
And a sufficiently clever compiler
can omit some (not all) checks in cases where it can be statically
proved that overflow doesn't occur, and/or hoist some checks out of
loops.
Sure - but in practice having strict overflow checks would significantly >>> reduce optimisation and re-arrangement possibilities, as well as having
to include the checks themselves. You might allow non-strict checks in
some manner (thus allowing optimisations like "a + b - a" reducing to
just "b"), but I think that might be hard to specify and would reduce
the debugging help of the checks.
IMO resonable and easy definition is: computation either delivers
mathematically correct result or traps, and it is not allowed to
trap in cases where naive bottom-up evaluation does not trap.
In more formal way optimization is not allowed to introduce
stronger precondition, but may weaken it.
It is always the case that an implementation can weaken preconditions
and strengthen postconditions and remain correct - though it might then
be less efficient than you expect. But if you are /requiring/ a weaker precondition and /requiring/ a strong postcondition - such as by
insisting on traps on overflow - you are changing the function or
operation specification, and it is not necessarily a good thing.
In C, the integer addition operation "c = a + b;" has a precondition :
(a + b) <= INT_MAX, (a + b) >= INT_MIN
It has the postcondition :
c == a + b
Saying that it must trap if there is overflow weakens the precondition
to any "a" and "b", but makes the postcondition much more complicated.
It means it is no longer true that the result of an addition operation
is the sum of the operands.
Addition is no longer a "pure" function -
now it has side-effects that are completely unpredictable at the site of use. Programmers can no longer rely on the timing of the operation,
stack usage, interaction with other code, or even that the operation
ever finishes.
If your code is correct, and overflow never happens, then this is all a
big disadvantage in terms of understanding and analysing the code. And
it does not in any way reduce the effort needed to be sure that your
inputs are appropriate for getting the desired results of the operation.
Trapping like this can certainly be useful for debugging. But as a
general feature it gives a false sense of security, complicates
mathematical analysis, introduces massive additional possible code path choices which are either real or almost certainly untested in practice,
or not real (because the compiler can see they are not taken) and untestable.
That is not qualitatively worse than "who knows what will
happen" UB, but it is not significantly better.
<snip>
The correct way to handle the situation is to avoid it - be sure that
you are not dividing by zero in the first place. Identify and handle
the problem where it occurs - when this zero is created, or the
circumstances leading to that point - rather than trying to do a
post-mortem after the failed division. And if you are doing that, then
what benefit is there in having trapping for division by zero? It
becomes just a waste of effort.
What is value of certification required for some software? If
programmer did good job then program will work correctly.
Yes.
Trap give assurance that programmer indeed correctly handled
tricky problem.
No, it certainly does not. And one of the reasons to dislike traps is
that it makes people think like that. A trap can only happen if the programmer did /not/ handle the problem correctly.
And I expect that if
the programmer is able to write an appropriate specific trap handler for
the failing expression (rather than a program-global "crash with error message" handler), then he/she would be able to avoid the problem in the first place.
Sometimes, of course, you are trying to write code that has some input
which is supposed to be correct, but you are not sure - and you can't
change the calling code. How you handle that situation will depend on
the program and the situation. But I don't see trapping as "correct handling" unless the whole program is written with the expectation of
traps for error handling. You might, however, end up deciding that
trapping is the least bad option.
And once you know that computation works
according to math rules other forms of verification are easier.
You also seem to have bias to real time control: if you need
value just at given moment, then it is hard to do something
reasonable. But at least in some control areas there is
notion of "safe state", for example working heavy machine
is dangerous, stopped one usually is considerd safe. If
there is safe state, then anything not expected by program
should trigger transition to safe state.
I think if you are /not/ concerned with high efficiency in the code,
then you should be seriously questioning the choice of C as the language
in the first place. And even if you use C, there are often things you
can do to avoid having problems in the first place. The obvious one for integer overflow is to make more use of bigger types.
In general computation, if you need correct value and have some
time there are options which may involve re-doing computation at
higher precistion, which may get rid of occasional overflows
and divisions by zero due to overflow. Division by zero may
be due to bad input data, traps allow indentification of
such data (doing it in other way may be computationaly quite
expensive).
[snip]
Maybe there is scope for compilers to have better options for handling
old code, other than the usual "Use -O0 to avoid optimising on UB"
solution. You could come a long way with a "treat all variables as >volatile" flag, for example.
[snip]
That can certainly happen. But that's just bugs in the code. I don't
see why UB should be considered as something special here.
People
making changes to existing code sometimes misunderstand things, or >accidentally break something that worked before. That's life as a >programmer, and there are techniques to reduce the risk - code reviews, >linters, testing regimes, etc. Nothing gives 100% guarantees, and >everything has to weigh risks, consequences, costs and resources. UB is
not special here.
UB means precisely that I can choose trapping, or IB, or optimising on
the assumption it does not happen. If signed integer overflow were
defined as wrapping, then compilers could not put in traps to catch the >errors because as far as the language is concerned, they are not errors.
If they are defined as causing traps, then that's the semantics -
compilers could not optimise code assuming overflow does not happen,
unless it can prove there is no overflow.
And making it defined behaviour gives programmers the mistaken idea that >they don't need to avoid overflow because there is no UB.
Making this UB is an admission of the blindingly obvious - there is no >correct answer when signed integer overflow occurs. It tells
programmers that it is a mistake to let your arithmetic overflow, and it >allows tools to help programmers avoid these mistakes, and it allows >compilers to give programmers the most efficient results from known good >code rather than adding unnecessary run-time checks that are never >triggered.
[snip]
(I think you missed a bit of your answer here?)
[snip]
I realized that we were not speaking the same language _at all_.
He and I both wanted a language where we could write programs
that yield efficient object code. He saw UB as essential for
that; but what I want is a language with well-defined semantics
that can be aggressively optimized.
I too want a language with well-defined semantics that can be
aggressively optimised. But I do not see UB as a hinder to that.
UB is literally the opposite of well-defined.
I want good definitions of things that should be defined. Things that >cannot have good definitions, are fine left undefined. A language
standard should not be trying to define the behaviour of /everything/.
I am happy knowing that I cannot divide by 0,
Yup. That should be a trap.
For some programs, yes. For others, no.
or find the square root of a negative number (in the real
domain).
Yup. That should be a trap.
For some programs, yes. For others, no.
I am happy knowing that I cannot add two ints if their sum
overflows the range of their type,
Yup. That should be a trap (if you want wrapping semantics, you
should request it explicitly).
I agree that wrapping semantics should be something you have to ask for.
(As an aside, I think it is a mistake for languages to have types that
have wrapping semantics - it's the operations that should wrap, not the >types. Zig gets it right by distinguishing between "x + y" and "x +% y".)
I don't want to pay the price for checks, traps, and limited
re-arrangements and optimisations when I know my expressions don't
overflow. But I am also happy to be able to get a trap when I ask for it.
But I think it is equally bad to give things a definition simply to be
able to say there is no UB.
It is, IMHO, entirely /wrong/ of a language
to define integer overflow as wrapping simply so that it is not UB. I
do not see a guaranteed incorrect result that likely has catastrophic >consequences in a program as being better than UB.
(I believe Rust
defines integer overflow as trapping in "debug" mode and wrapping in >"release" mode, which I think is a horrendous idea.)
So I was the one who said "well-defined semantics" and I had a
specific meaning in mind. Your definition is incomplete with
respect to that meaning: in addition to what you said, invalid
inputs should be rejected, either as a compile time error, or by
generating an exception or panic at runtime. If you want to
live dangerously and turn the runtime checks off for performance
reasons, then you get 2's complement behavior for integers or
whatever the machine does for the others.
I am all in favour of compile-time checks and rejecting code with errors >(not just UB) as soon as possible. The "perfect" language is one where
you really can follow the old Ada saying - if you can make it compile,
it's ready to ship.
I don't live dangerously by not having run-time checks on integer
overflows. I make sure my code does not have them, so checks are >unnecessary. For some of my code, if it "panicked" somewhere in >calculations, that would be a disaster - when you have code controlling >power electronics, a sudden stop can mean short-circuits and components >releasing their magic grey smoke.
Thinking that run-time checks will save you from UB is wishful thinking.
How are you going to have run-time checks that a pointer parameter
points to a valid object of the right type?
You can check for a
null-pointer, but that's about it. Some things that are potential UB in
C are inherent in the type of language - checking for such problems (at >compile-time or run-time) needs a language that has a different way of >handling objects and pointers so that you cannot have arbitrary pointers
to arbitrary objects.
C is not a language suitable for such run-time or compile-time checks -
it is a language for getting the highest efficiency because the
programmer takes responsibility for getting things right.
You are
correct that large programs normally have bugs (of which UB is just one >class) - the risk of bugs goes up with the size of the code base. The >corollary is that C is not a language suitable for large programs.
Rust, I think, reduces the risk of some kinds of bugs. So does C++,
when used carefully. Most code, however, is best written in languages
where these issues cannot occur - or at least where checks can be done >without a measurable impact. For example, if you use Python, you never
have integer overflow, and you never have invalid pointers.
[snip]
Consider one of the examples you gave: signed integer overflow.
The standard doesn't say that you _can't_ add two numbers
together if you overflow, it just says that if you do, the
language imposes no requirements on the resulting behavior. It
may trap, it may elide the addition entirely, or it may do it
and let the result be whatever the underlying machine does.
That is, the _language_ does not say that it's a bug; it says
that it's not going to say anything about it at all.
I'd be happy for the C standard to say that signed integer overflow is a >bug, or that code is not allowed to overflow its integer arithmetic. I >would not be happy if it said compilers must trap on the bug or handle
it in some specific way - what happens when a bug is reached is still
UB. And if the wording of the standard were changed to call it a "bug" >rather than "UB", it would make absolutely zero difference to the way I >write my code.
[snip]
In my field, people usually put a lot of effort into writing code simply
and clearly. You avoid mistakes not by being "clever", but by being >meticulous and careful. I don't think successful C programming requires >greater intellect, knowledge or experience compared to other programming >languages - but it /does/ require an appropriate attitude. You are
working with sharp knives - pay attention to what you are doing, and
you'll be fine.
My experience is that most programmers are highly intelligent,
capable people. They are not wrong to want behavior they can
rely on, particularly when things are not obvious, as they
often are not. They also want a language that requires a less
lawyerly read of to understand its semantics; that could go the
way of formality (my preferred approach) or just clearer
exposition. Either would be preferable to the current state.
I was avoiding signed integer overflow long before I had read any C >standards or even knew about the term "UB". Programming in C does not
need a lawyer knowledge of the language. It is just like programming in
any other programming language - use features that you know are correct,
and if you want to do something and don't know how to do so correctly,
look it up.
[snip]
Exactly. The footguns hiding in C code that has worked
perfectly for decades, dating back to before the standards
existed, are legion. Caveat emptor.
_Or_ the code may have been written with careful regard for the
standard, but something _else_ may have been changed that now
leads to exposure to UB. For example, perhaps code was written
that multiples two numbers, `a*b`; a known to be `unsigned int`
when written, but `b` is a signed int. But maybe that is hidden
behind a typedef; some time in the future, the typedef is
changed so that `a` is now `unsigned short`; perhaps someone
realized that the domain values never exceed 16 bits and by
changing the definition some critical structure now fits in a
single cache line. But also now the type promotion rules kick
so that `a*b` happens with the factors as `signed int` and in
there exist values of `a` and `b` where `a*b` overflows: UB.
The code had no UB; the change was elsewhere; no one saw this
because the tests all passed and everything looked ok; then
someone upgrades the compiler and now things break.
Who's fault is that?
There's no simple answer here.
But one thing is clear to me - "UB" is irrelevant here (and in many of
your points). It would not matter if everything had fully defined >behaviour. The point is that something is changed in one part of the
code that has unexpected consequences in another part of the code. Who >cares if there is UB or not? The issue is that the code does not work
as intended or expected. UB can provide situations where you have >unexpected bugs - but so can all sorts of other things.
And no, this is not contrived; this is exactly the sort of thing
that happens on large, long-lived projects.
As said earlier, C is what it is. I suspect that it will
continue to make incremental improvements, but we're basically
stuck with what we have.
Agreed.
...but be careful blaming the programmer.
Or the language, or the tools.
Here's the problem that I have with this line of reasoning. C
is a language that has considerable history; there was a large
body of C code written before the first standard was ever
created, in 1988; C was a teenager. And it took many years for
decent quality ANSI C compilers to be ubiquitous. C could
legally drink by then.
"Undefined Behavior", in C, in the manner usually discussed in
this newsgroup, was introduced with the first standard. That
means that there is --- still --- a large body of software that
has "UB" that was put there before UB existed as a thing
programmers needed to worry about in C.
On 14/06/2026 16:33, Dan Cross wrote:
...
Here's the problem that I have with this line of reasoning. C
is a language that has considerable history; there was a large
body of C code written before the first standard was ever
created, in 1988; C was a teenager. And it took many years for
decent quality ANSI C compilers to be ubiquitous. C could
legally drink by then.
"Undefined Behavior", in C, in the manner usually discussed in
this newsgroup, was introduced with the first standard. That
means that there is --- still --- a large body of software that
has "UB" that was put there before UB existed as a thing
programmers needed to worry about in C.
"undefined behavior", defined as "behavior ... for which this
international standard imposes no requirements" Was introduced by the
first standard. However, before there was a standard there was K&R C,
the closest thing they had to a standard. And though the phrase
"undefined behavior" was not in use, there was "behavior for which K&R C >imposes no requirements". In fact, there was a great deal more of it,
since K&R C was not written as carefully and precisely as the first
standard, so it left a great deal more behavior that was "undefined by >omission of any relevant definition" than there was in the first standard.
David Brown <david.brown@hesbynett.no> wrote:
On 15/06/2026 12:43, Waldek Hebisch wrote:
David Brown <david.brown@hesbynett.no> wrote:
On 15/06/2026 00:55, Keith Thompson wrote:<snip>
David Brown <david.brown@hesbynett.no> writes:
[...]
Making this UB is an admission of the blindingly obvious - there is no >>>>>> correct answer when signed integer overflow occurs. It tells
programmers that it is a mistake to let your arithmetic overflow, and >>>>>> it allows tools to help programmers avoid these mistakes, and it
allows compilers to give programmers the most efficient results from >>>>>> known good code rather than adding unnecessary run-time checks that >>>>>> are never triggered.
Trapping or raising/throwing an exception on overflow would also be an >>>>> admission of the blindingly obvious.
It is obvious - to me, anyway - that signed overflow is a mistake in the >>>> code. It is trying to do something that cannot be done. What is the
single-digit sum of 5 and 8? There is no answer. The answer is not 3, >>>> or 9. Putting your hand in the air and asking the teacher for help
might be appropriate sometimes, but it is not a correct answer.
Throwing some kind of exception or trap can definitely be helpful at
times. And I agree that it would make it obvious that there has been a >>>> problem detected. But throwing exceptions or traps can cause more
problems (the Ariane 5 failure was caused by the exception handler, not >>>> the overflow fault). That does not mean it is better to ignore
overflows - it means there is no appropriate action that is suitable in >>>> every situation. I am far from convinced that there is even a
reasonable choice of default action that could be usefully made.
And a sufficiently clever compiler
can omit some (not all) checks in cases where it can be statically
proved that overflow doesn't occur, and/or hoist some checks out of
loops.
Sure - but in practice having strict overflow checks would significantly >>>> reduce optimisation and re-arrangement possibilities, as well as having >>>> to include the checks themselves. You might allow non-strict checks in >>>> some manner (thus allowing optimisations like "a + b - a" reducing to
just "b"), but I think that might be hard to specify and would reduce
the debugging help of the checks.
IMO resonable and easy definition is: computation either delivers
mathematically correct result or traps, and it is not allowed to
trap in cases where naive bottom-up evaluation does not trap.
In more formal way optimization is not allowed to introduce
stronger precondition, but may weaken it.
It is always the case that an implementation can weaken preconditions
and strengthen postconditions and remain correct - though it might then
be less efficient than you expect. But if you are /requiring/ a weaker
precondition and /requiring/ a strong postcondition - such as by
insisting on traps on overflow - you are changing the function or
operation specification, and it is not necessarily a good thing.
In C, the integer addition operation "c = a + b;" has a precondition :
(a + b) <= INT_MAX, (a + b) >= INT_MIN
It has the postcondition :
c == a + b
Saying that it must trap if there is overflow weakens the precondition
to any "a" and "b", but makes the postcondition much more complicated.
No. Precondition is the same. Postcondition has additional term "computation finished with no traps".
It means it is no longer true that the result of an addition operation
is the sum of the operands.
Oposite of that: no traps means that regardless of precondition
the result of an addition operation is the sum of the operands.
Addition is no longer a "pure" function -
now it has side-effects that are completely unpredictable at the site of
use. Programmers can no longer rely on the timing of the operation,
stack usage, interaction with other code, or even that the operation
ever finishes.
The difference is that without traps programmers do not know if
arithmetic operations give correct result.
With traps they do
not know if program will successfully finish, but if it
finishes they know that arithmetic gave correct results.
If your code is correct, and overflow never happens, then this is all a
big disadvantage in terms of understanding and analysing the code. And
it does not in any way reduce the effort needed to be sure that your
inputs are appropriate for getting the desired results of the operation.
One needs to use correct formulas, there is no way around that.
Without traps programmer must analyse ranges of all intermetiate
expressions. That is tedious and error prone.
People work
around that by activating traps during testing, but it is
quite hard to find worst case values, so errors may be
easily missed during testing. Having traps active during
production runs means that you may discover problem. You
apparently think that ignoring possible problems at
runtime is good thing.
For simple programs you may analyze
it well enough to be sure that nothing bad happens at
runtime, but in general computing we use a lot of "interesting"
programs which are too complex to analyse. We hope that
they will run OK, but have no proof. Sometimes hope is
based on statistical tests and on low probability input
program may fail. Traps are useful to make sure that
wrong results will not propagate further.
Trapping like this can certainly be useful for debugging. But as a
general feature it gives a false sense of security, complicates
mathematical analysis, introduces massive additional possible code path
choices which are either real or almost certainly untested in practice,
or not real (because the compiler can see they are not taken) and
untestable.
You get extra code paths only if you attempt to handle traps.
Trapping of overflows gives you assurance that in computation that
you did and which finished with no traps there were no errors of
certain kind (that is wrong results due to overflow). That is
really not different than insistence on static types.
Neither
assures you of no bugs, but each tells you that some bugs
did not happen. Of course, trapping at runtime is less
satisfactory than compile time checking, but tight a priori
bounds on ranges are notoriusly hard to obtain, so trapping
is the best we can have for high performance software with
current state of art.
That is not qualitatively worse than "who knows what will
happen" UB, but it is not significantly better.
<snip>
The correct way to handle the situation is to avoid it - be sure that
you are not dividing by zero in the first place. Identify and handle
the problem where it occurs - when this zero is created, or the
circumstances leading to that point - rather than trying to do a
post-mortem after the failed division. And if you are doing that, then >>>> what benefit is there in having trapping for division by zero? It
becomes just a waste of effort.
What is value of certification required for some software? If
programmer did good job then program will work correctly.
Yes.
Trap give assurance that programmer indeed correctly handled
tricky problem.
No, it certainly does not. And one of the reasons to dislike traps is
that it makes people think like that. A trap can only happen if the
programmer did /not/ handle the problem correctly.
Yes.
And I expect that if
the programmer is able to write an appropriate specific trap handler for
the failing expression (rather than a program-global "crash with error
message" handler), then he/she would be able to avoid the problem in the
first place.
Rather non-specific trap handler could work as "redo the computation
in arbitrary precision". If problem (like division by zero) persists,
then there is logic bug, otherwise it means that precision was
inadequate and problem is resolved.
Howver, you should think about such traps similarly to parity error
which can be signaled by some hardware. There is low but nonzero
probablity that such error can occur. Parity check gives you
reasonable chance to detect it.
Handling is at least as problematic
as with overflow. Absence of traps gives you less info: no
overflow traps mean no overflow, no parity traps means that
parity was correct, but intent of parity check it to discover bit
error and they are possible even with correct parity. So, do you
think that parity check inside MCU-s are useless?
Sometimes, of course, you are trying to write code that has some input
which is supposed to be correct, but you are not sure - and you can't
change the calling code. How you handle that situation will depend on
the program and the situation. But I don't see trapping as "correct
handling" unless the whole program is written with the expectation of
traps for error handling. You might, however, end up deciding that
trapping is the least bad option.
And once you know that computation works
according to math rules other forms of verification are easier.
You also seem to have bias to real time control: if you need
value just at given moment, then it is hard to do something
reasonable. But at least in some control areas there is
notion of "safe state", for example working heavy machine
is dangerous, stopped one usually is considerd safe. If
there is safe state, then anything not expected by program
should trigger transition to safe state.
I think if you are /not/ concerned with high efficiency in the code,
Well, if efficiency does not matter traps can be implemented as
a software layer above the language. Or one can use arbitrary
precision arithmetic. Traps matter when efficiency matters,
so they should be implemented in place giving best efficiency,
at best in CPU and if that is not possible then in optimizing
compiler.
then you should be seriously questioning the choice of C as the language
in the first place. And even if you use C, there are often things you
can do to avoid having problems in the first place. The obvious one for
integer overflow is to make more use of bigger types.
Which may be best choice if efficiency is not important. But
some calculations require surprisingly large accuracy to avoid
overflow. Worse, in vast majority of cases lower accuracy
may be adequate, so there is pressure to use "sufficient"
accuracy overlooking special cases.
In general computation, if you need correct value and have some
time there are options which may involve re-doing computation at
higher precistion, which may get rid of occasional overflows
and divisions by zero due to overflow. Division by zero may
be due to bad input data, traps allow indentification of
such data (doing it in other way may be computationaly quite
expensive).
One might also define data structures for control and status
registers using bitfield structs.
e.g. for the SATA UAHC_GLB_OOBR register:
union UAHC_GBL_OOBR {
uint32_t u;
struct UAHC_GBL_OOBR_s {
#if __BYTE_ORDER == __BIG_ENDIAN
uint32_t we : 1; /**< R/W/H - Write enable. */
uint32_t cwmin : 7; /**< R/W/H - COMWAKE minimum value [...] */
uint32_t cwmax : 8; /**< R/W/H - COMWAKE maximum value [...] */
uint32_t cimin : 8; /**< R/W/H - COMINIT minimum value [...] */
uint32_t cimax : 8; /**< R/W/H - COMINIT maximum value [...] */
#else
uint32_t cimax : 8;
uint32_t cimin : 8;
uint32_t cwmax : 8;
uint32_t cwmin : 7;
uint32_t we : 1;
#endif
} s;
};
Dan Cross <cross@spitfire.i.gajendra.net> wrote:
In article <1100g0e$1lt8i$1@kst.eternal-september.org>,
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
and in fact
it *won't* occur during execution because foo() isn't called.
A compiler can't generate code with arbitrary behavior just
because it can't prove that there will be no UB. If it could,
every signed or floating-point arithmetic operation with unknown
operand values would grant the same permission.
But that's not the situation here. The situation is that the
compiler can prove that something _is_ UB.
In the program quoted at the top of this post, the UB occurs in
a function foo() that's never called. A compiler can replace the
body of foo() with a trap, and it can certainly warn about the UB,
but I don't believe it can reject the entire program. A clever
compiler could prove that the UB never occurs.
So there are two things that are at play here.
First, this notion that UB is _only_ a runtime matter. The text
of the standard contradicting that aside, if a translator can
detect that the behavior of a construct is provably undefined if
executed, then it seems axiomatic that UB is clearly something
that plays a role at translation time, as well.
I think that this paragraph (and several other it this post and
other posts) represent fundamental misanderstanding. This may
be due to the way C standard is written. AFAIK Extended Pascal
standard (once you translate terminalogy) states the same things as
C about UB, but in clearer way. Some relevant parts below:
: 3.1 Dynamic-violation
: A violation by a program of the requirements of this International
: Standard that a processor is permitted to leave undetected up to,
: but not beyond, execution of the declaration, definition, or
: statement that exhibits (see clause 6) the dynamic-violation.
: 3.2 Error
: A violation by a program of the requirements of this International
: Standard that a processor is permitted to leave undetected.
...
: 5.1 Processors
...
: e) be able to determine whether or not the program violates any
: requirements of this International Standard, where such a
violation is : not designated an error or dynamic-violation,
...
: 5.2 Programs
...
: b) if it conforms at level 1, use only those features of the
language : specified in clause 6;
UB in C standard corresponds with 'error' in Pascal standard. [...]
I think that lawyerish style of current C standard is mostly
inertia,
and making standard more mathematical would improve it.
But giving formal semantic in the standard would mean
significantly bigger change.
antispam@fricas.org (Waldek Hebisch) writes:
[...]
I think that lawyerish style of current C standard is mostly
inertia,
I wouldn't use a term like lawyerish to describe the text in the
ISO C standard. Can you explain what quality you mean to ascribe
to "lawyerish" writing in the C standard without using any term
related to lawyering or legal documents?
and making standard more mathematical would improve it.
Could you elaborate on that statement? In what ways would giving
a more mathematical treatment of C semantics improve the quality
of the ISO C document? How would doing that advance the stated
purposes or goals of the C standard?
But giving formal semantic in the standard would mean
significantly bigger change.
Due to the nature of C, I believe it is effectively impossible to
give a formal mathematical definition of the semantics of C. Do
you think such a thing is feasible or practicable? If so can you
explain the reasoning behind your thinking?
antispam@fricas.org (Waldek Hebisch) writes:
Dan Cross <cross@spitfire.i.gajendra.net> wrote:
In article <1100g0e$1lt8i$1@kst.eternal-september.org>,
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
and in fact
it *won't* occur during execution because foo() isn't called.
A compiler can't generate code with arbitrary behavior just
because it can't prove that there will be no UB. If it could,
every signed or floating-point arithmetic operation with unknown
operand values would grant the same permission.
But that's not the situation here. The situation is that the
compiler can prove that something _is_ UB.
In the program quoted at the top of this post, the UB occurs in
a function foo() that's never called. A compiler can replace the
body of foo() with a trap, and it can certainly warn about the UB,
but I don't believe it can reject the entire program. A clever
compiler could prove that the UB never occurs.
So there are two things that are at play here.
First, this notion that UB is _only_ a runtime matter. The text
of the standard contradicting that aside, if a translator can
detect that the behavior of a construct is provably undefined if
executed, then it seems axiomatic that UB is clearly something
that plays a role at translation time, as well.
I think that this paragraph (and several other it this post and
other posts) represent fundamental misanderstanding. This may
be due to the way C standard is written. AFAIK Extended Pascal
standard (once you translate terminalogy) states the same things as
C about UB, but in clearer way. Some relevant parts below:
: 3.1 Dynamic-violation
: A violation by a program of the requirements of this International
: Standard that a processor is permitted to leave undetected up to,
: but not beyond, execution of the declaration, definition, or
: statement that exhibits (see clause 6) the dynamic-violation.
: 3.2 Error
: A violation by a program of the requirements of this International
: Standard that a processor is permitted to leave undetected.
...
: 5.1 Processors
...
: e) be able to determine whether or not the program violates any
: requirements of this International Standard, where such a
violation is : not designated an error or dynamic-violation,
...
: 5.2 Programs
...
: b) if it conforms at level 1, use only those features of the
language : specified in clause 6;
UB in C standard corresponds with 'error' in Pascal standard. [...]
Does it? In C a syntax error is undefined behavior, but it
requires a diagnostic. (I don't mean to single out just syntax
errors; there are other examples.)
scott@slp53.sl.home (Scott Lurndal) writes:
One might also define data structures for control and status
registers using bitfield structs.
Yeah. This kind of application (among others) I consider one of
the motivating forces behind bitfields.
[Some whitespace trimming done in the excerpt below.]
e.g. for the SATA UAHC_GLB_OOBR register:
union UAHC_GBL_OOBR {
uint32_t u;
struct UAHC_GBL_OOBR_s {
#if __BYTE_ORDER == __BIG_ENDIAN
uint32_t we : 1; /**< R/W/H - Write enable. */
uint32_t cwmin : 7; /**< R/W/H - COMWAKE minimum value [...] */
uint32_t cwmax : 8; /**< R/W/H - COMWAKE maximum value [...] */
uint32_t cimin : 8; /**< R/W/H - COMINIT minimum value [...] */
uint32_t cimax : 8; /**< R/W/H - COMINIT maximum value [...] */
#else
uint32_t cimax : 8;
uint32_t cimin : 8;
uint32_t cwmax : 8;
uint32_t cwmin : 7;
uint32_t we : 1;
#endif
} s;
};
To me it seems kind of goofy to use uint32_t for the bitfields type.
I would just use unsigned, which is just as sure to work as intended,
isn't it?
| Sysop: | DaiTengu |
|---|---|
| Location: | Appleton, WI |
| Users: | 1,124 |
| Nodes: | 10 (0 / 10) |
| Uptime: | 15:07:00 |
| Calls: | 14,380 |
| Files: | 186,385 |
| D/L today: |
1,408 files (593M bytes) |
| Messages: | 2,541,993 |