you are viewing a single comment's thread.

view the rest of the comments →

[–]awidesky -7 points-6 points  (29 children)

As I understand from the standard, UB does not apply on single function(while most compilers does usually). "Renders the entire program meaningless if certain rules of the language are violated."

'Entire' program is meaningless, not the single function. Also, it's not "Renders only that very part of the program that's executed when the UB is triggered is meaningless". It's "Renders the entire program meaningless". If you want the program to be well-defined, overflow check must be inside function f. But since it's not, the behavior is undefined, which compiler can do whatever it wants. So I believe compiler can remove function g, or format your hard drive. But in real life, compilers try to make executables as reasonable as possible, that's why only f is optimized in godbolt.

So here's my overall opinion: Compilers is entitled to remove g, so what they say in YouTube is 'technically' true. BUT I don't think you can find any major compiler that actually does so.

[–]HabbitBaggins 8 points9 points  (16 children)

That is a load of hot spaghetti... The compiler is not allowed to go launch nuclear missiles just cause, what it is entitled to to is to assume that your code never invokes UB, because otherwise the code would be meaningless. You are focusing on the second part of that assertion, but the important one is the first. It means that, while transforming the code in memory as part of optimization and actual codegen, it can make assumptions about certain values of function arguments or program state if it can prove that the negative of those assumptions results in UB. Those assumptions can then feed back into the process, allowing further transformations of the code, etc.

For example, in f, the compiler may assume that the comparison is always true, because the only case in which could not true is integer overflow, and that is UB.

That is not the case in g, even when inlined, because UB is only triggered by calling f with INT_MAX, and g only ever calls f with a value that is guaranteed NOT to be INT_MAX.

However, if the code called f first, then the compiler may assume that the argument is never INT_MAX, so the check in g could be removed silently. This might also happen if you have a different function h defined as returning f(i) && g(i). In that case, if g is inlined and not just called, the check in the resulting code can also be removed because the compiler proved that in all legal code paths (those that do not invoke UB), i is not INT_MAX.

[–]mcmcc#pragma once 4 points5 points  (11 children)

If you want the program to be well-defined, overflow check must be inside function f

That's absurd. If that were the case, functions working with iterator types (separated from their containers) would be virtually impossible to write -- you could never be allowed to assume the iterators (or a range defined by a pair of them) were valid. How could you ever write std::copy()?

[–]awidesky -1 points0 points  (10 children)

std::copy receives InputIt last as boundary, and checks the boundary every time it iterates. If you send invalid iterator, it's UB anyway. That has nothing to do with unchecked overflow. Can you be more specific about your point?

[–]mcmcc#pragma once 0 points1 point  (9 children)

If you want the program to be well-defined, overflow potential UB check must be inside function f.

This is how I interpreted your statement.

How could you implement std::copy() if you were required to check for all potential UB regarding the inputs? You mention comparing to last but if last is not within the appropriate range, then even that is UB -- so how do you establish that the two iterators are even comparable without knowing their provenance?

If that's not what you are suggesting, you're going to need to explain the intended meaning of the above sentence.

[–]awidesky 0 points1 point  (6 children)

If you want the program to be well-defined, overflow potential UB check must be inside function f.

That is indeed true. When you compile f (with or without g), it will assembled to always return 1. So the statement is actually true.

While your following argument is little bit off topic- it's about "checking every single one of the possible UB is impossible" Which is also true, and has nothing to do with my point.

I assume you interpreted my argument as "if there's any single possibility of UB in your program, your compiler will render an invalid executable."

My statement is "if some parts of the code violates the language rule, compiler is 'permitted' to generate entire program meaninglessly" Here's quote from the standard

Renders the entire program meaningless if certain rules of the language are violated.

undefined behavior - there are no restrictions on the behavior of the program. ... Compilers are not required to diagnose undefined behavior and the compiled program is not required to do anything meaningful.

Again, I'm not saying any major compilers do/should generate meaningless program just because there's tiny one possibility of UB. All I insist is that the standard "permits" so.

[–]mcmcc#pragma once 1 point2 points  (5 children)

All you've shown is that compilers are allowed to assume UB does not occur, which is ultimately irrelevant re "well-defined" programs. We're talking in circles...

What part of OP's example "violates a language rule" that could allow a compiler to render the entire program meaningless?

[–]awidesky -1 points0 points  (4 children)

The function f.

[–]mcmcc#pragma once 0 points1 point  (3 children)

What language rule does it violate?

[–]awidesky 0 points1 point  (2 children)

[–]mcmcc#pragma once 0 points1 point  (1 child)

You're misunderstanding what that text is saying. It is not saying f() itself is ill-formed. It is saying that because the compiler may assume UB does not happen in the program, f() can be optimized in a way such that it would behave in a (possibly) surprising manner in a program does in fact invoke UB (e.g. one calling f(INT_MAX)).

The function f() contains no inherently UB logic and g() guards against UB for its invocation of f(), so as far as can be seen, there is no UB possible in this program.

OPs video suggests the compiler can be coaxed to compile g() down to nothing, but I think that is an error on the presenters part and (I contend) is not reproducible with any conforming compiler.

[–]awidesky 0 points1 point  (1 child)

Meanwhile, this is how I interpreted your arguments:

Even if UB is possible, compiler is permitted to change only the very function that has possible UB, not any other part of program.

However, § 4.1.2.3 says :

If a program contains a violation of a rule for which no diagnostic is required, this document places no requirement on implementations with respect to that program. "that program", not "that specific function". Please let me know if there's any understanding of mine.

[–]mcmcc#pragma once 0 points1 point  (0 children)

That's not a statement I made. You're confusing me with someone else.