
How can C Programs be so Reliable? (2008) - adamnemecek
http://tratt.net/laurie/blog/entries/how_can_c_programs_be_so_reliable
======
notacoward
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's handle, so you _very quickly_ migrate
toward the second mode. By contrast, programmers in exception-based languages
often get to a state where they catch some exceptions in some places where
their tests have found bugs, but large sections remain in quick-and-dirty
mode. The code tends to keep running longer after the actual error, leaving
latent bugs that are harder to debug when they actually surface. The kinds of
errors that are characteristic to C, such as segfaults from bad pointer
handling, have obvious effects and more often have obvious causes as well.

I'm not totally anti-exception, and I think manual memory management is only
appropriate in an increasingly small set of situations. I'd rather program in
Python than C when I have the choice, but I acknowledge that the Python code I
write is probably a bit less robust than the C code I write. I'm probably not
alone. Theory aside, _in practice_ the greater prevalence of sloppy error
handling in languages other than C seems to leave the field pretty level when
it comes to robustness.

~~~
RyanZAG
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 an exception,
you print out the exception and exit. So it's much the same in this case.

Now put the url fetch code into a larger application. With exceptions this is
pretty easy - you can catch any exception thrown by your url fetch code and
then retry it a few times until it succeeds, or you inform the user of the
app. Without exceptions this is tougher as you need to unroll everything
manually in order to retry.

Finally we have something complicated like running a simulation. If something
gives an error, we probably don't want to stop the whole simulation. We'd want
to check the specific error result and then fix the simulation and continue.
In this case exceptions would add a lot of boiler plate code and would need to
be handled just as carefully as error results (eg, wrapping each call in its
own try and handling each error).

So it's pretty clear that _it depends_ is very applicable to when you'd rather
use exceptions or error results. In general I think exceptions are better as
long as they're only used for actual error cases as it gives the user of the
functions more control over where and at what level to handle exceptions.

~~~
nightski
But what if the exception was in your code and not the system call? This is
incredibly dangerous.

Monads are perfect for this. Really combines the best of both worlds without
all the unspecified/undefined behavior.

~~~
memracom
You could always write Monads in C... [http://blog.sigfpe.com/2007/02/monads-
in-c-pt-ii.html](http://blog.sigfpe.com/2007/02/monads-in-c-pt-ii.html)

------
azov
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 language will likely be
more reliable - simply because it will likely be shorter. C has many
properties conductive to great longevity of software written in it, but error
handling is not one of them.

Now, to the rant about not knowing the exact number of exceptions a function
can throw. Most sane languages [3] give you exception hierarchy. You know that
every function can throw an Exception (or Throwable, or whatever the root of
all things evil is called in your language), and you either handle it or let
in propagate. The important bit is _you catch exceptions based on what you can
handle in a current context, not on what the functions you call can throw_
[4]. As long as you realize that every call can potentially throw, it is not
important what exactly is thrown - letting you abstract away the exact nature
of an error is a feature, not a bug, that's what exceptions are there for!
Let's say some function deep below changed and now throws a new exception
type. So what? It didn't change what you can handle, so there's no reason to
modify calling function - just let the exception propagate. What if nothing in
your program can handle that exception? You let it crash (hopefully to the
debugger, and no, it's not ironic that I suggest it in a topic about reliable
programs - crashing is a reliability feature invented to expose problems that
would be hidden and hard to debug otherwise). Of course you may also put a few
catch-all clauses in a very limited number of strategic places.

If you approach exceptions this way they are vastly superior to error codes.

[1] Scope of the problem, amount of effort spent on development, programmer
skills

[2] Or at least on the same order of magnitude

[3] With a notable exception of C++ - I'm not sure what they were smoking when
they decided that throwing arbitrary values is a good idea

[4] And this, by the way, is what's wrong with checked exceptions in Java

PS. Didn't mean that having no exceptions is insane - but that if a language
has exceptions, the sane thing to do is to organize them in a hierarchy with a
single root.

~~~
fleitz
Yes!

Most software I've seen can be dramatically improved by removing exception
handlers. (And then fixing the underlying problems).

On the server side I prefer one exception handler which is the OS, it will
kill your process, release and free all memory, file handlers, etc. Then I
wrap it in a bash script / other monitoring tool, and have it immediately
restart whenever it fails.

As for the server itself I'll also add an automatic reboot to the server to
ensure nothing out of the ordinary persists for very long (like that PATH
modification that someone forgot to add in the appropriate place to get set on
restart). If it's virtualized I prefer to destroy the entire server if
possible.

~~~
dtwwtd
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)

