Why I hate C

I dedicate this page to my personal and very strong dislike for the programming language C. C is possibly the most wide spread and most used programming language today, does that make it the best, or even fit for todays world? I think not. I think C is for programming languages, what Windows is for operating systems: barely good enough, and mainly used because their users does not know or dare to try anything else.

Have in mind that this is my thoughts as a programmer of everyday applications for desktop and server computers. For micro-controllers and other hardware- centric programming C is a good choice. For the other 99.9% applications out there it is not. It is common sense this day that assembler is not a feasible alternative, the total control and possibility of more performance is not worth the trouble. In this text I shall put forth my reasons as to why I think C is not a feasible alternative either.

The main problem with C is that programmers make mistakes, I do, if you are a programmer you do too. No programmer can be required to write thousand lines of code with not a single error. Less code, means less errors to find and remove. C by design gives more code to write, and provides less mechanism for preventing, finding and correcting errors than most modern, or even ancient, programming languages do. A good programming language should make the right thing to do the obvious thing to do, and everything else possible to do. And above all; the best code, is less code. Therefor redundancy should be avoided, and all the basics provided.

Error handling is tedious and inconsistent

There is no way to in a nice and consistent way handle all errors in a single place, using a single method. Error-handling should be treated as exceptions to normal program-flow, not as part of the normal program-flow. The standard libraries can report errors by:

  • Returning zero.
  • Returning non-zero.
  • Returning a NULL value.
  • Setting errno.
  • Requiring a call to another function.
  • Seg-fault or other crash.

Open up any of your C code and count the lines, how many are there for handling errors, and how many are there for doing what your application is actually meant to do? How many of your applications are actually able to recover from an error?

This leads to sloppy code, when did you last check the status code of printf()? And do you know which way is the right one to check it? And if it is safe to ignore errors from printf(), why not malloc() or fwrite()?

The syntax allows for ambiguity, is inconsistent, and unintuitive

It is easy to write incorrect code, but hard to spot errors. The characters in the string char *foo = "bug" can be accessed using:

foo[0]     => "b"
1[foo]     => "u"
*(foo + 2) => "g"

Accidentally typing = instead of == when doing a logical compare is all too common. The assignment operator should be more clearly different from the equality operator. For your own code it is merely an anoyance now and then, for maintaining code written by others it is a nightmare.

if (foo = bar + 1) { ... }
Without taking your time to understand the context of that code snipped you have not chance to say for sure if the programmer accidentally forgot to write ==, or if the "shortcut" assign-and-test-in-one-go was actually intended.

A normal defense is that using := as an assigment operator would require one more character to type. That C makes for short and nice programs a prevalent lie. Take the binary and boolean logic operators. In any day to day application using boolean operators beats binary operators 100 to 1. Open up any of your C code and check how often you do boolean logic, vs how many binary logic you have?

Please declare a type for a function pointer to a function that takes an array of strings, and a pointer to a function that takes a string and returns a boolean, and itself returns a string? While you think about that I do it in Pascal:

type foo = function(strs: array[] of string; 
    function(str:string):boolean): string;
And in D for good measure:
typedef char[] function(char[][] strs, 
    bool function(char[] str)) foo;

Array and pointer operators are post-fix and pre-fix making it hard to declare for example; a pointer to an array, or an array of pointers, without ambiguity. And why can I declare three integer variables with:

int a, b, c;
But I can not declare three integer arguments with:
void foo(int a, b, c);

Why am I required to manually write #ifndef, #define and #endif in every single header-file, when a seperate #import directive would have solved the problem more neatly long ago?

C lacks the basics

Even in the early 70 when C came into being applications had the need to process text, command lines, user input, configuration files, and more. Yet support for even the basics are lacking or completely missing. Forcing developers to re-inventing the wheel over and over, and spend precious time writing support routines that could have been spend solving real problems:

  • String
  • Arrays (Memory-pointers and syntactic sugar for indexing does not count)
  • Hashtables
Spend a moment to think of how many times you have implemented a linked list, how many of those time could a build in dynamic array, or list type, have saved you?

The programmer must baby-sit the environment

Many C programmers complain that other languages (Pascal is often mentioned) baby-sits the programmer. My problem is that I have to baby-sit C. Garbage collection comes to mind, it is said that handling the memory manually is faster. And if done properly it is, unfortunately it is hard, very hard, to do properly in C. As there is no way to test if a pointer is pointing to allocated or free memory it is up to the programmer to at all times hold C's hand.

Add the additional complexity of error handling making a single exit point hard to maintain, and you have applications that easily becomes prone to memory leakage, crashes from accessing freed memory, or double freed pointers. In reality the programmers end up taking the paranoid safe way, adding redundant checks just to be sure, and a garbage collector beats the hand written code despite the theoretical claim.

Type safety and checks

Yes agreed in production code checking the bounds for every array access can be redundant. But crashing or even worse; silently overwriting data for development code, is just an invitation for hard to find bugs. As arrays are not bound checked it makes it very easy to do buffer overflow insertions, something that has been used many times with malicious code. Something as simple as bounds checking would be a huge step for writing more secure software.

Type safety is another problem, most notable in variadric functions such as printf(), where it is left to the programmer to make absolutely sure correct values are passed, and no help of any kind is provided to do so at compile or run-time.

This is even more important in any code that is supposed to be secure, or robust. A programmer is bound to make mistakes, even making mistakes in the code that checks for mistakes and errors. Letting all programmers re-implement each and every check themselves for each and every application instead of implementing them in the compilers where they belong, and are exposed to much better review for correctness and improvement, is a tragic waste of resources.

The portability myth and reality

You can write portable code in C, and if you do not count Windows then it is almost easy. But it is within "almost" the problem lies. Using multiple #ifdef and other pre-processor hacks is not portability, is working around inherent problems to support known platforms, using such methods can not guarantee that your code is "portable" to any unknown or future platform where even more quirks might be introduced. If C had been portable then any code that compiles and runs without warnings on your machine should compile and run without warning on any other machine that also claims to have a "C compiler".

As it is today, using the standard libraries, and carefully, with even more baby-sitting, adhere to ANSI- or ISO-C you will still need pre-processor directives to get it to run for any real world sized applications. C is not portable, it has merely fooled allot of people to think it is, and still be glad when they have to take extra steps to ensure the claimed "portability" themselves.

So what is the alternative?

Yes after all this ranting, perhaps I should say what you should to use instead. No I should not, it is you who should go out there and see for yourself what else it is, and what it can do for your productivity. There is no answer to the question "what programming language is the best?". There are many good ones out there, and many more will come, and if C, and the C- families of languages would loosing it choke-hold on innovation even more would and will come.

And remember that just because riding a bicycle takes some training, walking does not automatically become the best transportation. You learned to walk once as well, and I am sure you can learn to master something better than C in no time.