What’s to love about C?

Antiquated, clunky, and unsafe. Though beloved to some, C is a language that many choose to hate. The mass opinion is indeed so negative it’s hard to believe that anybody would program anything in C. Yet they do. In fact a lot of things, even new things, are programmed in C. The standard was recently updated and the tools continue to evolve. While many are quick to dismiss the language it is in no danger of disappearing.

So why do people still use C? What features does this language have that other languages don’t? Too often newer languages are so intent on avoiding C’s pitfalls they completely ignore all of its positive qualities. This ultimately leads to C’s survival simply because there isn’t much to replace it. Here I’ll look at a several of the key reasons why C is still in use, and what features it offers for system programming.

Access to the hardware

If you are attempting to work at the level of the hardware you need a language that can actually expose the hardware. You can’t have a complex a layer between your program and the underlying chips on which it is running. You often need direct access to specific memory locations, registers, and CPU instructions. This even applies to pure memory use where many concurrent algorithms require, or are best served, by a very tight control of memory.

The abstraction C offers above the machine is limited, and the layer between you and the actual machine is thin to nothing. For cases where even this is too much, C compilers tend to offer inline assembly exposing the instruction set directly to your program. These assemblers also allow access directly with higher-level C code, thus providing a very comfortable integration.

Guaranteed simple flow

I believe that exceptions are a necessity in programming any complex system. At the same time however there are situations where you absolutely do not want exceptions, such as device control and low-level concurrency. It is often beneficial to have extremely strict return handling and not worry about jumping out of the current scope (either via an exception or an implicit function call, such as a destructor or defer statement).

C also doesn’t have any VM nor garbage collection threads. The guarantees of the underlying OS are provided directly to your code. Anything which requires fine control and quick response requires a high level, if not a guarantee, of predictability. This makes resource and signal management more difficult, but at times is absolutely essential.

Clear and unambiguous

As much as I love overloading, constructors, namespaces, member functions and templates, they can, at times, leave one wondering what exactly is happening in a piece of code. Normally this obscuring of details is a bonus, but at times it can be a definite loss. If you consider the two other features, access to hardware and guaranteed flow, it only follows that you will also need to know exactly what your code is doing.

If you call a function by name you will always get the same function; the types of the parameters do not influence this. If you refer to a variable by name you either get the local function variable or the global variable. There is no chance for ambiguously matching a member variable, or one of several embedded namespace names. Operators always do the same thing. Data structures don’t have implicit initialization, or de-initialization code. Basically what you see is what you get. While at times this may be tedious it is rarely in question what function is being called or what is being modified.

A macro preprocessor

Many languages like to focus on generic reusable patterns. Instead of having to repeatedly type the same thing you can abstract into a structure which can be applied many times. This allows you to encapsulate ideas and algorithms in their essential form. It provides a nice layering which keeps the code clean and understandable. Templates, interfaces, dynamic typing, and more serve this purpose.

But often these abstractions are infuriatingly difficult, and sometimes just not possible. It is often easier, and even more correct, to just create a macro which repeats the code for you. Perhaps the overhead of the abstract structure is also unacceptable: you can’t always rely on the optimizer eliminating the redundancy. Further cases may require repetition of the declarative syntax of the language itself, and perhaps only in bits and pieces. A preprocessor resolves many such issues.

Conditional compilation

Target platforms vary greatly. When working at a low level you’ll frequently encounter situations requiring platform specific handling. Simply using a high-level “if” construct may be insufficient since some of the code blocks may not even be compilable on certain platforms. Sometimes even for a single platform you may wish to offer different builds with different features.

This could be seen as an extension of the macro preprocessor, but I consider it a very distinct and highly valued feature of C. Some languages even offer this feature without having a full preprocessor. That C allows you to intimately combine these two aspects of the preprocessor is just another benefit of the language.

Libraries and API

C is still the standard for a shared library API. If a library needs to be used by multiple projects, in possibly multiple languages, then a C header file will be provided. The C function signatures are simple and easy to understand. Importantly they are easy to support in cross-language situations. The calling convention is standard and simple to implement.The boundaries the C API imposes are very strong and thus make it suitable for shared libraries. Only fundamental and POD types are possible, thus there is no concern about how memory is managed. There are no exceptions to handle. While extremely limited it is also extremely portable.

