5 ms·
C biggest "mistake" (there are historical reasons for this, though, but it doesn't make it acceptable nowadays) is the concept of undefined behavior, period.
by jylam 3y ago
C biggest "mistake" (there are historical reasons for this, though, but it doesn't make it acceptable nowadays) is the concept of undefined behavior, period.
- jovial_cavalier 3y agoEvery language has undefined behavior - it's just that some compilers emit an error when it's encountered. You can make a C compiler that refuses to compile code with undefined behavior.
- circuit10 3y agoThere is a tradeoff though because it allows the code to be optimised more which is often something you want from C code
- remexre 3y agoShould a compiler be allowed to optimize: int x = 0; int y = 42; foo(&x); bar(y); to: int x = 0; foo(&x); bar(42); without performing analysis on foo? If so, how can that optimization be legal without UB?
- anovikov 3y agoHow is this UB? foo() won't have any access to y so won't be able to modify it, so we can be more or less certain that y is still 42 when bar() is called? What is it that i'm missing?
- remexre 3y agoThat's it -- the optimization is legal because there's no way foo() could modify y, because any (&x)[1] = 20 shenanigans would be UB. This is how UB is used in optimizations; it's (usually) not that the compiler is finding positions where UB occurs and explicitly choosing to perform mischief, it's performing a bunch of simple and obviously-what-you-want rewrites that are only correct because of UB; otherwise, arbitrary pointer writes are too powerful.
- deleted 3y ago[deleted]
- Dylan16807 3y agoUB is what allows `(&x)[1]` to happen at all. If you didn't have UB, there would be no reasonable way to allow code that messes up arbitrary pointers. A typical non-UB language wouldn't allow `(&x)[1]` and it would be easy to allow the optimization. It would be difficult or obtuse to design a non-UB language that doesn't allow optimizing y away.
- remexre 3y agoStandard Forth, to my knowledge, doesn't have UB and consequently doesn't allow the optimization. That'd just be : foo CELL + 20 SWAP ! ; there. Machine languages also generally don't have this kind of UB, and don't allow the optimization. It's not impossible to imagine a C without UB, it's just not a particularly desirable language.
- Jtsummers 3y agoThe UB permits the optimization, but is not necessary for it. If a language had a stricter definition of pointer and memory access behavior then the optimization would still be applicable if it conformed to the assumption that people are making around the UB in C. UB is not necessary for optimization, it just permits some optimizations which may or may not be valid and may or may not be consistent across compilers and platforms because people get to fill in the gaps with whatever they want.
- kevin_thibedeau 3y agoY is never visible from foo()s scope. You'd have to employ ABI specific stack manipulation to get to it. Optimization in the outside frame doesn't depend on foo().
- tyler569 3y agoAnd that ABI specific stack manipulation would be UB, so the optimizer can assume it doesn't happen. With no UB, that's not true.
- Dylan16807 3y agoYou assume a langue without UB would allow stack manipulation. But then it would have to be defined. How would you possibly define it fully?
- remexre 3y agoThere's an area of memory into which variables with automatic storage duration are allocated at implementation-defined addrs, at any point exactly those variables have addresses.
- Dylan16807 3y ago> at any point exactly those variables have addresses That doesn't sound like it lets me increment any stack address I want and store into the resulting pointer. And are return values still on the same stack? I probably should have said I meant the traditional kind of C stack.
- remexre 3y ago> That doesn't sound like it lets me increment any stack address I want and store into the resulting pointer. Why not? > And are return values still on the same stack? Other implementation-defined things might have addresses too, those things just aren't variables. I think you might be able to still allow inlining without UB if you make it implementation-defined per call-site what other things might get addresses.
- torstenvl 3y agoI'm okay with undefined behavior, but the language should revert back to the original (an exhaustive list of permissible behavior by the host when the C abstract machine's behavior is undefined) rather than the current mere list of examples of possible behavior by the host.
- stouset 3y ago"I'm okay with undefined behavior, as long as its behavior is well-defined." You keep using that word. I don't think it means what you think it means. :)
- Dylan16807 3y agoA behavior like "corrupts whatever is in memory at that location" when your code screws up a pointer is not exactly "well-defined", but it does disallow "pretends it could have corrupted RAM and does whatever it feels like instead". To put that another way, you can treat certain behaviors as if they're opaque functions until after you're done optimizing. Similar to how you might handle volatile. Some of these changes would be very easy. For example, instead of treating "dereference zero" as unreachable code, treat it the same as "dereference unknown location".
- stouset 3y agoYour platform almost certainly does do something sane and reasonable for UB-exhibiting code. Where your problem lies is what happens when you optimize that code. And guaranteeing optimizations do something superficially sane on unsound code while still performing reasonable and obvious optimizations for well-formed code is insanely hard. The fundamental problem is that the design of C guarantees that lots and lots and lots of syntactically valid and intuitively written code is semantically total nonsense. Part of this is that C is low-level and part is just that it was designed 50 years ago before anyone had the faintest idea what kinds of terrible mistakes were being made. If there were simple fixes here it wouldn’t still be a problem.
- 3y ago
- pornel 3y agoOn the contrary, I don't think C would have been so ubiquitous without it. People use C for maximum speed and efficiency. When maximum speed isn't a requirement, there are plenty of more productive and less footgunny languages to choose from. Without the things that C allows to be ignored during optimization, you either wouldn't have compilers that optimize the "simple" language equally well, or you'd have to make the language bigger to give all the right hints and guarantees where they're needed. I see UB commonly characterized as compiler being mean for silly reasons, with implication that compilers could just stop doing the bad UB and do the good UB, but that isn't accurate or realistic view. The creeping UB is just a symptom of how optimization passes are implemented, and they would be exponentially harder to implement equally well without the concept of UB. "Just do what I mean" is not a well-defined compilation target, and there are tons of surprisingly bad performance issues. e.g. without signed overflow indexing of arrays by int could not use 64-bit CPU's addressing modes, because they overflow differently. You can use size_t, but good luck being diligent about it in a language that loves its ints.
- bsder 3y agoNo. The mistake is conflating "undefined behavior" with "unspecified behavior". Signed overflow was not intended to be undefined--it was unspecified because it has a perfectly acceptable specification that differs between processors, and it was expected that the compiler would define it. The fact that gcc and clang pick non-sensical specifications in order to gain 3% in performance was not something anybody expected back when this stuff was written. "Division by zero" is, on the other hand, genuinely undefined behavior. "Dereferencing a NULL pointer" is a genuinely undefined behavior. There generally isn't a good way for a program to continue after encountering them.
- aw1621107 3y ago> Signed overflow was not intended to be undefined--it was unspecified This seems wrong to me. The Standard very much calls signed overflow undefined behavior (Section 3.3): > If an exception occurs during the evaluation of an expression (that is, if the result is not mathematically defined or not representable), the behavior is undefined. In addition, overflow is explicitly listed as undefined behavior in at least two other places. For example, in section 1.6, Definitions of Terms: > An example of undefined behavior is the behavior on integer overflow. And in the list of undefined behavior (A.6.2): > An arithmetic operation is invalid (such as division or modulus by 0) or produces a result that cannot be represented in the space provided (such as overflow or underflow) (3.3). If signed overflow was not intended to be undefined, then it seems like a rather large oversight to say it's undefined behavior and to give it as an example of such. > because it has a perfectly acceptable specification that differs between processors, and it was expected that the compiler would define it. What you're describing is implementation-defined behavior in the C standard. If it was expected that implementations would define the behavior, why didn't the committee just... use that definition, instead of saying signed overflow is UB but actually meaning it's implementation-defined behavior? In addition, unspecified behavior is a third category of behavior distinct from undefined and implementation-defined behavior. The Standard imposes no requirements on unspecified behavior in a correct program, and does not require that an implementation document what it does in such a situation. [0]: https://port70.net/~nsz/c/c89/c89-draft.html https://port70.net/~nsz/c/c89/c89-draft.html
- tpoacher 3y agoUB is not a bug, it's a feature.