
Deconstructing K&R C (2010) - experiment0
http://c.learncodethehardway.org/book/krcritique.html
======
brudgers
Shaw mischaracterizes K&R by making anachronistic assumptions about its
intended audience and ignoring the context in which it was written.

When it was written, a beginning C programmer was most likely coming from a
background in assembly and accessing the computer as a professional in the
workplace or a student with substantial privileges. The intended audience was
sitting near the cutting edge and was assumed to be sophisticated. Data
validation could be left as an exercise for the reader in good conscience.

This audience is distinctly different from those who learned programming
typing code from magazines and Shaw's current audience for whom he is
simulating that experience.

Editorially, K&R has chosen to remain a slender tome. It has let others create
fat cookbooks and "for idiots". Forty years on, Shaw criticizes the _Wright
Flyer_ by the criteria of Second World War aviation.

We don't hold K&R on a pedestal because of its pedagogical methods, but
because of the power of the language it describes. _The C Programming
Language_ was a byproduct of creating a language.

Kernigan and Ritchie were programming. Their book is properly judged by
different standards than Shaw's educational project.

None of which is to suggest that Shaw"s project may not achieve a comparable
level of esteem

~~~
mr_luc
He mentioned that the intended audience was different, and even talks about it
a little -- what he objects to is that people learning C are being pointed to
it in a way that removes all of that context. Especially, it seems like
holding K&C up as a paragon of "style" offends him.

That's what he means when he says it should be relegated to 'history.'

I'd submit that K&C is the fastest way to learn C if you knew nothing about C,
or the best way to acquire the Zen of C, and that for those purposes it's
still second to none, and its conciseness is a virtue ... but that Zed might
be right about people pointing to it as a paragon of style to newbies who hope
to write it professionally someday.

In fact, I'm disappointed that Zed let this project drop. Seemed neat.

~~~
diminish
you can always read and benefit from an old text without anachronistic
mistakes, and any text written today will also be obsoleted quickly. it is
usually better reading the masters than the pupils.

------
zeteo
> we will be modernizing the code in K&RC [...] It will be more verbose, but
> it will be clearer

It's been at least a year since the article first showed up on HN, and the
author still hasn't made good on this promise. It's actually hard to do,
because verbosity and clarity are usually at odds with each other.

One of the main attractions of K&R C is exactly its _lack_ of verbosity, its
extraordinarily high content-to-length ratio. In a very short space you learn
to do a lot of sophisticated programming, and most if not all code examples
fit on one page or less.

Of course, optimizing for conciseness has its costs, as anyone who has
debugged segmentation faults knows. So you avoid some of this shooting-
yourself-in-the-foot that C is infamous for by using various crutches: add an
extra argument for some functions, build your own Pascal-style strings etc.
And if you pass in external input then you should definitely use some of them,
such as strlcpy (which is actually preferable to the four-argument function
that this article is getting to).

But there are also lots of cases where plain old strcpy will do fine, and for
simplicity sake it's better to use it. I believe one of these cases is a
learning experience in which you want to get the big story as soon as
possible, and are willing to wait until later to get acquainted with the
inevitable caveats and detours.

~~~
Ingaz
>> One of the main attractions of K&R C is exactly its lack of verbosity, its
extraordinarily high content-to-length ratio. In a very short space you learn
to do a lot of sophisticated programming, and most if not all code examples
fit on one page or less.

Funny.

The same goes for "Programming Erlang" by Armstrong.

On contrary: "Erlang and OTP in Action" is quite boring (in comparison with
book by Armstrong). Definitely it's not a book for "enlightment" but for
practice, sometimes dull: "Do it in such way, you don't need understand why,
you'll get accustomed to it in future"

------
NickPollard
I think Zed is right to point these errors out - there are quite a lot of
issues with edge case inputs and undeclared assumptions, that people
definitely need to hear about.

