Live data from Hacker News

Everything in C is undefined behavior

blog.habets.se

551–560 of 748 posts

Re: Everything in C is undefined behavior

#551
post #218

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.

At which point it feels like some sort of high-level assembly-like language, which is simple enough to compile efficiently and stay crossplatform, with some primitives for calls, jumps, etc. could find a nice niche. Maybe this already exists, even? A stripped down version of C? A more advanced LLVM IR? I feel like this is a problem that could use a resolution, just maybe not with enough of a scale for anyone to bothe…

There's Vale [0] as a structured high-level assembly language, but pretty far from usable right now. I do hope it matures. Basically: All non-control-flow instructions can be directly supported. Control flow is lofted to a higher level and implemented in C-style structured blocks and keywords, which map directly to a subset of the ISA that modifies the program counter. This separation means it's not a proper superset of traditional assembly languages -- you can't paste in arbitrary blocks of existing code -- but a lot of interesting things (for them, implementations of cryptographic primitives) are pretty trivial to port over. And in exchange, you get a well defined Hoare logic that can talk about total correctness, not just [1]'s partial correctness.

[0] https://github.com/project-everest/vale

[1] https://nickbenton.name/coqasm.pdf

Re: Everything in C is undefined behavior

#552
post #540

I fear I will be downvoted into oblivion but I also want to learn from this. First let me state the case for C. It’s meant to be used as a systems language that’s as close to assembly as possible while remaining portable (compared to assembly). As such it’s the first high-level language developed for any new processor. Given the above predicate: Isn’t everything described in the article as it should be? Add too much…

> Given the above predicate: Isn’t everything described in the article as it should be?

I think the real trick question is "as it should be for whom?".

Reading the comments I think people underestimate the complex interaction between:

