Live data from Hacker News

How can C Programs be so Reliable? (2008)

tratt.net

151–160 of 230 posts

Re: How can C Programs be so Reliable? (2008)

#151
I've never in my life written as careful and premeditated code as when I was writing C on my Amiga when I was young.

One miss and you would tilt the whole system.

There was no memory protection and everything was system wide. If stuff failed, you had to deal with it because if you didn't you probably left something unlocked or left a resource open which hogged it and prevented other applications from using it. And anything left open would leak memory as well. And there was only system memory so other programs would eventually fail memory allocations because your program didn't close some handle somewhere.

Surprisingly, most of the programs I wrote worked correctly early from the beginning. There was no "I'll just code this thing up and fix bugs later" phase because there were absolutely no safety nets. And fixing bugs in retrospect usually meant rebooting your machine, starting up the editor and compilers again for another round.

I'm pretty pedantic on Unix too but I'm still a lazy bastard compared to those times. And my C programs on Unix do take a whole lot more iterations to "stabilize" than those I wrote on my Amiga.

In some perverse masochistic way I somehow miss that.

Re: How can C Programs be so Reliable? (2008)

#152
Kent Pitman's idea of "languages as political parties" [1] seems to me to offer the best perspective on the surprising reliability of C. The core community shaping C over the years has been:

1. Unix-centric, so the community has a coherent view on the preferred sort of semantics offered to users of code;

2. Applies Kernighan--Ritchie--Pike coding style that gives a clear "code smell" [2] for C code; and

3. Is the natural home of "Worse is Better", which favours source-code simplicity over specification simplicity.

For example, the "C party" favours procedures "succeeding" with non-zero exit codes on failure where other language parties have language constructs to represent failure. This can be better from the point of view of source-code simplicity, both in code and compiler, than exceptions, and worse in terms of semantics. The LK coding style, among many other C coding styles, prefers the use of goto (or sometimes setjmp/longjmp) to handle exit status [3], which is frowned upon in nearly all non-C programming language communities; the restriction of goto/longjmp to handling errors in this way avoids the problems Dijkstra pointed out [4,5], and correct usage of goto/longjmp in properly checking exit status has good code smell if generally accepted coding conventions for the use of goto to handle failure are followed.

There's a nice fragment of code illustrating the idea on SO [6].

[1]: Pitman 1994, Lambda: The ultimate political party, http://www.nhplace.com/kent/PS/Lambda.html

[2]: https://en.wikipedia.org/wiki/Code_smell

[3]: From http://www.tux.org/lkml/: So now we come to the suggestion for replacing the goto's with C exception handlers. There are two main problems with this. The first is that C exceptions, like any other powerful abstraction, hide the costs of what is being done. They may save lines of source code, but can easily generate much more object code. Object code size is the true measure of bloat. A second problem is the difficulty in implementing C exceptions in kernel-space. This is convered in more detail below.

[4]: Dijkstra 1968, A case against the goto statement, http://www.cs.utexas.edu/users/EWD/transcriptions/EWD02xx/EW...

[5]: Knuth, Structured programming with go-to statements, http://pic.plover.com/knuth-GOTO.pdf

[6]: http://stackoverflow.com/a/741517/222815

Re: How can C Programs be so Reliable? (2008)

#153
post #148

Earlier quoted context omitted.

Most computing environments don't have a user to speak of, and the correct response of an application to an out of memory error could range from doing nothing to sounding an alarm. As a user, I find it incredibly frustrating when my old but indispensable music software runs out of address space (I have plenty of RAM) and, instead of canceling the current operation (e.g. processing some large segment of audio), just d…

I think most computing environments do have a user of, if you consider a "user" to be something that be notified and can act on such notifications (e.g. to close applications). Your music software's problem seems to be a bad algorithm - not that it doesn't check the return values of the `*alloc` functions.Aas you say, it should be able to process the audio in constant space. While I assume that I can always acquire m…

