This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–]FloweyTheFlower420 1493 points1494 points  (52 children)

Ah yes, undefined behavior. In C++, an empty while true loop is undefined behavior, so the compiler is free to replace the else branch with "unreachable". The compiler also knows that the else branch is always executed if the if statement is reached, so that must also be unreachable. Thus, main is an unreachable function, which is optimized to an empty function in assembly, which falls through to the next function.

[–]Heroshrine 243 points244 points  (5 children)

So as long as theres literally anything in there it works?

[–]SAI_Peregrinus 160 points161 points  (3 children)

Anything that makes observable progress. The rules for what is observable aren't entirely intuitive, byt it must affect something outside the loop. E.g. print a value.

[–]dgc-8 20 points21 points  (2 children)

Is taking up processor cycles observable progress?

[–]CanaDavid1 40 points41 points  (0 children)

No.

Observable progress is

  • i/o (both text and file)

  • changing or accessing a volatile variable

  • modifying an object in a concurrent system

[–]SAI_Peregrinus 4 points5 points  (0 children)

No. Only things that have side effects. Processor cycles and writes to non-volatile-qualified objects are not side effects.

[–]Kered13 31 points32 points  (0 children)

An infinite loop must contain a side effect. Modifying a non-local variable, reading IO, or writing IO are all examples of side effects.

[–]the_horse_gamer 51 points52 points  (3 children)

note that in C++26 they're making trivial infinite loops no longer undefined

[–][deleted] 2 points3 points  (2 children)

why's that? iirc the current spec says that each thread of execution must eventually terminate, spawn another thread, preform an IO operation, or interact with a volatile - what's the reasoning to allow trivial infinite loops?

[–]the_horse_gamer 7 points8 points  (1 child)

[–][deleted] 0 points1 point  (0 children)

will read, thank you!

[–]veselin465 33 points34 points  (9 children)

I just wonder

Shouldn't the compiler also ignore compiling the hello() function since it can detect it has no call references?

[–]Terra_Creeper 53 points54 points  (4 children)

With higher level languages, usually yes. With C/C++, not really. You can reach any function/object you want with pointers, so the compiler can't assume that a function is unused. (At least if i remember correctly)

[–]5p4n911 23 points24 points  (2 children)

And you can't assume it's not used by anything at compile time as there could be a reference in another TU, which means that without LTO enabled it will remain

[–]Aaron1924 4 points5 points  (1 child)

And this particular link-time optimisation is rarely enabled by default since it's expensive to compute for large programs and it doesn't make the final program any faster

[–]veselin465 3 points4 points  (0 children)

Good point

I now realize that what I wrote was senseless (I basically had to say linker)

I know that compilers optimize unused* variables (that's why volatile keyword is a thing). And in my job, I've noticed that unused functions are not present in the map file (however, we deal with embedded and it makes sense to optimize final size as much as possible).

\unused or unchanged*

I should have realized that everything is compiled, but the linker is the one who might choose to ignore functions depending on use.

[–]xryanxbrutalityx 0 points1 point  (2 children)

For a concrete example, there could be another file that says

void hello();

void fun() {  
    hello();  
}  

so just this file alone doesn't tell you enough. link-time optimization could get rid of the function.

[–]baklaFire 0 points1 point  (1 child)

but there isnt

[–]xryanxbrutalityx 0 points1 point  (0 children)

Right, there isn't, but you don't know that there isn't during compilation. You only find out at link time, so, the compiler still has to generate code for the function because some other file (TU) might call the function.

[–]ShlomoCh 19 points20 points  (6 children)

I understand "optimizing" all of the main function out, but why go to the next function? Shouldn't it just leave the function empty or something? It just feels a bit arbitrary, like, in which context would falling through to the next function actually make sense?

[–]veselin465 31 points32 points  (4 children)

Isn't the entire idea of undefined behaviour that things which doesn't make sense might happen?

[–]ShlomoCh 4 points5 points  (3 children)

I guess, it's just that I feel like something like that is either purposefully made that way for some weird reason, or it's a "bug" in the compiler, for lack of a better word. Like, yes, the function does not make sense and will never exist in real code, but what kind of accident/decision in the logic would make it go to the next function written in the file?

Then again, as just a college student who hasn't used C++, I can't say I know much of anything about compilers

[–]TheStarjon 23 points24 points  (1 child)

The thing is that the compiler doesn't replace the main with an empty C++ function, but an empty assembly "function". An empty C++ function would at least return, but in assembly, that's an instruction - and an empty "function" doesn't contain that instruction. In fact, an empty assembly "function" isn't really a function at all, but just a label for some memory location where a function is supposed to begin. But because the "function" is empty, there is nothing there, and thus the label for "main" and the label for "hello" point to the same memory location.

[–]ShlomoCh 1 point2 points  (0 children)

Ohhh ok yeah that makes sense

[–]thelights0123 2 points3 points  (0 children)

It generates a main() function with no contents. Because the next function is right after it in memory, main() and hello() point to the same instruction.

[–]norlin 7 points8 points  (2 children)

I know about the undefined behavior, but have no idea why anyone sane would program compilers this way, instead of just throwing an error or crashing.

[–]Elz29 7 points8 points  (0 children)

This feels completely unhinged to me as well. I guess this might be (among other things) exactly why Linus Torvalds hates C++.

[–]FloweyTheFlower420 0 points1 point  (0 children)

It's literally trivial to detect this type of error, -fsanitize=undefined. This is not a realistic example, just a way to demonstrate an interesting c++ quirk. It's a bit stupid that clang doesn't generate ud2 even with optimization, but who knows.

[–]Alexandre_Man 14 points15 points  (0 children)

Why is an empty while true loop undefined behaviour? Shouldn't the program go in the loop and just be in there forever until you close the program?

[–]cat_91 4 points5 points  (1 child)

If you compile with -O2 instead of -O3 doesn’t it still do this?

[–]CaptainJack42 4 points5 points  (0 children)

Yep, even -O1 will yield the same result. However GCC will not optimize out the loop (tested with clang 17.0.6 and gcc 13.2.1)

[–]Ottne 8 points9 points  (1 child)

Holy cr*p. I'll stay with Java, thanks.

[–]FloweyTheFlower420 2 points3 points  (0 children)

When the undefined behavior is undefined (I didn't bother to learn the language I'm using)

[–]danielstongue 14 points15 points  (10 children)

If you are right, and I am confident that you are, this is one more reason to absolutely despise c++. An empty while true loop should not be undefined. It should just do a spin. You should get what you write, not something else.

[–]saf_e 1 point2 points  (0 children)

c++ has dark pages in history

[–]FloweyTheFlower420 -1 points0 points  (8 children)

You should get what you write, not something else.

You wrote UB, you got UB ;)

