I am aware of C11 and even talked about it several times about it on both the forum and the wiki. What I meant was that many aspects of the language are in desperate need of modernization. But, unfortunately, that can't happen because of the need for backwards compatibility.
Combuster wrote:do you have any alternatives that do not suffer from your list of personal annoyances?
They are not personal annoyances. I can explain quite objectively what is wrong with the language. The short story is that it asks for a lot and offers to little in return. Here is a longer story, which better explains the previous sentence:
- It has many pitfalls (undefined, implementation-defined, and unspecified behavior) which invariably translate bugs every so often, even in the case of expert C programmers. What's worse, correct code sometimes requires ugly workarounds. The reason why these "features" were introduced to the language was to allow optimizers to perform well while still keeping compilers relatively simple. However, compiler technology has suprassed that point.
- It lacks modern code reusability/abstraction mechanisms, such as user-defined namespaces and classes. The namespace issue is usually worked around by prefixing identifiers with "LIBRARY_" which results in overly verbose code. Classes can also be implemented but the resulting code will be far less expressive than it would have been with language support.
- Error handling in C cannot be performed in a clean manner. There are 3 options:
- Using traditional idioms: return values and thread-local variables, such as errno (eww!). Both require that the error handling and normal control flow be interleaved.
- Using goto statements that jump to the clean-up code positioned at the end of the functions in which the errors ocurred, à la Linux. However, it is in general difficult to handle the situation where a library detects an error but expects the client code to handle it.
- Exception handling can be implemented (e.g., using macros and setjmp/longjmp). Unfortunately, since C lacks RAII, things don't get destroyed as they go out as scope so a finally block must also be implemented (again, resulting in more error-prone code). VLA's are also tricky to clean up.
- There is no support for serialization. To avoid using files or arrays (which are not expressive enough to work with in this situation), most people rely on compiler-specific extensions, such as GCC's "__attribute__((packed))" to pack structures. You know a language is no good when you need to look for extensions here and there to make up for its shortcomings.
- Implicit declarations---seriously? If you forget to include the appropriate header, you may end up using a function that doesn't exist and the compiler won't complain. Have fun hunting the problem down through the build system.
- Certain micro-optimization aspects are exposed to the programmer, forcing them to write micro-optimizations that do not perform well everywhere. These could have easily been abstracted. The most obvious example is string processing.
- While we're at it, what's up with the null-terminated strings? Surely, it is better to store the character count and perhaps data structure size somewhere... The only reason for this design is the standard library (which was incepted long ago).
- I expect people to start shouting about this one but... it lacks automatic memory management and it's easy to mess up by going out of array bounds when using its pointer arithmetic design (any object in C is actually stored as an array). The compiler should do as much as possible and the programmer as little as possible. This heavily increases the chances of ending up with more correct programs.
Since I mentioned places where the standard has accumulated dust or where ugly solutions were found, I feel like I should provide a couple of examples:
- The so-called standard integer types (i.e., char, short, int, etc.) were not standardized with the appropriate constraints, leading the committee to introduce the minimum-width integer types (i.e., (u)int_leastN_t) in C99.
- All sorts of compiler hints that were introduced as hacks because yesterday's compilers sucked (e.g., void foo(int a[static 42]), register, inline). As far as micro-optimization is concerned, the standard says that the compiler is free to ignore them entirely.
- Because computers and parsers used to be slow, C was designed such that compilers require few passes. This means identifiers only become visible after the point where they have been declared rather than the entire scope. This leads to things like forward declarations which need to be kept in sync with the actual definitions.
I can
gladly go on all year long. It's true that all languages have their idiosyncracies but unless they have something to compensate for them...
You asked for alternatives... Well, it depends on what the task is. For numerical computation, I'll use Octave. For logic programming, Prolog. As far as most systems are concerned, functional programming is more elegant because (a) it allows for simpler solutions with fewer constructions and in which the programmer only needs to concentrate on the solution in space but not time, (b) it can be parallelized better, (c) very good static analysis can be performed on it. To give some examples, I often use Scheme, Common Lisp, Standard ML, and JavaScript (which has matured beyond anyone's expectations and is, as Owen said, basically "Scheme in Java clothing"). I also encourage people to look into Smalltalk.