Live data from Hacker News

Everything in C is undefined behavior

blog.habets.se

451–460 of 748 posts

Re: Everything in C is undefined behavior

#451

Earlier quoted context omitted.

I think it’s a really easy sell, actually: if you go past the end of the array far enough you end up accessing the stack which includes parts of the program like “where does this function return to” or “what is the index used to perform this access” or “there is no page mapped there”. None of these are arbitrary values.

The "anything can happen" means that the compiler can simply silently refuse to emit the code does the access. Documenting that the instructions to access will always be eliminated makes it easier to predict what will happen.

Can you unravel this further (for those of us who don’t know compilers)? I’ve always assumed access past the end of an array can’t always be detected in C, so I don’t see how those instructions could be eliminated.

For example, a dynamically linked library that takes in a pointer, and then writes to the 10 ints after it—whether or not this behavior is defined is determined after that library is compiled, right?

Re: Everything in C is undefined behavior

#452
post #113

Yes there is tons of surprising and weird UB in C, but this article doesn't do a great job of showcasing it. It barely scratches the surface. Here's a way weirder example: volatile int x = 5; printf("%d in hex is 0x%x.\n", x, x); This is totally fine if x is just an int, but the volatile makes it UB. Why? 5.1.2.4.1 says any volatile access - including just reading it - is a side effect. 6.5.1.2 says that unsequenced…

Author here. > It barely scratches the surface. I agree. The point of the post is not to enumerate and explain the implications of all 283 uses of the word "undefined" in the standard. Nor enumerate all the things that are undefined by omission. The point of the post is to say it's not possible to avoid them. Or at least, no human since the invention of C in 1972 has. And if it's not succeeded for 54 years, "try hard…

> The point of the post is to say it's not possible to avoid them. Or at least, no human since the invention of C in 1972 has.

What are you talking about? UB was coined only in the first C standard, in 1989. Prior to that there was no "If you do this, anything can happen". It was "If you do this, that will happen".

Re: Everything in C is undefined behavior

#453

Earlier quoted context omitted.

Many, many programmers come to C (and C++) with a lower-level understanding that actually gets in the way here. They understand that all types "are" just bytes and that all pointers "are" just register-sized integer addresses, because that's how the hardware works and has worked for decades. It's perfectly reasonable to expect any load through `int*` to just load 4 bytes from memory, done and done. They get surprised…

> Meanwhile, the actual computers we have been using for decades have no problems actually just loading 4 bytes through any arbitrary pointer with zero overhead. But no. Not if those 4 bytes span a cacheline boundary, that will most likely result in 1/2 throughput compared to loading values inside a single cacheline. And if it causes cache-misses it takes up twice the L2 or L3 bandwidth. Even worse, if the int spans…

I don’t think any of that is justification for making unaligned access UB. It’s reason to avoid it or discourage it in certain scenarios, but it’s infinitesimally rare that loading 8 bytes instead of 4 is even measurable, and that includes embedded.

Re: Everything in C is undefined behavior

#454

Earlier quoted context omitted.

If they do, that is no longer an implementation of C. It is a dialect of C, and there are many (GNU C being the most popular), but there are real drawbacks to using dialects. This is in contrast to the other category that exists, which is "implementation-defined".

The thing is that the actual compiler behaviour matters more for real-world projects than what the C standard says. E.g. the C standard was always retroactive, it merely tried to reign in wildly different compiler behaviour at the time when the standard was new. It mostly succeeded, but still the most useful C and C++ compiler features are living in non-standard extensions.

Unaligned access being fine in one architecture, but not in others would create separate dialects, regardless of being blessed by ISO C.

Just don't do unaligned access, it's a dialect that doesn't exist currently, and should never exist.

Re: Everything in C is undefined behavior

#455

Earlier quoted context omitted.

Print x twice. Not all “side effects” care about order. Better yet, define an order for parameter evaluation.

You're missing the point. Volatile forces two loads of a value that may have changed in the middle. So the value of "x" may depend on the time/order of load.

