1. 20
  1. 16

    A lot of “embedded” libcs do some pretty sketchy things; frustration with this sort of thing is what prompted the development of musl. I imagine given its dependency on a linux kernel it wouldn’t work in the kind of environments the author is dealing with, but fwiw here’s rand.c:

    #include <stdlib.h>
    #include <stdint.h>
    
    static uint64_t seed;
    
    void srand(unsigned s)
    {
    	seed = s-1;
    }
    
    int rand(void)
    {
    	seed = 6364136223846793005ULL*seed + 1;
    	return seed>>33;
    }
    
    1. 11

      User with the username “nomemory” submits a post about an unexpected malloc. Hmmm. ;)

      Good article though, I just thought the coincidence was amusing.

      1. 21

        :).

        Got the nickname as silent protest to my parents who in 99 or 98 refused to buy me additional 32RAM so I can play Half Life…

        I got past this unfortunate event, but I kept the username.

      2. 10

        More generally: any libc function may call malloc. If this matters to you, then you should look at the libc internals and audit any function that you care about. Folks that ship a libc need to think about this in a few places. For example, FreeBSD libc uses jemalloc, which uses locks to protect some data structures, but the locks call malloc and so have their own bootstrapping path.

        1. 6

          The specification of a lot of functions doesn’t have a suitable failure mode, so no, they can’t really call malloc (and require it to succeed) without being non-conformant.

          1. 5

            That’s not true, async-signal-safety is a thing.

          2. 4

            BTW, using rand is a bad idea since some implementations of it return extremely poor quality results. Use random instead. From the Linux man page:

               on older rand() implementations, **and on current
               implementations on different systems**, the lower-order bits are
               much less random than the higher-order bits.  Do not use this
               function in applications intended to be portable when good
               randomness is needed.  (Use random(3) instead.)
            

            (Emphasis mine)

            1. 3

              Also known as, “why implicit global state will always fuck you”.

              1. 2

                In this case it’s not the global state per se, but that for some reason the global state is heap-allocated on first use, even though it just consists of a single integer.

                1. 2

                  The global state is heap-allocated because the function has to be re-entrant, or at least because the re-entrancy is enabled in the libc options. That means if you have multiple threads they each get their own rand() state… because rand() is designed around having implicit global state.

                  1. 2

                    The C standard has no such mandate for rand().

                    1. 2

                      The global state required by the C standard is the seed used by rand() that can be set with srand(unsigned seed). I strongly prefer the rand_r(unsigned int *seed) function defined in the blog post since that makes the state explicit and up to the caller whether it is global.

                      1. 1

                        rand() needing global state yes, being re-entrant, no.

                        1. 2

                          Right, because the standard was written before my old ass was born, when threads didn’t exist on Unix.

                    2. 2

                      You’re talking about per-thread state, i.e. a thread-local variable. It doesn’t necessarily need to be heap-allocated, although usually that’s an implementation detail of thread-local variables.

                      1. 2

                        Ah you’re correct, I forgot thread-locals exist. My bad.