Live data from Hacker News

Everything in C is undefined behavior

blog.habets.se

661–670 of 748 posts

Re: Everything in C is undefined behavior

#661

Earlier quoted context omitted.

Every implementation so far has predictable behavior in all cases. Sometimes the rules for predicting it are very obscure. But it's all fully defined within the compiler's binary code. And none of them link to nasal portals.

How do you propose to predict the behavior of a true race condition with only the binary, faithfully translated by the compiler? Moreover, this is at best an incredibly pedantic point, not something that changes how programmers need to approach UB. You can't review the source code of a compiler that hasn't been written yet.

I didn't suggest that implementations should entirely eliminate every form of UB. There is plenty of middle ground. For example, you could easily limit the consequences of integer overflow by specifying or partially specifying overflow behaviour, with very little runtime cost.

I'm not suggesting you change how you write code, but with a better implementation the code that you do write - that lives in the real world where mistakes are made - might work better. How is that being pedantic?

An interesting case where compiler writers did something like that is casting via union members, but I'm running out of time, so we can talk about that another day.

Re: Everything in C is undefined behavior

#662

Earlier quoted context omitted.

I think the article's point is that you don't actually have to get weird at all to run into UB. Lots of people mistakenly think that C and C++ are "really flexible" because they let you do "what you want". The truth of the matter is that almost every fancy, powerful thing you think you can do is an absolute minefield of UB.

My go-to example of "UB is everywhere" is this one: int increment(int x) { return x + 1; } Which is UB for certain values of x.

TBF that is the same as saying "signed overflow is UB".

Re: Everything in C is undefined behavior

#663

Earlier quoted context omitted.

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".

> UB was coined only in the first C standard, in 1989

Pre 1989, when C did not have a standard, was the behavior unspecified or undefined? That is, of course, a trick question. Because in this context the very definitions of the words come from the standard itself.

Before a language gets a specification, is the de facto specification the five words "you know what I mean"?

The very definition of "UB" in C later became "[…] this document imposes no requirements". Is that not the same thing as "there is to specification (yet)"?

It sounds very zen, but "a non existing specification imposes no requirements".

But I don't think it's meaningful to argue the semantic difference before the (in-context) existence of the words "undefined" vs "unspecified".

> Prior to that there was no "If you do this, anything can happen".

Of course it was. You relied on "common sense".

> It was "If you do this, that will happen".

Haha, of course it wasn't. Before a specification there is neither a definition of "this" nor "that".

Unless you mean ye olde "the compiler implementation is the specification". In which case we'll get dragged into "what even is a language" and "what is the sound of one hand clapping?".

Or, alternatively, it's as true then as it is today. If you go by "GCC x.y.z on platform Z kernel Y, (etc…) is the specification" then there is no UB.

Re: Everything in C is undefined behavior

#664

Earlier quoted context omitted.

My go-to example of "UB is everywhere" is this one: int increment(int x) { return x + 1; } Which is UB for certain values of x.

TBF that is the same as saying "signed overflow is UB".

yes but it is a 'picture' that makes you think about it in a different way.

Re: Everything in C is undefined behavior

#665
post #591
post #567

Earlier quoted context omitted.

No, that's actually UB. The important bit here is "compiler defined" -- UB means the compiler is allowed to assume it never happens while compiling. Consider, for example, an implementation defined function f() -- which can also diverge/crash horribly, etc. If I write if p { print("p is true") } else { g() } if p { f() } Then either we: - print p is true and execute f - do nothing This is true regardless of if f imme…

I see what you're going for, but I don't see how your example is UB. If `p` is a pointer, and, after your `if (p)` check, `p` is dereferenced unconditionally, then yes, your check for `p == NULL` could be removed, and the code under the `if` would be removed as well. But the example you've constructed is not UB.

You misunderstood their example, I think.

If doesn't matter what 'p' is in their example. The point is: if 'f' is undefined behavior (rather than just impl-defined), then the optimizer concludes that the "if p { f() }" can never happen... which means that we're allowed to assume that 'if p { ... } else { ... }' (in the first part of the example) will always take the else branch. The compiler will optimize accordingly and just always call g() unconditionally.

Re: Everything in C is undefined behavior

#666

Earlier quoted context omitted.

Every implementation so far has predictable behavior in all cases. Sometimes the rules for predicting it are very obscure. But it's all fully defined within the compiler's binary code. And none of them link to nasal portals.

