top | item 13054705

Modern C [pdf]

392 points| brakmic | 9 years ago |icube-icps.unistra.fr

385 comments

order
[+] jackhack|9 years ago|reply
I wish I could like this book, but after reviewing the first chapter I can only imagine the confusion of students. I support very much the idea of breaking the book into levels, but it attempts to cover far too much, far too quickly and I don't believe this book would be useful for those who are not already familiar with the language.

I've been writing C since the late 1980s, moved to mostly C++ by the mid 90s, C# in the 2000s, and now I've come back to C. Most recently built some realtime components and drivers, having to drop back to C77. I mention this as I've taught many colleagues along the way and I'm sensitive to the places where beginners tend to get hung up with problems and I've come to anticipate many of the questions along the way. Let me take a moment to illustrate the base of the problems i see:

"Too much, too fast." The best example is right on page 2: a program which demonstrates a complex printf format string, along with arrays and loops. I can't help but sarcastically ask "Are you sure that is how you want to introduce someone to the language?" A beginner's eyes will glaze over.

Seriously, the way to introduce the language is simple examples. Explain the main is the entry point where all programs begin running, and that main returns it's success or failure to the operating system (or other program that called it). 3 lines of code.

Then add a SIMPLE print, if you wish, or a variable declaration. Int. Float. char. again, it MUST be simple. Introduce loops. Then show how to move some functionality out of main into a subroutine/a new method/new function, how to call that function, and return results. Talk about header files, etc.

From there, dive into the rest of the base language... talk up arrays, memory management, heap/stack, pointers, libraries, exceptions, etc.

But this is only my experience, and I'm sure that it is different for others. Kind regards.

[+] komali2|9 years ago|reply
This has always been my problem with websites like codeacademy, and I started my entire career by learning through that website.

Take the Javascript course - a fantastic way to get introduced to the syntax of the language, and I highly recommend it for total noobies. But then you come out of it with no understanding whatsoever about what javascript is. If I asked someone who just finished the course to make an "app" that console.log'd to the console, they wouldn't understand where to start. They wouldn't know that JS is a language run in the browser, that they need an HTML file with a script tag or a node file that they can run in the terminal. They wouldn't know about DOM manipulation, etc.

This reminds me of the Java class I took in highschool - the teacher was going on about ints and floats and loops, and the only questioned I wanted answered, and never got an answer for, was "what does `public static void main` mean?" I think the fact that I never got an answer to questions like that are why it took me nearly 3 years into my career to figure out I should be a developer.

[+] pselbert|9 years ago|reply
Your experience sounds spot on to me.

No matter what you are teaching, whether it is fundamental like reading, physical like a sport, or technical like programming, it is critical to teach ONE THING at a time. Teaching multiple concepts at once muddles the exercise and slows down learning. Breaking large concepts into discrete blocks lets the student focus and then build on that concept as they continue.

That's certainly how I work, and how I've heard experienced teachers explain it.

[+] xenihn|9 years ago|reply
Do you have any recommendations for books?
[+] colanderman|9 years ago|reply
While I like a lot of what's in here,

    for (size_t i = 9; i <= 9; --i)
is a pretty terrible example to put in the second chapter. I would not let that line pass code review. There is no need or place for cutesy cleverness in C.

EDIT: Ugh, just found this too:

    isset[!!largeA[i]] += 1;
Not only is that confusingly cutesy, but largeA[i] is a double. Please DON'T write – or encourage beginners to write! – such smug code!

EDIT2: In section 5 is the statement than unsigned integers "can be optimized best." This is flatly untrue on x86 and I suspect many other architectures. Compilers can and do take advantage of undefined signed overflow to optimize signed arithmetic; the same is not possible with unsigned arithmetic. See https://kristerw.blogspot.com/2016/02/how-undefined-signed-o...

[+] cygx|9 years ago|reply
I would not let that line pass code review.

Obviously. However, it is an appropriate example if your goal is to teach the intricacies of the C language. You're right that if you provide such an example this early, it could perhaps use some additional commentary.

Not only is that confusingly cutesy, but largeA[i] is a double.

That was the point of the exercise: !! is an idiom to convert to boolean (which happen to be integral in C) and something a C programmer (or JavaScript programmer, for that matter) should be familiar with.

This is flatly untrue on x86 and I suspect many other architectures.