Which is, if I understand correctly, the entire point of volatile. Don't use it if you don't want that behavior.

And in fact, in the example given, if there is something (another thread or whatever) that can change the value of x, then you don't know what either number will be. Well, in that circumstance, without volatile, it may print the same number both times, but you still don't know what the number will be (unless the read gets optimized away entirely).

Re: Everything in C is undefined behavior

#456

Earlier quoted context omitted.

> How would you optimize: (x * 2) / 2 I'd do the math myself and just write x. I don't even use * for multiplication anymore, I use __builtin_mul_overflow and then check the result. Anyone who doesn't is gonna hit the overflow case one day, and they'll be lucky if their program isn't exploited because of it. I've been making an effort to use all the overflow checking builtins by default in most if not all cases. I've…

The things you want from C isn't C. Id advice you to use another language.

No. I like C. I've learned about a dozen languages by now. I always end up coming back to C. I've just accepted it.

There is no reason whatsoever that C can't be improved. Compiler attributes and builtins are already doing quite a lot of heavy lifting. Recent addition: counted_by, an attribute that allows compilers to properly track the size of memory referenced by pointers. All C programmers should be making liberal use of this stuff.

Re: Everything in C is undefined behavior

#457

Earlier quoted context omitted.

Signed overflow checks are typically not free unfortunately they have a cost of about 5% or thereabouts

In hot paths it can be even more. This is why even Rust defines it as wrapping but elides the overflow panic in release builds.

It is defined as an error. That error’s default handling is wrapping when debug_assertions is off, and panic when it’s on, but since it’s an incorrect program (though not UB) either behavior is acceptable in any mode.

Re: Everything in C is undefined behavior

#458
post #405

Earlier quoted context omitted.

If code has undefined behavior, the entire execution path that leads to that UB has no assigned semantics in the C model. So there are no volatile accesses in this code according to the C abstract machine - the entire execution path is UB, so it can be assumed it doesn't happen at all.

> An object that has volatile-qualified type may be modified in ways unknown to the implementation or have other unknown side effects. Therefore any expression referring to such an object shall be evaluated strictly according to the rules of the abstract machine The execution path has unknown side effects, and so the execution path must be strictly followed. That's uh... The entire point of that section in the C stan…

Previously discussed here: https://news.ycombinator.com/item?id=33770277

UB supersedes volatile, once the compiler hits UB then all bets are off. Compilers can and do optimize out UB branches, which is almost never what you want... yet here we are.

Re: Everything in C is undefined behavior

#460

The UB in unaligned pointers is even worse: an unaligned pointer in itself is UB, not only an access to it. So even implicit casting a void*v to an int*i (like 'i=v' in C or 'f(v)' when f() accepts an int*) is UB if the cast pointer is not aligned to int. It is important to understand that this is a C level problem: if you have UB in your C program, then your C program is broken, i.e., it is formally invalid and wron…

The problem with C UBI is that originally it meant the compiler has the freedom to map your code to the hardware inspite of machine instructions differing slightly between one another. The same C program may express different behaviour depending on which architecture it is running on. This type of UB is fine and nobody really complains about hardware differences leading to bugs. However, over time aggressive readings…

> The problem with C UBI is that originally it meant the compiler has the freedom to map your code to the hardware inspite of machine instructions differing slightly between one another. The same C program may express different behaviour depending on which architecture it is running on.

You're conflating undefined behavior with implementation-defined behavior. If it was only to do with what we think of as normal variance between processors, then it would be easy to make it implementation-defined behavior instead.

The differentiating factor of undefined behavior is that there are no constraints on program behavior at that point, and it was introduced to handle cases where processor or compiler behavior cannot be meaningfully constrained. One key class is of course hardware traps: in the presence of compiler optimizations, it is effectively impossible to make any guarantees about program state at the time of a trap (Java tried, and most people agreed they failed); but even without optimizations, there are processors that cannot deliver a trap at a precise point of execution and thus will continue to execute instructions after a trapping instruction.

Post reply on HN