1. 10
  1.  

  2. 19

    There is little of needless code in standard library. And what there is, believe me, over the decades was really polished.

    realloc, all of locale.h, strtok, strcpy, strcat, gets… I like C a lot, but I wouldn’t call the stdlib polished. It’s a product of its time and has various footguns and bad APIs.

    1. 12

      Yeah, and all of this (and POSIX) has been designed before threads were a concern. For example, getenv is theoretically impossible to use in a multi-threaded environment, which makes time.h dangerous, because it may read the TZ env var.

      1. 7

        The poor and confusing stdlib is one of the least friendly bits of C. Nothing is ever dropped from the stdlib and only rarely dropped from the OS APIs, so all the bad stuff just hands around forever to confuse new programmers.

        1. 4

          You haven’t lived until you’ve called gets.

          1. 3

            Strings.

            1. 1

              Why don’t you like realloc?

              1. 2

                There are two problems with realloc - scope creep and its API.

                Scope creep - realloc attempts to resize a buffer, but will also act as malloc if you pass it a special value (NULL ptr) and as free if you pass it a special value (size 0). This makes it difficult to tell what any given realloc call does when reading code and since it’s rare to actually want that functionality (because malloc/free exist) it tends to paper over bugs when accidentally triggered. It’s likely that the implementation drove the design; it would have been better to disallow resizing a null allocation and disallow resizing an allocation to 0, but it probably ‘fell out’ of how the initial implementation was written so became part of the functionality instead of keeping it tight and focused.

                API - the intuitive way to use realloc is incorrect.

                void *buf = ; // some existing allocation
                buf = realloc(buf, new_size);
                

                That will leak memory if the resize fails - null is returned and it overwrites the original buf pointer.

                The correct way to use it requires this song and dance, which is not a common or intuitive pattern.

                void *buf = ; // some existing allocation
                void *tmpbuf = realloc(buf, new_size);
                if (tmpbuf != null) 
                    buf = tmpbuf;
                

                realloc often gets a wrapper to prevent misuse because this is such a common issue. FreeBSD has reallocf that will automatically free the passed in buf pointer on allocation failure.

                It would have been better if realloc had separated the error signaling from the buffer parameter instead of trying to collapse both into a single return value. Example alternate definition

                int realloc(void **buf, size);
                void *buf = ; // some existing allocation
                if (!realloc(&buf, size)) {
                    free(buf); // resize failed
                }
                
                1. 4

                  Realloc has other problems. Its efficiency depends heavily on the design of the allocator. Early malloc implementations stored the size in the header word and so could easily either expand the size if the memory after the allocation was free or contract it by carving the space into two runs. This family of allocators is generally slow and difficult to make efficient in a multithreaded setting. Modern allocators tend to be sizeclass allocators, which round the requested size up to some fixed-size bucket and then use a slab allocator of that size for the allocation. These are much faster and scalable but typically end up transforming realloc calls into malloc, memcpy, free sequences, syntactically hiding the fact that realloc is now a very slow operation.

                  The realloc function needs to be modelled as a free in user code: it invalidates all pointers to existing allocations. A shocking amount of software suffers from memory-safety violations when realloc actually reallocates. If you have any aliasing of the object, code that calls realloc must go and rewrite all of the pointers after the call. That’s easy if there is one pointer (but the C type system doesn’t give you any help ensuring that this is the case) and very hard in the general case.

                  To fix this problem, a lot of code depends on undefined behaviour, using something like:

                  void *old = somePtr;
                  void *new = realloc(old, newSize);
                  if (new != old)
                  {
                     // Go and rewrite pointers that are equal to old.
                  }
                  

                  The problem here is that it is UB in C to compare anything to a freed pointer. If new and old are equal, this is well-defined behaviour. If the realloc call freed the old object, then this is UB, so it’s completely fine for a compiler to assume that the body of this function is never reached (reaching it is only possible if your program contains UB and programs that contain UB are in an undefined state and anything is allowed).

            2. 11

              Aside: please don’t use a fixed-width font with justified spacing. This is the worst of both worlds!

              1. 1

                Linux man page utils does this by default.

              2. 2

                You don’t need to worry if you will be able to use this language somewhere

                I have become a fan of C for this reason. I don’t actively build things in it at work, but I work with project written in it every day. So, learning it has really helped me work with those systems and feel more confident about how I’m using them.

                For anyone whose wanted to learn, but can’t get passed issues with pointers. I really recommend this course that is specifically about pointers. After working my way through it, I finally felt capable of understanding pointers and working my way through segfaults on my projects.

                1. 3

                  You don’t need to worry if you will be able to use this language somewhere

                  IMO this is more or less the only good thing about C. The compiler is already installed on the system, no matter what the system is. That’s it.

                  1. 1

                    Even that is less of a benefit than it used to be. GCC used to be a good C compiler and a fairly crappy C++ compiler. Both GCC and Clang are now focusing on C++ for optimisation and so you’re likely to have at least as good a C++ compiler available as you have a C compiler (and that C compiler is probably written in C++, so you need to have a working C++ compiler for bootstrapping). Visual Studio is now, finally, getting C11 support, but was stuck on C89 for ages, so even 8-9 years ago you had C++11 everywhere but only C89 everywhere. C99 was a modest jump from C89 compared to C++98 to C++11 but it’s still quite painful using C89 in comparison (and not having C11 atomics makes it impossible to write the kind of low-level lockless data structures that you actually want a C-like language for).