Agreed.

[+] Nadya|9 years ago|reply
Given the explanatory text - I think this was more of a "test of intuition" accompanied by a "gotcha!"

As someone unfamiliar with C - I initially thought it would go on forever. The explanatory text explained why I was wrong and this can be equally parts "clever code" or a "gotcha!" depending how you view it. With your experience you're seeing it as overly clever code. With mine, I'm seeing it as a "gotcha". I don't think it is supporting writing code like that. :)

>The third for appears like it would go on forever, but actually counts down from 9 to 0. In fact, in the next section we will see that “sizes” in C, that is numbers that have type size_t, are never negative.

[+] yoha|9 years ago|reply
Decrementing loops is the one place where I have indulged in some trickery. I do:

    for (size_t i = 9; i --> 0; )
This has the advantage to be very easy to pattern-match once known. Obviously, for a beginner, I would just do:

    for (int i = 9; i >= 0; i -= 1)
[+] imron|9 years ago|reply
> I would not let that line pass code review.

At least it's not

    for (size_t i = 9; i >= 0; --i)
:-)
[+] pawadu|9 years ago|reply
The author has also been involved in development of "musl", a modern C11 compliant standard library implementation:

http://www.musl-libc.org

https://gustedt.wordpress.com/2014/10/14/musl-1-1-5-with-ful...

[+] stinos|9 years ago|reply
complaint

yeah I hear that often when talking about C11 :]

[+] verandaguy|9 years ago|reply
I've been following musl for a little while because of its support for C11 threads. I'm surprised that none of the bigger libraries haven't implemented that.
[+] SwellJoe|9 years ago|reply
It's been a decade or more since I've worked in C (and have never been a heavy C coder). Is "modern C" really a thing?

I mean, is there some subset of C that is safer than what I think of when I think of C? I know about stuff like reference counting techniques, rather than manual memory management, for example, and that goes miles towards safer coding. But, even so, the variety of ways you can shoot yourself in the foot with C are seemingly beyond counting. Are threads and async easier and/or safer now than 10-20 years ago, and with more direct language or standard library support? Is memory management in the standard library safer today? Are there concurrency primitives (beyond low-level interacting with epoll or kqueues or even fork or whatever)?

I mean, it's obviously possible to write reliable, safe, secure, software in C (Linux, Git, SQLite, all come to mind), but how much easier has it gotten? Would anyone choose C for a new systems project with no legacy baggage or dependencies, in a world with Rust and Go?

[+] SQLite|9 years ago|reply
Go has chosen to omit assert(), because assert() is frequently misused they say. Antibiotics are also frequently misused, but that is not a good reason to prohibit them. The omission of assert() makes Go a non-starter.

Rust seems more promising, but it is still not to the point where I am interested in rewriting SQLite in Rust, though I may revisit this decision in future years.

Some current reasons to continue to prefer C over Rust:

(1) Rust is new and shiny and evolving. For a long-term project like SQLite, we want old and boring and static.

(2) As far as I know, there is still just a single reference implementation of rustc. I'd like to see two or more independent implementations.

(3) Rust's ever-tightening interdependence with Cargo and Git is disappointing.

(4) While improving, Rust still needs better tooling for things like coverage analysis.

(5) Rust has "immutable variables". Seriously? How can an object be both variable and immutable? I realize this is just an unfortunate choice of terminology and not a fundamental flaw in the language, but I believe details like this need to be worked out before Rust is considered "mature".

[+] dbcurtis|9 years ago|reply
> It's been a decade or more since I've worked in C (and have never been a heavy C coder). Is "modern C" really a thing?

Well, I've done C off-and-on, sometimes heavily, since the days of VT-100s and DEC-Writers. Modern C has always been a thing since the 1970's. Its just that the definition of "modern" keeps changing :)

Yes, things are easier. It is possible to use the compiler features to write code that can be compile-time checked better than in the old days. A good IDE can use that to advantage to flag a lot of errors before you even compile. Yes, memory management can be easier. That said, most of the C I do now is for embedded microcontrollers with no OS underneath -- so memory safety and threads are DIY, and fork() is not a thing.

As someone once said to me about 30 years ago, "C doesn't get in your way." Also, with C, I can, if I wish, get extremely fine-grained control over memory layout. So currently I tend to use C on bare metal, and Python 3.5 whenever I can get away with it, with wee, tiny, C extensions to Python where necessary. C still has a place, but since the advent of Python my motto is: "Life is too short for C++".