That said, he appears to be dealing in absolutes too much. If you care about
performance (and let's face it, if you're using C you do, otherwise you
probably shouldn't be using C) then sometimes you can't handle as much error
checking or error correcting as you'd like.

In games (where most of my experience is), it's common to have functions that
are 'unsafe' by his definition, but that are hidden in compilation units and
not exposed in the header, so that the programmer can control exactly where
they're called from. If you have a limited number of 'gatekeeper' interface
functions that are actually called from outside the module, and these either
check/sanitise/assert on their inputs, then the internal private functions can
safely assume that they have valid input and just run as quickly and as simply
as possibly.

~~~
jacquesm
There are many other good reasons to use C besides performance.

For instance:

    
    
      - cross platform portability
    
      - predictability
    
      - reliability
    
      - long term stability / maintainability
    
      - low run-time overhead, fine grained control of memory

~~~
PommeDeTerre
Don't forget the people.

Long-time C coders are among the best programmers around. It's hard to
understand just how damn good they are until one has gotten a chance to work
with one or more of them.

The good ones know all levels of the hardware and software stack they're
working with. Coupling this knowledge with the raw power of C, they can put
together amazing and resource-efficient software in very little time, yet
without sacrificing maintainability, security, portability and the other
factors you've listed.

These are truly the people who make the impossible become possible.

~~~
bjoe_lewis
if this is true, then I'm going to take C more seriously. I have to move
myself forward from TurboC programming.

~~~
jerf
It is true. How much of the truth is credited to "longtime" and how much is
credited to "C" is up for debate, though. Personally I'd put most of it on the
"longtime" side, and observe that if you want to learn how to write really
efficient, performant code quickly, there are a number of other options
developing where you can skip the part where you stab yourself with the
language for five years learning where the sharp bits are. C is not dead yet,
but I'm feeling increasingly confident we've finally entered the era where its
days are now numbered.

~~~
gillianseed
based upon what? from what I've seen C is doing great, it has it's particular
domains which in my opinion are low-level programming and performant, portable
code with a small footprint.

I can't recall seeing a new language challenging C in the aforementioned
areas.

~~~
jerf
Based upon the increasing number of languages showing up that combine high
performance with higher levels of abstraction than C, and the increasing
number of serious languages gunning for C specifically, like Rust.

C has ridden for a long time on the fact that we didn't know how to combine
high performance and systems functionality with high abstraction languages, so
you had your choice of C or C-like and fast, or high abstraction and slow,
like Python or Perl or Ruby. This gap is closing, fairly quickly now, and once
it does C will start experiencing a rapid decline into a niche language,
rather than the lingua franca of computing. It has advantages that have kept
it going for a long time, but it has terrible disadvantages too, and once the
two are separable, people are going to want to so separate.

Already a great deal of what was once automatically C or C++ has gone to some
sort of bytecode (Java or .Net), even on things you would used to simply
automatically assume to be "embedded", like your cell phone. The decline has
already started.

Of course it won't _die_. Computer languages never really _die_. You can still
get a job doing COBOL, and in fact get paid quite well. But 10 years from now,
I think on the systems considered "modern" at the time, it will not be the
"default" language anymore.

~~~
jacquesm
Any new language will have a maturity gap to make up for that will be hard to
bridge.

Java has to some extent displaced C++ (and the .net framework has done some
more of that), but I haven't seen any language that displaced C in an arena
where C is strong.

For rust to make this happen they'll have to finalize the language spec, gain
a decade+ experience in what the quirks are (these things only come out over
time it seems), sway a generation of programmers to adopt it rather than the
incumbent.

Go is shooting for the same space and it already lost the plot in several
aspects (for instance: newer Go versions break older code).

~~~
Locke1689
Go is not shooting for the same space. Go is garbage collected.

------
eliben

        // use heap memory as many modern systems do
        char *line = malloc(MAXLINE);
        char *longest = malloc(MAXLINE);
    
        assert(line != NULL && longest != NULL && "memory error");
    
        // initialize it but make a classic "off by one" error
        for(i = 0; i < MAXLINE; i++) {
            line[i] = 'a';
        }
    

So, you create something that does not fulfill the C library invariant of what
constitutes a "string", and then pass it to a copy function that assumes this
invariant? It isn't a fair thing to do, and frankly, I doubt it many beginner
programmers care about things like this. Yes, they may run into such a
"defect" and be very miserable for a while, but that will just teach them
about debugging, and most important, invariants.

Zed, I appreciate your work, but if this is the direction you'll be taking
with these articles, then don't bother.

~~~
chj
You are being too kind. I was expecting some earth shaking discoveries, only
to find the author doesn't understand C.

~~~
PommeDeTerre
Zed and his writings are not to be taken too seriously.

~~~
to3m
I suspect this one is a diversionary tactic.

I guess Zed Shaw suffers from nerd burnout. As in, a sort of more emotional
burnout from having had to deal with them all the time in the past - or at
least, that's what I get from some of his writings anyway. So I imagine him
popping this stuff in as a sort of early warning system. It's all true enough
to be right, and true enough to get his point across, BUT IT'S NOT TRUE ENOUGH
FOR A NERD. So any time somebody complains about his strcpy example, or
0-terminated C strings, or whatever, that's his nerd alert. This person is not
worth dealing with, and now he can block them, or set up a mail filter to put
their email in the junk folder, or whatever, without having had to invest any
time in finding this out the long way.

There was also a bit in one of his essays about the way ruby fans were always
these stupid armchair pop psychologists.

~~~
jlgreco
I think it functions less as a "nerd" alert but more as a _"recognizes I may
not be as profound as I like to imagine myself"_ alert.

I mean, he's right.. but he's not being profound. You may as well tell me that
I should be careful about losing precision while using floats. Yes... _no
shit?_

------
Camillo
There is no reason to assume that the correct length parameters would be
passed to safercopy. Indeed, there are many buffer overflows and off-by-one
errors in C programs which involve buffers with explicit size values instead
of null-terminated C strings.

The real problem with C is that it relies on bare pointers, where it would
have been better to use slice-type structures that describe a buffer by
pairing the base pointer and size, so that they are naturally kept in sync.
This article takes a lot of time to "deconstruct" C strings, but never gets to
the real issue.

The "stylistic issue" is also debatable. With the indentation given in the
example, nobody would think that the "while-loop will loop both if-
statements", as the author claims.

~~~
Evbn
In short, Zed had all of Rob Pike's sneering anger and none of his technical
mettle.

What has Zed said about C that wasn't already answered more thoroughly by Go?

------
judofyr

        A = {'a','b','\0'}; B = {'a', 'b', '\0'};  safercopy(2, A, 2, B);
        A = {'a','b'}; B = {'a', 'b', '\0'};  safercopy(2, A, 2, B);
        A = {'a','b','\0'}; B = {'a', 'b'};  safercopy(2, A, 2, B);
        A = {'a','b'}; B = {'a', 'b'};  safercopy(2, A, 2, B);
    

This analysis only tries different values of A and B, not the lengths. A
proper analysis of "for what values does it fail" should include _all_
parameters. What happens if you do `safercopy(3, A, 2, B)` or `safercopy(3, A,
3, B)`?

~~~
tcarney
Exactly what I noticed. The "safer" function relies on the length arguments
being correct, just as the copy function relied on the strings being null-
terminated. The safer function is more explicit so less prone to error, but
both rely on the programmer doing the right thing, which is what he was trying
to avoid...

------
michaelfeathers
Zed is dinging examples in K&R for incorrectness, but he's inadvertently
deconstructing the notion of correctness. It's more contextual than most
developers want to believe.

~~~
p4bl0
Agreed. And it starts from his very first example, about the implementation of
the copy() function. C is not a type safe language and programmers who use it
knows that, sometimes it might even be an advantage. For instance for me, if
I'm writing or using such a copy() function which does not ask for a length to
copy, I know it is because it will need a proper C string. For some others it
may be that they know that their functions will use a global maximum length
and that the inputs they provide to the function are respecting that. But no
half-decent C programmer will use this function as a totally safe one on any
possible inputs. Especially if they saw the implementation.

The "correctness" the author is asking for here is not what you want from a
typical C functions. If you really need this kind of "correctness" then maybe
you are using the wrong language and should check our either a high-level
tolerant scripting language or a statically typed one.

~~~
michaelfeathers
Yes, and that is just one level. When you write about programming you have to
be very clear about what you are trying to get across, and when you put that
focus into practice other things suffer because you are competing against the
rest of the world for the reader's attention.

Kernighan and Ritchie could have decided to write the book as absolute hard-
asses, making the most bullet-proof copy routine imaginable, but in the end
they would've been writing a different book, not a primer for a language.

~~~
Tloewald
If you're illustrating concepts — such as iterating through an array to find a
specific value which will be there because the data structure being examined
is defined to contain it at some point, then writing a function that assumes
the value won't be there is didactically bizarre.

Zed is suggesting that the teaching point of the code sample be ignored in
favor of an altogether different and less useful teaching point. This is not a
case of not understanding C (which he cleary does understand) but not
understanding how to teach.

At best, his entire point could be addressed in K&R by a cautionary footnote
or a later discussion of code hardening.

------
foxhill
the point of the exercise is to understand how a copy works, and stylistic
issues aside, the point is made.

if you supply a function with inputs outside of it's specification (NULL-
terminated strings), then undefined behaviour is (by definition) going to
happen.