~~~
falcolas
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 with
unplanned exceptions as well.

~~~
lmm
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.

~~~
falcolas
Not typically: using forking daemons does not mean that you can't also use
threads. The ideal model probably uses both - so they can be stuck to a
processor, but still use threads per request. It's nice to have a library to
abstract the implementation details away for you, but not necessary.

~~~
lmm
Right, but doesn't the error handling approach you describe mean allowing a
whole process to fail whenever an error condition occurs, which would cause
any requests that were being handled by other threads of that process to fail
even though they were perfectly valid?

~~~
fleitz
Can you really tell me that you know after an exception that the other threads
are really in a well-defined state let alone 'perfectly valid'?

Look at something like ZeroMQ that is being rewritten specifically to avoid
the non-determinism inherent in throwing an exception.

Once you're using threads it's pretty much anyone's guess as to what state the
system is in at any point, add exceptions and it just gets worse.

~~~
lmm
An exception in one thread shouldn't affect others - why would it?

I agree that unstructured use of threading primitives leaves you with an
unpredictable system, but it's possible to build safer, higher-level
abstractions and use those.

~~~
fleitz
Because fundamentally the only reason to use threads rather than fork is to
share memory. If an exception leaves shared memory in an undefined state then
all threads that share that memory are in undefined states.

~~~
lmm
True, but why would an exception ever leave memory in an undefined state? I
can imagine it corrupting a thread's own stack in some languages, but you
wouldn't use a thread's stack for shared memory (at least, not without some
construct that told other threads when it was safe to access it)