[+] cygx|9 years ago|reply
Would anyone choose C for a new systems project with no legacy baggage or dependencies, in a world with Rust and Go?

Note that Go does not entirely compete in the same space, and Rust is only starting to gain traction.

[+] solidsnack9000|9 years ago|reply
> Would anyone choose C for a new systems project with no legacy baggage or dependencies, in a world with Rust and Go?

While Rust might be feasible, Go can't really provide libraries like SQLite: Go's C compatibility story is disappointing and awkward.

[+] santaclaus|9 years ago|reply
> Would anyone choose C for a new systems project with no legacy baggage or dependencies, in a world with Rust and Go?

I imagine it is easier to hire C programmers than Rust programmers, at the moment, especially in fields like embedded development.

[+] CmdrSprinkles|9 years ago|reply
> Would anyone choose C for a new systems project with no legacy baggage or dependencies, in a world with Rust and Go?

From a professional standpoint, you might as well be asking if I have infinite money and time. Nothing happens in a vacuum

But ignoring that, I would say, for me, it boils down to:

1. How important is performance? Is this a case where all that really matter is that it works? That I have a decent algorithm? Or am I going to be doing "real" optimization, even if only for a single platform? Even at just the "is my algorithm good?" stage I would lean toward C/C++ for systems work.

2. And this is the important one: Do I need this code to exist and be usable in one year? Five? Ten? Stuff like Rust might be fine for the one year frame, but for even as few as five I am going to want something established that I know will have support. And that means C/C++ for systems development (python, ruby, and js for scripting, and so forth).

[+] chrisd1100|9 years ago|reply
I'm really surprised by the "hate" for C that is appearing in these comments. What ever happened to actually enjoying the danger of getting low level? Is assembly also useless because it isn't readable?

There is a lot of great code written in C, and a lot of crappy code written in C. Because C doesn't protect you from yourself, it exacerbates any design flaws your code may have, and makes logical errors ever more insidious. So in this sense, the quality of the C you write is really a reflection of you as a C programmer, not the shortcomings of the language. Maybe you've been badly burned by C in the past, but keep an open mind and understand that C can be beautiful.

[+] yekim|9 years ago|reply
Hear, hear!

Unfortunately, C does get a lot of hate on HN. I suspect it has to do with this site's demographics. Many (not all) of the HN clan seem to be oriented towards / mostly familiar with web based technologies. I suspect that for many who have tried, going from a web dev environment to a C oriented dev environment feels like a robust shock to the system.

I'd also be willing to bet that there's an age bias at play here; C has been around, like, forever. It is certainly not the new hotness. Most (not all) people that I know who enjoy it and are proficient at it, are 40 or older. Much of the web based dev crowd that hang around HN seem to be in their 20s, and as it is a time honored tradition to poo-poo the ideas / methods / tech of the older generation(s), it's not surprising that C doesn't get a lot of love.

