• lobsters__mort@crustaceanson.theshelf.topM
    link
    fedilink
    arrow-up
    0
    ·
    edit-2
    10 days ago

    Your complaint is … you agree that UB has the qualities outlined by TFA, ~olliej and others, but you think the comments should be more positive somehow?

    What you can do is: learn C. Learn what the standard guarantees, what choices it leaves up to implementations (implementation-defined behavior) and what it labels undefined behavior. Then try to avoid writing code which invokes undefined behavior. That’s it. Don’t do what so many wants to do and dismiss it because “I only target one platform” because it’s not that simple.

    Here’s a few notes about types and aliasing:

    • Every object has an “effective type”. The effective type of stack-allocated objects have the type you declared them as (e.g an int foo has effective type of int, a struct sockaddr_in addr has the effective type of struct sockaddr_in).
    • Heap-allocated memory starts out without an effective address (yes, malloc is magic; you may treat memory obtained from mmap the same way). They gain an effective type the moment you write anything to it (either by writing through a pointer or through functions like memcpy).
    • Do not dereference an object of effective type X through a pointer type of type Y. If you have an int, don’t dereference it through a pointer to float. There are two exceptions:
      • You can look at the memory of any object by going through a char * or unsigned char *. This is how e.g memcpy can copy bytes without knowing the effective type.
        • Note that this does not work the other way: if you have an object of effective type char (e.g as a part of a stack-allocated array char[N]), you may not access it through a pointer to some other type. This has implications for how you can use stack-allocated buffers.
      • If you have two struct types X and Y with a “common initial sequence” (meaning the first few fields are the same types in the same order), you may have an object with effective type X and look at those common initial fields through a pointer to Y.

    The consequence of this is that you should be using memcpy a lot more; if you have some memory buffer and you know there’s an int at offset K, don’t make an int * to it, instead make a local int variable and memcpy the data. The compiler will figure out how to make it fast.

    Then there are the obvious ones: don’t dereference null pointers, don’t overflow integers (not even through addition or bit shifting), don’t write outside of memory you allocated.

    There’s more of course, this is just what I remember off the top of my head right now as things which might catch people off guard if they learned about C by learning that “memory is a big flat array of bytes and pointers are indexes into that array”. That is true when you’re implementing C but not when you’re programming in C.

    Then there are kinds of UB which you can safely ignore for a given platform. For example, ISO C disallows casting between an object pointer (such as void *) and a function pointer; however the POSIX dynamic linker API provides one function dlsym which returns a void *, which the caller is expected to cast to a function pointer type and then call. As a result, every implementation which supports the POSIX dynamic linker API allows casting between object pointers and function pointers. And both GCC and Clang (and probably others) explicitly allow you to violate the effective type rules by going through a union.

    Maybe I should write a blog post about this…


    score 1 · canonical

    • lobsters__pervognsen@crustaceanson.theshelf.topM
      link
      fedilink
      arrow-up
      0
      ·
      edit-2
      10 days ago

      yes, malloc is magic; you may treat memory obtained from mmap the same way

      As for how you can justify and think about this interpretation of mmap in terms of the existing abstract machine semantics of malloc, I really like @ralfj’s exposition at https://www.ralfj.de/blog/2026/03/13/inline-asm.html. I believe a possible formalization of storytelling is something like the FFI oracle in CakeML’s semantics. Although a language like CakeML doesn’t have to contend with the complexity of a C-like or Rust-like memory model, the idea should generalize to whenever you’re trying to bridge two alien worlds and you can explain the locally visible effect of the remote world in terms of local semantics. What seems hard is coming up with the right explanations for tricky examples, as in Ralf’s article. There’s a concept I’m originally familiar with from linearizability where you’re allowed to explain a final set of observations retroactively, which allows for and often requires similar creativity.


      score 1 · canonical