Live data from Hacker News

How can C Programs be so Reliable? (2008)

tratt.net

161–170 of 230 posts

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

#161

The author makes a good point about the discipline imposed by not having exceptions. Programmers tend to write code in one of two modes: the quick-and-dirty mode where you consistently don't check return values, or the built-to-last mode where you consistently do. If you start in the first mode and have to fix a bug, you often have to add error checking all up and down the call chain from where it occurs to where it'…

I think you may have hit on something subtle. To me, it's better to have two completely distinct modes: I'm either dealing with errors or not. Then if an error occurs, I can expect one of two things -- either it should be handled or it should crash. I've noticed in some exception-using code I've encountered that a lot of times, error conditions are tested and then not really handled -- they're either silently ignored, or dealt with by some half-measure. To me, silent failures are much more insidious than crashes. This halfway style of error handling is the worst of all worlds, IMO, and it might be encouraged by exceptions. Java forces you to acknowledge errors that you have no need to, and probably pushes you toward this gray area when you're not sure how to handle things yet. Then, later, it's totally non-obvious what's truly handled and what isn't. It also clutters up your code. Clarity is often more beneficial than reflexive error-checking.

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

#162

Earlier quoted context omitted.

> 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 a…

You state a lot of half truths ("you'll never be able to catch all exceptions"), don't justify the assumptions and didn't handle the core of my argument (abstraction leakage). In the hurry to insult me, did you actually read my argument?

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

#163

Earlier quoted context omitted.

> 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…

"A well designed abstraction will catch and wrap the exception into something that makes sense at that level of abstraction, never leaking implementation details." This makes recovering from the error rather difficult. If the problem is that a disk is full, I need to do something about the disk being full (maybe ask the user to delete some files). If the problem is that the disk was disconnected, I need to do somethi…

I'm not familiar with the concept of restarts. I do concede, though, that wrapping extensions limits recovery. Either the library can recover on its own, or it can't fulfill its designed service.

On the other hand, the most revered architectures we have aren't leaky. You don't see network stack code trying to recover from Ethernet collisions at the IP level, or app logic trying to salvage an SQL transaction when a restriction has been tripped. The price for non leaky abstractions is not zero, but the gains are also definitely not zero.

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

#164
post #67

Earlier quoted context omitted.

What if you application is the fly-by-wire for an airliner? Can you imagine that there might be better options than just calling abort(3)?

Embedded systems like this do not use dynamic memory allocation.

I knew that people would nit-pick on this and not address the actual issue. Next time, I should try harder to come up with a better example.

My point is: sometimes it is worth trying to recover when malloc() fails.

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

#165
post #141
post #49

There are multiple factors that contribute, but here's a few that I haven't (to the best of my recollection) seen mentioned so far: tooling (crucial), "do the simplest thing that could possibly work" attitude brought about by (lack of) a built-in collections library and simple syntax, and lack of rapidly changing requirements during development. Tools for C are powerful, mature, and available on near every platform.…

> Tools for C are powerful, mature, and available on near every platform. Every single C programmer that I know uses multiple memory debuggers, for example: e.g., valgrind for leak checks and more on smaller code segments, LLVM address sanitizer on unit test runs, jemalloc or tcmalloc/google-perftools, and more. This is exactly the point. C developers need to make use of external tools to improve the language's flank…

Type safety is a lie. Everything is a piece of memory. C does not hide this from you, it's a design feature and not a bug.

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

#166
post #49

There are multiple factors that contribute, but here's a few that I haven't (to the best of my recollection) seen mentioned so far: tooling (crucial), "do the simplest thing that could possibly work" attitude brought about by (lack of) a built-in collections library and simple syntax, and lack of rapidly changing requirements during development. Tools for C are powerful, mature, and available on near every platform.…

>Tools for C are powerful, mature, and available on near every platform. If only this was true for embedded platforms! I have no tools at my disposal aside from a vendor supplied debugger. No pre-existing test infrastructure exists, what we have we have had to build up ourselves. One of C's last remaining strongholds is in embedded, and the tooling in this world is atrocious! (Though some of the instruction and perfo…

Last remaining strongholds?

C is everywhere.

I'm in a similar situation with embedded stuff at the moment. We're basically stuck with debug-by-printf over a serial port, far from ideal!

I have worked on other embedded projects that were better, for instance you can make a gdb server that operates over serial or tcp/ip and allows you to use gdb (or even graphical debuggers like ddd) on a host machine to step through code as it runs on your device. This does rely on there being resources available to do this though I guess...

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

#167
post #150

Earlier quoted context omitted.

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…

> 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.:-

Sorry pal, you're doing it wrong. At least, this is not the way I've done it and seen it done in large C code bases.

You're supposed to have only one return statement, and one block that frees everything. For example if you initialize `foo`, `bar`, and `qux` to `NULL`. Then testing these pointers for `NULL` de facto tells you how far you got in the function, and which buffers need to be freed. Just before your one single return statement (can't emphasize this enough) you call `free` on all of them regardless of success or failure. It's much more composeable than what you described - allocations for `foo`, `bar`, `qux` can fail, the ones that will be not yet allocated at that point in time will be `NULL`, and `free(NULL)` is a harmless no-op.

None of this business of "I've got to return now, let's see, how many of these buffers do I need to release at this point in time?", with varying amounts of the same free statement appearing redundantly. Write the cleanup block once when the pointers are about to fall out of scope, have it able to run in both success and failure cases and be done with it. Think of it as a more manual RAII if you like.

As for what to replace those early `return`s with, the two common schools are `goto` into the cleanup block, or repeatedly checking some kind of failure status variable before performing new actions.

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

#168
post #165
post #141

Earlier quoted context omitted.

> Tools for C are powerful, mature, and available on near every platform. Every single C programmer that I know uses multiple memory debuggers, for example: e.g., valgrind for leak checks and more on smaller code segments, LLVM address sanitizer on unit test runs, jemalloc or tcmalloc/google-perftools, and more. This is exactly the point. C developers need to make use of external tools to improve the language's flank…

Type safety is a lie. Everything is a piece of memory. C does not hide this from you, it's a design feature and not a bug.

> Type safety is a lie. Everything is a piece of memory.

So speaks an Assembly developer

> C does not hide this from you, it's a design feature and not a bug.

I guess the design goal was to make security exploits as easy as taking caddies from children.

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

#169
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…

Don't you find performance suffers? AIUI this approach means you can only handle as many concurrent requests as you have processes, and the OS scheduler has less information to work with than if you were using threads.

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

#170
post #104

Earlier quoted context omitted.

If your server's running Limux, it's going to kill your process with no questions asked if you run out of memory. You're better off practicing crash-only error recovery and having robust clients that can handle a reconnect. HTTP is stateless already, so crash and restart all you want!

The OOM killer is more likely to kill some other process and trash your server. Thankfully that sort of behavior has been vastly reduced since the thing was introduced, but disabling overcommit for high-reliability applications is still a reasonable course of action.

The OOM killer might eventually kill something, after it thrashes the system for a few hours.

I had a server last week in which swap hadn't been configured. A compilation job took all memory and the OOM started thrashing. Thankfully there's always one SSH session open but I couldn't kill anything, sync or shutdown; fork failed with insufficient memory.

Left if thrashing overnight and had to power-kill it next day.

Post reply on HN