Yes, I realize I'm painting with broad strokes here. It'd be interesting to see a survey or three that correlates age ranges and tech used on a day-to-day basis to see if these assumptions or legit. (Anyone got any survey data up their sleeve they'd be willing to share?)

Me personally - I love it all. C, C++, Java, Python, Javascript, Rust, Haskell, Scheme, etc. Making computers do things for you, and for other people, by writing detailed instructions is quite possibly one of the funnest things in the world. Double bonus for getting paid to do it!

[+] Koshkin|9 years ago|reply
People have strong feelings about C because C is far from being perfect by modern standards and yet it continues to be the single most important programming language of our time. There is nothing wrong or surprising with people being frustrated about this fact. I only wish that there was less irrational hate on this forum in this regard.
[+] rileymat2|9 years ago|reply
There is also the issue of undefined and implementation defined behavior.

When developing on one platform for an extended period of time, it is human nature to forget which features are implementation defined as you use them day after day and then have unexpected errors/flaws when porting.

[+] jpfed|9 years ago|reply
>enjoying the danger

This is a hilariously bad attitude for any software that other people will use. When software crashes, people lose work and time. When software has vulnerabilities, bad guys take advantage of them and build stronger botnets. "The danger" isn't like wiping out when you're pulling a stunt; "the danger" is wasting the good guys' time and empowering bad guys.

>There is a lot of great code written in C, and a lot of crappy code written in C

This is true of any mainstream language, so it's completely uninformative and pre-emptively shuts down the possibility of any meaningful language criticism.

[+] pcwalton|9 years ago|reply
> So in this sense, the quality of the C you write is really a reflection of you as a C programmer, not the shortcomings of the language.

Can't you substitute "C" with just about anything in this sentence?

It's all well and good to talk about how "beautiful" a language is, but when people are literally endangered because of totally preventable security vulnerabilities that don't happen in programs written in other languages, it's hard to sway me as to how important this so-called "beauty" is.

[+] wolfspider|9 years ago|reply
Yes I totally agree with this and think that it's funny when people go off on the danger or C due to the fact I learned it along with many other kids between the ages of 8 and 12. At Oglethorpe University they ran a coding camp for children like me interested in learning C, QBasic, and ASM. At the behest of a fan letter I wrote to LucasArts as soon as I saw a C instructional class for younger people I had access to I signed right up. Being in Florida that was the closest me and my family could find and within our budget. I remember there was one kid in the ASM class who made a TSR for another more obnoxious student that re-wrote his hard drive until it physically broke. It was quite the statement but some of us apparently didn't consider these design features dangerous rather we considered them powerful. As far as writing good C code is concerned if a bunch of pre-teens could do it then I'm sure its possible for anyone given enough practice.
[+] pjmlp|9 years ago|reply
C was not the only way of doing it.

Many of us were enjoying the danger of getting low level with Think/Quick/Turbo Pascal and Modula-2.

[+] asveikau|9 years ago|reply
I think it's not being taught very much or used professionally as much as it used to. So people if they do have exposure feel the frustration of beginners, and never reach the point where they are productive with it and start to appreciate its strengths.
[+] progman|9 years ago|reply
As for me, I like C because I consider it a "high level assembler", as a backend for modern programming languages like Nim which profit from the C compiler's strong code optimizations. If there is any new hardware platform, there usually is a C compiler, too. This makes porting source code really easy.
[+] nickpsecurity|9 years ago|reply
" So in this sense, the quality of the C you write is really a reflection of you as a C programmer, not the shortcomings of the language. "

That's not true. BCPL language was specifically designed to get something to compile on a machine with no resources to run safety checks, programming in the large, anything. C tweaked it a bit to run on a PDP-7 and then PDP-11. The problems that are in C are there specifically due to the challenges of non-language experts getting a relic of a language to run on machines with no resources.

Later on, Wirth designed Modula-2 that was safe-by-default where possible (eg overflow checks), low-level, easy to read, faster to compile, allowed integrated assembler, and so on compiled also through a PDP-11. They did whole OS's in languages like that. There were numerous languages like that with similar performance to C but way safer and easier to extend. Then there's languages like SPARK 2014 that let you write code that it automatically verifies free of common errors. As in, they can't happen under any circumstances in such apps rather than whatever you thought of during testing.

Having seen those and knowing C's history (i.e. justifications), a number of us know its problems aren't necessary, are easy to avoid with better design, and you still get most of its benefits. Worst case scenario is wanting that but also wanting benefits of C compilers that received a ton of optimization work over the decades or its libraries. In that case, the better language can generate C code as a side effect and/or use a FFI with interface checks added. Still safer-by-default than programming C by hand.

Heck, there's even typed, assembly languages these days you can prove stuff about. Also work like verification of LLVM's intermediate code. So, even for low-level performance or something, C still isn't either the lowest, safest level you can get. It's just the effect of inertia of years of doing stuff with it as a side effect of UNIX's popularity and tons of code that would have to be ported. Again, you can use that without even coding in C at all past some wrappers. So, people liking safety & simplicity of Modula-2, Component Pascal, etc prefer to avoid it since we know the problems aren't necessary at all. Some want extra benefits of stuff like SPARK or Rust, too.

[+] sbov|9 years ago|reply
It's because C is terrible when it's not strictly necessary, and it's not strictly necessary for the vast majority of things people work on here.
[+] FrancoDiaz|9 years ago|reply
I don't hate C. I'd rather program in C than C++. There's a "uniformity" and simplicity about C that makes it beautiful. You've got structs, functions, and pointers...that's it.

I remember reading some of ID's engine code and admiring how well I could follow it and know what's going on. With C++ and other OO languages, it's much harder.

Don't get me wrong, I'm not going to write my next web app in C, and there's some obvious benefits to the features that C++ offers, but C++ ain't beautiful.

[+] marmaduke|9 years ago|reply
It's nice to see this perspective kept alive. I put some effort into a numerical library (github.com/maedoc/sddekit) in C99, and I didn't find the language lacking until I tried to imitate inherited interfaces with virtual dispatch by hand (empirically I can say, a poor move in C lib design).

I did find it useful to apply rules like only use uint32_t, double & bool as primitives.

My main wish is that it would be possible to opt into automatic const & restrict, as a compiler flag or pragma, so that something like

https://github.com/maedoc/sddekit/blob/master/doc/C.md#alias...

would be easier to do.

[+] ape4|9 years ago|reply
Goto is considered useful by the book:

The use of goto and similar jumps in programming languages has been subject to intensive debate, starting from an article by Dijkstra [1968]. Still today you will find people that seriously object code as it is given here, but let us try to be pragmatic about that: code with or without goto can be ugly and hard to follow.

[+] thegeomaster|9 years ago|reply
I've found goto to be a good way of dealing with exceptions in low-level C. For example:

    void* foo() {
        int handle = get_some_handle();
        if (handle < 0) {
             goto fail;
        }

        void* something = some_function(handle);
        if (something == NULL) {
            goto free_handle;
        }

        void* something_else = some_other_function(something);
        if (something_else == NULL) {
            goto free_something;
        }

        return something_else;

    free_something:
        free_something(something);
    
    free_handle:
        free_handle(handle);

    fail:
        return NULL;
    }
I've seen this pattern frequently in the Linux source code. I think this is an example of a case where usage of goto improves readability and reduces errors.
[+] zunzun|9 years ago|reply
I personally prefer Prehistoric C, which only has the two language keywords "ugh" and "grunt". Modern C has too many keywords for my taste.
[+] nabla9|9 years ago|reply
What alternatives there are for C/C++ if you want to write library that you can call from Python, R, Matlab, Java, Rust, Lua, node.js ... and have good performance?

Old ones like Ada and Fortran of course.

There are newcomers like Rust and Go. Are their C api's mature and portable?

[+] pcr0|9 years ago|reply
For someone who studied basic C/C++ in university and is interested in hacking around in C, should I read this over K&R?
[+] Waterluvian|9 years ago|reply
I've had no luck learning a language on its own. But I've had a lot of luck learning languages as part of something bigger. Like C# via. Unity, Swift via. 2D game dev in XCode.

Any suggestions on what I should apply C to as a way to learn it?

[+] kruhft|9 years ago|reply
The best book I had for learning more about C was titled 'Writing Bug Free Code For Windows' from the late '90s early 2000's. It contained a complete object oriented system using simple header tricks and data hiding plus covered all sorts of pre-processor tricks that aren't evident until you really dig into what C can really do. I'm sure it's impossible to find now, but recommended.
[+] qwertyuiop924|9 years ago|reply
Can any C programmers evaluate this book? I don't do a lot of C, so I can't really do it.

Does it advocate good best practices?

Does it talk about pitfalls?

Does it overemphasize new, possibly less widely implemented, features?

Does it do/not do anything else we should know about?

[+] frag|9 years ago|reply
I want to cry... remembering the old good time of C programming... ohhhh
[+] awinter-py|9 years ago|reply
Yikes. important words that don't appear in this: 'static analysis', 'verification'.

On the 'wow' side, had no idea there was a _Generic macro. Pretty cool.

[+] pksadiq|9 years ago|reply
I would recommend that anyone who hires a programmer should test his/her knowledge in C (especially in areas like code that produces undefined or unspecified results), even if the candidate is never going to code in C, ever.

If he/she knows these concepts well, that means he/she have invested much time, and probably know other things well enough (or can learn them easily).

[+] joveian|9 years ago|reply
I haven't looked at this updated version (site is busy :/) but the version I looked at a while ago is quite good.

The author's use of register to avoid aliasing is something I hadn't heard before and seems like a good idea in some cases.

Beyond the learning C aspects, I really hope that some of the author's suggestions for language extensions are implemented.