Live data from Hacker News

Everything in C is undefined behavior

blog.habets.se

471–480 of 748 posts

Re: Everything in C is undefined behavior

#471

Can anyone explain why this is undefined behaviour? UBSan calls it "indirect call of a function through a function pointer of the wrong type" struct foo {int i;}; int func(struct foo *x) {return x->i;} int main() { int (*funcptr)(void*) = (int (*)(void*)) &func; struct foo foo = { 42 }; return funcptr(&foo); } While this is all kosher per the language lawyers: struct foo {int i;}; int func(void *x) {return ((struct f…

C23 §6.5.2.2p7

> If the function is defined with a type that is not compatible with the type (of the expression) pointed to by the expression that denotes the called function, the behavior is undefined.

Compatible types requires integrating texts from several different paragraphs, but the general notion is "identical type, in a frontend sense", not "same ABI." This means that "const void " and "void " are not compatible types, much less "void " and "struct foo ".

Re: Everything in C is undefined behavior

#472

Earlier quoted context omitted.

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?

I think the disconnect here is that you're operating on the assumptions built by using common architectures that have solved these problems in implementation specific forms, and you're used to those solutions.

But just because those forms are common, doesn't mean the behavior is actually defined.

Ex - I might be using a vendor specific compiler for custom embedded devices where dynamic linking isn't available at all, and which might have complicated storage mechanisms that look nothing like standard memory pages.

Re: Everything in C is undefined behavior

#473
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…

> Here's a way weirder example:

Well, yes; but when the C standard authors wrote like this, they surely had in mind "the reads could be in either order, therefore the output could display the polled values in either order". Not C++ nasal demons.

And yeah, being able to say "reading is a side effect" is important when for example you interact with certain memory-mapped devices.

Re: Everything in C is undefined behavior

#474

Can anyone explain why this is undefined behaviour? UBSan calls it "indirect call of a function through a function pointer of the wrong type" struct foo {int i;}; int func(struct foo *x) {return x->i;} int main() { int (*funcptr)(void*) = (int (*)(void*)) &func; struct foo foo = { 42 }; return funcptr(&foo); } While this is all kosher per the language lawyers: struct foo {int i;}; int func(void *x) {return ((struct f…

It's undefined behavior due to the "strict aliasing" rule. You're simply not allowed to cast one pointer type to another (ever!) except for the following exceptions: - casting an object pointer to or from void* - casting an object pointer to or from char* You're not doing either of those things. A function pointer is not an object pointer (the standard does not guarantee that the two kinds of pointer even have the sa…

Sorry, this explanation is plain wrong.

You can cast between pointer types freely so long as they can be representable in one another (some casts are undefined because the address would be unaligned in the target pointer type, and there's actually no guarantee that pointers to objects and pointers to functions have the same representation).

Strict aliasing rules don't kick in at pointer type casting, but rather kick in at lvalue access--when you dereference a pointer, in other words--and you've also given the list of strict aliasing rules completely incorrectly.

Re: Everything in C is undefined behavior

#475

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…

> People just don't use this because they want to write "portable" "standard" C

Something that bothers me is the Venn diagram of people that think abstraction is slow and error prone and people that only write portable C.

How many C implementations do you actually need to compile against? I don't think I've seen more than 3 outside Unix software from the 90s. Using non portable extensions is in fact totally doable for your application and you should probably do it, and just duplicate/triplicate code where you have to. It's not that hard to write and not hard to read.

Re: Everything in C is undefined behavior

#476
post #413
post #317

Earlier quoted context omitted.

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…

> 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. Chec…

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

#477

Earlier quoted context omitted.

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.

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

#478
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…

The problem is that in the quest to win benchmark games, compilers started to take advantage of UB for all kinds of possible optimizations, which is almost as deterministic as LLM generated code, across compiler version updates.

Re: Everything in C is undefined behavior

#479

Earlier quoted context omitted.

Author here. So I see your counter points are all "so just don't do that, then". And the point of my post is that this particular "just don't do that, then" has never been achieved by humans. If if there's no example of a program without these bugs in a language, then I do think it's fair to blame the language. A knife with 16 blades and no handle. > Expecting C to handle "address zero" in physical memory in ways tha…

"Just don't do that" is the correct approach to errors, even when they are easy to overlook and the programming language provides many opportunities for mistakes. For example, you seem to underestimate how wrong placing negative values in a signed char is: ordinary character encodings do not use negative codes, so either those negative values are not characters and they have no business being treated as such, or some…

> "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 absolute dictatorship, all we need to do is "just" choose the benevolent one.

Of course we should always try to not make mistakes. But given more than half a century of empirical data that nobody has been able to avoid UB, ever, it takes quite some hubris to say "but it might work for us".

> you seem to underestimate how wrong placing negative values in a signed char is

Shrug. You don't make that mistake. There are thousands of mistakes like it, especially in C or C++.

Of course "don't do that". That is not the same as "So just don't do that!". The former is good advice. The latter is one of a million rules, and to expect even experts (see OpenBSD) to never make a mistake is unrealistic to say the least.

You may even have spotted the UB in https://pooladkhay.com/posts/first-kernel-patch/. But you would not spot all of them. Nobody in history has.

Re: Everything in C is undefined behavior

#480
post #403

Earlier quoted context omitted.

> 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…

> because Assembly doesn't have UB To be pedantic, old hardware like 6502 family chips (Commodore 64, Apple II, etc) had illegal instructions which were often used by programmers, but it was completely up to the chip to do whatever it wanted with those like with UB.

> illegal instructions... were often used by programmers

Intentionally, with an expected effect? I'd need a citation for that.

Post reply on HN