Small to tiny binaries

While C obviously comes with a standard library, and there are thousands of additional libraries, it’s still possible to create very small C programs. First off, unlike many languages, you can actually do a lot of systems programming without even needing the standard library. Secondly, even when you do use it, you don’t have to import the entire thing into your project. C has a compile and link model that allows the linker to prune any unused code. This means that you can produce binaries that are exceptionally small. It also allows you to produce tiny binary fragments suitable for embedding.


I’m putting this last in the list of features that attracts people to C. Certainly C has the ability to produce ultra efficient and blazingly fast programs but it is not really alone in this class. Languages such as C++, Ada, and Fortran can produce equally fast, or faster code, for many algorithms. Systems have also become fast enough, and optimizers good enough, that even in low-level programming the need to do micro-optimizations is often no longer essential. While the other features of C are still required, speed is perhaps not the primary one.

Still, performance is never irrelevant and many applications can gain substantially gains not possible in other languages. Certain cache oblivious, cache aware, and concurrent algorithms actually require specific memory layouts to work correctly, or efficiently. And for situations where ever minor gain is truly helpful C provides for micro-optimization.

Forever C?

C is still widely used for systems programmings because there aren’t many alternatives. While C++ retains a lot of these features, it is at times difficult to avoid the higher level functionality and provide guarantees about behaviour. Nonetheless C++ is usually a good choice if you need the features that C offers. Ada needs to also be mentioned here as it is used for critical systems programming. Though not standard, common Ada compilers offer a preprocessor and inline assembler. Integration with C is also quite straight forward. So perhaps this is also a suitable combination.

So while you may still not love C, as I do not, you can certainly bemoan the lack of these features in other programming languages. While perhaps often domain specific, I can’t recall ever working on a project that couldn’t have benefited from at least a couple of the above features.

