News for hackers. Stuff that matters.
Re:Everything in C is undefined behavior by jstimpfle on Wednesday May 20, 2026 @06:20AM (#48205525)
I would agree that C is "really flexible", but I would say it's primarily flexible because it lets you cast say from a void pointer to a typed pointer without requiring much boilerplate. It's also flexible because it lets you control memory layout and resource management patterns quite closely.

If you want to be standards correct, yes you have to know the standard well. True. And you can always slip, and learn another gotcha. Also true. But it's still extremely flexible.

Re:Everything in C is undefined behavior by crote on Wednesday May 20, 2026 @06:58AM (#48205798)
The problem is that a lot of the flexibility introduced by UB doesn't serve the developer.

Take signed integer overflow, for example. Making it UB might've made sense in the 1970s when PDP-1 owners would've started a fight over having to do an expensive check on every single addition. But it's 2026 now. Everyone settled on two's complement, and with speculative execution the check is basically free anyways. Leaving it UB serves no practical purpose, other than letting the compiler developer skip having to add a check for obscure weird legacy architectures. Literally all it does is serve as a footgun allowing over-eager optimizations to blow up your program.

Although often a source of bugs, C's low-level memory management is indeed a great source of flexibility with lots of useful applications. It's all the other weird little UB things which are the problem. As the article title already states: writing C means you are constantly making use of UB without even realizing it - and that's a problem.

Re:Everything in C is undefined behavior by ablob on Wednesday May 20, 2026 @07:22AM (#48205991)
If we're talking two's complement it's not undefined that is right. Having to emit checks though, that is where I beg to differ. A check is only useful if you want to actually change the behavior when it happens, otherwise it is useless. Furthermore, it might be "essentially free" from a branch prediction point, but low and behold caches exist. You would pollute both the instruction cache with those instructions _and_ the branch prediction cache. From this it doesn't follow at all, that there is no cost.

In the end small things do add up, and if you're adding many little things "because it doesn't cost much nowadays" you will end up with slow software and not have one specific bottleneck to look at. I do agree that having the option for checked operations is nice (see C#), but I have needed this behavior (branching on overflow) exactly once so far.

Re:Everything in C is undefined behavior by Xirdus on Wednesday May 20, 2026 @08:34AM (#48206677)
> A check is only useful if you want to actually change the behavior when it happens, otherwise it is useless.

You almost always want to change the behavior to erroring out on overflow. The few cases where overflow really is intended and fine can be handled by explicit opt-out.

And I refuse to buy the argument that "small things add up" in the world where we do string building and parsing every few microseconds. Checked math will have unnoticable impact compared to all the other things we do, in almost every type of program.

Re:Everything in C is undefined behavior by jstimpfle on Wednesday May 20, 2026 @09:51AM (#48207806)
This string manipulation stuff is very common, and that's why in 2026, an age where science fiction has become a reality, many things are still absurdly slow. Exactly because of such sloppiness, which does accumulate in many cases, and when one least expected it.
Re:Everything in C is undefined behavior by Xirdus on Wednesday May 20, 2026 @05:06PM (#48214135)
100% agreed on the sloppiness. But overflow checking is not sloppiness. It's the opposite of sloppiness. Unchecked math is sloppiness, allowing overflows to happen silently and uncontrollably is sloppiness. It just so happens this kind of sloppiness makes code faster, unlike other kinds of sloppines that make code slower. Not doing necessary safety checks is faster than doing these necessary checks, but it doesn't make these checks any less necessary. Not validating user input also makes code faster, and is also sloppy.
Re:Everything in C is undefined behavior by saagarjha on Wednesday May 20, 2026 @07:20AM (#48205973)
Signed overflow checks are typically not free unfortunately they have a cost of about 5% or thereabouts
Re:Everything in C is undefined behavior by vlovich123 on Wednesday May 20, 2026 @08:50AM (#48206855)
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.
Re:Everything in C is undefined behavior by steveklabnik on Wednesday May 20, 2026 @09:23AM (#48207300)
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 by jstimpfle on Wednesday May 20, 2026 @09:53AM (#48207834)
If it is defined as an error, but the compiled build will continue to run with the value wrapped around, I would say that's indistinguishable from UB.
Re:Everything in C is undefined behavior by 12_throw_away on Wednesday May 20, 2026 @11:19AM (#48209238)
No. An integer getting deterministically set to an unintended value is a bug. A bug is not the same thing as UB. (Even if it were non-deterministic, it would still not be anything like UB.) It's not the same ballpark, not even the same sport.
Re:Everything in C is undefined behavior by jstimpfle on Wednesday May 20, 2026 @05:55PM (#48214744)
What if the wrapped index is used to construct an invalid pointer? It might be possible, not sure. What if the integer is used to read the wrong data from disk, or corrupt data on disk by writing to the wrong location?
Re:Everything in C is undefined behavior by 12_throw_away on Wednesday May 20, 2026 @06:41PM (#48215305)
> What if the wrapped index is used to construct an invalid pointer?

Constructing an invalid pointer in rust is UB, yes, but integer wraparound is not.

> What if the integer is used to read the wrong data to a disk, or corrupt data on disk by writing to the wrong location?

Then it is a very bad bug.

> What if the program controls a nuclear power plant and the integer causes the control system to fail, causing memory errors due to radiation from the meltdown?

Then it is a very very bad bug.

> What if the wrapped integer causes the program to output the true name of god, and the programmer, in their last minutes of existence, looks up to see, overhead, without any fuss, the stars going out?

Ok, you got me, this one is UB.

Re:Everything in C is undefined behavior by kobebrookskC3 on Wednesday May 20, 2026 @08:49PM (#48216371)
> Constructing an invalid pointer in rust is UB

no, it is dereferencing, not constructing, an invalid pointer, that is UB. there is even a safe function provided to construct an invalid but non-null pointer: `https://doc.rust-lang.org/stable/std/ptr/fn.dangling.html`

Re:Everything in C is undefined behavior by 12_throw_away on Friday May 22, 2026 @02:37PM (#48239689)
you're of course 100% right, I was so proud of my nerd joke that I got like the most basic rust safety rule wrong :(
Re:Everything in C is undefined behavior by steveklabnik on Wednesday May 20, 2026 @09:42PM (#48216789)
> What if the wrapped index is used to construct an invalid pointer?

Using that pointer would be UB, but that is UB, not the addition.

> What if the integer is used to read the wrong data from disk, or corrupt data on disk by writing to the wrong location?

That is a bug, but it is not undefined behavior.

Re:Everything in C is undefined behavior by SAI_Peregrinus on Wednesday May 20, 2026 @01:20PM (#48211014)
It's indistinguishable from unspecified behavior, not from undefined behavior. Unspecified behavior has to pick from a finite list of allowed behaviors. Undefined behavior can do anything.
Re:Everything in C is undefined behavior by jstimpfle on Wednesday May 20, 2026 @05:58PM (#48214773)
A program with corrupted state can essentially do anything. Yes it's still a question of run-time checks the runtime has to protect against it. But the compiler is probably deriving a lot of assumptions from the assumption that there wasn't overflow.
Re:Everything in C is undefined behavior by steveklabnik on Wednesday May 20, 2026 @09:40PM (#48216776)
“Undefined behavior” is a term of art in programming languages that means something more specific than “the program may do something odd.”

The compiler is not allowed to derive any assumptions from it. It only could if it were UB.

Re:Everything in C is undefined behavior by jstimpfle on Thursday May 21, 2026 @01:51AM (#48218415)
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?
Re:Everything in C is undefined behavior by SAI_Peregrinus on Thursday May 21, 2026 @10:32AM (#48223313)
The semantics are well-defined in both modes. You can predict exactly what will happen in either case. In C, the semantics are not defined at all, you can't predict what will happen and it's allowed to change between compilations of the same source.

It will probably get omitted, since Undefined Behavior isn't allowed by the C abstract machine, but sadly compilers are allowed to emit code for UB in the source (partly because some UB is only detectable at runtime). Sometimes disabling optimizations will incorrectly allow codegen to run for source lines which have UB, tricking people into thinking that optimizations are breaking their program. Compilers are allowed to do this, since behaviors other than "omit the offending statement" are unfortunately allowed by the standard, so it's not a compiler bug.

Re:Everything in C is undefined behavior by jstimpfle on Thursday May 21, 2026 @03:43PM (#48227993)
UB is in general a runtime property. As far as you can statically verify some code parts, you can see UB at compile time, but the point of UB is exactly that it is about stuff you can't predict, or that is hard to predict as a compiler.

Now why you can cook up trivial artificial examples where a compiler will remove some code sections based on statically detected UB, instead of printing an error, you have to ask the compiler authors.

> The semantics are well-defined in both modes.

So they're not the same? So the behaviour is not uniquely defined by the source code alone, but is actually _very_ different based on compile mode? Between two modes whose point was never to have different semantics, but to have the _same_ semantics while being debuggable vs being fast?

> You can predict exactly what will happen in either case. In C, the semantics are not defined at all, you can't predict what will happen and it's allowed to change between compilations of the same source.

You can make the same "predictability" argument for C, you can easily write a compiler that has semantics exactly laid out. Case in point: -fwrapv. Case in point: UBSAN.

Re:Everything in C is undefined behavior by SAI_Peregrinus on Friday May 22, 2026 @07:51PM (#48243020)
You can write a C compiler with exactly laid out well-defined semantics. You can't assume those semantics hold for C-the-language, because it doesn't define those semantics. UB is a property of the language, not just of a given compiler. The Rust reference defines the semantics of the safe subset of Rust without any UB, so any compliant Rust compiler won't have UB in that subset. The reference also defines the guarantees which the programmer must uphold within `unsafe` blocks to avoid UB, as long as those are upheld there's no UB at all.
Re:Everything in C is undefined behavior by jstimpfle on Saturday May 23, 2026 @03:57AM (#48245715)
I understand that. It makes no practical difference. 99,99% of my additions don't rely on signed overflow for example, and if I'd ever need it there are ways to get just it.

Or tell me how you write a Rust program differently given that signed overflow is apparently defined? I bet you write it exactly the same way, and you get pretty much the same behaviour in practice. And that is talking only about the overflow situations, meaning there is a bug whatever the behaviour is.

C the language doesn't even guarantee that the machine has native integers with 8, 16, 32, 64 bits etc, that a cacheline is 64 bits, that a page is 4K, and here I am, writing programs for exactly that.

Re:Everything in C is undefined behavior by steveklabnik on Thursday May 21, 2026 @11:53AM (#48224818)
> 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. But yes, in practice, the semantics are different based on various things: it could be debug vs release, it could also be flags that control the behavior. But that's still distinct from "undefined behavior" as a concept. The behavior is well defined, with multiple possible options for behaviors.

Re:Everything in C is undefined behavior by jstimpfle on Thursday May 21, 2026 @04:24PM (#48228417)
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 an emotionally unstable user with destructive tendencies. The user also never intended the program to abort with a (nicely formatted) error message, unless they are a very very sad depressed nihilistic user who also never runs their program in Release mode.

To say that overflow would be defined in Rust is at least half a lie. We could agree that cargo has a choice of diagnostic policy though, a policy how to handle what is essentially a state with no defined or useful path forward, or in other words, UB.

Throwing errors might be a wanted property to detect oversights. C ecosystem has UBSAN too! But essentially the same is still true: Basic arithmetic operations are not closed over the numbers 0..2^N. Rust doesn't have a (unique and useful) definition for those operations for a subset of numbers. Even if you claim the operations are defined (say wrapping arithmetic in Release mode), it's not what the programmer wants. Probably the majority of algorithms work over natural numbers or integer numbers. These algorithms don't work when the arithmetic on integers modulo 2^N.

So the user has to constrain the set of valid inputs, and do manual sanitization, just like in C.

Re:Everything in C is undefined behavior by steveklabnik on Thursday May 21, 2026 @08:13PM (#48230372)
> 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.

In the context of "undefined behavior", it is not a lie at all.

> So the user has to constrain the set of valid inputs, and do manual sanitization, just like in C.

No, because the consequences of how the two languages define these behaviors are very, very different.

Re:Everything in C is undefined behavior by jstimpfle on Friday May 22, 2026 @04:50AM (#48233494)
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 by steveklabnik on Friday May 22, 2026 @09:33AM (#48235639)
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 by jstimpfle on Friday May 22, 2026 @12:57PM (#48238480)
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?

Re:Everything in C is undefined behavior by jstimpfle on Friday May 22, 2026 @04:51AM (#48233502)
> 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 by steveklabnik on Friday May 22, 2026 @09:35AM (#48235656)
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 by saagarjha on Thursday May 21, 2026 @03:27AM (#48219083)
Yeah on average. On some paths it's almost free
Re:Everything in C is undefined behavior by jstimpfle on Wednesday May 20, 2026 @08:35AM (#48206683)
You can run your code under ASAN and UBSAN nowadays, it will catch many or most of issues as they happen.

But that's completely besides the point. UB on signed overflow, or really most of UB, is not unrelated to C flexibility. It is a detail of the spec related to portability and performance. IIRC it is even required to make such trivial optimizations as turning

    for (int i = 0; i < n; i++) func(a[i]);
into

    for (Foo *p = a, *last = a + n; p < last; p++) func(p);
saving arithmetics and saving a register, on architectures where `int` is smaller than pointers. But there is also options like -fwrapv on GCC for example, allowing you to actually use signed overflow.
Re:Everything in C is undefined behavior by jstimpfle on Wednesday May 20, 2026 @10:37AM (#48208606)
*is not related to C flexibility
Re:Everything in C is undefined behavior by Chinjut on Wednesday May 20, 2026 @09:39AM (#48207593)
How is undefined behavior necessary for this transformation?
Re:Everything in C is undefined behavior by jstimpfle on Wednesday May 20, 2026 @10:15AM (#48208223)
IIRC computation of the address is done by computing offset from base pointer as a multiplication in (32-bit) int, (like p + (i * sizeof (Foo)). The right term might overflow, but due to signed overflow being UB, the compiler is able to assume that it does not, so the transformation to do the arithmetic entirely in (64-bit) pointer space is valid.
Re:Everything in C is undefined behavior by tyg13 on Wednesday May 20, 2026 @02:31PM (#48212005)
Exactly. You as the programmer know that the loop counter won't overflow, and in general, essentially nobody would actually write it that way. But if you don't assume it can't happen, the possibility for signed overflow is everywhere in address computations.

This is also a major blocker for auto-vectorization. Can't coalesce a load of a[i], a[i+1], a[i+2], a[i+3] into a load of a[i:i+3] if there's a possibility that `i+1`, `i+2` or `i+3` wrapped around (thus causing your "contiguous" load to be non-contiguous). This is a big reason why you shouldn't use `unsigned` for loop counters, especially if they're going to be used as an index into an address calculation.

Re:Everything in C is undefined behavior by Chinjut on Thursday May 21, 2026 @09:54AM (#48222595)
But surely the more natural approach than making this undefined behavior would be making the computation of a[i] take place in 64-bit pointer space rather than 32-bit int space? Why does the compiler need the freedom to emit nasal demons?
Re:Everything in C is undefined behavior by simonask on Wednesday May 20, 2026 @06:38AM (#48205643)
It's not flexible in practice, because knowing the standard isn't optional. If you make the choice to not follow the standard, you're making the choice to write fundamentally broken software. Sometimes with catastrophic consequences.
Re:Everything in C is undefined behavior by jstimpfle on Wednesday May 20, 2026 @06:57AM (#48205786)
I'm making the choice to pass pointers as void to get low-friction polymorphism. I'm making the choice to control the memory layout of my data structures, including of levels and type of indirection. I'm making the choice to control my own memory allocators and closely control lifetimes, closely control (almost) everything that happens in the system.

That has nothing to do with not following the standard.

Re:Everything in C is undefined behavior by saagarjha on Wednesday May 20, 2026 @07:20AM (#48205980)
But be as you may you’re not following the standard.
Re:Everything in C is undefined behavior by jstimpfle on Wednesday May 20, 2026 @08:24AM (#48206569)
what is your point?
Re:Everything in C is undefined behavior by Xirdus on Wednesday May 20, 2026 @08:37AM (#48206706)
If you don't follow the standard, gcc -O2 can introduce bugs to your code that you never even wrote. Skipping null checks, executing both branches of a conditional, and so on.
Re:Everything in C is undefined behavior by jstimpfle on Wednesday May 20, 2026 @08:39AM (#48206724)
Where did I say I'm not following the standard?
Re:Everything in C is undefined behavior by Xirdus on Wednesday May 20, 2026 @08:58AM (#48206945)
I interpreted these words:

> If you want to be standards correct, yes you have to know the standard well.

to mean that being standards-correct is optional. It's not. Every C programmer needs to know every possible UB by heart and never introduce any of it to their code, or else they'll be constantly introducing subtle, hard to debug bugs that contradict the actual code they wrote.

Maybe you meant something different by those words, but then I'm confused what the "if" was supposed to mean.

Re:Everything in C is undefined behavior by jstimpfle on Wednesday May 20, 2026 @10:06AM (#48208035)
Of course it's optional (although I didn't mean to imply that). Even using computers at all is optional. I never said that I don't aim to follow the standard, have a clean compiling program without warnings and without UB, etc. I do strive to achieve all of that.

But it's not entirely black and white, either. In practice I'm fine accepting that some bugs are technically UB but whatever, we've found a bug by whatever manifestation (like NULL dereference most likely leading to segfault in practice). I just fix the bug as a bug, and life goes on.

The standard is not perfect, it does have shortcomings. It can be improved. And it can be interpreted to fix some issues. Let's not hold theory over practicality, and let's expect the compiler writers also strive to do the reasonable thing.

Re:Everything in C is undefined behavior by Xirdus on Wednesday May 20, 2026 @04:57PM (#48214046)
In practice, GCC -O2 will happily erase entire swathes of code and turn perfectly logical source into nonsense assembly whenever it gets as much as a sniff of UB anywhere in the code path. Nobody would be talking about UB if GCC wasn't so aggressive in abusing the freedom UB gives.

To paraphrase your earlier comment - you lose low-friction polymorphism (unpredictable compiler output causes a lot of friction). You lose control of memory allocations (because they may have been elided) and lose control of lifetimes (because free can be moved before last use causing a crash, or removed entirely causing a leak). You lose control of (almost) anything that happens in the system. And it has everything to do with not following the standard.

You do retain control of the memory layout of data structures, though.

Re:Everything in C is undefined behavior by jstimpfle on Wednesday May 20, 2026 @06:13PM (#48214950)
Then I'm almost ashamed to admit that I'm not sure I've ever witnessed any surprising form of UB in the wild. For example, I will reliably get segfaults on NULL dereference in practice. Typical manifestations of UB are entirely predictable and obvious. Of course I'm also running most code without most optimizations, most of the time, while developing.

On the other hand, what I've observed with my own eyes is interesting phenomenons like performance drops, e.g. memory bandwidth dropping from gigabytes/sec to 300 KB/sec due to false sharing on an ARM SOC for example.

Re:Everything in C is undefined behavior by saagarjha on Thursday May 21, 2026 @03:34AM (#48219131)
Compilers do not surprise all that often which is why it is extra surprising when it does happen
Re:Everything in C is undefined behavior by Xirdus on Thursday May 21, 2026 @09:13AM (#48222108)
There was once a privilege escalation vulnerability in Linux kernel that only happened when compiled with optimizations. In kernel space, address 0 is just regular memory that can be read from and written to if there's a page mapped to it. But in C standard, reads and writes to null pointer are UB.

There was some function that read from a passed pointer unconditionally whether it's null or not. It made sense in context. Then it checked if the pointer is null - if it is do early return, if it's not do privileged operation. The pointer was null iff the user didn't have permissions to do the operation.

What GCC did is notice that a pointer is accessed before its null check. Since accessing a null pointer is UB, and GCC assumes UB never happens, it figured out the null check is superfluous. And removed the check and the early return. The pointer read stayed, mind you. The optimized function would unconditionally read from the pointer even if it's null, then unconditionally execute the privileged operation without checking permissions. That allowed obtaining root access from anywhere.

I saw a few other writeups of interesting UB behavior on The Old New Thing blog. I especially like the time travel one: https://devblogs.microsoft.com/oldnewthing/20140627-00/?p=63... (apologies to people of the future, links to MS devblogs tend to die often).