Join the discussion

Write your take first — we'll ask for email only when you're ready to publish.

  • Hacker News
  • Well, 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.

  • The 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

  • We 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.
  • A concrete example of undefined behavior caused by an unaligned pointer: https://pzemtsov.github.io/2016/11/06/bug-story-alignment-on...
  • Specifically on x86 where it's assumed that won't cause problems.
  • Is 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'.
  • Yes, that's a good summary.
  • Intuitively 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.
  • Some 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.

  • I 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!
  • As much as I agree with the intro, these examples aren't good and the overall article is just a veil for pushing LLM coding.
  • Not good how? Are they TRUE? If so that's super bad.
  • Agreed. One after another these are standard things you avoid when writing portable code (or don't need, like accessing the object at address 0). They come across like from someone who wants to write whatever they want and have it work the same on everything. To make it into a language that allows this would remove its advantage of being able to write to the platform when you want to.
  • I 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?
  • I have the opposite experience, so many subtle bugs that bite you only on specific scenarios, so much that I can't count.
  • Exactly, you write for your target, not some imaginary spec. The spec is only as useful as to predict what your target roughly does, it's not normative.

    Compilers might have bugs where the spec is supposed to work but it doesn't, and many extensions without standard equivalents, or implementation-specific behaviour where undefined things in the standard do get assigned a meaningful outcome.

  • Because most of the people who post/write these articles do not actually know the C language specification nor understand its design.

    Understanding three important concepts properly in C allows one to easily identify what can/cannot result in UB viz. 1) Expressions 2) Statements 3) Sequence Points and "Single Update Rule". It is not that hard at all.

    I wrote about it here with links to further reading provided - https://news.ycombinator.com/item?id=48144734

  •     There are more things in heaven and earth, Horatio,
        Than are dreamt of in your philosophy
    
    You've probably been churning out possibly malformed code for years. Now you're becoming aware of your shortcomings. This is usually considered the transition from intermediate- to senior-level programmer.
  • Because 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.
  • Computers used to be cool; now they're dangerous.

    Every company keep harping on about safety and being exposed (being in the news): so the narrative against 'unsafe' is up the wazoo.

    The new world is basically a bunch of city dwellers who haven't seen raw nature and you show them a lawn mower, they freak out. Blades that spin?!?!?! Madness!!

  • Excuse me, what? I was writing both C and C++ 20 years ago, and UB was a huge part of the conversation (and the curriculum) back then as well.

    There were a few high-profile "scandals" around GCC 3.2 (IIRC) because the compiler finally started much more aggressively using UB in optimizations, which was a reason that lots of people stayed on GCC 2.95 for a very long time. GCC 3.2 came out in 2002.

  • Hacker News is still skewed towards people interested in programming languages (as opposed to actually programming). Probably some sort of Y-combinator Lisp heritage. There's also a persistent minority of CS grads who think that developing / using new programming languages is the most fascinating thing in the world, and some of them hold on to that thought.

    It's reasonable that such people would also be interested in design aspects of languages, and UB in C is in that field. Though I would argue that a lot of it was originally accommodating old CPU architectures without compromising performance too badly, and about as much a "design choice" as wheels being round...

  • The 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).
  • 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? ).

    Another commenter suggested using LLMs, but I disagree. Having clangd emit warning squiggles for unchecked operations (like signed addition) would be a good start.

  • That's a feature, not a problem.
  • The point in the article that 'It's not about optimisations' really got my attention. I've previously done some work where we wrote an analysis pass under the assumption that it executed last in the transformation pipeline and this was needed for correctness. The assumption was that since no further optimisations happened it was safe. Now I'm not so sure...
  • Yes, that is a problem, but this is also the most useful feature and reason for UB. People that suggest to just define it or make it unspecified, miss, that the compiler being able to remove whole parts of a program is the point. When I write code, that is UB for certain inputs, it is because I do not intend the program to have any behaviour for these inputs. I do want the compiler to optimize those away or do anything that effects from the behaviour of the other defined cases. It is deeply satisfying to add some conditions triggering log strings and see that they do not occur in the binary, because they can be only reached via UB.
  • Yes, a crash is about the most benign UB: at least it's highly visible.

    In worse scenarios, your programme will silently continue with garbage, or format your hard disk or give attackers the key to the kingdom.

    by eru
  • One example along this path as an example is that every function must either terminate or have a side effect. I don't think one has bitten me yet but I could completely see how you accidentally write some kind of infinite loop or recursion and the function gets deleted. Also, bonus points for tail recursion so this bug might only show up with a higher optimization level if during debug nothing hit the infinite loop.
  • The 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.