How comes that, after over half a century, C is still a relatively popular and widely used language when others have withered into obscurity? Why, over all this time, was nothing able to fully replace it? Why is it still taught in schools?
Let's have a look at some of the best, in my opinion, aspects of the language (although not all) that contributed to such a state of affairs.
Spirit of C #
Let's start with a quote from document C99RationaleV5.10:
The C89 Committee kept as a major goal to preserve the traditional spirit of C. There are many facets of the spirit of C, but the essence is a community sentiment of the underlying principles upon which the C language is based. Some of the facets of the spirit of C can be summarized in phrases like:
- Trust the programmer.
- Don’t prevent the programmer from doing what needs to be done.
- Keep the language small and simple.
- Provide only one way to do an operation.
- Make it fast, even if it is not guaranteed to be portable.
"Mid-level" #
In regards of level, there are two types of languages: low and high.
Low-level languages are close to the hardware, the only closer thing to CPU would be electricity itself. Those languages are divided into machine code and Assembly. The former is a stream of raw, usually binary, data. If somebody is required to work with it, usually does it using more "readable" hexadecimal form.
Second-generation languages - Assembly - provide one abstraction level on top of the machine code. Those languages are mostly only a mapping of human-readable symbols, including symbolic addresses, to opcodes, addresses, numeric constants, strings and so on. Also are different for each processor.
How do high-level languages, providing more abstraction, compare? Quoting Wikipedia:
In contrast to low-level programming languages, it may use natural language elements, be easier to use, or may automate (or even hide entirely) significant areas of computing systems (e.g. memory management), making the process of developing a program simpler and more understandable than when using a lower-level language. The amount of abstraction provided defines how "high-level" a programming language is.
In big oversimplification: low = more machine friendly, high = more human friendly.
C is high-level, but back when it was created, most of the work was still being done in low-level Assembly. As a result, C has lower level of abstraction than other (still) widely used languages and is often utilized for low-level programming, hence I like to call it "mid-level".
You can also easily (with less language bloat) compile C code to Assembly and examine what instructions processor will execute.
And if it there is a need, many popular C compilers offers you the option to level down and use inline Assembly to squeeze everything out of CPU. It's a feature not really implemented with many other languages.
Fairly simple #
Low-level languages are harder to program in. Not because they are more complicated, but because they are more error prone and thus require way more commitment, memorizing and fiddling.
C is mid-level, so "by definition" it's easier. But there comes the surprise, learning it is easier compared to higher level languages! Why? Because of not extensive syntax it doesn't take so much to learn the basics. Loops, functions, structures, pointers, variables, types - the core of language. Intense week to get the general idea. The rest is "just" maths and CS theorem.
But, but, but, but! Don't get me wrong!
Language is simple, programming not necessarily!
To master anything you will need a lot more practice!
A lot! And it's truth for anything out there!
Fast, lightweight and flexible #
Standard C library is small compared to other languages (e.g. Java). It's small enough for you to try to memorize all functions successfully (not that it would be a huge benefit). Yeah, many things should be deprecated long ago, there obviously is some bloat (try to maintains something without bloat for few years, let alone few decades), but there is not much enough of it to hinder the performance.
And what if libc is still too much? Nothing stands in the way of not using it
at all! Just don't include any of its headers - not even simple printf()
will
be present. Replace it with any other library of your choice.
Maturity, emphasis on proper memory management, inline Assembly, small abstraction and little bloat gives programmer really good control over the program.
This makes C an ideal choice for OS kernels (Linux, Windows NT or macOS's XNU to name a few) or other languages (e.g. Python). That's also why C is so popular on embedded systems, where you cannot afford to waste resources.
Standard, no blessed implementation #
This one relates to previous and next point. The C programming language is definied basically only by a document published by International Organization for Standardization every few years. Contrary to languages like Python, Rust or Java, there is no partucullar implementation that is the C language.
Combine it with flexibility and you have a language which can easily target any platform.
Ubiquity = portability #
Does there exist any (still) significant platform with no C compiler available? Yes, those work exclusively on Assembly only; for all others, C is available. C programs are present on your high-end gaming PC, on NASA spacecrafts and in ticket machines. Literally everywhere. C software runs the world.
In accordance to previous paragraphs, C is peculiarly strong choice for microcontrollers and other forms of embedded systems, which surround us every day.
And have you heard about FFI? Turns out many other languages like to have some kind of compatibility with C.
You don't need to worry if you will be able to use this language somewhere as for 99% you can! (Although it doesn't mean you should…) It means, that while code may not be 100% portable, you will be a portable programmer.
The influencer #
C has both directly and indirectly influenced innumerous amount of languages. C++, Java, Go, D, Rust, Perl, even PHP and Python - those are but few examples.
Obviously, knowledge of C isn't needed to learn any of them and sometimes may even push you to use not the best practices.
Nevertheless, I think it's beneficial to remember the roots. And if you are cautious, familiarity with C might give you some foothold. It's especially the case with C++.
Rich collection of libraries #
I suspect all this talk about fastness, lightness, mid-level, Assembly etc. might
have give you an idea, you will need to implement everything yourself. There may
indeed not be any LinkedHashMap
or other functionalities like garbage collection available for C… except… not entirely.
C is mature and popular language, so while those features aren't build-in, believe me, name a thing and somebody somewhere already created library for it (although if think about something too obscure to find, but it does exists).
You want garbage collector? Boehm GC has you covered. TUI? Nothing like timeless ncurses. Examples can be listed almost infinitely: GTK, PDCurses, libcurl, ALSA, Genann, libsoundio, SDL, SQLite, getopt, OpenGL, inih, GMP, cJSON, MuPDF, libXDGdirs, OpenSSL…
It's very universal language - you can program basically anything: web server, video game (e.g. classics from id Software), operating system, other programming language or wrapper forcing Firefox to obey XDG Base Directory specification, because when I'm an administrator, the programs will do exactly what I told them to! There were madlads doing WebDev in C via CGI Scripts (and nowadays with WebAssembly).
However, please, remember - the fact you can, doesn't mean you should. For example, if you want to create a video game, you really ought to turn your eyes to C++. And you should know, that…
C++ is highly backward compatible #
Why do I even make the whole point out of C++ here? Because it's one of most widely used languages today and you encountering it is more than certain.
In contrary to other languages embracing C compatibility, C++ was created as its direct descendant and committee goes to great lengths to keep the "copy-paste" compatibility with it - in most cases you can compile C code as C++ just fine.
All examples from the previous point not only can be, but often are used in such way.
Even libraries already compiled with C compiler can be made somewhat compatible with C++,
thanks to extern "C"
linkage specifier.
Safety #
In my opinion, this comment by Reddit user u/tim36272 catches this point perfectly:
You're thinking of things like type safety, garbage collection etc.
I'm talking about safety in terms of people dying. Things like garbage collection are the opposite of life safety. What if your airplane decided it needed to free up memory ten seconds from touchdown so it ran the garbage collector? What if running the garbage collector caused a valve to respond 0.1 seconds late to a command, which caused a chain reaction resulting in a hydraulic line bursting and losing control of the rudder?
C can be safe because it does exactly what the programmer tells it to do, nothing more and nothing less. There's no magic going on behind the scenes which could have complex interactions with other behind the scenes magic.
A common example is
std::vector
from C++. This container expands as needed to accommodate as many elements as you need. But you have a limited amount of memory on the system, so you need to do static analysis to determine the maximum size of that vector. And you need to be sure that you have enough memory for that plus everything else in your system.Well, now you've eliminated a lot of the convenience of using
std::vector
. You might as well just allocate that max size to it and avoid all the overheadstd::vector
imposes by growing in size.The other main advantage of
std::vector
are templates. If you were to use a template in safety critical code you'd need to prove that the code generated by the compiler is correct for every template. Now that you're diving down into all this auto-generated machine code, it would be easier to just write the code yourself and avoid the complexity introduced by the compiler's template generator.So, if we eliminate all the usefulness of
std::vector
, why use it at all?Repeat that process for most features in most languages and voila! You're back at C
Important note: if you want such safety, you throw portability out of the window!
Preprocessor #
C (and its direct derivatives like C++ or Object-C) is the only language I know of, which includes a lexical preprocessor in its specification.
Understandable, considering the fact that many newer languages contain mechanisms
which make preprocessing partially obsolete.
And in the need there are always fallbacks:
- using other language (or even itself) as preprocessor (e.g. Python as preprocessor to Java)
- using external preprocessor (e.g. m4)
- …or C preprocessor (yes, there is nothing stopping you from preprocessing JavaScript with C compiler!)
I will repeat the link with proper title: The C Preprocessor in Javascript? - I really recommend reading this short text, as I think it's enought to understand why having a standarized, portable preprocessor is a good thing in C.
Program in C song #
Lyrics
Ariel, listen to me OO languages? It's a mess. Programming in C is better than anything they got over there. The syntax might seem much sweeter Where objects and subtypes play But frills like inheritance Will only get in the way! Admire C's simple landscape Efficiently dangerous! No templates or fancy pitfalls ... like Java and C++! Program in C Program in C Pointers, assembly, Manage your memory With malloc() and free()! Don't sink your app with runtime bloat Software in C will stay afloat Do what you want there Close to the hardware! Program in C!Conclusion #
Learning C is a valuable experience and may be really worth it. If not as your first language, then as second, third, fourth or whatever. There are advantages, but (as always) also some disadvantages; at least trying won't hurt. Give it a chance, who knows, you may truly get to like it.
And don't believe people saying "C is dead". Love it or hate, C is still kicking and the amount of crucial projects will keep it relevant for few next decades too.