> if you have 1GB of on-RAM memory already allocated, wouldn't it only be new processes that are slow?

No - the memory sub system, will swap out pages based on usage and some other parameters. A new application would most likely result in already running applications least used pages being swapped out.

Re: How can C Programs be so Reliable? (2008)

#154
post #94

Earlier quoted context omitted.

I grew up using the Amiga, when having memory allocation fail was routine (a standard Amiga 500 for example, came with 512KB RAM, and was rarely expanded to more than 1MB, so you would run out of memory). What you do when malloc() fails depends entirely on your application: If a desktop application on the Amiga would shutdown just because a memory allocation failed, nobody would use it. The expection was you'd gracef…

Isn't fork the real offender, which requires Linux to overcommit by default? Disabling swap shouldn't affect that, right? Just makes your problem happen later, in a somewhat non-deterministic way. Without fork, what reason do you not disable swap? I can only think of an anonymous mmap where you want to use the OS VM as a cache system. But that's solved easily enough by providing a backing file, isn't it?

> Isn't fork the real offender, which requires Linux to overcommit by default?

fork() != Linux.

Each UNIX system does it on its own way.

Re: How can C Programs be so Reliable? (2008)

#156
post #63

Popular C software is so reliable because of enormous amount of effort spent developing, testing, and polishing it over many years - not because error handling in C is superior to that in higher-level languages. If your python script sticks around for 30 years constantly being used by millions of people - I bet it will be rock solid as well. All other things [1] being equal [2], a program written in a higher-level la…

Wow, the first comment is some posturing, opinionated douche spewing dogma and self-importance. On HN no less. How novel.

Re: How can C Programs be so Reliable? (2008)

#157
post #150
post #19

Earlier quoted context omitted.

It depends on the problem you're trying to solve, I think. Let's consider a command line application that fetches a URL, like wget. Without exceptions, you would check the return code of all the system functions you call (dns, sockets, etc), and if any of them fail you can't really recover. You just write out an error message and exit. With exceptions, you could wrap the whole thing and if any system function throws…

C libraries don't just exit. A C executable could in some circumstances just fprintf() to stderr and exit(1) but usually even that requires sane deinitialization so before exiting you will effectively end up back in main() from the lower levels. C libraries generally handle their errors on some level, possibly jumping directly out from the lowest levels and then doing deinit before passing back an error code to the c…

Exactly. Writing libraries in C means you have to be much more strict than simple throwaway single execution binaries.

What our company has ended up doing is wrappering most low level functions with the common code around it. For example, fopen() may fail and return EINTR if the process received a signal just as it was opening a file (we use signals to tell processes to reread their config so this does happen albeit rarely) so the wrapper calls fopen() in a do/while loop that repeats on EINTR (and a few others). It saves you from writing the same 50 odd lines each time you want to open a simply open a file.

In an exception led world you'd still have to do the same thing, but with different syntactic sugar. You can't just fail and bubble up the exception without dealing with the few exceptions you must deal with and repeat, and you can't just repeat on every exception as most will just fail again and again. You end up writing code to do the same thing just in different styles.

Checking the return values of every single function call, and dealing with it, can make the code verbose (one line of code and then 10+ lines dealing with errors) but it is worth it in the end for bullet proof programs. Also code where you have to undo all of the work already done in the function up until that point, which can get a little tedious, i.e.:-

    APP_RET makefoo( char *fname )
    {
      char *foo,*bar;
      thingy_t *qux;
      size_t len;
    
      ASSERT( fname );
    
      len = strlen( fname );
    
      foo = malloc( len+1 );
      if( foo == NULL ) {
         return( ENOMEM );
      }
      bar = malloc( (2*len)+1 );
      if( bar == NULL ) {
         free( foo );
         return( ENOMEM );
      }
      qux = malloc( sizeof( thingy_t ) );
      if( qux == NULL ) {
         free( foo );
         free( bar );
         return( ENOMEM );
      }
      ...
      return( APP_OK );
    }
