Live data from Hacker News

Everything in C is undefined behavior

blog.habets.se

721–730 of 748 posts

Re: Everything in C is undefined behavior

#721

Earlier quoted context omitted.

Sure, but the point is that code written against such a compiler is not C and is not portable. It is written in a dialect of C, and that comes with drawbacks. Writing C (or any language) means adhering to the standard, because that's the definition of the language.

You can't make any useful software in "Portable C" - or any portable language for that matter. Side effects matter, and they are always non-portable/implementation defined/dependent on the hardware. What printf() actaully does is implementation defined - what does "printing mean", does a console even exist? Maybe a user expects it to show graphical ascii/utf8 glyphs on a LCD display? Well, not every computer has that…

> You can't make any useful software in "Portable C" - or any portable language for that matter.

Have you heard of Java or even Python or JavaScript?

> Side effects matter, and they are always non-portable/implementation defined/dependent on the hardware.

Granted. But how does the need for implementation defined excuse undefined behaviour?

> What printf() actaully does is implementation defined [...]

> Well, not every computer has that, so now what?

The standard can be written conditionally: 'if the computer has display, printf shall show something.'

Re: Everything in C is undefined behavior

#722

Earlier quoted context omitted.

I don’t mean this in a rude way but you should really read the posts I linked, it’s interesting and part 3 especially answers these questions Direct link to part 3 (but read the others first for context if you can): https://blog.llvm.org/2011/05/what-every-c-programmer-should... You don’t want warnings for every piece of code in a library you’re not using or sanity check you added that isn’t supposed to be hit And yo…

> I don’t mean this in a rude way but you should really read the posts I linked, it’s interesting and part 3 especially answers these questions I have read the entire series. Both in the past and more recently. > If you warned whenever undefined behaviour could be happening then e.g. every single pointer deference would say “warning: compiling assuming pointer is not null or unaligned” I am not proposing that at all,…

When your code can be completely reshaped by earlier passes and macro expansions and inlining I don’t think it’s feasibly to make a useful warning for that

If you include a library function with a null check that gets inlined you probably don’t want a bunch of warnings there for code you don’t even control

Re: Everything in C is undefined behavior

#723

Earlier quoted context omitted.

Sure, but the point is that code written against such a compiler is not C and is not portable. It is written in a dialect of C, and that comes with drawbacks. Writing C (or any language) means adhering to the standard, because that's the definition of the language.

Maybe it’s a generation thing. Languages like ML and Lisp have many implementations, while newer languages like Perl and Python are steered by a single organization. It’s way easier for the latter to have a single source of truth. The C standard reminds me of Posix. You have a rough guideline if you ever wanted to port a program, but you actually have to learn the new compiler and its actual behavior before doing so.

C started off as a single implementation, then a bunch of implementations, and only later the standard.

There's multiple implementations of Python, but you are right that CPython is the big one. Part of that dynamic is that CPython runs on nearly everything well enough (well, as good as Python runs), so that we don't really need multiple implementations. Unlike the first C Compilers.

Of course, Lisp is a family of languages these days. Scheme and Common Lisp have many implementations, but Racket or Arc only have one.

Re: Everything in C is undefined behavior

#724

Earlier quoted context omitted.

> The examples are unequivocally UB. Full stop. Tbh, already the first example (unaligned pointer access) is bogus and the C standard should be fixed (in the end the list of UB in the C standard is entirely "made up" and should be adapted to modern hardware, a lot of UB was important 30 years ago to allow optimizations on ancient CPUs, but a lot of those hardware restrictions are long gone). In the end it's the CPU a…

I agree. I meant to elaborate more on how to think of UB. For most C software on x86_64, UB is "fine" with very strong bunny ears. But it is preferable for one to, shall we say, write UB intentionally rather than accidentally and unknowingly. Having an awareness of all the minefields lends for more respect for the dangers of C code, it makes one question literally everything, and that would hopefully result in more c…

How is UB fine? It can make your programme rather exploitable, depending on compiler. And the compiler might change her mind tomorrow.

Re: Everything in C is undefined behavior

#725
post #720

Earlier quoted context omitted.

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.

> Unaligned access being fine in one architecture, but not in others would create separate dialects, regardless of being blessed by ISO C. That doesn't mean unaligned access would need to be UB. It could be implementation defined. Or could just be defined to result in an error on all machines.

Implementation-defined without further constraining the behavior is not much better than undefined.

Defining it to always error would add overhead even for proper aligned access on x86, as the generated code would need to explicitly check in many cases.

Re: Everything in C is undefined behavior

#726

Earlier quoted context omitted.

So in Rust, you are actually specificing TWO programs with a single source? Those Rust users are surely too clever for my liking! You can tune a C compiler as well to have a very specific defined behaviour for integer overflow. You can add -fwrapv or you can add UBSAN. The user never intended overflow to happen, because if they did, they could have used something like __builtin_mul_overflow() or whatever. Or they are…