besides, what's to stop someone from calling safercopy like so;

    
    
        safercopy(strlen(str1), str1, strlen(str1), str2);
    

then strlen will fail (albeit a bit more safely - perhaps).

it's a safe bet, that in production code, we'll not be working with fixed
length strings. so we need to get the length of the string somehow. all his
safercopy does identify a problem that he himself already points out is
impossible to solve - how do we differentiate from a NULL-terminated string,
and one that isn't?

the only real solution (i can think of) is a string class, where the
constructor is guaranteed to return valid (or no) strings. then (assuming
other functions can't overwrite our memory - already an unsafe assumption) we
could guarantee a safe string copy.

programming is hard.

------
coofluence
I am wondering when will the author take on correcting errors in MMIX code in
Knuth's books. He will at least earn a hex dollar for his efforts.

Jokes apart, I admire the bravery in questioning K&R C's status. I have only a
few personal insights to share as I don't code much these days.

1\. I got introduced to C in 80's via some popular book. K&R not only taught
me C but also was my entry point into the systems programing world. Before
K&R, the BASIC programming books never allowed me to deal with memory or
interrupt vectors in the way C allowed me.

2\. C has a great power dynamic built in to it. You are on your own in dealing
with this power. It's you who crashed the machine, not C and surely not K&R C.

3\. Almost every language since C has borrowed something from C. Hence,
anytime I saw a familiar notation or code block in any language that reminded
me of C, I got confidence that I can learn this language.

K&R's many virtues give it an unique status. It did something that no C book
or website can do it. It is the word of the language designer themselves. They
shared their reasons of the choices they made.

------
experiment0
Theres an analysis of his deconstruction here:

[http://functional-
orbitz.blogspot.se/2013/01/deconstructing-...](http://functional-
orbitz.blogspot.se/2013/01/deconstructing-zeds-k-deconstruction.html)

------
Proleps
Even if I made a function copy(char to[], char from[], int lenTo, int lenFrom)
it would still be incorrect using his reasoning because most of the possible
inputs would still cause the software to crash. I could symply add a wrong
lenTo and lenFrom.

~~~
guard-of-terra
Garbage in, garbage out.

------
xentronium
I don't understand it. His solution to non-safe introduction to non-safe
language is to slap defensive checks everywhere? I am not sure that scaring
his students shitless by having them check every input is as effective as
concise explanation why and when you should check your inputs.

Criticizing K&R because of their safety assumptions is a faux pas.

------
aufreak3
Though Shaw raises a valid point based on the fact that we program in more
diverse and hostile situations today than K&R did back in their days, I'm
unsure how deep one needs to go into program correctness when teaching a
language. Wouldn't it suffice, for example, to have program correctness
highlighted as a chapter?

Take this -

> Also assume that the safercopy() function uses a for-loop that does not test
> for a '\0' only, but instead uses the given lengths to determine the amount
> to copy.

It is possible to write a safercopy() conforming to that loose specification
that will not terminate. Just make the test something like "i != length"
instead of "i < length". Then you can supply negative length arguments and get
it to fail. Of course, that would be stupid, but it already illustrates the
art of specification. .... Well, with finite precision integers, "i != length"
would terminate at _some_ point due to wrap around, but would take the
universe to end if you'd used 128bit integers. To do it even more simply,
`safercopy(1,2,3,4)` can crash the program.

Is the moral of this story that programs are not valid outside the context
they were created for? .. or is it to never use data structures whose
integrity cannot be proved without failure? .. or is it that proving a
program's correctness using some method only indicates a failure of
imagination? .. or, to put it differently, that you can only prove a program
wrong but never one right?

------
mikeash
This article is bashing just for the sake of bashing. While it pretends to
offer constructive critiques and solutions, it fails miserably. One can
certainly have productive discussions about the proper way to handle buffers
in C, but this isn't it. Zed Shaw should stick to Ruby or whatever area he's
actually good at.

~~~
Evbn
He did the same thing in Ruby first, before everyone got sick of him and he
moved to C.

------
killahpriest
Annoyingly clever. Even while running Ito JavaScript I have to debate whether
I should be annoyingly clever or not.

    
    
      /* bad use of while loop with compound if-statement */
      while ((len = getline(line, MAXLINE)) > 0)
        if (len > max) {
            max = len;
            copy(longest, line);
        }
     if (max > 0) /* there was a line */
        printf("%s", longest);
    

What do you do?

~~~
polshaw
_(i'm not certain what aspect you are trying to discuss but i'm assuming
braces-- as that seems the primary point in the article)_

My personal solution (again for JS) would be to use a new line with no braces
to split up an if-statement, but to never nest a braced statement as part a
pseudo-one-liner, nor to nest many levels of one-liners - as these situations
could lead to confusion.

Eg for the above;

    
    
      while ((len = getline(line, MAXLINE)) > 0){
        if (len > max) {
            max = len;
            copy(longest, line);
        }
      }
      if (max > 0)
        printf("%s", longest);
    

This is a personal preference- i find it adequately splits up a one-line
`if(max > 0) printf("%s", longest);` statement to be clearly identifiable as
an if (while/for/etc) block, without the verboseness of the extra line/2 for
braces, which i personally find makes code harder to read.

~~~
ams6110
If I'm intentionally writing a one-line if I will write it on one line. I
think using an indented second line without braces is less clear, and more
prone to problems later when the code is modified.

So I'd write:

    
    
      if (max > 0) printf("%s", longest);
    
    

or

    
    
      if (max > 0) {
        printf("%s", longest);
      }
    

but never

    
    
      if (max > 0)
        printf("%s", longest);

------
mkumm
I have been through both, K&R and Shaw. K&R is the resource I point new
developers to who want to learn C.

------
mtkd
This book introduced me to modern languages, from my previous experience
coding in assembler. It inspired the millions of lines of code I've written
since.

I now understand why my subsequent programs, and those of many in my
generation, have been riddled with bugs for 3 decades.

~~~
jacquesm
K&R is hardly the only book written about C, nor is it the best. You should
definitely not stop learning about a language after you've read just one book
about it.

K&R C was best practice in 1980 or so, since then we've learned a lot about C,
about what to do and what not to do. If you still program C like it is 1980
then you can't really blame that on a book from 1978.

~~~
rcamera
I am midway through K&R, but now I am curious, what other book would you
suggest?

~~~
telemachos
Popular suggestions (not introductory):

    
    
        C Interfaces and Implementations (David R. Hanson)
        Expert C Programming (Peter van der Linden)
    

I'm also a fan of _C Programming: A Modern Approach_ (K. N. King) as an
introductory book, but it's _very_ different in intended audience from K&R.
King is writing for students, and he assumes nearly nothing. K&R are really
writing for experienced fellow programmers, I think. So it's an apples to
oranges comparison. But King is certainly more modern. He includes a fair
amount of C99 material. The writing can't compare with K&R. It's nowhere near
as dense or elegant. But if you find parts of K&R a bit _too_ dense and
elegant, it's a good trade-off. (I used them both in tandem while trying to
learn C this summer. It worked well for me. When King got too verbose, switch
to K&R. When K&R lost me, switch to King.)