It's the job of anything calling makefoo() to deal with the various errors it can return, but the idea is to return the minimum number of unique error codes as necessary to avoid proliferation of error codes to the higher and higher functions. Many calling functions will only really care about success or failure, and will just use the return code to log out the reason for the failure.

The wrappers help deal with many situations; did fwrite() write all of the bytes we wanted or just some of them? Well, our wrapper around fwrite will handle short writes and repeat the call depending on the result of ferror().

Re: How can C Programs be so Reliable? (2008)

#158
post #105

Earlier quoted context omitted.

Are the servers you're building serving concurrent clients? An exception could take out multiple in-flight requests. (not against your idea, just curious how you handle it)

One advantage of forking servers; kill and reboot the parent, and you don't loose in-flight connections. That said, I do this as well, even the best behaved daemon can get... funky... after a few months. Planned outages for a daemon restart are ok in my experience, particularly if you can fail over to other nodes as part of a rolling restart. Of course, this refers to planned restarts, though forking servers helps wi…

This is 2013 calling. Init daemons now handle all this for you. Your silly experience building servers out of matchsticks is hereby considered irrelevant.

Re: How can C Programs be so Reliable? (2008)

#159
post #21

Earlier quoted context omitted.

Your experience with languages with exceptions seem to come from people who misuse them. Randomly placing catch clauses around in the code is not good practice, even if perhaps a majority of all programmers in safe languages code that way. That causes latent bugs that are incredibly hard to debug. The trick is to almost never ever catch exceptions. For example, in his post he describes a bug caused by accessing beyon…

> The trick is to almost never ever catch exceptions. I strongly disagree. Not catching exceptions leaks abstraction layers. If I have a Prefs::save() method, I don't want it throwing a DiskFullException when the Prefs class is an abstraction of a preferences datastore. I don't care what is the final store, as long as it fits the abstraction. A well designed abstraction will catch and wrap the exception into somethin…

I don't think you understand proper exception handling. Catching and wrapping DiskFullException is pretty pointless because what are you going to do about it? Nothing. It's nonsensical for a preferences class to deal with that situation. Instead let it bubble up and so that the caller has the option of handling it, for example by showing a dialog "Delete temporary files and try again?"

You'll never be able to catch all exception. In addition to your DiskFullException, you have PermissionDeniedException, NFSException, NullPointerException, InvalidFilenameException, PathToLongException ad infinitum. By trying to be "nice" by trying to wrap all those exception you are actually doing your api users a great disservice.

Re: How can C Programs be so Reliable? (2008)

#160
post #149
post #63

Popular C software is so reliable because of enormous amount of effort spent developing, testing, and polishing it over many years - not because error handling in C is superior to that in higher-level languages. If your python script sticks around for 30 years constantly being used by millions of people - I bet it will be rock solid as well. All other things [1] being equal [2], a program written in a higher-level la…

I disagree with being polished. Newer C programs can be robust as well. In fact, I write C programs both for fun and at work and so do a lot of people I know, and these programs still end up being much more robust than I would even expect myself. And when you run them continuously, you will find some bugs in the program but the next bug will appear much later until you don't remember when was the last time a bug caus…

I worked with PHP for a while and I found it to behave like you describe: I rarely found bugs in code once it reached a stable state. I haven't used Python (I might be the only one), so I can only speculate on whether it's truly more bug-prone than PHP. But one possible reason could be the relatively straightforward behavior of PHP, and C code. (My experience is with non-OO PHP, mainly from hacking Drupal modules, which might make a difference). C and (older) PHP have a small number of data types and few or no hidden behaviors. Neither is OO. I wonder if OO might contribute to long-term instability, because since OO languages are rich in types, they tend to let the language make inferences and allow types to alter the meaning of syntax. For predictable code, you probably want a language that isn't too smart and always gives the same meaning to the same code.
Post reply on HN