Live data from Hacker News

Everything in C is undefined behavior

blog.habets.se

651–660 of 748 posts

Re: Everything in C is undefined behavior

#651
post #577

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…

> Now, is it exploitable that `find` also reads the uninitialized auto variable `status` (UB) from a `waitpid(&status)` before checking if `waitpid()` returned error? (not reported) I can't imagine an architecture or compiler where it would be, no. I presume you're referring to this code: pid = waitpid(pid, &status, 0); if (WIFEXITED(status)) rval = WEXITSTATUS(status); else rval = -1; The only signal handler find in…

Couldn’t waitpid return EINTR if the (parent) process were stopped and then continued?

EINTR scares the crap out of me because nobody expects it!

Re: Everything in C is undefined behavior

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

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…

> Or at least, no human since the invention of C in 1972 has.

No human without proper tools maybe, but what about seL4? It goes beyond proving the code is UB-free and actually formally verifies the code works as intended. And the code is written in C. (the proofs of course aren't)

The proof is interesting because it goes beyond just proving the C code is correct. For some platforms, they compile the code with an ordinary compiler, and verify that the machine code does what the C code is supposed to do. (that's because just writing correct C code doesn't help you if you trigger a compiler bug)

This works even if the compiler (in this case, GCC) isn't verified - they verify a specific output of the compiler, not that the compiler always generates machine code correctly.

Re: Everything in C is undefined behavior

#653
post #77

Earlier quoted context omitted.

Don't mix up what processors do with what the C standard allows you to get away with.

...and don't mix up the C standard with what actually existing compilers allow you to get away with ;) In the end the standard is merely a set of guidelines. What matters is how compiler toolchains behave in the real word, and breaking code which does unaligned memory accesses by 'UB exploitation' would be quite insane.

Sanity ain't a hard requirement for C compilers.

Re: Everything in C is undefined behavior

#654
post #72

Earlier quoted context omitted.

That's a bit silly. UB means literally no restrictions. So if you standard says 'you have to crash with an error message' that's already no longer UB.

> So if you standard says 'you have to crash with an error message' that's already no longer UB. Sure. For crashes . But when you instruct an LLM to do something, the output is probablistic, so you may get behviour that is unexpected and/or unwanted. Like storing security tokens in code. Or nuking the production database.

If you fix the random seed you use for sampling, your LLM is perfectly deterministic.

And there's no requirement for C compilers' UB to be deterministic either.

Re: Everything in C is undefined behavior

#655

Anyone who uses the construction "C/C++" doesn't write modern C++, and probably isn't very familiar with the recent revisions despite TFA's claims of writing it every day for decades. Far from being just "C with classes", modern C++ is very different than C. The language is huge and complex, for sure, but nobody is forced to use all of it. No HN comment can possibly cover all the use cases of C++ but in general, unle…

You can write C++ in a way that's similar to C if you want and run into some of the same UB. Normally I don't like the "C/C++" thing, but in this context it makes sense.

Re: Everything in C is undefined behavior

#656

Earlier quoted context omitted.

That isn't true, for UB the compiler is allowed to assume the UB can never happen. For example if you dereference a pointer and only after check if it is NULL, the compiler can remove the NULL check, since it is clearly impossible (nevermind that you might be on a microcontroller where NULL is a valid address). The fallout of this are quite large! If behaviour is implementation defined the compiler has to stick to on…

I think parent commenter made a joke. UB can be seen as "implementation defines this to reformat your hard drive. No we don't document it". That is, the compiler de facto defines what happens when you compile UB code. So you're not wrong, but I think you missed the sarcastic spin of parent.

>That is, the compiler de facto defines what happens when you compile UB code.

That is not what undefined behavior is though, that is unspecified behavior.

The entire point of undefined behavior is to cover the cases where the compiler can't define the semantics of your program either because doing so is genuinely not possible, or is incredibly onerous to deduce, or would require introducing runtime checks whose performance cost is at odds with C and C++'s predominant use cases.

Re: Everything in C is undefined behavior

#657

Earlier quoted context omitted.

If some architecture traps on unaligned access, then the compiler can and should simply generate the correct code so that it loads the integer piece by piece instead. Load multiple integers and shift and mask away the irrelevant bits, done. This is exactly what modern architectures already do in hardware. Works, it's just a little slower. This is exactly what the compilers do if you use a packed structure to access u…

> If some architecture traps on unaligned access, then the compiler can and should simply generate the correct code so that it loads the integer piece by piece instead. LMAO what?! The compiler should pessimize each and every memory access everywhere with an alignment check on the pointer and a branch, or forego the efficient memory access method of the platform entirely and just do bytewise loads only?!

Unaligned access. Not every access. Compiler should be able to analyze code, determine alignment invariants and optimize everything it can. If not, __builtin_assume_aligned could help whenever it needs to be made explicit. Alignment should have been part of the type itself to begin with but there's no fixing that now.

Re: Everything in C is undefined behavior

#658

Earlier quoted context omitted.

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.

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

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

#659

Earlier quoted context omitted.

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

I get that it's defined that way, but I'd really like to know why. I can see the value in saying that struct x* isn't compatible with struct y*, because they could have different alignment or packing rules. But struct x* and void*, which is already special-cased to allow assignment without a cast? Why aren't these considered compatible in function pointer parameter definitions? Is there any work involved in casting v…

Yes, some restrictions seem arbitrary. Just like why these two types are not compatible:

  struct a {int data;};
  struct b {int data;};
I know, I know, changing it would break existing code, etc.

Re: Everything in C is undefined behavior

#660
post #594

Earlier quoted context omitted.

If some architecture traps on unaligned access, then the compiler can and should simply generate the correct code so that it loads the integer piece by piece instead. Load multiple integers and shift and mask away the irrelevant bits, done. This is exactly what modern architectures already do in hardware. Works, it's just a little slower. This is exactly what the compilers do if you use a packed structure to access u…

> If some architecture traps on unaligned access, then the compiler can and should simply generate the correct code so that it loads the integer piece by piece instead. Wouldn't the compiler have to assume that every pointer access might be unaligned and do the slow "piece by piece" access every time? It can hardly guess the runtime value of a pointer during compilation.

It should be able to make a lot of inferences. For example, taking the address of some value allocated by the compiler itself results in an aligned pointer unless the programmer overrides it. Compiler should be able to trace it from there. Pointers from malloc are also aligned.

If compiler is not doing it for some reason, __builtin_assume_aligned can be used to explicitly mark a pointer as aligned.

Post reply on HN