14 ms·
Everything in C is undefined behavior
- dmitrygr 5mo agoI stoped reading about here: > bool parse_packet(const uint8_t* bytes) { > const int* magic_intp = (const int*)bytes; // UB! Author, if you are reading this, please cite the spec section explaining that this is UB. Dereferencing the produced pointer may be UB, but casting itself is not, since uint8_t is ~ char and char* can be cast to and from any type. you might try to argue that uint8_t is not necessarily char, and while it is true that implementations of C can exist where CHAR_BIT > 8, but those do not have uint8_t defined (as per spec), so if you have uint8_t, then it is "unsigned char", which makes this cast perfectly safe and defined as far as i can tell. Of course CHAR_BIT is required to be >= 8, so if it is not >8, it is exactly 8. (In any case, whether uint8_t is literally a typedef of unsigned char is implementation-defined and not actually relevant to whether the cast itself is valid -- it is)
- raphlinus 5mo agoThe issue is not type punning (itself a very common source of UB), but the fact that the `bytes` pointer might not be int-aligned. The spec is clear that the creation (not just the dereferencing) of an unaligned pointer is UB, see 6.3.2.3 paragraph 7 of the C11 (draft) spec. Of course, this exchange just demonstrates the larger point, that even a world-class expert in low level programming can easily make mistakes in spotting potential UB.
- dmitrygr 5mo agoThat cast is valid. Spec does not guarantee same bit sequence for resulting pointer and source pointer. But as the cast is explicitly allowed, it is not UB. Compiler is free to round the pointer down. Or up. Or even sideways. All ok. Dereferencing it — indeed not ok. But the cast is explicitly allowed and not UB. Pointer casts changing pointer bit sequences is common on weird platforms (eg: some TI DSPs, PIC, and aarch64+PAC). And it is valid as per spec. Pointer assignment is not required to be the same as memcpy-ing the pointer unto a pointer to another type. You misunderstood the spec. No promises are made that that cast copies the pointer bit for bit (and thus creates an invalid pointer). Therefore, your objection to invalid pointers is null and void. :)
- raphlinus 5mo agoI'm not assuming anything about bit representations. In this case, the spec language is quite clear and unambiguous. 6.3.2.3 paragraph 7: A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned[footnote 68]) for the referenced type, the behavior is undefined. Otherwise, when converted back again, the result shall compare equal to the original pointer. When a pointer to an object is converted to a pointer to a character type, the result points to the lowest addressed byte of the object. Successive increments of the result, up to the size of the object, yield pointers to the remaining bytes of the object. This is a subsection of section 6.3 which describes conversions, which include both implicit and conversions from a cast operation. This language is not saying anything about bit representations or derefencing. I happen to be wearing my undefined behavior shirt at the moment, which lends me an extra layer of authority. I'm at RustWeek in Utrecht, and it's one of my favorite shirts to wear at Rust conferences. But let's say for the sake of argument that you are right and I am indeed misunderstanding the spec. Then the logical conclusion is that it's very difficult for even experienced programmers to agree on basic interpretations of what is and what isn't UB in C.
- dmitrygr 5mo agoI do not see there a promise that the cast will produce an invalid pointer, nor anything prohibiting the compiler from rounding the pointer down, thus producing a valid one. “Converted” does not require bit copy. I don’t see how this interpretation is against any section of the spec.
- cyclopeanutopia 5mo ago> Otherwise, when converted back again, the result shall compare equal to the original pointer. Doesn't this part exclude the possibility of rounding down?
- dmitrygr 5mo agoNo cause that requires initial alignment.
- gritzko 5mo agoC of course is ancient. It remembers the Cambrian explosion of CPU architectures, twelve-bit bytes and everything like that. I wonder if it is possible to codify some pragmatic subset of it that works nicely on currently available CPUs. Cause the author of the piece goes back in time to prove his point (SPARCs and Alphas).
- flohofwoe 5mo ago> Of course, this exchange just demonstrates the larger point, that even a world-class expert in low level programming can easily make mistakes in spotting potential UB. A "world-class expert in low level programming" knows that unaligned memory accesses are no problem anymore on most modern CPUs, and that this particular UB in the C standard is bogus and needs to fixed ;)
- formerly_proven 5mo ago… it’s only UB if the pointer is actually misaligned. It’s not possible to tell from these two lines whether that’s the case.
- deleted 5mo ago[deleted]
- stevenhuang 5mo agoByte and int has different alignment requirements. It is UB the moment you make such a ptr. Great way to demonstrate the point of the article.
- gritzko 5mo agoThat better be marked "historical". At least, Lemire says: On recent Intel and 64-bit ARM processors, data alignment does not make processing a lot faster. It is a micro-optimization. Data alignment for speed is a myth. // https://lemire.me/blog/2012/05/31/data-alignment-for-speed-myth-or-reality/ https://lemire.me/blog/2012/05/31/data-alignment-for-speed-m... (while in the olden days, a program may crash on unaligned access, esp on RISC)
- eru 5mo agoDon't mix up what processors do with what the C standard allows you to get away with.
- flohofwoe 5mo ago...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.
- eru 5mo agoSanity ain't a hard requirement for C compilers.
- dmitrygr 5mo agoWithout memcpy there is no guarantee that that line produces an invalid pointer I don’t see what spec part would prohibit that cast from validly compiling to BIC r3, r0, #3 Spec only guaranteed round-trip through char* of properly aligned for type pointers. This doesn’t break that.
- thomashabets2 5mo agoAuthor here. > A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned71) for the referenced type, the behavior is undefined. C23 6.3.2.3p7.
- weinzierl 5mo agoA fun one that'd fit list be sequence point violations like i = i++
- radiospiel 5mo agoFun, sure, but also GCC and Clang will both warn with -Wall (-Wsequence-point / -Wunsequenced).
- leni536 5mo agoOnly in C, that one is defined in C++. edit: I'm not sure it's even undefined in C.
- account42 5mo agoThis would also be a code smell even if it was well defined.
- marcosdumay 5mo agoYes, it should be an explicit error. Not undefined.
- nokeya 5mo agoOk, and?
- deleted 5mo ago[deleted]
- wg0 5mo ago"Rewrite everything in Rust. OMG universe is written in Rust so memory safe with zero allocations"
- veltas 5mo agoFrom the ANSI C standard: 3.16 undefined behavior: Behavior, upon use of a nonportable or erroneous program construct, of erroneous data, or of indeterminately valued objects, for which this International Standard imposes no requirements. Permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message). Is it just me or did compiler writers apply overly legalistic interpretation to the "no requirements" part in this paragraph? The intent here is extremely clear, that undefined behavior means you're doing something not intended or specified by the language, but that the consequence of this should be somewhat bounded or as expected for the target machine. This is closer to our old school understanding of UB. By 'bounded', this obviously ignores the security consequences of e.g. buffer overflows, but just because UB can be exploited doesn't mean it's appropriate for e.g. the compiler to exploit it too, that clearly violates the intent of this paragraph.
- dataflow 5mo ago> but that the consequence of this should be somewhat bounded or as expected for the target machine. Aren't "unpredictable results" and "no requirements" contrary to the idea that the behavior would be "somewhat bounded"?
- veltas 5mo agoNotice though "ignoring the situation" thru "documented manner characteristic of the environment". Even though truly you can read this in an uncharitable way, you could also try and understand the intent of this paragraph, and I think reading it for its intents is always the best way to interpret a language standard when the wording is ambiguous or soft, especially if you're writing a compiler. I don't think you could sincerely argue that this definition intends to allow the compiler to totally rewrite your code because of one guaranteed UB detected on line 5, just that it would be good to print a diagnostic if it can be detected, and if not to do what's "characteristic of the environment". Does that make sense?
- 5mo ago
- stackghost 5mo agoAnyone 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, unless you have a very good reason not to: - eschewing boomer loops in favor of ranges - using RAII with smart pointers - move semantics - using STL containers instead of raw arrays - borrowing using spans and string views These things go a long way towards, shall we say, "safe-ish" code without UB. It is not memory-safe enforced at the language level, like Rust, but the upshot is you never need to deal with the Rust community :^)
- rectang 5mo ago> the upshot is you never need to deal with the Rust community In the end, everything comes down to culture war.
- stackghost 5mo agoPerhaps we should rewrite our culture in Rust.
- jim33442 5mo agoI've never noticed an issue with this using any of those three languages
- veltas 5mo agoAlthough some people, like Bjarne Stroustrup, object to the term C/C++, it's a bit like Richard Stallman objecting to the term "Linux". The fact is it can mean "C or C++", and I wouldn't assume the author thinks they're the same, but they're talking about both of them together in the same sentence. This seems reasonable given this is about undefined behavior, and it's trivial to accidentally write UB-inducing code in C++ even with modern style (although I'd say you should catch most trivial cases with e.g. ubsan, and a lot of bad cases would be avoided with e.g. ranges, so I think the article is exaggerating the issue).
- bestouff 5mo agoThe problem of UB is not really that it may crash in some architecture. The real problem is that the compiler expects UB code to NOT happen, so if you write UB code anyway the compiler (and especially the optimizer) is allowed to translate that to anything that's convenient for its happy path. And sometimes that "anything" can be really unexpected (like removing big chunks of code).
- anilakar 5mo agoRemoving code paths that the programmer has explicitly laid out in the source code should be made a hard compile error unless the operation has been tagged with an attribute (anyone who wants to add the unsafe keyword to C? ). Another commenter suggested using LLMs, but I disagree. Having clangd emit warning squiggles for unchecked operations (like signed addition) would be a good start.
- 4gotunameagain 5mo agoThis is trickier than it initially seems. Using preprocessor directives to include or exclude swaths of code is a very common thing, and implementing a compiler error as you described would break the building of countless C codebases.
- amoss 5mo agoDead code elimination is run multiple times, including after other optimizations. So code that is not initially dead may become dead after propagating other information. Converting dead code into an error condition would make most generic code that is specialized for a particular context illegal.
- flohofwoe 5mo ago> Removing code paths that the programmer has explicitly laid out in the source code should be made a hard compile error unless the operation has been tagged with an attribute (anyone who wants to add the unsafe keyword to C? ). Dead code elimination is essential for performance, especially when using templates (this is basically what enables the fabled "zero cost abstraction" because complex template code may generate a lot of 'inactive' code which needs to be removed by the optimizer). The actual issue is that the compiler is free to eliminate code paths after UB, but that's also not trivial to fix (and some optimizations are actually enabled by manually injecting UB (like `__builtin_unreachable()` which can make a measurable difference in the right places).
- raluk 5mo agoIn C / C++ there are two kinds of undefined behaviour. One is where there is written in standard what UB is. Another one is everthing else that is not in standard.
- thaumasiotes 5mo agoTechnically, that's only one kind, because it's written in the standard that anything not mentioned in the standard is undefined behavior.
- cepepe 5mo agoOne kind, but two different classes of undefined behaviour.
- deleted 5mo ago[deleted]
- wiseowise 5mo agohttps://en.wikipedia.org/wiki/There_are_unknown_unknowns https://en.wikipedia.org/wiki/There_are_unknown_unknowns
- llggbbtt 5mo ago[flagged]
- __0x01 5mo ago> A problem with this is that in order to confirm the findings, you’ll need an expert human. But generally expert humans are busy doing other things. The article suggests using LLMs to identify and fix UB. However as per the above, I think the issue is that we need more expert humans. LLM generated code will eventually contain UB. EDIT: added "eventually"
- lelanthran 5mo ago> LLM generated code will eventually contain UB. Yes. Even in languages other than C (i.e. you will get behaviour that nothing in the input specified). When LLMs generate code, all languages have UB.
- eru 5mo agoThat'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.
- lelanthran 5mo ago> 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.
- eru 5mo agoIf 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.
- flohofwoe 5mo agoIt would already help a lot when the C and C++ standards start to clean up the list of Undefined Behaviour (e.g. there's a lot of nonsense UB currently in the C standard which could easily become Defined Behaviour - like the "file doesn't end in a new-line character" thing): https://gist.github.com/Earnestly/7c903f481ff9d29a3dd1 https://gist.github.com/Earnestly/7c903f481ff9d29a3dd1
- jraph 5mo agoYet another push to use LLMs after casting fear. Now it should be illegal not to use LLMs. A good start of the day. (I hope casting fear is not UB)
- wg0 5mo agoThe irony is unmistakable.
- stevenhuang 5mo agoThere is nothing ironic in letting an llm have a pass at identifying potential UB and other correctness issues in C code. I say this as an experienced C developer.
- wg0 5mo agoIt is ironic because the behaviour of an LLM itself is UB. Guaranteed.
- raverbashing 5mo ago> (I hope casting fear is not UB) I'm sure that's UB in C In C++ just use <reinterpret_cast>
- jdw64 5mo ago[dead]
- momo26 5mo agoDebugging in C is soooo hard. When I was writing Malloc Lab in system course, there were uncountable undefined and out of range :(
- flohofwoe 5mo agoYet, debugging memory corruption issues in C and C++ code with modern compiler toolchains and memory debugging tools is infinitely easier than 25 years ago. (e.g. just compiling with address sanitizer and using static analyzers catch pretty much all of the 'trivial' memory corruption issues).
- feelamee 5mo agoI think vice versa - C is so simply, that debugging it is just a pleasant walk. Especially compared with modern languages with lambdas/exceptions/virtual functions and so on. The one thing I see can make it harder is function pointers.
- logicchains 5mo agoThe concept of undefined behaviour is also a very useful lens for understanding LLM-based coding. Anything you don't explicitly specify is undefined behavior, so if you don't want the LLM to potentially pick a ridiculous implementation for some aspect of an application, make sure to explicitly specify how it should be implemented.
- cracki 5mo agoWe know. This is not news.
- boxed 5mo agoIt seems to be to many many programmers who keep using C++
- liamd1988 5mo agoWhen use C ,keep using char* not mess with int*
- nurettin 5mo ago[dead]
- fithisux 5mo agoUB can also have impact in logical cohesion of codebase.
- my-next-account 5mo agoHello, it's me. I'm not afraid of UB.
- my-next-account 5mo agoTo be honest, miscompilations because of UB is exceedingly rare, and we do a lot of weird shit in our code.
- saagarjha 5mo agoYou should be!
- debugnik 5mo agoAs much as I agree with the intro, these examples aren't good and the overall article is just a veil for pushing LLM coding.
- boxed 5mo agoNot good how? Are they TRUE? If so that's super bad.
- IshKebab 5mo agoThey are true but I agree it's not a great article. C has an unending list of UB and given the title I was expecting a more comprehensive survey, but they actually just picked a few that are both fairly well known and not very interesting.
- thomashabets2 5mo agoAuthor here. As I stated: > The following is not an attempt at enumerating all the UB in the world. It’s merely making the case that UB is everywhere, and if nobody can do it right, how is it even fair to blame the programmer? My point is that ALL nontrivial C/C++ code has UB. It's about that point, not about how to avoid it. Because you can't.
- HelloNurse 5mo agoSome of the examples are somewhat formally true in theory and bullshit in practice; some are quite hallucinatory. - Creating a potentially troublesome misaligned int pointer is a precisely localized and completely explicit user mistake, not something that just happens because it's C. - Passing signed char to character classification functions that expect an unsigned char (disguised as an int) is a very specific dumb user error. The C standard could specify that all negative inputs, including EOF and invalid signed char values, are classified as not belonging to the character class, but I doubt the current undefined behaviour in isxdigit() etc. implementations ever went beyond accepting invalid inputs. - Casting floating point values to integer values in general requires taking care of whether the FP values are small enough to be represented and what to do with NaN and Inf values: not the language's responsibility. C offers a toolbox of tests, not ready-made application specific error handling. - Expecting C to handle "address zero" in physical memory in ways that conflict with NULL in source code denotes a complete lack of understanding of what a program is. Where stuff in an executable is loaded in memory, in the rare cases when it matters, can surely be affected with platform specific extensions, possibly at the level of linker commands with nothing appearing in the C source code.
- greysphere 5mo agoThe examples aren't really undefined behavior. They are examples that could become UB based on input/circumstances. Which if you are going to be that generous, every function call is UB because it could exceed stack space. Which is basically true in any language (up to the equivalent def of UB in that language). I feel like c has enough actual rough edges that deserve attention that sensationalism like this muddies folks attention (particularly novices) and can end up doing more harm than good.
- stevenhuang 5mo agoThe 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 quicksand. That's the danger of UB.
- flohofwoe 5mo ago> The examples are unequivocally UB. Full stop. Tbh, already the first example (unaligned pointer access) is bogus and the C standard should be fixed (in the end the list of UB in the C standard is entirely "made up" and should be adapted to modern hardware, a lot of UB was important 30 years ago to allow optimizations on ancient CPUs, but a lot of those hardware restrictions are long gone). In the end it's the CPU and not the compiler which decides whether an unaligned access is a problem or not. On most modern CPUs unaligned load/stores are no problem at all (not even a performance penalty unless you straddle a cache line). There's no point in restricting the entire C standard because of the behaviour of a few esoteric CPUs that are stuck in the past. PS: we also need to stop with the "what if there is a CPU that..." discussions. The C standard should follow the current hardware, and not care about 40 year old CPUs or theoretical future CPU architectures. If esoteric CPUs need to be supported, compilers can do that with non-standard extensions.
- IshKebab 5mo ago
- quelsolaar 5mo agoThe 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."
- Ygg2 5mo ago> -Acceptance: "Just dont write UB." Just switch to a saner language. And before I get attacked for being a Rust shill, I meant Java :P The bar is so low it's floating near the center of the Earth.
- p2detar 5mo ago> Just switch to a saner language. And where's the fun in that?
- psychoslave 5mo agoThat’s a taste matter. Being recalled that what is expressed is always depending on some technical details on every move, this is great when one is loving technical details and have all the leisure time to pay attention to them. This is going to be hell compared to sound defaults for someone willing to focus on delivering higher order feature/functionality which will most likely work just fine. Unedefined behaviour means "we couldn’t settle on a best default trade-off with fine-tuning as a given option so we let everyone in the unknown".
- xeyownt 5mo ago[flagged]
- dns_snek 5mo ago> And before I get attacked for being a Rust shill, I meant Java :P If all you want is C but less insane then the obvious answer here is Zig.
- rahadbhuiya 5mo ago[dead]
- black_13 5mo ago[dead]
- rurban 5mo agoVery bad advice. Of course good new LLM's know about UB, but you still need to use ubsan (ie - fsanitize=undefined), and not your LLM.
- formerly_proven 5mo agoCoding agents write unsound Rust any day, too. unsafe impl Send … is much easier than fixing a bad design and it might even work momentarily.
- VimEscapeArtist 5mo agoWait until he discovers PowerShell ;D
- grougnax 5mo agoUse Rust!
- mbrock 5mo agomost languages don't even HAVE a specification so in most languages literally EVERYTHING everything is undefined behavior
- oersted 5mo agoUB doesn't mean that it is not specified (actually it is often very well specified), it means that compilers can and do assume that such code patterns will not be present. Those cases may not be considered and can lead to unexpected behaviour. Additionally, some (most?) UB is intentionally UB so that optimisers are free to do fancy tricks assuming that certain cases will never happen. Indeed, this is required for high performance. If they do happen, again, it can lead to unexpected behaviour. PS: Most languages that don't have a specification declare their primary implementation to be specification-as-code. Rust is an example of that, and it does still have UB: the cases that the compiler assumes will not happen.
- mbrock 5mo agoundefined behavior is the behavior of code patterns "for which this International Standard imposes no requirements" and the behavior is in fact almost always predictable and agreed upon by compiler vendors and the users of the language, which is why you are able to use programs that rely on undefined behavior probably every single second you are using the computer edit: for example I'm typing this into Safari which means probably every key press and event is going through JSC JIT compiled functions—which have, structurally and necessarily and intentionally, COMPLETELY undefined behavior according to the spec—and yet it miraculously works, perfectly, because the spec doesn't really matter
- beeforpork 5mo agoThe UB in unaligned pointers is even worse: an unaligned pointer in itself is UB, not only an access to it. So even implicit casting a void*v to an int*i (like 'i=v' in C or 'f(v)' when f() accepts an int*) is UB if the cast pointer is not aligned to int. It is important to understand that this is a C level problem: if you have UB in your C program, then your C program is broken, i.e., it is formally invalid and wrong, because it is against the C language spec. UB is not on the HW, it has nothing to do with crashes or faults. That cast from void* to int* most likely corresponds to no code on the HW at all -- types are in C only, not on the HW, so a cast is a reinterpretation at C level -- and no HW will crash on that cast (because there is not even code for it). You may think that an integer value in a register must be fine, right? No, because it's not about pointers actually being integers in registers on your HW, but your C program is broken by definition if the cast pointer is unaligned.
- tovej 5mo agoBut that seems obvious. You can't load an integer from an unaligned address. It's not only C-level is it. There's no (guarantee across architectures for) machine code for that either.
- mbel 5mo agoUnless your code targets some exotic architecture, like idk x86.
- cataphract 5mo agoNot really. Wait until the compiler starts vectorizing your code and using instructions requiring alignment (like the ones with A or NT in the mnemonic).
- saagarjha 5mo agoUsually the compiler will probably not generate those
- akiarie 5mo agoC is still, by far, the simplest language that we have. Although many newer languages are safer (with the exclusion of Rust, primarily by being slower) the same kinds of issues that are there in C are there in these languages, their effects are just harder to see. People complain about C as though they know how to fix it.
- simonask 5mo agoC is not a simple language in the sense that writing software in C is simple, and I think that's the only useful way to understand the word "simple" in this context. Brainfuck is "simple" by any other definition as well, but that's not a useful quality.
- spacedcowboy 5mo agoC is a far simpler language than, for example, Swift. It's cognitive load in order to actually write something is pretty small - even the authors state that their book about C is intentionally slim because the concepts to understand are not that many. That doesn't mean the C is a safer language than Swift, or a less-capable language than Swift. But in terms of "easy to understand along the happy-path", it's a lot easier to get going in C. Swift, for example, bakes a whole load of CS-degree-level ideas and concepts into the basic language with its optionals, unwrapping, type-inference, async/await, existential types, ... ... ... . C doesn't do any of that. There are (many!) more footguns in C, but the language is less complex as a result. Brainfuck is not at all simple, from that point of view. This is a valid Brainfuck program: >+++++++++[<++++++++>-]<.>+++++++[<++++>-]<+.+++++++..+++.[-]>++++++++[<++++>-]<. >+++++++++++[<+++++>-]<.>++++++++[<+++>-]<.+++.------.--------.[-]>++++++++[<++++ >-]<+.[-]++++++++++. This is the equivalent C program #include <stdio.h> int main() { printf("Hello world!\n"); } One of these is far simpler than the other. [edit: changed to make the examples do the same thing]
- simonask 5mo agoThe point I'm getting at is that your definition of "simple" (a word that should be banned among programmers) is not useful, if it is even meaningful. The brainfuck example is "simpler": Only 8 kinds of tokens! Not really useful, though. The cognitive load of _actually delivering software_ written in C is immensely greater than doing so with Swift, or Rust, or Python, or Java, even Zig, despite all of those leveraging much heavier machinery in order to deliver a friendlier abstract model for you to program against. The tragedy of C is that, in addition only delivering very baseline abstraction tools, it also adds its own set of seemingly arbitrary rules and requirements that come from nowhere but the C standard. Fictitious limitations to suit a bygone era. The abstract model of C is fine in some places, but definitely not fine in other places, and my hypothesis is that most UB in practice comes from a mismatch between programmer intuitions and C's idiosyncracies.
- muvlon 5mo agoYes 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 side effects on the same scalar object (in this case, x) are UB. 6.5.3.3.8 tells us that the evaluations of function arguments are indeterminately sequenced w.r.t. each other. So in common parlance, a "data race" is any concurrent accesses to the same object from different threads, at least one of which is a write. In C, we can have a data race on a single thread and without any writes!
- simonask 5mo agoI 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.
- 3form 5mo agoAt 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 bother, vs. learning C, assembly of given architecture, or one of the new and fancy compiled languages.
- simonask 5mo agoWell, Zig is aiming to be a "saner C", and mostly succeeding so far. I hope they make it to production. Rust is a somewhat more thorough attempt to actually course-correct.
- ivandotcodes 5mo ago[dead]
- reinhash 5mo agoRust.
- SanjayMehta 5mo agoI used to teach C programming and one time I got anonymous feedback: "when this instructor doesn't know the answer he says "it's compiler dependent."" Shrug.
- codeflo 5mo ago> The compiler, and really the underlying hardware too, is playing a game of telephone with your UB intentions. The part about hardware is wrong BTW. In all the cases about null pointers and out-of-bounds access and integer overflow and whatnot, the hardware semantics are clearly defined, and the assembler code does exactly what is written. The way modern compilers act on your code makes C less safe than assembler in that sense.
- thomashabets2 5mo agoAuthor here > The part about hardware is wrong BTW Could you be more specific? I think by "wrong" you may mean "not actually relevant to UB", and you're right about that. If that's what you mean then that part is not for you. It's for the "but it's demonstrably fine" crowd. > the hardware semantics are clearly defined Yup. The article means to dive from the C abstract machine to illustrate how your defined intentions (in your head), written as UB C, get translated into defined hardware behavior that you did not intend. I'm not saying the CPU has UB, and I wonder what part made you think I did. That's what I mean game of telephone. The UB parts get interpreted as real instructions by the hardware, and it will definitely do those things. But what are those things? It's not the things you intended, and any "common sense" reading of the C code is irrelevant, because the C representation of your intentions were UB.
- codeflo 5mo agoIt seems like I simply misunderstood the point of the "game of telephone" metaphor. To be honest, even with your added explanation, I don't fully get why you express it that way. But I think we're in agreement on the substance, and I shouldn't have worded my response so harshly.
- maple3142 5mo agoIs this a correct understanding of UB in C? A program P has a set of inputs A that do not trigger UB, and a complementary set of inputs B that do trigger UB. A correct compiler compiles P into an executable P'. For all inputs in A, P' should behave the same as P. However, for any input in B, the is absolutely no requirements on the behavior of P'.
- simonask 5mo agoIntuitively yes - the program will be compiled as if B-inputs are never passed to the program, and that can include eliminating code that tries to detect B-inputs.
- mbrock 5mo agoThis is a description of an imaginary compiler, evoked by the ANSI/ISO standards documents, which has never existed and will never exist. To understand what the program will do, you just have to understand the compiler behavior on your target platforms. A helpful intuition pump is: imagine the ANSI/ISO specifications simply do not exist; now what? Well, you just continue your engineering practice, the way you would for any of the myriad languages that never even had a post hoc standards document.
- simonask 5mo ago> just That word is carrying a lot of weight here. Compilers are unbelievably complex these days, and it's impossible for any one human to fully understand the entire compilation process, including the effects of any arbitrary combination of compiler flags. Any assumptions you have about what the compiler does in the face of UB will collapse on the next patch release of that compiler, or the moment somebody changes the compiler flags, or the moment somebody tries to compile the code for a slightly different OS, not to mention architecture. There is no other way to understand what C compilers do than reading the standard.
- mbrock 5mo ago
- parasti 5mo agoI have never in my 20 years of writing C heard so much about undefined behavior as I have in the past 6 months on Hacker News. It has never entered the conversation. You write the code. If it doesn't work, you debug it and apply a fix or a workaround. Why does the idea of undefined behavior in C get to the front page so consistently?
- Etheryte 5mo agoBecause the production environment might be a completely different architecture, these details matter a lot. Works on my machine is not useful if your actual target is a small embedded system on top of a cell tower in the middle of nowhere. Granted, most people don't work on stuff like that, I imagine the vast majority of devs here are web developers, but even still it's an interesting discussion even if you haven't run into it yourself. Maybe even more so in that case.
- spacedcowboy 5mo agoUm, as an embedded developer, you don't develop the code to run on your machine, you develop it to run on the same target as you expect to deploy to, sitting on your desk next to you. I have lots of my code running day-in, day-out on literally hundreds of millions of machines. The approach to "getting it working" is exactly OP's. I'll admit to being pretty defensive and anal in checking values and return-codes (more so than most, I suspect), and I'm a firm believer in KISS principles in software engineering ("solving hard problems with complicated code is easy, solving them with simple, understandable algorithms is the hard bit") but generally there's no real difference in approach to the code I write to work on my workstation, and the code I write to work in the field.
- dmpk2k 5mo agoEmbedded developers often suffer under archaic toolchains. There's plenty of reasons for that, but one of them is UB: a newer version of the compiler can completely change an embedded program's behaviour.
- mjs01 5mo agoInteger promotion seems to be the source of many signed integer overflow UB. Why does C have it? Does integer promotion ever have a good part?
- saagarjha 5mo agoYes, it simplifies a lot of code that would otherwise be littered with casts.
- peterfirefly 5mo agoCould be fixed by having a nicer casting syntax (like Rust) or by not having so damn many scalar types that are used in practice. "Explicit casts only" worked fine in Modula-2, which doesn't have as many scalar types.
- saagarjha 5mo agoYeah most modern languages settle on having fewer types that are used most of the time and don't require as many casts.
- rom1v 5mo agoA concrete example of undefined behavior caused by an unaligned pointer: https://pzemtsov.github.io/2016/11/06/bug-story-alignment-on-x86.html https://pzemtsov.github.io/2016/11/06/bug-story-alignment-on...
- gblargg 5mo agoSpecifically on x86 where it's assumed that won't cause problems.
- lelanthran 5mo agoI read through this in detail... Is it just me, or are these things that are invoked by intentionally bypassing the typing? I mean, you have to go out of your way and use a cast to get the UB in the first example. For the `isxdigit` implementation, using a parameter to index into an array without a length check is pretty suspect already. I don't think any of my code actually indexes an array without checking the length in some way. For the float -> int conversion, converting a float to an int without picking a conversion does not make sense in the first place - math.h has rounding and ceiling functions. > For all you know the compiler has no internal way to even express your intention here. I'm human, not a compiler, and even I cannot tell what the intention is behind trying to call NULL as a function. What exactly is expected to happen? > Because the argument needs to be a pointer, and the NULL macro may be misinterpreted as an integer zero. I don't think this is true for C. The NULL macro is defined to be a pointer in the C standard, AFAIK. Just because comparisons with zero are allowed, does not imply that the standard implicitly promotes NULL to `int`. I think only the final one is of note (the 24-bit shift assigned to a uint64_t).
- account42 5mo ago> I don't think this is true for C. The NULL macro is defined to be a pointer in the C standard, AFAIK. Just because comparisons with zero are allowed, does not imply that the standard implicitly promotes NULL to `int`. Probably confusion with C++ where NULL is 0 which is a special case that can be implicitly cast to both integers and pointers, unlike non-zero constants. C doesn't need this because it doesn't require explicit casts from void pointers to others.
- bvrmn 5mo agoI really like Zig's approach to UB. Especially alignment is a part of type. And all this wordy builtins for conversions. Starring to it makes you think what you doing wrong with data model it requires now 3 lines of casting expression.
- fjfaase 5mo agoIs comparing a signed integer with an unsigned integer UB? I resently wrote some code and compiled it with gcc to x86_64 (without optimization) that returned an incorrect answer.
- benchloftbrunch 5mo agoIt's not UB. Integer promotion applies, the signed int is implicitly coerced to unsigned (or the other way around - don't remember which.)
- Karliss 5mo agoNo UB, but the integer promotions rules apply. When comparing signed and unsigned integers of same size the signed one will be converted to unsigned. In a reasonably configured project compiler will warn about it. In case of integers smaller than int, promotion to int happens first. In case of signed and unsigned integers of different size, the smaller one will be converted to bigger one.
- deleted 5mo ago[deleted]
- alper 5mo agoIsn't the article mostly saying that SPARC sucks?
- keyle 5mo agoWhen talking UB, putting C and C++ in the same basket is basically like comparing drunk driving a car and riding a bicycle sober... Both means of transport, very different experience.
- benj111 5mo agoThe issue for me with posts like this is that it misses the issue. Unaligned pointer accesses are UB because different systems handle it differently. This 'should' be to allow the program to be portable by doing what the system normally does. Instead it's been highjacked by compiler writers, with the logic that "X is UB, therefore can't happen, therefore can be optimised away." Int c = abs(a) + abs(b); If (a > c) //overflow Is UB because some system might do overflow differently. In practice every system wraps around. That should be a valid check, instead it gets optimised away because it 'can't' happen. C gives you enough rope to hang yourself. The compiler writers don't trust you to use the rope properly.
- justmarc 5mo agoThe art is actually making sure it all stays defined behavior
- y42 5mo agoshameless plug, it's part of the Nerd Encyclopedia: it's also called "nasal demons". https://nickyreinert.de/2023/2023-05-16-nerd-enzyklop%C3%A4die-28---dam%C3%B6nen-aus-der-nase/ https://nickyreinert.de/2023/2023-05-16-nerd-enzyklop%C3%A4d...
- elnatro 5mo agoIs there a way to avoid undefined behavior Im C then? Could we write a new C compiler that adds some checks and fixes (e.g. raise documented exceptions) to each undefined behavior?
- u1hcw9nx 5mo agoThat post is just a hyperbolic rhetorical piece, not even a good technical shade. There are plenty of tools that restrict C into defined behavior subset. HN is just not aware of them. NASA, Aerospace and car industry are big customers, static analyzers and compilers. Good open source ones: Frama-C IKOS (from NASA)
- elnatro 5mo agoIt’s been a while since I programmed in C. Thank you for these resources.
- saagarjha 5mo agoNot all of them but there are many tools that can try to define behavior for this code to help shake them out of your codebase.
- peterfirefly 5mo agoubsan. Doesn't catch all of it.
- elnatro 5mo agoWill take a look thanks!
- ricardobeat 5mo agoI’ve been heavily invested in https://c3-lang.org/ https://c3-lang.org/ the past couple months. How does it look from this perspective to someone with C experience?
- jb1991 5mo agoSome of the C++ code in this article has not been idiomatic in over a decade, and would be considered a code smell today. The language has evolved into quite a different language than when it was first created. As soon as I saw all of those raw pointers and direct pointer access, it was clear that at least part of this article should be taken with a grain of salt. The other obvious issue with the overall perspective is that C and C++ are being thrown together directly as if somehow they’re nearly the same language, but they are really very far apart nowadays.
- deleted 5mo ago[deleted]
- debugnik 5mo agoI was about to call out that the code is supposed to be C and not C++, but I double checked and I realised it actually says std::atomic<int>, not atomic_int!
- jb1991 5mo agoExactly, this is very old C++ on display in this article. It’s certainly not as safe as a language like Rust, but quite a lot of undefended behavior and things that will shoot yourself in the foot have been changed over the last 10 years. Most C++ today will be immediately obvious and not accidentally mixed up with C.
- pjmlp 5mo agoUnfortunely most C++ today keeps making use of C idioms, including at companies with seat at WG21 table.
- deleted 5mo ago[deleted]
- tenego 5mo ago[flagged]
- bullen 5mo agoEverything in Java is defined behaviour, you need a VM with GC to remain sane. Everything else is a waste of time!
- danborn26 5mo agoThe scariest part is how many production systems rely on undefined behavior without anyone knowing until a compiler update breaks everything.
- coolThingsFirst 5mo agoWhere does scary part come from, they run on planes?
- synergy20 5mo agoif c is more ub unsafe than it seems,what is the solution here
- NooneAtAll3 5mo agofeels like https://xkcd.com/1499/ https://xkcd.com/1499/ the only people complaining about being able to do awful things are people that do awful things
- gritzko 5mo ago- a metal bar always sinks - unless you are trying to sink it in mercury. then it floats - unless it is an uranium bar - go sink uranium bars in mercury yourself
- wyldfire 5mo agoMaybe we should criminalize writing articles about Undefined Behavior that have a "So what do we do now?" subheader but omit any mention of UBSan.
- JonChesterfield 5mo agoWell, you can't write malloc in conforming C, which hurts rather more than remembering to write bitcast as memcpy on char pointers. Doesn't matter though because you aren't writing standards conforming C. You're writing whatever dialect your compilers support, and that's probably (module bugs) much better behaved than the spec suggests. Or you're writing C++ and way more exposed to the adversarial-and-benevolent compiler experience. The type aliasing rules are the only ones that routinely cause me much annoyance in C and there's always a workaround, whether if it's the launder intrinsic used to implement C++, the may_alias attribute or in extremis dropping into asm. So they're a nuisance not a blocker.
- amiga386 5mo agoCan 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 foo *)x)->i;} int main() { int (*funcptr)(void*) = &func; struct foo foo = { 42 }; return funcptr(&foo); }
- tomp 5mo agoCasting to a pointer of incompatible type is UB. The exception is casting to char*.
- amiga386 5mo agoTell me why struct* is incompatible with void* when it's such a standard case in C that you don't need a cast: struct foo *x = malloc(sizeof(struct foo)); /* malloc returns void* */ Or rather, tell me why the C11 standards committee decided to declare that struct* is incompatible with a void*
- tomp 5mo agook so Claude says I was wrong, it's more subtle. (1) you can cast between any pointer types (no UB - assuming they're aligned), but accessing memory through a wrongly-typed pointer is UB (2) the only exception is char*, which allows you a "byte view of memory" (3) calling a function through a pointer requires the parameter pointer types to be compatible, and none of these are: int*, struct foo *, void*, char*
- kingforaday 5mo ago[dead]
- j16sdiz 5mo agoTwo function pointer (in practice) compatible or not depends on machine specific calling convention. I guess enumerating all the possibility is just .. don't look right? make the standard too long and complex?
- nullpwr 5mo agoExcellent post. But it's addressed to the wrong people. The problem lies with compilers, not with the language and its specification, or with the creators of the C programming language. Anyone can write a compiler that transforms all undefined behaviors (UB) into defined behaviors (DB). And your compiler will be used by people, including me.
- HarHarVeryFunny 5mo agoI'd say the unaligned pointer one is the language's fault. The language should not let you create an an invalid pointer, or at least warn you when you are doing so. OTOH one could argue that creating truly portable programs is not possible since a programming language is a leaky abstraction - different machines have different endianness, different alignment requirements, different amounts of memory, etc. One could argue therefore that the language should not make any assumptions about the alignment restrictions, or lack of them, on the machine you are compiling for. Just document that "manually created" pointers may be unaligned and have machine-dependent behavior. A nice compiler could still generate a warning or error if you create a pointer that doesn't meet the alignment requirements of the target you are compiling for. C/C++'s provision of type casts reflects that the language has made the design decision to not restrict the user, and let them step outside the bounds of any guarantees the language provides if they want to. Unions are also a form of type cast.
- nullpwr 5mo ago> The language should not let you create an an invalid pointer, or at least warn you when you are doing so completely agree!
- casey2 5mo agoThat's a nonsensical statement, a language cannot warn you, only a compiler can (-Wcast-align). The compiler can also decide what is and isn't an invalid pointer, this way the language avoids leaky abstractions.
- stackedinserter 5mo agoHow can it be valid implementation of isxdigit? ``` int isxdigit(int c) { if (c == EOF) { return false; } return some_array[c]; } ``` If you write code like this, then everything in programming is UB.
- deleted 5mo ago[deleted]
- DostLeFan 5mo agoVery interesting article. I'm in love with C++, and I cannot say that I'm a good developer, but interesting to discover where UB can be. (Sorry I'm not a good english speaker)
- deleted 5mo ago[deleted]
- sltr 5mo agoFor a deep dive on UB with printf, see https://srs.fyi/see-conversions/ https://srs.fyi/see-conversions/ > When programming in C, to avoid unexpected pitfalls, one must be acutely aware of a whole slew of implicit behaviors (some of which are implementation-defined or even undefined).
- Webhix 5mo agomaybe rewrite this in go?)
- 1vuio0pswjnm7 5mo ago"My point is that ALL nontrivial C and C++ code has UB." Is "nontrivial" defined How would one identify "nontrivial" C code Is there an objective measure (defined) Or is it a matter of personal opinion that could vary from person to person (undefined)
- up2isomorphism 5mo agoU just need to read the title and 5 lines to know this must be a rust guy.
- QuiEgo 5mo agoC does not abstract differences in underlying hardware well. Systems programmers know if they have an architecture that can't handle unaligned accesses or that the address they are doing load/stores from is a mmio register. Systems programmers know the difference between a virtual address and a physical address and have debugged MPU faults or MMU table walks and page faults more times than they want to think about. C is horrible for trying to write a portable user-mode program in 2026. There are lots of better options. C is great for writing low-level system code where you need to optimize performance down to the last cycle. It not abstracting away the hardware is super important for some use cases. A classic example is all of the platform-specific flavors of memcpy in the Linux kernel that are C/assembly hybrids hand-optimized for the SIMD pipelines of some CPUs. C is a tool, Rust is a tool, Java is a tool, Python is a tool. Use the right tool for the job ¯\_(ツ)_/¯.
- kajaktum 5mo agoI want a language that is a group of bit (0,1) and the xor operator. Everything else is built on top of that.
- benchloftbrunch 4mo agoXor isn't Turing complete sadly.
- kajaktum 4mo agoThe only thing missing is the goto right?
- pphysch 5mo agoIt's also worth highlighting that C is perhaps the most officially standardized programming language in history. What a contradiction. Strong evidence that standard-driven programming language development is much worse than implementation-driven development. Standards should be used for data types and external interfaces/protocols, not programming languages.
- EGreg 5mo agoa good case can be made that use of C++ is a SOX violation So Linus was right? But for a second reason too: C++ is a horrible language. It’s made more horrible by the fact that a lot of substandard programmers use it, to the point where it’s much, much easier to generate total and utter crap with it. Quite frankly, even if the choice of C were to do _nothing_ but keep the C++ programmers out, that in itself would be a huge reason to use C. That is, accepting C++ code from programmers who use C++ could be a SOX violation ;-)
- tomcam 5mo agoI 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 to the language and it becomes less possible to implement on new architectures, right? Because the undefined behavior lets implementors stand up new compilers fairly quickly. For less undefined behavior isn’t it better to use languages that have that in their DNA? D, Zig, Go, Java, etc?
- vladms 5mo ago> 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.
- bkallus 5mo ago> the OpenBSD project has not been very receptive in the past for bug reports, my sense of “this is probably fine, in practice”, and that if OpenBSD wants to weed out UB from their code base, then that’s a major project that should be done in a better way than me just being the middle man between the LLM and them for a patch here and there. Part of the reason for all the UB in OpenBSD is that UBSan doesn't run on that platform. When I ported OpenBSD's httpd to Linux, I found that UBSan tripped before the server even came up because the config flag parsing shifts into the MSB of a signed integer. I tried to contribute back a patch (just make the flag bitfield unsigned), but it was ignored. I think if UBSan ran natively on OpenBSD, then there would be a lot more of these patches, and the maintainers would have to take an official stance on whether they think these bugs matter.
- el_pollo_diablo 5mo ago> probably meaning on an address that’s a multiple of sizeof(int), but who knows Sigh. s/sizeof(int)/_Alignof(int)/. There are good reasons for an implementation to have sizeof(int) = _Alignof(int) and not a mere multiple of it, but if you are going to discuss subtle points and UB, just stick to the language guarantees. > But let’s say you have a modern machine, where NULL is a pointer to address zero, and you actually have an object there. You don't program in C on such a machine. Or maybe memory is virtualized, and it does not matter that your object lives at physical address zero, as long as you can map a non-zero virtual address to it. > So how do you print an uid_t? if ((uid_t)-1 < (uid_t)0) { // uid_t is signed printf("%" PRIdMAX, (intmax_t)id); } else { // uid_t is unsigned printf("%" PRIuMAX, (uintmax_t)id); } > It’s not rare for the denominator to come from untrusted input. It's not rare for the array index to come from untrusted input. It's not rare for the supposedly valid UTF-8 string to come from untrusted input. ... Why single out division? This problem affects every partially defined operation. In the case of division at least, everyone learned in school that thou shalt not divide by zero. Adding two untrusted integers and forgetting that signed overflow is UB, not defined as a modulo? Your average programmer is much less likely to see that coming. > unsigned char a = 0xff; > unsigned char b = 1; > unsigned char zero = 0; > bool overflowed = (a + b) == zero; > > unsigned char a = 0x80; > uint64_t b = a << 24; Please. Convert your operands to wide enough types before the operation. Convert your results back to narrow enough types to compensate for integer promotion to wider types than you would have liked. Do that consistently, and you're good. Here: unsigned char a = 0xff; unsigned char b = 1; unsigned char zero = 0; bool overflowed = (unsigned char)(a + b) == zero; unsigned char a = 0x80; uint64_t b = (uint32_t)a << 24;
- creatorsstack 5mo ago[flagged]
- pizlonator 5mo agoThe problem is incorrectly assuming that the spec is meaningful in some kind of rigorous way. It’s not. All that matters is what C compilers actually do and what real C programs expect. This is a good thing. It creates a culture where the two sides meet each other where they’re at
- BearOso 5mo agoWe also have a very limited number of compilers and a small number of prevalent architectures today. As long as you know the behavior of the target compiler and architecture, the behavior is defined, it's just not specified.
- pizlonator 5mo agoThis is true. But why I’m saying has always been true. What has changed is that the effective portability of C and C++ code has increased due to the reduction in number of compilers and arches
- groby_b 5mo ago"not correctly aligned (probably meaning on an address that’s a multiple of sizeof(int), but who knows)" I stopped reading there. If you have decades of experience in C/C++ and don't know what that means (and that it's arch specific), I'll assume those decades were mostly the same year over and over. C/C++ are horrible languages, but they deserve better opponents than that.
- saltyoldman 5mo agoProbably not "everything" the vast vast vast majority of everything you are looking at on your screen right now is written in C.
- commandlinefan 5mo agoA lot of this stems from trying to insist that char just means "small" and not "8 bits" and that int means "bigger than that" and not "32 bits". In fairness, K&R dealt with an era where 9 bit architectures existed, but char is 8 bits now. Everywhere.
- jeroenhd 5mo agoIn the world of microcontrollers, CHAR_BIT can be 16 or some other funky number. char is usually 8 bits in size, though.
- psim1 5mo agoI like the ideas of this article but would not use SPARC as a main badguy in my examples. A naive and probably popular takeaway would be, "Thank goodness I am not writing for SPARC and don't need to worry about these SPARC architectural concerns!"
- jim33442 5mo ago[dead]
- 0x20cowboy 5mo agoLife is undefined behaviour.
- JayJSpringpeace 5mo ago[flagged]
- hunterpayne 5mo agoWhat all these C programmers are pointing out is 2 fold: - Making a Turing machine have deterministic and predictable results is hard. - Modern hardware is complex and getting all hardware to behave the same way requires a strong mathematical abstraction. C was never intended to be a fully defined mathematical abstraction. It was a language which was easy to write a compiler for. That's its original strength. Trying to make it something it isn't is the problem. Either choose a language which does have such abstractions or understand the drawbacks of the tool you are using. Right tool for the right job.
- casey2 5mo agoAnd that's a good thing. UB is another mechanism to speed up the development of compilers, many other languages fall trap to over defining while we lack the methods to solve such problems cleanly (believe me, the modern c++ people have tried). Usually this is the case because they believe strongly that their methods work despite evidence. As for UB, the compiler has the final say. Nobody should write nontrivial c without understanding their compiler, the same as nobody should write c without understanding their text editor. Code in other languages breaks between versions, in c there are projects with code from every version at once! Looking at it another way, work put into a c compiler enables you to write nontrivial code.
- feelamee 5mo ago> We need some way of fixing UB at scale, without committing AI slop nor overwhelming human reviewers. Write compiler which will define all this behavior. Usually people forget that UB exists only in standard. In practice it is always defined. P.S. of course, while your hardware + firmware staying unchanged P.S. not always defined in documentation - I mean defined in e.g. code
- UltraViolence 5mo ago[dead]