Jens Gustedt is a co-editor of the ISO C standard, so he knows his shit. Modern C is probably the best book about modern C, short of reading the standard.
It's far easier to read than the standard, too, since it's explanatory rather than declaratory. The standard tells you what C can do, Modern C teaches you how.
What does "modern" C imply? AFAIK, there are not very many new language features; is it about organizing code differently than what one would learn from K&R?
Apart from the new features added in C11 or C17 that others have mentioned, both Beej's Guide and Modern C also cover threads and string encoding (neither of these topics are covered in K&R 2nd edition, as far as I remember). Beej's Guide also has a section on internationalization and one about date/time handling.
But yes, the code is also quite different than in K&R which is relatively terse. See for example this comment in K&R about strcpy on print page 105 (page 119 of the PDF)[1], where after showing two versions of strcpy that are pretty readable and easy to follow, the book says:
> In practice, strcpy would not be written as we showed it above. Experienced C programmers would prefer
According to the introduction of the book, C17. Which is mostly just bugfixes on C11. But, compared to c99, you get nice things like generic expressions, better unicode, and (standard) multithreading.
Beej himself lists this as an 'alpha-quality document' on the download page [0] and if I remember correctly, it has been so for years. Wonder why this is posted here on HN.
I think posts like this are handy just for the extra exposure an alpha document might need; there's a lot of good feedback and discussion here that hopefully beej comes across someday and can finish the book
On a quick skim of some introductory parts I found:
> When you have a variable in C, the value of that variable is in memory somewhere, at some address. Of course. After all, where else would it be?
It would be in a register. Of course. Or it would be eliminated by a compiler optimization. Of course.
Same error later on:
> When you pass a value to a function,a copy of that value gets made in this magical mystery world known as the stack
No. In most common cases, arguments will not be passed via the stack. This goes on to clarify in a footnote that the implementation might not actually use a stack, and that the stack has something to do with recursion. That part is true, but the values saved on the stack for recursing are not the same as function arguments.
Neither in the Variadic Functions chapter nor anywhere else are the default argument promotions mentioned -- this will bite someone who tries to write a variadic function that gets floats out of the variadic argument list, which you cannot do, since passing a float to a variadic function promotes it to double.
Speaking of floats... This is one of those tutorials that are very confused regarding their target audience. For example, in the "Variables" section it goes out of its way to define: "A “byte” is an 8-bit binary number. Think of it as an integer that can only hold the values from 0 to 255, inclusive." (which isn't the C definition, but this really is nit-picking) but then happily goes on to talk about Booleans and floats without explaining what those are. What reader has a background that would make this useful?
Overall, from the little I've seen, I'd give this an initial rating of "broadly correct but with definite mistakes".
Even if it were fully correct, I dislike the verbose style, and I wouldn't recommend this tutorial. For example, in the Hello World chapter, we have the following "explanation" of the line "#include <stdio.h>":
> Now, what is this#include? GROSS! Well, it tells the C Preprocessor to pull the contents of another fileand insert it into the code rightthere.Wait—what’s a C Preprocessor? Good question. There are two stages (well, technically there are more thantwo, but hey, let’s pretend there are two and have a good laugh) to compilation: the preprocessor and thecompiler. Anything that starts with pound sign, or “octothorpe”, (#) is something the preprocessor operateson before the compiler even gets started. Commonpreprocessor directives, as they’re called, are#includeand#define. More on that later.Before we go on, why would I even begin to bother pointing out that a pound sign is called an octothorpe?The answer is simple: I think the word octothorpe is so excellently funny, I have to gratuitously spread itsname around whenever I get the opportunity. Octothorpe. Octothorpe, octothorpe, octothorpe.Soanyway. After the C preprocessor has finished preprocessing everything, the results are ready for thecompiler to take them and produceassembly code8,machine code9, or whatever it’s about to do. Don’t worryabout the technical details of compilation for now; just know that your source runs through the preprocessor,then the output of that runs through the compiler, then that produces an executable for you to run. Octothorpe.What about the rest of the line? What’s<stdio.h>? That is what is known as aheader file. It’s the dot-hat the end that gives it away. In fact it’s the “Standard I/O” (stdio) header file that you will grow to knowand love. It contains preprocessor directives and function prototypes (more on that later) for common inputand output needs. For our demo program, we’re outputting the string “Hello, World!”, so we in particularneed the function prototype for theprintf()function from this header file. Basically, if we tried to useprintf()without#include <stdio.h>, the compiler would have complained to us about it.How did I know I needed to#include <stdio.h>forprintf()? Answer: it’s in the documentation. Ifyou’re on a Unix system,man printfand it’ll tell you right at the top of the man page what header files are required. Or see the reference section in this book.:-)Holy moly. That was all to cover the first line! But, let’s face it, it has been completely dissected. No mysteryshall remain!
Only one sentence of this is relevant for an introductory Hello World chapter: "Basically, if we tried to use printf() without #include <stdio.h>, the compiler would have complained to us about it." None of the rest is relevant or helpful to a beginner who is just seeing their first ever C program. Also "completely dissected" isn't true either; there is a lot more to be said about headers.
> It would be in a register. Of course. Or it would be eliminated by a compiler optimization. Of course.
Since you mention relevance for beginners later on in your post I'd argue this isn't relevant either. This concept holds true for simple code that doesn't do advanced stuff like working with hardware. As soon as you do &variable, you get an address and can work with it. If the compiler optimized something away you never use you might as well just pretend it's in memory somewhere for the sake of a mental model that's easy to grasp. Same with passing variables via stack. A simple compiler could do it just like that.
That isn't to say the tutorial is good/not good, but these points in particular seem rather sane to me. Far from "Mastering C Pointers" at least :)
> It would be in a register. Of course. Or it would be eliminated by a compiler optimization
As long as you’re taking, and using, the address of that variable, it’s almost guaranteed to be in memory. Even if it won’t, the compiler guarantees the output of the program will be equivalent to unoptimized code.
> arguments will not be passed via the stack
I’m not sure explaining nuances of various calling conventions, and how they differ across processors and OSes, is useful information in a document about C and targeted towards beginners.
You’re talking about things which are underneath C in the abstraction layer hierarchy. The abstraction has many layers, the lowest one being quantum physics. One has to stop somewhere, and this article decided to stop at C, as opposed to assembly.
> Only one sentence of this is relevant for an introductory Hello World chapter: "Basically, if we tried to use printf() without #include <stdio.h>, the compiler would have complained to us about it."
Quite the contrary in my opinion. As a beginner I was very frustrated with most approach that say "Just put that thing that is needed and will be explained latter. And it work good job attaboy!"
And maybe 250 pages latter if the author didn't forgot in the meantime you get a one liner mention that link back to the first introduction of the syntax.
At least this guide don't let the reader in the fog wondering.
IMO, pointers are less difficult to comprehend than other abstractions, like lambdas are.
If you know how to walk down a street and stop at the right street number, then you have used pointers. And if you've ever observed that one tall building may "cover" a range of street numbers, such as 200-220, then you should understand how to move from one 4-byte "value" to the next in an array in memory.
Anyway, many more analogies... probably better than this one.
Maybe unions could make using pointers a bit more challenging, but again, tall buildings next to short buildings and so on. We do this kind of pointer calculation in real life.
What pointers basically are is not particularly hard to grasp. What is harder to grasp it what can be done with them and how you can shoot yourself in the foot with them in non-obvious ways.
I think I only understood much of it once I learned Rust, because you realize: Ah, that thing I once did in C is something that maybe ahouldn't be possible at all without extra steps. Even if I were to nwver use Rust again, this definitly helped to understand how to use pointers more safely.
What's difficult to understand about pointers isn't the concept of a pointer itself, or even * and &, it's the fact that working with pointers requires you to simultaneously understand different abstraction levels. While it's not unique to pointers, and it's in fact the case for most nontrivial programming tasks, what's unique about C is that pointers are so pervasive you can't really do anything if you don't understand how to work with them.
IME languages like Python aren't any easier than C to work with (ignoring UB issues of course), but it's certainly the case that you can probably kinda sorta get your job done with Python even without understanding the first thing of what you're doing, and that's not happening if you write in C.
In C, pointers require you to think deeply about the ownership and lifetime of any "allocated object" at runtime. How long does it live, who is responsible for the deallocation, how many pointers does your program hold to that object (dangling issues). Ultimately, it can lead to a cleaner design if these issues are taken seriously up-front.
These are good points. I can sometimes feel that Python is more pointer-y (?) than people expect, with stuff like:
a = {"one": 1, "two": 2 }
b = a
b["two"] = 99
print(a["two"])
The above prints 99, since "b = a" does not copy the value (the dictionary) but just the reference to the value ("the pointer", kind of). This is surprising to some people.
Pointers aren't that complicated but C syntax is misleading imho until someone comes along and tells you that the declaration syntax "follows use", which does not seem like the greatest idea. Then you get used to it and forget about it but when you don't know the principle behind declaration syntax, it does not help you reason about the language.
Yup. I spent a ton of time trying to make sense of pointer syntax (* operator/modifier), until I realized that it didn't really make sense (except for the simplest cases) and then I could get on my life.
Pointers are like swazzles[1]. The construction is very simple. The principle behind how they work is very simple. Learning how to use one well enough that you can (a) consistently make it do what you want, and (b) not injure yourself in the process, though, is no mean feat.
Well, you've demonstrated that memory addresses aren't that hard. But you've also demonstrated how easy it is to get undefined behavior in C programs.
C's pointers aren't memory addresses. Ok, they tend to be represented as such at run time, but that's not what the spec actually says the are. And as far as compiler authors are concerned, they can do anything they want as long as it's within spec. Further, the spec even requires some additional behaviors pure memory addresses aren't capable of. See https://www.ralfj.de/blog/2020/12/14/provenance.html for examples of the extra requirements.
Compared to that mess, lambdas are trivial. They're just functions.
> If you know how to walk down a street and stop at the right street number, then you have used pointers. And if you've ever observed that one tall building may "cover" a range of street numbers, such as 200-220
I see that as a European, I have virtually no chance to understand pointers using street numbers. :)
(Fortunately I've never had problems either with lambdas or with pointers.)
C is an abstraction over assembly, really (benefits being that it is simpler by being more abstract and portable across CPU types).
I've always thought that an introduction to CPUs (can take a simpler one as example) and how they work, how memory is (usually) organised, and to assembly would go a long way in helping understand many programming issues and C.
My experience is that C or programming concepts are often taught in a very abstract/mathematical way, which can be hard to grasp compared to a more practical approach.
If you take a concrete example where memory is effectively an array and indices are addresses (which holds true for most cases and, in any case is a good example) then understanding pointers becomes basically common sense and notations are simply conventions of the language you're using.
Treating C as an abstraction over assembly is a surefire way to step into all the thousands of sharp edges C has. In fact I would hazard a guess that the majority of bugs found in software written in C are a result of programmers treating it as a portable assembler instead of a language for programming an abstract machine. So many incorrect assumptions arise as a result of telling people to treat C as a portable assembler that I think it's safe to call it an extremely bad bit of advice.
For anyone who wants to learn about pointers I can recommend studying a language simpler than C like for instance Oberon where pointers are more restricted. Having a look at Oberon can also broaden your view even if you know pointers in C.
There was a "C Unleashed" book, a massive tome of 1000+ pages written by many famous programmers, many of them who where quite active in comp.lang.c, like Richard Heathfield and CB Falconer, had quite insightful material in it.
Any one remember the heyday of comp.lang.c? I wonder what goes on in there now.
I wasn't sure anyone but the authors remembered C Unleashed! I wrote the chapter on binary search trees and balanced trees.
Comp.lang.c was important to me for many years. I've met 5 or so of the regulars at least once. The most famous comp.lang.c regular is probably Tim Hockin of the Kubernetes project.
I bought that book ages ago. Good stuff. comp.lang.c still has a small group of knowledgeable regulars, but a lot of the "old guard" seems to have stepped away. And Usenet is a shadow of its former self, obviously.
That's what I remember from comp.lang.c -- lawyer-ly obsession with ANSI C, rather than C as it's being used. That is an important distinction which I respect, but they should have had some kind of sister forum that's more practical. They denied that hardware exists, etc.
Also, obsession with ANSI C, analogous to obsession with POSIX shell, is sort of "middlebrow" in the sense that the people who WRITE the spec need to go outside of it to create a new version of it. Good specs are derived from real usage.
Yes, usenet was huge for me back in the early 90s when I had questions on C programming. I would ask them on comp.programming.c and had some of the best programmers providing guidance. Of course they were strict with questions/discussions being specific to ANSI C.
> It’s especially insidious because once you grok pointers, they’re suddenly easy. But up until that moment, they’re slippery eels.
I'm sort of a C beginner myself. I understand pointers, and I do remember they clicked in my mind suddenly. The moment before, I didn't understand at all. I also love the quirkiness of this guide. Definitely going to give this a read.
Do you mean the general concept or like: a is a pointer to an array of functions which return pointers to functions which return ints and take double arrays as parameters.
This somehow never really clicked (or actually it clicked and declicked somehow)
For me it was the practical understanding. I understood the concept of a pointer, but I wasn't confident in writing code that used that used them or (more importantly) reading code. I would see an asterisk, multiple asterisks, or ampersands, and would get confused with the code. I do think some of the issues I encountered had to do with the notation of pointers. The asterisk serving as a symbol to both declare a pointer variable and dereference one.
If you always read from right to left (and put occasional braces for readability) you can get through anything. Most tricky ones are in job interviews, while ones found in the wild will often make use of typedefs to break down the complexity.
I love Beej’s guide to network programming. Back when I was just starting to learn to code, I wanted to get right down to the low level C stuff and his guide was what I used. It was simple and approachable and I had a running TCP client by the end of the day. It was a thrilling experience for a young novice.
There's fair amount added to the core since C89, actually... And if you include the library changes, game over! I had no idea how much had been added.
More challenging is to find what's been subtracted.
This no longer defaults to int:
static i;
And gets() is toast.
Anyone know any others?
But yes, the code is also quite different than in K&R which is relatively terse. See for example this comment in K&R about strcpy on print page 105 (page 119 of the PDF)[1], where after showing two versions of strcpy that are pretty readable and easy to follow, the book says:
> In practice, strcpy would not be written as we showed it above. Experienced C programmers would prefer
Followed by a paragraph saying the condition could be simplified, and that> the function would likely be written as
Make of that what you will :-)[1] https://kremlin.cc/k&r.pdf#page=119
Stuff that should be avoided: [...]
Beej's Guide to C: http://beej.us/guide/bgc/output/html/singlepage/bgc.html
Full of mistakes.
[...]
Could someone confirm this? I've seen a lot of threads here on HN praising beej's guides so I am somewhat confused.
[0] http://www.iso-9899.info/wiki/Main_Page
edit: Formatting
[0]: http://www.beej.us/guide/bgc/
And I'm sure it's full of mistakes. It's over 500 pages, most of which has yet to be edited, so if there are fewer than 1000 defects, I'd be shocked.
But I fix them all as I find them, or as they're pointed out. And after an eventual editing pass, things will be better.
And if it's not useful to someone, I take no offense I'd they don't like it. :-)
> When you have a variable in C, the value of that variable is in memory somewhere, at some address. Of course. After all, where else would it be?
It would be in a register. Of course. Or it would be eliminated by a compiler optimization. Of course.
Same error later on:
> When you pass a value to a function,a copy of that value gets made in this magical mystery world known as the stack
No. In most common cases, arguments will not be passed via the stack. This goes on to clarify in a footnote that the implementation might not actually use a stack, and that the stack has something to do with recursion. That part is true, but the values saved on the stack for recursing are not the same as function arguments.
Neither in the Variadic Functions chapter nor anywhere else are the default argument promotions mentioned -- this will bite someone who tries to write a variadic function that gets floats out of the variadic argument list, which you cannot do, since passing a float to a variadic function promotes it to double.
Speaking of floats... This is one of those tutorials that are very confused regarding their target audience. For example, in the "Variables" section it goes out of its way to define: "A “byte” is an 8-bit binary number. Think of it as an integer that can only hold the values from 0 to 255, inclusive." (which isn't the C definition, but this really is nit-picking) but then happily goes on to talk about Booleans and floats without explaining what those are. What reader has a background that would make this useful?
Overall, from the little I've seen, I'd give this an initial rating of "broadly correct but with definite mistakes".
Even if it were fully correct, I dislike the verbose style, and I wouldn't recommend this tutorial. For example, in the Hello World chapter, we have the following "explanation" of the line "#include <stdio.h>":
> Now, what is this#include? GROSS! Well, it tells the C Preprocessor to pull the contents of another fileand insert it into the code rightthere.Wait—what’s a C Preprocessor? Good question. There are two stages (well, technically there are more thantwo, but hey, let’s pretend there are two and have a good laugh) to compilation: the preprocessor and thecompiler. Anything that starts with pound sign, or “octothorpe”, (#) is something the preprocessor operateson before the compiler even gets started. Commonpreprocessor directives, as they’re called, are#includeand#define. More on that later.Before we go on, why would I even begin to bother pointing out that a pound sign is called an octothorpe?The answer is simple: I think the word octothorpe is so excellently funny, I have to gratuitously spread itsname around whenever I get the opportunity. Octothorpe. Octothorpe, octothorpe, octothorpe.Soanyway. After the C preprocessor has finished preprocessing everything, the results are ready for thecompiler to take them and produceassembly code8,machine code9, or whatever it’s about to do. Don’t worryabout the technical details of compilation for now; just know that your source runs through the preprocessor,then the output of that runs through the compiler, then that produces an executable for you to run. Octothorpe.What about the rest of the line? What’s<stdio.h>? That is what is known as aheader file. It’s the dot-hat the end that gives it away. In fact it’s the “Standard I/O” (stdio) header file that you will grow to knowand love. It contains preprocessor directives and function prototypes (more on that later) for common inputand output needs. For our demo program, we’re outputting the string “Hello, World!”, so we in particularneed the function prototype for theprintf()function from this header file. Basically, if we tried to useprintf()without#include <stdio.h>, the compiler would have complained to us about it.How did I know I needed to#include <stdio.h>forprintf()? Answer: it’s in the documentation. Ifyou’re on a Unix system,man printfand it’ll tell you right at the top of the man page what header files are required. Or see the reference section in this book.:-)Holy moly. That was all to cover the first line! But, let’s face it, it has been completely dissected. No mysteryshall remain!
Only one sentence of this is relevant for an introductory Hello World chapter: "Basically, if we tried to use printf() without #include <stdio.h>, the compiler would have complained to us about it." None of the rest is relevant or helpful to a beginner who is just seeing their first ever C program. Also "completely dissected" isn't true either; there is a lot more to be said about headers.
Since you mention relevance for beginners later on in your post I'd argue this isn't relevant either. This concept holds true for simple code that doesn't do advanced stuff like working with hardware. As soon as you do &variable, you get an address and can work with it. If the compiler optimized something away you never use you might as well just pretend it's in memory somewhere for the sake of a mental model that's easy to grasp. Same with passing variables via stack. A simple compiler could do it just like that.
That isn't to say the tutorial is good/not good, but these points in particular seem rather sane to me. Far from "Mastering C Pointers" at least :)
As long as you’re taking, and using, the address of that variable, it’s almost guaranteed to be in memory. Even if it won’t, the compiler guarantees the output of the program will be equivalent to unoptimized code.
> arguments will not be passed via the stack
I’m not sure explaining nuances of various calling conventions, and how they differ across processors and OSes, is useful information in a document about C and targeted towards beginners.
You’re talking about things which are underneath C in the abstraction layer hierarchy. The abstraction has many layers, the lowest one being quantum physics. One has to stop somewhere, and this article decided to stop at C, as opposed to assembly.
Appreciate the feedback. Some good suggestions here that I'll add.
Quite the contrary in my opinion. As a beginner I was very frustrated with most approach that say "Just put that thing that is needed and will be explained latter. And it work good job attaboy!"
And maybe 250 pages latter if the author didn't forgot in the meantime you get a one liner mention that link back to the first introduction of the syntax.
At least this guide don't let the reader in the fog wondering.
Deleted Comment
If you know how to walk down a street and stop at the right street number, then you have used pointers. And if you've ever observed that one tall building may "cover" a range of street numbers, such as 200-220, then you should understand how to move from one 4-byte "value" to the next in an array in memory.
Anyway, many more analogies... probably better than this one.
Maybe unions could make using pointers a bit more challenging, but again, tall buildings next to short buildings and so on. We do this kind of pointer calculation in real life.
I think I only understood much of it once I learned Rust, because you realize: Ah, that thing I once did in C is something that maybe ahouldn't be possible at all without extra steps. Even if I were to nwver use Rust again, this definitly helped to understand how to use pointers more safely.
Exposure to an assembler makes pointers easy to understand.
IME languages like Python aren't any easier than C to work with (ignoring UB issues of course), but it's certainly the case that you can probably kinda sorta get your job done with Python even without understanding the first thing of what you're doing, and that's not happening if you write in C.
https://www.ralfj.de/blog/2018/07/24/pointers-and-bytes.html
https://www.ralfj.de/blog/2020/12/14/provenance.html
https://www.ralfj.de/blog/2019/07/14/uninit.html
[1] https://www.atlasobscura.com/articles/swazzle-punch-and-judy
C's pointers aren't memory addresses. Ok, they tend to be represented as such at run time, but that's not what the spec actually says the are. And as far as compiler authors are concerned, they can do anything they want as long as it's within spec. Further, the spec even requires some additional behaviors pure memory addresses aren't capable of. See https://www.ralfj.de/blog/2020/12/14/provenance.html for examples of the extra requirements.
Compared to that mess, lambdas are trivial. They're just functions.
I see that as a European, I have virtually no chance to understand pointers using street numbers. :)
(Fortunately I've never had problems either with lambdas or with pointers.)
I've always thought that an introduction to CPUs (can take a simpler one as example) and how they work, how memory is (usually) organised, and to assembly would go a long way in helping understand many programming issues and C.
My experience is that C or programming concepts are often taught in a very abstract/mathematical way, which can be hard to grasp compared to a more practical approach.
If you take a concrete example where memory is effectively an array and indices are addresses (which holds true for most cases and, in any case is a good example) then understanding pointers becomes basically common sense and notations are simply conventions of the language you're using.
Any one remember the heyday of comp.lang.c? I wonder what goes on in there now.
Comp.lang.c was important to me for many years. I've met 5 or so of the regulars at least once. The most famous comp.lang.c regular is probably Tim Hockin of the Kubernetes project.
Reddit has more traffic nowadays.
Also, obsession with ANSI C, analogous to obsession with POSIX shell, is sort of "middlebrow" in the sense that the people who WRITE the spec need to go outside of it to create a new version of it. Good specs are derived from real usage.
Deleted Comment
Beej's Guide to C Programming - https://news.ycombinator.com/item?id=26100391 - Feb 2021 (1 comment)
Beej's Guide to C Programming (2007) - https://news.ycombinator.com/item?id=15198093 - Sept 2017 (79 comments)
As long as we're talking C programming, I'd single out this large thread with C Standards committee members from last year:
Tell HN: C Experts Panel – Ask us anything about C - https://news.ycombinator.com/item?id=22865357 - April 2020 (962 comments)
These tutorials are the gold standard of tutorials. I wish more content would be as straight to the point and easy to follow.
I'm sort of a C beginner myself. I understand pointers, and I do remember they clicked in my mind suddenly. The moment before, I didn't understand at all. I also love the quirkiness of this guide. Definitely going to give this a read.
This somehow never really clicked (or actually it clicked and declicked somehow)