~~~
noarchy
King's book was my introduction to _serious_ programming when I was younger.
In an era of my life where I usually prefer ebooks, I still have King's book,
in dead-tree format, and love it.

------
dakimov
Ironically, I most likely wouldn't hire Kernighan or Ritchie because of their
poor programming style.

~~~
mturmon
Your hubris takes my breath away, which makes it difficult to laugh at your
ignorance. Painful.

~~~
dakimov
I am sorry, there was no 'hubris' at all. You obviously misunderstood my
comment along with other few not very bright people. Recently I had to conduct
a number of job interviews on C/C++, and then I came across this article, and
a thought came to my mind that great people cannot be measured with a scale of
mediocrity, and that the mainstream in every field of human knowledge is used
to be very focused on non-essential detail. Sapienti sat, but in your case it
is the reversal.

~~~
jacquesm
Do you have this a lot, that other, not very bright people misunderstand you?

~~~
dakimov
Looking at your responses, I see you are a guy with very straightforward
stereotypic thinking, you are aggressive, and your little taunt is also
childishly primitive and straightforward. Does it make sense to talk to you at
all? So the answer is 'no', of course, I do not 'have this a lot', as I
usually simply ignore this kind of people, but unfortunately there is an
option to downvote a comment for the benefit of the stupid.