How do you propose to predict the behavior of a true race condition with only the binary, faithfully translated by the compiler? Moreover, this is at best an incredibly pedantic point, not something that changes how programmers need to approach UB. You can't review the source code of a compiler that hasn't been written yet.

It's fully defined by your CPU's silicon masks and your compiler's binary code that one of several things will happen.

Re: Everything in C is undefined behavior

#667

Earlier quoted context omitted.

I suspect that many undefined behaviors reflect the inability of the standard committee to come to a consensus on the nuances involved. “Punt to the implementers” is a way to allow every tool vendor to select their own expected behavior in those cases.

You seem to be operating under the assumption "undefined behavior" means "the compiler authors can decide what to do." That's not what it means. It means "any program that causes this behavior to be triggered is not a valid C program, the programmer knows this and did not submit an invalid program, and the programmer explicitly prevented this from happening elsewhere in ways automated analysis cannot detect. Proceed…

It means the C standard does not specify what the program does. Other documents may still specify what the program does. And the program definitely still does something, whether specified or not.

Re: Everything in C is undefined behavior

#668

Earlier quoted context omitted.

> "Just don't do that" is the correct approach to errors We have 54 years of empirical data that literally nobody can follow this approach and reach UB-freeness. To stick to the plan is more like the in-debt gambler who just needs to work their system for a little longer, and they'll become rich. By this logic we don't need any traffic rules other than "just don't crash or hit anyone". And we can aspire to an absolut…

While, for the purpose of avoiding gratuitous mistakes, C is a serious disadvantage compared to less low-level languages, your discussion of UB pitfalls in C is aimed at a strawman. First of all, traffic rules are good, and similar to good C programming rules: check number value ranges when there is a chance of casting or overflow, check Inf and NaN floating point values, declare alignment strategically (e.g. in all…

> Second, nobody needs perfection and "UB-freeness"

Sure. You only care about the ones that manifest security issues, stability issues, or other corruption. But of course those change over time as compilers change.

So while far from every instance of UB will manifest in a problem, every single one has the potential to, by a low percentage. They're all tiny liabilities that add up.

But which ones will? Reminds me of https://www.lesswrong.com/posts/ooypcn7qFzsMcy53R/infinite-c...

> because the C implementation is neither weird nor hostile

Some people definitely were screaming at GCC for being hostile when it removed the NULL check in the kernel:

    int foo = bar->baz;
    if (!bar) {
      return -EINVAL;
    }
> the unfounded feeling of omniscience and unlimited resources that LLMs can give.

I definitely don't have that. I'm not saying LLMs find all bugs (now or in the future), nor that they are an unlimited resource.

I'm just saying that for finding UB and subtle bugs, they find orders of magnitude more, especially in C and C++.

I am not saying they find a strict superset of bugs, compared to a human. But take me running this against cosmopolitan libc: https://news.ycombinator.com/item?id=48206377. It took me basically zero human time to spin it off, it took a couple of minutes (5.5 in xhigh effort) to run, and found 5-10 cases of UB, one of which I think is a user visible parsing error of SSH keys. Another is a set of double-free, which is definitely a thing that gets exploited over and over.

Would I have found these, in an unknown-to-me codebase no less, given manual source code reading all day? Of course not. Would I have found it with the likes of UBSAN? jart claims to have used it (https://news.ycombinator.com/item?id=48205545), and apparently didn't.

LLMs are just one of the tools to use. A tool that does better than any tool or human has done in the last half century.

> I insist on the signed char example because it would be terribly wrong

The char situation is terrible in C. It's perfectly safe to hold bytes in a char, signed char, and unsigned char, and convert between them. But then integer promotion rules combine with the historical choice of having isdigit take an int to break things.

If isdigit took a char, of any signedness, then there wouldn't be a problem. But that EOF ruins it.

> processing who-knows-what as if it were a sequence of characters

A "char" hasn't been "a character" in any meaningful sense in a long long time. Or rather, "a character" is not a code point or grapheme cluster. For byte processing, since they cast perfectly fine, it's fine. Or do you have some interesting example?

Re: Everything in C is undefined behavior

#669

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.

Yeah on average. On some paths it's almost free

Re: Everything in C is undefined behavior

#670

Earlier quoted context omitted.

This would be a regression

Why? Automatic vectorization is pretty bad and has been for years, but wouldn't it be nice if the compiler could unroll-loops and use SIMD instructions to make your code faster while also being correct?

If you are using SIMD instructions on x86 you probably want the unaligned ones
Post reply on HN