------
akkartik
The author's point is weakened by the fact that the Converge VM is now (5
years after this article) written in python:
[https://github.com/ltratt/converge/search?l=c](https://github.com/ltratt/converge/search?l=c)
(as of
[https://github.com/ltratt/converge/tree/08dadda29b/vm](https://github.com/ltratt/converge/tree/08dadda29b/vm)).
Compare
[https://github.com/ltratt/converge/tree/converge-1.x/vm](https://github.com/ltratt/converge/tree/converge-1.x/vm).

 _" there are some obvious reasons as to why it might be so reliable: it's
used by (relatively) large numbers of people, who help shake out bugs; the
software has been developed over a long period of time, so previous
generations bore the brunt of the bugs; and, if we're being brutally honest,
only fairly competent programmers tend to use C in the first place. But still,
the fundamental question remained: why is so much of the software I use in C
so reliable?"_

Why could the fundamental question not be explained by all the points he just
mentioned?

I'm a C programmer, and this guy is hitting all my biases, but this isn't very
well thought-out.

~~~
gabriel
I believe he explains this switch here:
[http://tratt.net/laurie/blog/entries/fast_enough_vms_in_fast...](http://tratt.net/laurie/blog/entries/fast_enough_vms_in_fast_enough_time)

It's a _very_ good read. IIRC, the reasons aren't really rooted in the
language that is used. It's more about the tooling provided by PyPy for
constructing a compiler.

------
peterashford
Speaking as someone who has been a long time coder in C and Java, I think the
point he makes about checking errors is BS. You have to handle exception in
Java and you're perfectly free to consider all the possible failure paths if
you want though typically you'll handle the ones you can sensibly do something
about and ignore the rest (i.e.: log or just fail outright depending on
context). I do NOT miss the C world where you have to handle the results of
every single function call if you want to be safe. The exception handling
approach IMO is generally superior - the normal code executes simply and the
exception handling code gathers all the exception cases together. Much better
than interspersing both and especially better than handling the same error 10
times in a row rather than just doing it once.

That said, I love C. It's a very simple language and that's a virtue.

------
dllthomas
_" [Pointers are] arguably the trickiest concept in low-level languages,
having no simple real-world analogy[.]"_

I'm not sure how "address" is not a good, "simple real-world analogy" for
pointers. It may be that I've just internalized enough of the behavior of
pointers that I'm glossing over important differences, though... I struggled
with pointers long enough ago that I don't remember struggling with pointers.

~~~
breadbox
For a lot of people I think it takes a while to really internalize the
ramification of the fact that you can have a pointer to memory you're not
allowed to use. (That, plus the way they're used in C as a cheap answer to
templates/generics.) I understood that a pointer was an address from the
start, but it was still some time before I figured out how to use them
correctly.

~~~
salgernon
You've never arrived at a physical address to find a shuttered business or
gaping crater? I can understand the syntax of pointers being confusing, but
the concept has distinct real world analogies. (That being said, I find the
syntax of physical addresses in other countries to sometimes be confusing.)

~~~
philwelch
I've definitely never arrived at an address to find a gaping crater myself.

~~~
dllthomas
I have - after demolition following an earthquake. I wasn't lead there _by_
the address, though - just passing by.

------
lmm
Walking across a tightrope is a lot easier than you would think. When you're
walking on a tightrope you really concentrate on your walking; it's stressful,
but not actually hard. Still, I don't think many people would choose to walk
across a tightrope when an easier option was available.

Exceptions, meanwhile, allow a counterintuitive but effective strategy for
greater reliability. My boss likes to say that the cloud is great because
machines fail more often - the point being that rather than the impossible
task of making individual machines failure-proof, you instead design systems
that can handle the failure of any given machine. Often the best way to make a
resilient system is to break the problem into quite coarse-grained blocks, and
then define appropriate responses (such as failing a single message and
processing others, or retrying the whole block) whenever a given block fails.

------
plg
"Once one has understood a concept such as pointers (arguably the trickiest
concept in low-level languages, having no simple real-world analogy) "

huh?

So when you write a letter to someone, you actually attach their house onto
the outside of the envelope? No of course you don't. You write their ADDRESS
on the envelope.

All this lore about how difficult pointers are, is bunk. The syntax of
pointers vs addresses vs dereferenced pointer values, can be difficult at
first ... and can be very difficult if you read and/or write unecessarily
obfuscated code ... but there is no need.

~~~
ProAm
Sometimes I just write the contents of their house on the outside of the
envelope.

You have to admit it is one of the trickier concepts people learn when
programming. Once you understand it, it's definitely a "Duh, how else would it
work?" moment but getting to that point can take some time.

~~~
Scramblejams
I agree. The concept may be simple, but when you're just starting out as a C++
newbie and you've got pointers-to-pointers-to-pointers flying all over the
place in this ugly syntax trying to satisfy some contrived homework assignment
on a short deadline, things can get confusing rather quickly.

------
sytelus
C also has "better" I'd argue that average person programming in C is more
matured, and understand nitty gritty of programming, computer science,
performance and how things work at low level far better than, say, Visual
Basic or even Java programmer.

I think we have been putting too much faith in to languages. After certain
level of sophistication, the weight of quality shifts to person programming in
it instead of language itself. However most language designers would tell you
that their holly grail is to shift this threshold so programmers can be as
lower in skills and ignorant as possible while quality of program as high as
possible.

Lot of these things can be also said for JavaScript. I've now coded fair bit
in plain JS constantly avoiding temptation to use some other language that
compiles to JS (and even avoid TypeScript for that matter). Just 100% pure
JavaScript. And I can tell you that my habits and flow is very different when
I code in JS. Back then I though coding in pure JS would blow everything up
just because of lack of types or silly thing like missed semicolons or as
simple stuff like forgetting to pass 10 in parseInt. The fact is that none of
these bothers me at all anymore. I rarely find bugs that can be attributed to
these deficiency in JavaScript. Strongly typed languages now feels like some
kind of over hyped hoax.

------
zvrba
> it's reliable in the sense that it doesn't crash, and also reliable in the
> sense that it handles minor failures gracefully.

This is an incomplete definition of reliability. It seems that the author is
only considering programs which work as advertised when used as intended.

Most software is written optimistically, i.e., the "good path" is tested most.
As soon as you start feeding the program with invalid input, it is no longer
reliable, as witnessed by the myriad of low-level security bugs (stemming from
the nature of C) in virtually every nontrivial C program ever written.

Now, would you rather have your program crash (be "unreliable" by the author's
definition) or end up running arbitrary code injected by an attacker?

(Security exploits are the most prominent example; many programs will do
something weird when fed unexpected input.)

------
eliteraspberrie
There are idioms and methods for writing reliable C programs, which you learn
with experience and reading better code than yours. If you learned languages
in the order higher-to-lower, you eventually understand that you cannot write
a program in C at the same pace as in a higher level language.

What has worked for me is: always have a second terminal open for the manual
pages and the standards; and to regularly read quality C source code.
Opengroup.org has thorough references for the standard C library, including
its relationship to POSIX and the SUS; and the various kernels and core Unix
utilities are always a good read.

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

------
ChuckMcM
It is an interesting observation. I have found that scripted languages are
unreliable as a function of loadable modules (which is to say they depend on
some module that gets loaded, that at some point changed, and then broke the
script). C has that problem too with shared libraries but there are generally
fail stop policies in place (if you can't get the version you were linked
against exit rather than try to valiantly proceed)

What is perhaps most important is that software does not "rot" in the sense of
building materials becoming weaker after exposure to the elements. But it does
fair poorly against changes in things it assumes to be true. Some languages,
like Ada or Mesa, which have very detailed configuration specifications,
achieve high reliability at the cost of high configuration specificity. That
makes them brittle against even modest changes in their environment.

The art is striking the balance. Take something like thttpd, 20 year old code
still compiles, still does exactly what it always did (simple HTTP web server)
very reliable. But the barest minimum of reliance on the ambient environment
it runs in.

~~~
rmrfrmrf
> _I have found that scripted languages are unreliable as a function of
> loadable modules (which is to say they depend on some module that gets
> loaded, that at some point changed, and then broke the script)._

That's why I'm thankful for a lot of these newfangled virtual environments and
machines along with their respective package managers (and why more people
should use them!).

------
drdaeman
C programs are so damn reliable I spent last three weeks mostly trying to
debug others' code (from multiple software projects - I was lucky enough they
decided they'd all start hitting obscure bugs at approximately same time) and
waiting for it to crash again.

Just imagine my feelings when a critical server that ran just fine for 3 years
gets a bit more load and starts getting assertion failures. That was a known
and already fixed bug, but after version bump to next stable those had changed
to SIGSEGVs - and it's not trivial NULL pointer dereferencing but a weird
concurrency issue.

C has neither [relatively] strong guarantees of OCaml and Haskell (so I could
be at least sure there're no trivial bugs with that weird macros doing crazy
pointer juggling), nor dynamic flexibility of Python and Erlang (so I could
hack working runtime live, letting it fail and hot-swapping code at will).

------
macmac
Adjusted for effort I don't know of any evidence that they are...

~~~
dguaraglia
How does effort affect reliability? Even if you consider one as consequence of
the other ("it requires more effort to make a reliable C program than the
equivalent program in $language") that doesn't make the final product any less
reliable.

~~~
Anderkent
It does when you only have a set amount of effort that you can dedicate on a
project. Which is usually the case.

~~~
wonderzombie
I am not sure that's a useful criterion in practice, though. People are fond
of saying that languages will not make you orders of magnitude more
productive, and I think the corollary is that on average they will not make
you orders of magnitude _less_ productive either. (Controlling of course for
characteristics of problems and such.)

The problem is that there's not really a great way to reason about this,
either. If you're talking about how long it takes to write a program in C vs
Java, how do you measure time spent writing code vs debugging vs bug fixing,
et al?

All in all it's certainly possible it takes longer to write code in C, but for
all anyone knows, writing more reliable code up front means fewer bugs later
on. Code which is more performant by default may lead to fewer performance
regressions when someone induces pathological GC behavior. Contrariwise,
there's stuff like memory corruption as the OP describes, which is probably
way more difficult in a native environment instead of a hosted one.

It's hard not to reach the conclusion that we're all just talking out of our
hats anyway (myself included).

------
rzimmerman
Also, compile-time checks and the tendency of lower-level languages to fail
catastrophically rather than subtly or silently plays a part. The author also
has a lot of experience to draw on, which might just make his work more solid
and error free in the first place.

------
discreteevent
Some good points in the essay but I have to admit that for me the reason I
like C is that it's the first language I ever really did any serious
programming in and the first language that made me think hard. Because of that
it just feels like home when I come back to it and I can never really evaluate
it without that bias. Its the simplest model of the machine that you can get
without going down to assembly. Its the same reason I like riding and fixing
bicycles. In both cases sparse resources have kept the design to the bare
minimum.

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

"Do the simplest thing that could possibly work" is somewhat close to the
author's point: simply put, while there are widely used cross-platform hash
tables in C, it takes more effort to use one; hence, they aren't used e.g.,
when N is known to be 100 (or if maximum size is bound, there's likewise no
pressure to use the built in hash table/red-black tree and one can use perfect
hashing instead...). In another big distinction I see between code I wrote in
C vs. higher-level languages is that one can simply use a stack allocated
variable length array -- or (if random access doesn't happen) a linked list in
place of a heap allocated resizable array.

These exammples, I think demonstrated how C discourages you from interpreting
"the simplest thing that could possible work" as "the thing that is fastest to
code"; that's not to say development speed is not an issue -- it's a _huge_
issue! -- but it often leads to code where the unneeded complexity (such as
using a red-black tree or a skip list instead of a sorted stack allocated
array!) is hidden from the programmer but is nonetheless present.

Onto the last point, I agree sentiment expressed by others questions the
premise. I think the examples of C programmers where we explicitly know of
something as being a C program are heavily biased towards well known and open
source software; open source software, language runtimes, operating systems,
browsers (usually in a clean and limited subset of C++ like, e.g., Chromium
and likely Firefox as well -- I only mention Chromium as I've re-used base
libraries from its codebase), and so on. We don't, however, think of parking
meters, vending machines, and many other usually embedded (but not life
critical) systems that -- unlike open source systems software -- are built
from unclear, confused, and ever changing business requirements ("well, this
city introduced a new red-zone, so now we have to make sure to charge extra
0.33 cents between hours 8 and 13 except on all Holidays, but not July 4th").

Yet when people bring up Java, it's easy to forget about gmail or Minecraft,
but instead to think of the insurance claim management system that you had to
use after your rental car got rear-ended near 4th and Harrison, that crashes
with a user-visible stack trace when you click the wrong button and (even in
2013) doesn't use Ajax and requires opening a new page (slowly, as they don't
use Java's built in thread-pools but spawn new threads, because the system has
to run at a customer site that uses Enron's custom implementation of Java 1.3
on an AS/400) to make an changes.

~~~
com2kid
>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 performance profiling
tools are beyond amazing!)

Rigorous coding standards and a heavy reliance upon code reviews keep a code
base clean and sane, sort of like on any other project written in any other
language!

~~~
Nursie
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...

------
Nursie
Because those of us who have been doing it for 15 years or more know we're
tangling with tigers and have figured out how to use their strength against
them...

------
wonderzombie
These pieces are the reason why I still read HN today. Thanks for submitting
it and thanks for upvoting it, everyone.

------
codex
I should note that less experienced programmers tend to start with higher
level languages, as a rule, so the average experience of a C programmer is
likely higher than that of the HLL programmer. This general experience level
alone gives the resulting code a reliability boost.

~~~
jnbiche
With notable exceptions, I think this is true. I've been coding seriously for
2 years and am starting to move down the stack. I see others in my cohort
doing the same thing.

------
wfunction
> "Surprisingly few people can really program in C and yet many of us have
> quite strong opinions about it."

Maybe they have strong opinions precisely because they don't find C to be a
language which is easy to "really" program in?

------
puppetmaster3
C programmers have better culture. Than say Java programmers. If I was a
swordsman and had a rusty sword and you had a new one, the sword does not
decide the fight.

~~~
burstmode
Yes, mainly because new sword has an auto-grinder welded to it that might
start running at any given moment, even if you are in a deathly fight :-)

------
chalst
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](http://www.nhplace.com/kent/PS/Lambda.html)

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

[3]: From [http://www.tux.org/lkml/](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...](http://www.cs.utexas.edu/users/EWD/transcriptions/EWD02xx/EWD215.html)

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

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

------
Davidarch
Good Resource!

------
sillysaurus2
Tarsnap is the classic example of a C program whose reliability almost defies
belief. Its codebase is large; it solves a very hard problem; and it manages
to handle almost every cornercase flawlessly. (As of the latest version, it
seems to handle every cornercase flawlessly, as far as I know.)

But I've always wondered: if Colin were as experienced with Python as he is
with C, would Tarsnap be better served by writing it in Python? It seems like
the codebase would be half as large.

In other words, is C the reason why Tarsnap is good, or is it because of
Colin? Or is it both?

Example: Lisp, along with PG, is the reason why HN is good. Many of HN's
features simply wouldn't be easily accomplished without it, so they wouldn't
ever have been implemented. But I'm not sure the same could be said of
Tarsnap.

 _But let 's be honest - in how many languages can one retrospectively add a
garbage collector?_

Many. Lisp in particular. HN's links like /x?fnid=KxPuWC6qnkl4nxcUTDh4xs are
an example of this.

~~~
mikeash
Wait, HN is good? The community is interesting, but I've never been
particularly impressed by the implementation. In particular, the "feature"
where "More" links and such randomly expire after a while just screams "shoddy
implementation" to me.

~~~
AceJohnny2
The thing is, the "more" links are specific to each user and generated page.
They're not just "page 3 at the time you click on the link", but the actual
next 30 links following the 30 it's previously shown you. As a result, it
needs to store state for each of these links it's created (IIRC, the fnid is a
reference to the closure containing that state). For obvious memory management
purposes, these need to be expired after some time.

I agree that it's an odd implementation choice though. I guess it made sense
when HN was smaller and it could grant more ressources to each user. Nowadays,
Reddit's simpler system (with risks of duplicates or missing rising links as
you go deeper) would probably make more sense.

~~~
mikeash
I understand that it's a tough problem and can totally understand solving it
by just making the links quit working like that. But that sort of thing
doesn't strike me as "good", even if it is highly pragmatic.

------
blahbl4hblahtoo
Since everyone has access to decent c toolchain these days you tend to see
this over and over again. The "wisdom tradition" of UNIX? There is some kind
of need for this kind of counter factual "the ancients had powers we can't
believe" thing... the thing is that languages and frameworks surface sets of
techniques for solving problems that if not present end up needing to be
reinvented... this goes for C and UNIX, mainframes, os400, erlang...whatever.
Maybe I'm just being contrary, but it seems to me that the lessons I learn in
one language apply to all the others at least in some facet.

I worked my way through SICP last year and it really changed the way that I
think about code...while also not making me a lisp nut. It seemed to me that
lisp could have been anything else and the techniques would still be there.