- engineers that design hardware (they don't care much about the compiler, except when it has to fix their mistakes)

- engineers that do the compiler (they have to struggle with all quirks of the new architecture and all of the complaints of the users)

- users of the new system (hardware + compiler) that just want to take their 100k lines of code (libraries) and just use it on the new system with better performance (as that's what the hardware people promissed!)

- users working on one architecture all their lives

For the compiler people, yes, probably most what is described is as it should be. For the users (that care about performance and not making porting efforts), probably no.

Now, even when I was doing compiler work we had a hard time explaining our users why we couldn't do some things they wanted (while also improving performance and not changing code that was writting), so explaining that on the internet seems to me a lost battle.

I am sure there are things that can be improved, and standards evolve. But the problem is very complex given the sheer amount of code written and the strange architectures out there.

Re: Everything in C is undefined behavior

#553

Earlier quoted context omitted.

The examples are unequivocally UB. Full stop. How to think of this properly is that when you have UB, you are no longer under the auspices of a language standard. Things may work fine for a time, indefinitely even. But what happens instead is you unknowingly become subject to whimsies of your toolchain (swap/upgrade compilers), architecture, or runtime (libc version differences). You end up building a foundation on q…

The first example is dereferencing an integer pointer. That is a valid operation. Now if that pointer isn't valid (and being unaligned is one of many reasons it could be invalid) then calling the function with that invalid pointer will be UB. An honest discussion would be something more like 'dereferencing pointers can lead to UB on invalid pointers. Here are N examples of that. Maybe avoid using pointers. Maybe cons…

There is an important distinction here to the technical meaning of UB that is lost to many.

UB simply means the operation you are intending to perform has no defined semantic under the ISO C specification. That is all. Understand what this means but do not read further into it. It is easy to read further into this as you have and many do, and come to incorrect conclusions, and think this MUST result in incorrect behaviour, but this is not the claim. The claim is rather than once you write UB, you are no longer writing C the language with a defined spec, and that any manner of degrees of freedom (architecture, toolchain, etc) can now cause your code that was once behaving correctly to now behave incorrectly. That is the danger.

> That is a valid operation. Now if that pointer isn't valid (and being unaligned is one of many reasons it could be invalid) then calling the function with that invalid pointer will be UB.

This is incorrect. The moment you express this in source code, it is already UB wrt to the C abstract machine.

6.3.2.3. 755 If the resulting pointer is not correctly aligned for the pointed-to type, the behavior is undefined.

https://c0x.shape-of-code.com/6.3.2.3.html

The important distinction is to KNOW this is still UB; whether the operation yields the expected behaviour on your platform and architecture is completely a separate question.

The reason this is of utmost important is because the C compiler operates on the C abstract machine.

If you violate language invariants, the compiler can--keyword can--emit WRONG code and it will be CORRECT to do so because C unfortunately allows it to. When this happens it's silent and deadly and it's a pain to debug. The point of all this seeming language lawyering is not FUD, it is genuine frustration with these footguns of the language that we are trying to share with others. Understanding UB correctly really is what separates those that know C and those that "know" C.

Things will work and then they won't. This can be fine for most cases but not fine for others. If you use C in 2026 you need to understand this.

> come away more confused and scared

This is the correct take. One aught to be more confused and scared after learning about UB; the language simply leaves things under-specified and it is up to the developer to understand they are engaging in UB.

Once UB is acknowledged, one aught to impress upon themselves the software they build is dependent ever more on the whims of their particular compiler (clang/gcc), compiler flags (optimizations), architecture, and runtime environment.

Re: Everything in C is undefined behavior

#554

Earlier quoted context omitted.

The first example is dereferencing an integer pointer. That is a valid operation. Now if that pointer isn't valid (and being unaligned is one of many reasons it could be invalid) then calling the function with that invalid pointer will be UB. An honest discussion would be something more like 'dereferencing pointers can lead to UB on invalid pointers. Here are N examples of that. Maybe avoid using pointers. Maybe cons…

There is an important distinction here to the technical meaning of UB that is lost to many. UB simply means the operation you are intending to perform has no defined semantic under the ISO C specification. That is all. Understand what this means but do not read further into it. It is easy to read further into this as you have and many do, and come to incorrect conclusions, and think this MUST result in incorrect beha…

[deleted]

Re: Everything in C is undefined behavior

#556
post #403
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…

> In C, we can have a data race on a single thread and without any writes! You need to distinguish between a UB and a race, and I think that's something that discussions of UB miss. Take any C program and compile it. Then disassemble it. You end up with an Assembly program that doesn't have any UB, because Assembly doesn't have UB. UB is a property of a source program, not the executable. It means that the spec for t…

I specifically said data race, which is a known term of art and a type of language-level UB. It is separate from the races you're thinking about. Just like signed integer overflow or use-after-free, the compiler is allowed to assume data races never happen.

Re: Everything in C is undefined behavior

#557
post #299

Earlier quoted context omitted.

Fair enough! > And if it's not succeeded for 54 years, "try harder", or "just never make a mistake", is at least not the solution. And I 100% agree. UB is way overused by these standards for how dangerous it is, and as a consequence using C (and C++) for anything nontrivial amounts to navigating a minefield.

I think as compilers got smarter, UB changed somewhat in meaning. Originally the compilers didn't perform such complex analysis, and while invoking UB could break your program, it would still do something reasonable.

Yes, but compilers got smart enough for it to be a problem around 30 years ago, and we are still arguing about what to do.

Re: Everything in C is undefined behavior

#558

Earlier quoted context omitted.

“Implementation defined behaviour”: compiler author chooses, and documents the choice. A lot of UB should be implementation defined behaviour instead; this would much better match programmers’ intuitions as they reason about their code - you can even see it in the comments of this post: it’s always things like “this hardware supports / doesn’t support unaligned accesses”, it’s never nasal demons.

I told someone at a conference that UB actually means "implementation-defined, no documentation required". He started to refute me and then stopped.

Except that UB doesn't mean that. UB means "the developer must never write this".

Re: Everything in C is undefined behavior

#559

The 5 stages of learning about UB in C: -Denial: "I know what signed overflow does on my machine." -Anger: "This compiler is trash! why doesn't it just do what I say!?" -Bargaining: "I'm submitting this proposal to wg14 to fix C..." -Depression: "Can you rely on C code for anything?" -Acceptance: "Just dont write UB."

What stage is the "just make the compiler define the undefined" stage? Unaligned access? Packed structs. Compiler will magically generate the correct code, as if it had always known how to do it right all along! Because it has, in fact, always known how to do it right. It just didn't. Strict aliasing? Union type punning. Literally documented to work in any compiler that matters, despite the holy C standard never sayi…

> Strict aliasing? Union type punning. Literally documented to work in any compiler that matters, despite the holy C standard never saying so.

It does say so, actually, since C99 TC3 (DR 283).

Re: Everything in C is undefined behavior

#560

Earlier quoted context omitted.

Are you talking about creating a pointer (more than one item) past an array, or dereferencing that pointer? Both are currently UB. For the former, I kinda get it. It may need to be there for cases like with segmented address space where p+10 could actually be a value less than p, for the eventually generated assembly. Maybe it should be fine to create such a pointer, but have it be "indeterminate value" or whatever,…

I'm not saying that the result of the dereference be known, I'm saying that the instructions to do the dereference be always emitted. Right now, if a dereference results in UB, the compiler may omit it entirely.

I think I would defer to someone more of a language lawyer than we, but I'm not sure what you're describing can be expressed in the C abstract machine. If a pointer is invalid, not pointing to an object, then I'm not sure it means anything to "read from there".

I know what you mean, but I'm just not sure you're describing something that fits what C "is". We program C to the abstract machine specified in the standard (5.1.2), and the compiler's job is to translate that into something with identical behavior on particular hardware. Piercing the layers down to actual hardware or assembly isn't really done.

Even "volatile" just says (basically) "touching this object has side effects". It implies no double-loading, speculative store, etc, but doesn't say "don't emit assembly instructions to load this unless the program logic path takes the route where the C program does load it".

The standard is not using ancient language when it refers to "objects with static storage duration" instead of "heap" or ".data segment". It is the true class of objects in the abstract machine.

Post reply on HN