> You can tune a C compiler as well to have a very specific defined behaviour for integer overflow. You can add -fwrapv or you can add UBSAN. This is an example of a compiler flag that adds definition to undefined behavior, which is of course, legal to do. That doesn't change that in the standard, it is undefined behavior, and in Rust, it is not. > To say that overflow would be defined in Rust is at least half a lie.…

As you said, overflow is defined in Cargo, given a specific build type and/or specific build flags. It's not defined in Rust.

Just saying that it's defined and then not saying what the definition is, is no different from saying it's undefined.

Re: Everything in C is undefined behavior

#727

Earlier quoted context omitted.

But did the rust compiler assume that the integer would not overflow? It did so in Debug mode where runtime checks were added. If it's not the case in Release mode, does that mean semantics are different between Debug and Release?

> But did the rust compiler assume that the integer would not overflow? It did not. > It did so in Debug mode where runtime checks were added. It didn't assume in that case either. It did a well defined thing: add checks. > If it's not the case in Release mode, does that mean semantics are different between Debug and Release? Strictly speaking, the language doesn't know about "release mode", as that's a Cargo thing.…

> It didn't assume in that case either. It did a well defined thing: add checks.

It did. The compiler added the checks (which panic on overflow, from a quick web search) precisely so it (and importantly, the developer!) can assume the overflow didn't happen in the subsequent code. Unless you consider a panic a defined state, and consider wrap-on-overflow equally valid in all cases, it's essentially the same as UB. (panic seems to be considered "unrecoverable").

Difference is _at most_ that C spec gives compiler more freedom to "implement UB", but then again, hit any unsafe code in Rust with wrapped around integer, you probably have comparable practical result -- machine doing random things, corrupting memory and so on.

Re: Everything in C is undefined behavior

#728

Earlier quoted context omitted.

> You can tune a C compiler as well to have a very specific defined behaviour for integer overflow. You can add -fwrapv or you can add UBSAN. This is an example of a compiler flag that adds definition to undefined behavior, which is of course, legal to do. That doesn't change that in the standard, it is undefined behavior, and in Rust, it is not. > To say that overflow would be defined in Rust is at least half a lie.…

As you said, overflow is defined in Cargo , given a specific build type and/or specific build flags. It's not defined in Rust. Just saying that it's defined and then not saying what the definition is, is no different from saying it's undefined.

No, “release mode vs debug mode” is defined in Cargo. What’s defined in Rust is the debug_assertions flag, which is one of the things that Cargo will set by default as part of the debug mode by default.

> Just saying that it's defined and then not saying what the definition is, is no different from saying it's undefined.

It actually is, because, as I said earlier, “undefined behavior” is a term of art with very specific meaning. Regardless, it is defined: there are two possible behaviors, with one guaranteed with that flag and the other chosen by implementations.

Re: Everything in C is undefined behavior

#729

Earlier quoted context omitted.

> But did the rust compiler assume that the integer would not overflow? It did not. > It did so in Debug mode where runtime checks were added. It didn't assume in that case either. It did a well defined thing: add checks. > If it's not the case in Release mode, does that mean semantics are different between Debug and Release? Strictly speaking, the language doesn't know about "release mode", as that's a Cargo thing.…

> It didn't assume in that case either. It did a well defined thing: add checks. It did. The compiler added the checks (which panic on overflow, from a quick web search) precisely so it (and importantly, the developer!) can assume the overflow didn't happen in the subsequent code. Unless you consider a panic a defined state, and consider wrap-on-overflow equally valid in all cases, it's essentially the same as UB. (p…

Okay, I am going to leave this here, as it’s clear to me that we’re not coming to an understanding.

Re: Everything in C is undefined behavior

#730

Earlier quoted context omitted.

As you said, overflow is defined in Cargo , given a specific build type and/or specific build flags. It's not defined in Rust. Just saying that it's defined and then not saying what the definition is, is no different from saying it's undefined.

No, “release mode vs debug mode” is defined in Cargo. What’s defined in Rust is the debug_assertions flag, which is one of the things that Cargo will set by default as part of the debug mode by default. > Just saying that it's defined and then not saying what the definition is, is no different from saying it's undefined. It actually is, because, as I said earlier, “undefined behavior” is a term of art with very speci…

I think people make up way too much of it. What is the actual term of art? What is the meaning of UB? If you look in the standard, UB is basically what its name says, it is behaviour (or state) that is not defined. It can be anything. And that makes sense in many cases: What if you construct a random pointer, and read it or write it? It's not useful or practically possible to define the behaviour from then on. So the behaviour is left undefined, simple as that.

Now are there many cases of UB in C, many more than strictly need to exist on contemporary platforms? For sure there are. But does it affect me? Not unless I need a specific behaviour common to most contemporary platforms that I can't get within the confines of C, even considering compiler specific extensions. Honestly I can't come up with any of the top of my head. Maybe some integer-shifting stuff or such, if the compiler was able to prove I'm doing sth undefined, it can leave out that code (or delete my mail, for the doomers). Personally, it hasn't happened to me, and it's on the compiler authors to not do stupid things too.

Leaving all the semantic hair-splitting aside. What is the practical difference in how you write a Rust program compared to a C program, given that integer overflow is "defined" in Rust?

Post reply on HN