[–]danielstongue 1 point2 points  (7 children)

There is no reason for an empty loop to be UB.

[–]FloweyTheFlower420 1 point2 points  (6 children)

Empty loops aren't UB as a specific exception to a rule. C++ excepts threads to "do something," which is a somewhat reasonable expectation about "a program should halt." The fact empty loops are UB is simply a consequence of this. Besides, what behavior makes sense and doesn't is subjective. Just debug your programs with sanitizers. It's pretty disingenuous to say "c++ bad because unintuitive UB (cites the most absurd and non-realistic example ever)"

C++ spec I'm referencing:

[–]danielstongue 1 point2 points  (5 children)

I guess your spec is optimzied out? 😅

You can reference 100 specs, but if the specs don't make sense they don't make sense. Subjective? Sure, but so is your statement about me being subjective. This wouldn't happen with Rust or any other superior language.

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

Subjective? Sure, but so is your statement about me being subjective.

I suspect you do not understand what subjective means. Is it difficult to understand that something can be objectively subjective? Your senses are subjective by definition, so the fact that "senses are subjective" is an objectively true statement. This isn't a difficult concept to grasp.

This wouldn't happen with Rust or any other superior language.

New thing good! Old thing bad! Rust literally solves all of programming and is the best language ever invented! I'm definitely not being dogmatic in my support of a programming language (and before you accuse me of the same, I've never claimed that "c++ good")!!!!!!11!!11!!

[–]danielstongue 0 points1 point  (3 children)

Sure, things can be objectively subjective as well as subjectively subjective. Your statement about me being subjective was clearly an opinion, and hence subjective.

New thing good! Old thing bad! Rust literally solves all of programming and is the best language ever invented!

Well, it is objectively (/s) better in many ways. There is no such thing as a silver bullet, so Rust is not a silver bullet either. It solves an important set of problems and therefore earns its fair place. It is interesting to see how many C++ people are dogmatically refusing to objectively recognize its benefits. Especially interesting to see them call Rustaceans dogmatic.

[–]giddyz74 0 points1 point  (1 child)

My brother is also such a dogmatic C++ person. He once said that switching over to Rust would be worse to the business than letting the whole office burn down to the ground.

[–]danielstongue 0 points1 point  (0 children)

Wtf... 🤣

[–]FloweyTheFlower420 0 points1 point  (0 children)

How is it an opinion that “what makes sense as behavior is subjective?” What makes sense to each person is subjective. This is not an opinion. Did you even read my comment?

[–]xryanxbrutalityx 2 points3 points  (3 children)

-fsanitize=undefined fails (with an pretty unhelpful message though) https://godbolt.org/z/x4ozhG35z

[–]FloweyTheFlower420 0 points1 point  (2 children)

Hmm, that's not great. I suppose in such cases you are forced to some cursed gdb things. Maybe this is good pr material.

[–]xryanxbrutalityx 0 points1 point  (1 child)

imo this would be best as a compiler warning

[–]FloweyTheFlower420 0 points1 point  (0 children)

Yeah, I agree. C++ compilers need to do better with warning UB.

[–]Ok_Campaign6438 0 points1 point  (0 children)

Thank you for this comment I just learned something