61 replies »

  1. Really good article, I think it’s fun to program in C since you need to face your logical understanding in a much deeper way. You should not reinvent the wheel, but still it’s very fun to do so.

  2. In Scientific Computing the aspect you mentioned last (speed) is often the main reason for the usage of the language…

    • I don’t disagree, but if you look at a site like you can see however there are several languages that compete with C if speed is the only criteria.

      Sometimes slightly different constructs allow the optimizer more freedom to produce optimal code without you having to hand-optimize. Some of the new C++ 11 features may also give it another speed bump (like rvalue references and move constructors).

  3. C was my first language, it’s only in the past 12 months or so I’ve realised how fortunate I was to choose it. I believe the constraints you have when coding in C make you find ever more creative solutions.

  4. I think it’s maybe a bit harsh to say that the “overwhelming opinion” about C is negative. Most people that I know joke about segfaults, but when you come right down to it, actually like C for all the reasons you mentioned, not just tolerate it.

    • Obviously my statement isn’t based on a concrete stat, but based on my own impression. Often when I read blogs, or questions/answers at places like StackOverflow, comments against C are common. I wouldn’t say it is always against C, but the comments are often against certain constructs, hinting at a dislike for the language itself.

      I’d also consider the motivation for many new languages seems to be a dislike of C. I can base this strictly on the number of C features which are simply missing, often not even for technical reasons but just dogmatic ones.

    • Those that like it, use it. Those that don’t complain…you have to ignore the white noise of those that enjoy bitching and moaning just to see their word on the page.

  5. I’ve been programming in C (among other languages) for the past 10 years and I even though I love the power and freedom it gives you I also hate always having to deal with different architectures compatibility and all that hassle.

    Which is the reason you see languages like Go ( being created. It gives you the performance and power of C without the hassles and with a higher level API.

    I still love C though, don’t get me wrong :)

    • The architecture differences are often one of the main reasons to use C however. That is, it is kind of hard to tailor a bit of software for a specific platform if the oddities of that platform are not directly available to you.

      BTW, benchmarks seem to disagree about Go being as fast as C.

    • Don’t get me wrong.

      I’m not saying that dealing with the architecture difference is bad but having to always deal with it is a hassle. Sometimes it make sense, but some times I’d be happy if the compiler did it.

      I didn’t know that benchmark, thanks for the link. But still, IMHO Go is still a language to compete with C in the upcoming years. I know they are working a lot on performance and the language for sure gives you much more than C does. It allows you to manage memory only when you need to, makes a lot easier to build applications while still having differences depending on the architecture, it gives you better data structures and code organization tool and a much simpler build tool.

      Like I said, I love C and still uses it a LOT but we all know that there is a lot of room for improvements.

    • No doubt, dealing with many thinigs in C is painful and undesired. I use C++ myself, which removes much of the pain but adds its own layer.

      One dislike of Go is for a reason similar to my dislike of C: a lack of exceptions. My article on requiring exceptions (linked in article) explains my basic reasoning. Chances are if it fails on other points above I’ll also not like it.

    • Exceptions was one of the turn off for me in Go as well but I actually got used to the way errors are handled and nowadays I truly see the benefits of it although not fully enjoy it.

    • Ideally a language would use exceptions but allow sections to be entirely exception free. This would then be checked at compile time. This would allow those very low-level components, where exceptions get in the way, to be free of exceptions. The rest of the program would then still benefit from easier handling. I think the all-or-nothing approach of C++ is one of the biggest problems with exceptions.

    • The problem of supporting both is that it would add another layer of complexity and Go is really focused on being minimalist and simple, which IMHO is one of the biggest advantages of it.

      The reason to not have exceptions (in the traditional way) is that it kinda forces you to do something about it as close to the origin as possible, which turns out to help a lot when writing robust software. For the other cases, where a true exception happened you still have panic and recover. In the end it makes it harder and ugly to use exceptions for control flow at the cost of writing a lot of error handling.

      It still feels a little weird sometimes though.

    • Be it Go, JAVA or Python – They are of no use in my profession.
      I’m an Embedded Systems Developer. I’ve dealt with systems that have 256 Bytes(!) of RAM. Wherever you have to know every memory and register personally, C is the
      language of your choice.
      PERL is nice on the development machine to hack up some scripts and tools, but the job is done in C (well, and in VHDL, but that is anonther matter).
      In the last three years I wrote about 80KLoC, with 5-10% VHDL, 1-2% PERL for tools, and C for everythig else.
      C is available for about anything programmable (I have even seen a C compiler for turing machines!). Other languagues may be fancy, but C gets the job done.

  6. I think it’s a bit un-fair to say that the “overwhelming opinion” of C is negative. In my experience people joke about segfaults, but when you come down to it they like using C, and enjoy the language, for all the reasons you described; they don’t just tolerate it.

  7. I am surprised that (prior to my comment here), I cannot find the substring “nix” anywhere in this message. C and Unix were developed together. They are two sides of the same coin. You can have one without the other, sort of, but C was invented to write Unix, and Unix was and is written in C. The features which are “missing from the language” are “present in the operating system”. And this holds also for things like “Linux” which are do not use the unix name but do use its design.

    This leads to many of the other issues raised here (like libraries and api, as well as access to hardware).

    Meanwhile, the thing that keeps getting missed in the attempts I have seen to “replace C” is its essential simplicity. Simplicity is a virtue… Modern implementations seem to mask that simplicity (but it’s still there, buried somewhere).

    • Perhaps the birth of C is tied to Unix, but I don’t see that is its staple anymore. A lot of embedded systems are not Unix (though perhaps have POSIX, but so does windows). Indeed many embedded platforms work without, or only with a trivial operating system.

      I don’t think a language needs to be overwhelming simple. But it is very important to be able to decipher what is actually happening (not all domains of course).

  8. I really like Pascal. After BASIC, I learned Pascal, and only later C(++). I have used Pascal all my professional career. C’s been called a ‘shoot in the foot’ language, but Pascal comes from a little more mathematical background, and this is why I think it has less beginner’s pitfalls. It hasn’t reached the grand audience like C has, but the people at Borland, then CodeGear and now Embarcadero have treated it well, and adapted it pretty good to modern days. Working pointers was always less of a headache than C, object orientation, reflective code and now generics all work and got a nice syntax. There’s also a lively open-source scene with FreePascal and LLVM. Pascal is great, but I guess it just got snowed under with the advent of *nix and Windows 3 in the early nineties.

  9. “Ada needs to also be mentioned here as it is used for critical systems programming.” You should have mentioned that at past tense, because “the Department of Defense Ada mandate was effectively removed in 1997” (History for Ada on Wikipedia). Since then, at least when it’s about critical systems, C/C++ is the choice (see for example the coding standards for development of F-35 joint strike fighter here: Sure, old Ada code is still around and maintained (probably that’s the main reason of the existing Ada integration with C) but when new technologies are to be developed, there isn’t Ada anymore.

    • I was really uncertain about mentioning Ada, since as you say, new systems tend not to use it. In a way though this is also unfortunate since it also has features, particular in types, that no other language seems to offer.

      In the same sense I’m not sure how much Fortran is used either, yet it is still one of the fast languages. I really didn’t want to say C++ is the only alternative, though realistically that may actually be the truth.

    • I’m glad you mentioned Ada (and spelled it correctly!). In the late 80’s when I was writing Ada full-time, I was pleasantly surprised to find that, contrary to common knowledge, Ada was as fast as C (at least when exceptions and tasking were turned off).

      I also wanted to point out that Ada was a really nice language for writing Macintosh programs, whose API was in Pascal (which is to Ada what C is to C++).

  10. I don’t have any problems with C, even though I’d rather have my arm broken than have to program in it. The languages I ~do~ have a problem with, however, are all of the C-based languages! If you’re going to write a new language then why would you use such an assembly-looking basis? Yuck!

  11. Great article. I am using C as main language, although i move between C++ and Java i prefer to use C. Recently i required to learn R, and was extremely happy to discover that compiler functions in C can be used in R, which comes as a life saver for me.

  12. Although i like c++ and java, C is really faster than those languages. But the real problem where C runs behind and making programming complicated is the concept of C++ is OOPS (Object Oriented Programming).

    • There is no reason why C++ has to be slower than C. In many situations it is faster without much effort. Though theoretically both can achieve the same speed if hand optimized.

  13. I think my love for C is caused by the single fact that it does what you tell it to do, no more no less

    • I agree that this is a very favourable point to C. However, in light of optimizing compilers and concurrency I’m not sure it is entirely true anymore. There are algorithms that if executed strictly as written would work, but fail if optimzed and executed out-of-order by the CPU.

    • If the compiler optimization breaks your (otherwise correct) algorithm, then the compiler is wrong. The point of optimization is to make changes at the instruction level that do not change the results, but result in faster code. Same for CPU execution order – if changing the order of instruction execution gives different results, then it should not have done that. The before and after have to be equivalent.

    • In concurrency though this story changes. The C11 standard specifically addresses memory order to give you tools to deal with this type of thing.

    • That’s true of every programming language. They all do exactly what you tell them to do. The differences are all in where,when and how you tell them do it.

    • How much your program does what you tell it is a kind of degree. When I mention things like “doing what you say” I mean how closely the code you write relates to the instructions which actually execute. As you go higher in the language abstraction each line of code you write has a lot more things which can happen. In C the number of things that can happen per expression is quite low.

      In some domains this is necessary (microcontroller, drivers, etc.), whereas in other domains this is just a burden.

  14. in most cases the cost of C is much greater than any advantage. The programming errors it encourages often lead to software that is slower and buggier and memory hogging than if written with a higher level languages. The number of buffer overrun problems in most large C programs, even by allegedly experienced programmers, show that even programmers who think they are great cannot handle the language and that using C is just an invitation for bugs. In a larger program involving libraries, Object Oriented programming also is always better, leading to better written programs.

    • I agree that it has many disadvantages, but I think those could be fixed without losing the advantages I’ve listed here.

    • i have never seen a C program with memory problems so bad, that it could not be fixed using a visual debugger like ddd, and memory checking tools like valgrind or Insure.
      On the other hand, I have seen several projects written in languages with garbage collection (Java, Javascript) and hopeless memory problems. Things which should take a magabyte or two consume gigabytes. Android java programs which, could not allocate 50 MB on a device with gigabytes of memory. Programmer would be sheilded from the memory allocation problems, but just clueless about what is going on, why, and how to fix it. Assuming it was possible in the first place.

  15. I disagree when someone says C is a dangerous language. The danger is to let a bad programmer write something in C… just give this guy Java or whathever and he will be happy, C is to the programmers who know what they’re doing. With great power comes great responsability.

  16. Funny that “small binaries” would be considered part of C. I remember how much dismay I felt when I went from Assembly to C and saw my apps immediately become much larger. :-)

    • Since the first program you’ll write will be ‘printf( “Hello, World!\n” );’ which will drag in the whole printf-code complete with all possible floating point conversions it can do, of course the first binary will be huge. :)

      Easy to fix with a few linker options…

    • When writing in assembly, you have a big advantage over even the best optomizing compiler: You know what you intend.

    • Even better if you disassemble your C code to learn how the compiler generates assembler. Then you can have the best of both worlds :)

  17. Well, pretty much all of the things that are mentioned in the article are true.

    And yet there’s a glaring omission: the fact that C++ has made C obsolete years ago.
    Used in the right way, C++ is a much cleaner, safer and higher-level language that let’s programmers use much better abstractions – and here’s the best part – without sacrificing efficiency in any way! (Often, C++ programs are even faster than their C counterparts, due to judicious inlining.)

    Especially with the new [fantastic!] C++11 standard having been ratified last year and already implemented to a large degree in modern compilers, I’d argue that there is NO REASON WHATSOEVER to fall back to plain old C. C has had its time until approximately the mid-’90s, but it’s just terribly outdated now.

    No competent programmer should want or have to fall back to “C hacking” — it’s an approach that simply doesn’t scale and often times leads to bad quality results (security issues, lack of maintainability, etc.).

    • I generally agree, except a few points still remain. The overload and template situation in C++ can sometimes be overwhelming, thus C is often a bit clearer. I also find the exception syntax in C++ a bit too much at times and it is not simple to turn off. Now, I would not use C over C++ for these reasons, but I think it is important to consider them.

    • I don’t really like writing overtly negative articles about a particular language. I find it better to try and find the good things in each, and perhaps consider the bad things but only of generic concepts.

      Though I do have a series about what I find is broken in C++.

  18. I first learned BASIC in the mid-1980’s then C in the early 1990’s and C++ in the late 1990’s. In the early 2000’s I became a huge advocate for OOD and OOP but by now I have reversed course. In practice, C++ rarely meets any of its objectives and becoming proficient with it is both subjective and ridiculously difficult. Jumping in on a C project, you just need a list of functions and what parameters they want. The process flow is clear and easy to follow. In C++, you need to learn whole class hierarchies, vastly more complex to supply method parameters, and all sorts of other things that obscure and constrain the ability to make changes.

    That said, I’d like to mention that Javascript is moving into position as the C of the web. I think its OO model makes more sense for an interpreted language (even if JIT compiled).

    But of all the concepts that seem to reduce complexity, I find the UNIX philosophy of small tools and byte-stream piping the most interesting. It is inherently parallel except where sequence is important. Any data from anything is processable by anything else–a master at reusability where C++ classes tend to become too complex and interdependent for reuse… even change becomes difficult. I worked with Powershell’s piping of object pointers and found it to have a similar issue with choking bureaucracy.

    Let’s say a compiled or interpreted language were developed whereby atomic “functions/objects” are built that accept and send streams of bytes with standardized control (and escape) codes within. Layers of protocols could be wrapped. This is partially like the awesome signals and slots philosophy of the C++ QT toolkit. Now instead of classes hierarchies that make you constantly jump around trying to see what code is really where all the time, have mixable and mashable contexts that differentiates when a given function/object code is used over another flavor of the same. This is analogous to tool parameters in shell. At any point within a function/object, say behave this one when this, that but not those contexts exist.

    Keep the tool set reduced to one of each distinguishable kind of operation without respect for any particular purpose. Examples in Shell are: find, merge, concatenate, cut, and sort. None of these care what the subject matter is–they shouldn’t. They should be as universal and as small a set of fundamentally primitives as reasonably possible–like discovering the basic elements of the universe and from that, being able to construct anything you like. You can layer these, however such as for higher and lower level programming.

    I originally believed the mantra that OO represents the real world as it is. The world is not organized into objects instantiated from class hierarchies–people incorrectly interpret it that way. People organize them that way. Really, it’s all made of the same basic stuff sifted and churned more or less continuously in its semi-fluid parts and sub-parts.

  19. C is mother of all languages. all foundation software that people use are mostly written in c because if its low level access, speed and power to do almost anything. it is a language with which you can build almost anything. I love c.

  20. Love it or loath it. I hated C the day I started learning it at high school and later university. Then came PowerBasic – only available for DOS and later Windows. Now it’s 10th iteration gives you everything C gives you but with a syntax that people can actually understand and it includes an inline assembler for the die-hard coders who need to squeeze every little bit of performance out of the algorithm they’re developing. True, only Windows based for now and you can’t write device drivers with it due to the OS dependencies of the compiler. Nevertheless, PowerBasic beats C in every aspect.

  21. I enjoy working in both C and C++. They’re two terrific languages with different goals.

    What I love about C is its elegancy. To understand C, you just need a basic grasp of syntax and then you forget all of that and just think about the 0’s and 1’s. It’s so “bare metal” that it is elegant due to the sheer simplicity of it. String handling? Just think about how each character is stored in memory. Sure, we all stop and long for objects that make things like string handling less tedious, but C is wonderful at giving you a simple, unified interface to hardware without messing with assembly.

    C++ is a different beast. C++ is more about having good performance with complex, modern language constructs. Objects, overloading, inheritance, and all of that fun stuff are all supported. I tend to enjoy C++ more since it is so powerful “out of the box”, but that doesn’t necessarily mean that it’s better than C. C’s power is in the freedom. C++’s power is in the language features and STL.

  22. Here’s why C has survived and will survive. 1) If you ignore some of the modern add-ons and just use the subset of C that most programmers actualy write in, C is a sufficiently simple language that it’s practical to write your own C compiler that generates acceptably good code. 2) C is low-enough level that it is practical to write language translators for most other languages that compile them down to C source code. This isn’t true of today’s behemoths such as C++. and 3) C is such a nasty and ambiguous language that it takes Real Programmers to program in it reliably.

    I use an editor that was designed in the early 70’s and rewritten in C in the early 80’s. I’m still using the same C version thirty years later and I expect to continue using it for another 30 years, if I last that long. I’ve seen other languages rise and fall in the intervening years. Where are the Algol60 compilers? The Coral66 compilers? C has already had a longer lifetime than those languages ever had,

  23. I will always love C for its transparency alone (although there are other things I love about it, most of which are in your list).

    An example:
    In C, the line of code “a = 1;” will almost certainly compile to “MOV EAX, 1”.
    In C++, it may do the same, or it may call an operator-overload member function (which could do anything), depending on the type of “a”.
    If the type of “a” is hidden behind a daisy-chain of typedefs buried in a series of obscure header files, you could easily spend an entire day merely establishing what this seemingly-innocent line of code actually *does*.

    I am not against operator overloading or C++ in general – my point is that these features can be abused, and since C simply doesn’t have these features, there is no potential for their abuse in C.

    I am sometimes very happy to throw the baby out with the bath-water, just to be certain that the bath-water is really gone.

    • It is hard to provide features like overloading without people abusing them. It’s perhaps not a fault with C++ itself, but it is certainly open to abuse. I dislike this abuse and consider it to be a defective coding style. That is, if “a = 1” does anything other than assign a value of “1” to “a” the type of “a” is simple broken. I expect “a” to be a numeric type.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s