7 ms·
How can C Programs be so Reliable?
- scott_s 18y agoThe author has a unique perspective since he - somehow - skipped learning C until now. He programmed assembly before, and from his other work he clearly is familiar with dynamic and more modern languages. Consequently, his perspective on C is that of somehow who is new to the language, yet also understands both the fundamentals of what his code will compile down to, and the higher level facilities that later languages and programming models abstracted away.
- michaelneale 18y agothat was my experience when I went from ASM - to functional and then C when wrangling hardware. I am still surprised at how reliable C apps can be (linux kernel) - hats off to the genii involved in making things stable.
- alecco 18y agoI use Linux, but I am still surprised it is really stable. At low level it's quite messed up and shows the cons of being a pseudo-bazaar ecosystem. Lately it's changing to a cathedral with a handful of core developers acting quite dictatorial and perhaps that will clean up things a bit. I'm not advocating any of both camps, just bringing up it is far from perfect. In particular I've spent last week trying to figure out TCP/IP port management and it was quite frustrating. Lack of docs and plenty of hardcore operations unexplained, not explicitly marking all macros and inline functions, no clear distinguishing of scope for objects, and I could go on for a few dozen more lines of this. Only a fanboy or an egomaniacal author can argue that. Don't get me wrong, I like it overall and I prefer it to most other OSs.
- qwph 18y agoThe stability of any system is roughly proportional to how much automated testing is done against it.
- alecco 18y agoIt should be, but usually it is just user base. Microsoft has the advantage of everybody sweating hard trying to make their things work. Their beta users are in the millions, and mostly are experienced users or even developers. I see Linux developers talk about regression tests but couldn't find where is the central repository of this (is there one?) Regression tests IMHO should be distributed with the source code so everybody can run them. (I understand testing a kernel is not like testing any other user space program.) The BSDs do it, for example.
- orib 18y agoOr how much thought goes into it. I remember reading about a study about a very different style of development from years ago, where automated testing was NEVER done, but instead formalized models of the program were created and mathematically verified. If I remember correctly, the final result was a similar level of quality to an extensively tested system developed today, and the code tended to be of somewhat higher quality thanks to the extra thought put into it. (But I'm only going off vague memories here... I might be misremembering)
- qwph 18y agoThat's not an exclusive or though. If you have a proof of your code's correctness, I'd still like to see it being regressed against any changes to the code. Ideally the more different methods you have for validating that your code is doing what it's supposed to be doing, the better. I'm actually quite disappointed to see my original comment with a negative score. I'm going to assume it was just badly phrased, as I don't seriously think that anyone believes that more testing of software results in a decrease in quality.
- orib 18y agoOf course it doesn't make software worse, but you make it sound like it's the only factor in software quality. Far from it, and IMO the degree to which it has become a religion is disturbing. I suspect that might be the source of the downmods. I've seen projects with more tests than code, and I think that shows a problem with development methodologies. Simply reasoning about the flow through the code (and possibly using a powerful type system [think Ocaml] to help catch errors at compile time) would remove the need for many of these tests (not all tests should go, of course) and would result in tighter code.
- scott_s 18y agoIn contrast, I consider the Linux kernel code clear and clean. I have mostly looked through the scheduling and file system code; I can't speak for TCP/IP port management.
- wheels 18y agoSame here. I consider the Linux kernel code simple compared to systems code that I've dealt with in a professional environment.
- alecco 18y agoPlease elaborate why you think that. (e.g. style, interdependencies, or maybe compared to something?) :)
- scott_s 18y agoFirst, I'll grant that there's very little internal documentation. This hurts. But I find I can still figure out what's going on by reading the code, and that the really obtuse things have comments. The coding style is consistent - I don't think I've ever seen a single deviation. Linux kernel code is fit for print. This helps enormously with readability. It's rare to find such a large codebase written by so many people that uniform. The directory structure and file names are straightforward. Want to know where the main scheduling code is? kernel/sched.c. What about the data structures for scheduling? include/linux/sched.h. How is fork implemented? kernel/fork.c. This is important because it means the source is discoverable. Functions are just the right length. A single function conceptually does one thing, as it should. I've never read through a function and thought "This should be factored out into several different functions." Consistent and intuitive names. Even if I've never read a function, or seen the definition of a variable, I have a good idea of what it does just by the name. This is should be true for all code, but considering the size and complexity of the code, I find it impressive I've never thought "That's a stupid name." It helps that special functions follow certain naming conventions. For example, if I encounter a function named do_foo(), I know that it is the function called for a foo system call. And most importantly, any time I've wanted to know how something works in the Linux kernel, I've always been able to figure it out with focused study and tracing. Focused study, not a heroic effort. If it takes longer than I thought it would, it's always because I had to learn a few new concepts along the way, not because the code was obtuse. Reading the Linux kernel code helps if you do it using LXR, http://lxr.linux.no/ http://lxr.linux.no/, which does a cross-reference of the code. Most variable names, functions and structures are links to their definitions and occurrences.
- michaelneale 18y agoThere is an "aesthetic" to the kernel code I now realise (after having had it explained) - whilst some of it is no doubt odd looking, most of it is done that way for a reason, and reasonably consistently. But not to everyones taste.
- alecco 18y agoI'm not a style-nazi, any of the basic styles is fine for me (e.g. indent and keep the same style in all source.) My dislikes are more pragmatic, no good comments, packing multiple complex operations in a single line, and not making clear what is what (OK this last bit is style but every single coding style for C out there defines this!) [Again, I'm playing a bit Devil's Advocate as I'm a Linux user myself. There are many things I love from this team.]
- qwph 18y agoI'm still not convinced that there's anything particularly magical about C here. Reliable programs are written by people who: * understand the problem domain * know the implementation language and its supporting library * pay attention to detail Admittedly, some languages fit some problem domains better than others, but 90% of the time, picking the language you're personally most familiar with will be as good a choice as any.
- scott_s 18y agoThe author comes to a similar conclusion.
- jwilliams 18y agoThe thing about C is that you are generally very aware of the side effects. Aside from the libraries you use, the (data) structures are yours. When you set something to NULL, you know damn well that that means to you. As the author alludes to as well - in C you're made more aware of the error conditions you can handle and the ones you can't. So you can code to a level of robustness... Exceptions in Java are all well and good, but I haven't seen many implementations that do anything with IOException except for cascade it. This works really well for programs/modules that can fit in the head of a single programmer. When you go beyond that it gets pretty messy - which is where some of the advantages of metaphors like OO start to help... Course, there is the argument that modules should never get that big, but that's another debate.
- Hexstream 18y ago"As the author alludes to as well - in C you're made more aware of the error conditions you can handle and the ones you can't." You mean like when a function silently returns -1 to indicate failure and then you wonder why your program returned a wrong result (if you're lucky enough to even notice)? In the bigger part of most of my programs I want a big, flashy, loud, total failure by default if anything goes wrong.
- amackera 18y agoThe only time a function would "silently" return a -1 would be if you're not checking your returns for errors! Functions have return codes for a reason.
- Hexstream 18y agoBut oftentimes I want a correct result or an obvious failure, without having to handle tons of errors I don't even want to account for.
- holygoat 18y agoReturning -1 is an obvious failure. What you're saying is that you want your programs to blow up when a function returns a known error (such as "disk full"). One of the points of the article is that C makes you think about all the possible, well-documented, errors that each function can encounter. You know when you write C precisely what can go wrong at each stage, and decide the right way to handle it. Seeing a dialog box pop up with some exception backtrace is one of the things that annoys me about 'modern' Java-esque languages: they seem to encourage developers to default to ignoring most exceptions.
- sharkfish 18y agoIf, as in the case of extsmail, one wants to be robust against errors, one has to handle all possible error paths oneself. That's not really a bad thing, as the author points out. One thing I've always felt a sense of dread about in C# and Java (last Java I did was back in 2001) is that I never truly knew what errors were lurking with their exception handling. It would be really nice if all error possibilities were listed in the documentation so I could pick precisely what to handle.
- jd 18y agoIt's even worse. For top level functions it can throw the UNION of the exceptions thrown by the functions it calls. Therefore, by changing a low level function's exception signature the exception signature of all higher level functions changes too. That always worried me, but it's doesn't seem to be a big deal in practice.
- nostrademons 18y agoIt's a huge deal with checked exceptions in Java. You have to change the signature of every caller, and so on, when you change a low-level function. Abstraction leakage galore; it's why many libraries just throw a single generic exception type whenever anything goes wrong (which defeats the purpose of exceptions) or subclass RuntimeException (which defeats the purpose of checked exceptions). Unchecked exceptions don't seem to be a problem, because oftentimes you don't care what specifically went wrong, you just need to know that something went wrong and abort appropriately.
- jwilliams 18y ago> It would be really nice if all error possibilities were listed in the documentation so I could pick precisely what to handle. Yeah, that's a problem - it's a problem of style and implementation rather than a "Java" problem... but it's a problem none-the-less. Many exceptions in Java are simply == "it didn't work". Often there is no distinguishing between: (a) didn't work because you supplied something invalid (e.g. Integer.parseInt("notanint") and (b) it didn't work, but you can probably recover if you want and (c) it didn't work, there is nothing you can do about it.
- jd 18y agoPeople often write in higher level languages because they want lots of bad code fast. Almost all business applications are CRUD apps (create/retrieve/update/destroy) with some business logic, and they're generally written in C#. The app may crash when you click the wrong button, but the app is cheap to develop and the programmers are easily replaceable. Of course I'm generalizing, and a lot of C# programmers write great and reliable code. The point is that they often don't -need- to write great code, because lousy code is good enough for all business purposes (except when the software is your product, which is rarely the case). The second issue is that people who choose C for their projects tend to (a) understand low-level concepts, (b) care about speed / memory usage / reliability / dependencies, (c) don't consider development time that important. That you create more reliable software that way is obvious - the same programmers would create reliable software in C# (or similar language). But there aren't many situations in which development speed doesn't matter, speed and memory usage don't matter, dependencies don't matter but, for some reason, reliability is very important.
- scott_s 18y agoI come from the systems research world. I've seen people choose C not for the reasons you mention, but just because it's what they know best. This is not always the best thing to do. One instance is a colleague who needed to do data post-processing, and just did it in C because it was most familiar. A language like Perl or Python would have been a better choice, and saved him time in the long run, since string manipulation in C is tedious and error prone.
- bsaunder 18y agoYour colleague may be interested in pcre (perl compatible regular expressions) see http://www.pcre.org http://www.pcre.org But I agree, C isn't the best choice for that task.
- lhorn 18y agoI am not sure why are you bringing enterprise software into the discussion. Despite being the most popular form of employment for "programmers" it has always been the absolutely lowest form of life in a software ecosystem and, therefore, should be ignored and left out of any intelligent discussion about programming. In the 90s it was Visual Basic, now it's Java and C#, but it has absolutely nothing to do with what most of us consider to be discussion worthy.
- jodrellblank 18y agoConsidering it's the first (alpha) release of the first significant C program he's written which is for a new (and therefore little used) programming language, and the page of Converge tools has no mention of testing tools, and the page itself has error messages on it, the claim that it's "not riddled with bugs" seems a touch, erm, cheerful.
- alecco 18y agoPrograms compiled from C don't change much when run, while dynamic applications vary significantly on every run due to the large environment (e.g. GC.) This is a bliss for debugging. I've never seen anything like gdb on any other language. You can backtrack, set and change things, watch for expressions, and even run one line of compiled code at a time to see what's breaking. Also C programs usually are made to run many times and stay alive, so the attitude of the developer tends to be more careful.
- alecco 18y agoThe problem with C is they left too many things completely on the wild. Strings and memory management are all laissez-faire and everybody does whatever they think is right. A typical performance issue is calling malloc/free all the time, it can be avoided but there are no standard ways. I hope some great features of C++ get some day backported to C. But don't bet money on that :(
- liuliu 18y agoC believes that programmer doesn't make mistakes. About memory management, I don't think it should be a language feature because there are some good implemented libraries out there.
- alecco 18y agoLike? (No sarcasm.) I tend to see all major projects end up doing things their way: OpenSSL, Apache HTTPd, GCC, Linux, BSD, for example.
- scott_s 18y agoI used to do research with multithreaded memory allocators. The best of class is, I think, TCMalloc, which is a part of Google's perftools: http://code.google.com/p/google-perftools/ http://code.google.com/p/google-perftools/. Hoard is also suitable for use in real applications: http://www.hoard.org/ http://www.hoard.org/ Then there's my work, which is frankly not suitable for using in a real application since it's tested as heavily as the other two I mentioned: http://people.cs.vt.edu/~scschnei/streamflow/ http://people.cs.vt.edu/~scschnei/streamflow/
- alecco 18y agoCool! I didn't know Google did a plain C one (thought those were C++) and yours looks interesting. But my original point was there is no stantard way. It should be part of CXX, libc, or POSIX, IMHO. C is just too atomized.
- mleonhard 18y ago> What I realised is that neither exception-based approach is appropriate when one wishes to make software as robust as possible. The author misses the main point of exceptions: they let us separate data processing code from error handling code. This is why we are more productive in languages that have exceptions and our code is easier to maintain. C requires us to handle errors throughout our program, tightly coupling the data processing code with the error handling code. With apologies to Mr. Spencer, I would declare that: "Those who don't code in languages that lack exceptions are doomed to reimplement them, poorly."
- demallien 18y agoI have to disagree. From my perspective it is an error to think that error handling can be handled separately from the main code path. The classic example that I use to demonstrate this is to point to all of those tedious discussions that programmers have as to whether something is an exception or just normal behaviour of a system. Straight away for me that's a red flag that a non-real distinction is being made. I honestly don't see any advantage to exceptions over C-style return codes, with one important ...euh... exception: the boiler plate for exception handlers can be well handled by modern IDEs. All the other supposed advantages seem to be just waffling to me. Take the whole 'Oh, but exceptions make handling errors the default!' kind of argument (several examples on this thread already). Yes, sure, you do have to write exception handlers for all errors in a language such as Java. But my experience is that if I'm writing use-once-and-throw-away in C, I'll just not use the return code. In Java I'll just stick a whopping great big try/catch around the whole app, and be done with it. If I'm trying to write stable code that's going to be around for a while in C, I check the error codes returned by a function every single time, which gives me around about as much work as when I am using Java, and actually handling different exceptions correctly. All of which means, for me at least, that exceptions don't add anything to a language, but they do make the language just a little bit harder to learn (remembering exactly how any given language has implemented exceptions, and which resources can still be safely used when is a pain, as each language tends to have subtle differences that can bite you).
- wheels 18y agoThis misses two points I consider important: - A whole lot of C code is old. Old, actively maintained code, tends to be more reliable than new code. - C tends to be employed in relatively predictable sub-systems. Something like a device driver has a relatively predictable set of states relative to a GUI application. There aren't that many paths. C code for GUIs, in my experience, tends to be at least a buggy as code in other languages.
- VinzO 18y agoYou seem to forget that in embedded systems, C is still the most used language. So most of the new systems developed these days have new C code. And embedded systems are everywhere. Also a big part of embedded systems have to be reliable for years in hostile environement without external interventions. so I wouldn't say that this is easily predictable subsystems.
- wheels 18y agoI'll qualify this with the fact that I've never done any embedded programming, but my feeling is, that relative to a GUI application, that things are still fairly predictable. Unpredictability seems to arise mostly from interuptability. GUI applications have the ability to jump from a large bank of states to another large bank of states with almost endless permutations and maintain several, not one state simultaneously. At least in systems programming and I'd assume embedded programming (and like I said, I'm guessing -- I'd be interested in your take there) the chain of interruptability is much more structured and the logic flow inside of components is much more atomic.
- demallien 18y agoHmmm, I've worked in the embedded world for the last 10 years, and I can tell you that the bulk of embedded code is far from hardened. It appalls me the number of times that mobile phones, set top boxes, etc crash. Typically, reliability is low because it is hard to run decent unit tests in an embedded environment. You're obliged to write a type of simulator that you can run on a PC, but the simulator never has the exact same behaviour as the target system, so you can never truly verify correct code behaviour.
- msluyter 18y agoAfter having read a lot of these types of language debates, the only conclusions I can safely arrive at are 1) that every language has its advocates and 2) some people are highly productive in their preferred language, much more so than the average programmer would be in their preferred language. But the real question, imho, is how do average programmers compare? How will the same app written in C++ and Java compare, when written by non-superstars? The question merits some empirical research.
- axod 18y agoOn the downside, most bugs ever discovered are most likely in c code. Especially memory leaks, buffer overflows etc.
- jmah 18y agoSee also: No Silver Bullet
- lst 18y agoDo they? Mature C code: yes. Modern one: no, many times affected by using very bad algorithms...
- maurycy 18y agoAs a person who spent few years doing C programming, and then got trapped into the Ruby realm, I must say that the main difference is focus. If you have to focus about memory management, strings, you think much more about the code you write. Theoretically, high level programming should enable you to focus on the abstractions much more, but somehow it doesn't work this way.