Hacker News new | past | comments | ask | show | jobs | submit login
Modern C [pdf] (unistra.fr)
392 points by brakmic on Nov 28, 2016 | hide | past | favorite | 385 comments



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.


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.


> "what does `public static void main` mean?"

The problem with this question is that there is a ton of stuff you need to understand before you can really answer that question fully. To know what public means, you need to understand classes, and visibility rules for classes. To understand static fully, you kind of need to know how c++ works, since it's equivalent to a bare function in a namespace. Void is the type of the return, which means it doesn't have so that's pretty straight-forward. Main is the name of the function that gets run when you run the program, which kind of requires knowledge of program entry points (assembly) either that or the understanding of what a library is. The simplest thing you can say is that it's boilerplate to signify what function gets run when the program starts, but that doesn't really explain any of the pieces.


Then why start teaching programming with Java in the first place when understanding those concepts involves an at least mediocre understanding of object orientation?

There are many more languages that implement a "Hello, world!" with one line of code. If explaining "public static void main" is too hard, maybe one is using the wrong tool.


This is an idea that many people resist. I think they mistakenly believe that programming languages are genuinely difficult to learn, and students must start on one that is marketable.


It's hard to teach an introduction to programming without teaching a particular language. If you have to pick a particular language, it makes sense to choose something the student is likely to use in the future. This is not hard to understand.


That's silly. If you can reasonably claim to know how to program, the choice of language largely doesn't matter. Someone who knows how to program in [first language] should be able to pick up how to program in any language one is likely to encounter in an industry position.

So the first language should be one that gives the student a firm foundation from which one can become that competent.

I've personally seen excellent introductions to programming using C (CS50x), Python (Think Python, MIT 6.001), and Scheme (SICP).


My point is that you likely learned programming by learning the basics of file I/O in C or Python or something similar. You didn't sit down and learn all about automata theory etc. first. You need the context of what C.S. is used for before these things make sense to you, so you start by learning a bit of programming. Since you must be definition have A first language, why not pick one that is most likely to be used?


That's a big part of many bootcamps' marketing model. Start in web dev, move on to other things.


I don't really approve of this view. CS students are going to be programming in current-gen languages like Java, Python, C/C++ and JS in industry anyway (C is perhaps last-gen...). I think academia should lie a couple steps ahead and introduce students to stuff that will help them advance the industry. Next-gen and maybe experimental/academic languages and tools. Teaching students current industry languages/tools is good for the individual student but bad for the industry as a whole because it causes stagnation. Universities are big enough that they ought to be able to rise above this tragedy of the commons and do something for the greater good rather than think narrowly of the individual student.


I always assumed that's why Python was so popular in universities. It's OOP & it's low on syntax.


Because, College Board.

I'm so glad I learned Lua before taking APCS, otherwise that class probably would have set me back 6 years.


Because it's stuff the will teach eventually and don't want to force students to learn a different syntax a few weeks in. I'm not sure if students can handle the syntax switch or not though.

For me and probably many others having it go unexplained was like dangling carrot in front of me. I went home and read more about it.


Java is a terrible first language. Python is even worse. They both require a good understanding of a ton of different topics to understand them as anything but magic incantations.


"The problem with this question is that there is a ton of stuff you need to understand before you can really answer that question fully."

Which is why Java is a terrible language for an introductory programming class.


It was the first object-oriented language that I learned, and some of my classmates' first language. It wasn't too bad to be told "Just type it for now; we'll get into object orientation next week and explain it then." From my own experience, I don't think it's a big issue if it's introduced correctly.


I suspect you are far more willing to learn by rote than some other students.

For a particular kind of curious student, "just do it because you have to" is a very fast way to them checking out and doing nothing at all.


I held onto the promise of a forthcoming explanation (and read ahead in the book, anyhow). Honestly, someone who can't deal with a little ambiguity when starting out on a new technology isn't going to last long in the field anyhow, especially with the amount of self-learning that you end up doing.


Agreed, for a number of students the promise to explain it later will suffice. Others - when given magic boilerplate that does strange, intended things - will recognize it for the black magic it is and will want to wield it as well.

They will dissect, examine and research it on their own and their learning will be much deeper.


Well, that's me in a nutshell though. If I don't get the answers I need soon, I get frustrated and it becomes nearly impossible to stay focused or motivated. With web development, I was lucky in that I had teachers that were willing to humor my curiosity, and I could also hop on the internet and IRC channels.

Self learning is the opposite of the classroom issues I had. I can start a webpack tutorial, it'll mention Make, and I can click links and chase down information, and just keep my stupidly ADHD brain entertained with more and more new new new things. This is how I have to learn things, apparently.


That sounds more like a problem with incoming student's expectations than with the teaching method. Almost every interesting (i.e., sufficiently complex) subject isn't arranged in a pedagogical hierarchy. You can't explain history from the first day, because you'd have to explain where that knowledge came from. You can't explain math without pushing things for later because students are not prepared for generality on day 1. You can't explain human biology by describing various cells. Etc..

If a student is going to 'check out' because they didn't get a question answered, then they're probably just going to fail. See your teacher after class, ask a friend, look it up online... If are a 'curious student' doesn't see these very obvious methods of satisfying their curiosity, then they're probably not worth a teacher's efforts.


I've always thought Java was an awful introductory language. It's a decent teaching language but in order to actually understand what's going on and why, you need to already be familiar with huge swathes of modern computer science.

Python is even worse, because it's so high level and so much happens 'by magic'. Don't get me wrong, it's a great teaching language because it does cover so much ground, but it's terrible as a first introduction for a new programmer.

They need to start with something simple and fairly concrete. Maybe even start with a simulated 'toy' assembler (first semester CS101 = Zachtronics games?) then something like Pascal to teach the basics of control flow and sequential processing.

Once students understand primitive data types, control structures, functions, and compound user-defined data types, they're probably ready to learn some OO.


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.


Do you have any recommendations for books?


I learned C from Kernihan & Richie (K&R) under supervision of experienced developers. I was really lost for a few days, the book is kind of terse. I had been developing in assembly for several years, so C felt like a really high level language. I still recall being baffled by pointers and handles, dealing with segmented memory (this was 386 days, Turbo C on the PC, MPW on the Mac for C & Pascal) It was about a month before things "clicked" and pointers made sense.

I think the follow-up C book I read after that was "Learning C". I don't recall the author's name(s) but I think it was from two brothers. Dan, something? (I'll check my bookshelf when I get home tonight and update here...).

I learned C++ initially as just 'C with Classes'. It was informal, by joining a C++ project already underway, and following the senior developer's guidelines. Instruction was informal, and under supervision of others, yet I hadn't made a complete mindshift to OO until probably six months to a year after using it.

I liked "Thinking in C++" (Bruce Eckel @ http://mindview.net/Books/TICPP/ThinkingInCPP2e.html ) quite a lot -- in fact I re-read it several times about six months apart and it seems I always pick up some new nugget of knowledge every time through. That or I forget what I don't use. Possible.

Keep in mind the newest of these is a decade old, at best. Surely not "modern" C. But after completing a basic tour of K&R C, a reader should be ready for the book at the top of this discussion. And that will transport them into this century.

hope this is helpful.



K&R is amazing, really makes you appreciate how simple C is as a language


Is that still relevant to how c looks and acts nowadays? I know you will learn a lot from it and it's an excellent book, but surely there is a better reference that is more up to date. Maybe not though.


I think it's still really helpful to learn C and as a reference, but the code has a very terse and difficult to read style that I wouldn't recommend actually coding in, for example this is introduced in the first chapter, before anything is even studied in depth: https://www.dropbox.com/s/4hbwyid5jwen43t/Screenshot%202016-...


I cannot recommend "C Programming: A Modern Approach" [1] enough.

[1] - https://www.amazon.com/C-Programming-Modern-Approach-2nd/dp/...


CS:App book, from 15-213 at CMU goes well with K&R to teach unsigned/TMIN/Float/pointers at the asm level, gdb, Valgrind to check for mem leaks, compilation gotchas and more. http://csapp.cs.cmu.edu/



K&R is great if you already know how to program.

If you're new to programming, Harvard's CS50x on edx is probably the best introduction to programming online and uses C. You'll learn enough to breeze through K&R and then some.


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


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.


I agree they are great examples to test understanding, but if they're going to be present in the first chapter of a pedagogical book, they need a disclaimer. People copy things.


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.


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)


Everyone should write your second example. The first does nothing but confuse. C has enough hazing rituals without garbage like "-->".

The fewer tricks and patterns you use in C, the higher chance actual bugs have of being caught. Cutesy tricks like "-->" confuse human analysis and gain nothing.


This is about weighing correction versus readability. In the "arrow operator" version, the readability is decreased; in the "proper" version, a type cast is required, and this can lead to bugs with values greater than 2^sizeof(ssize_t).

Obviously, I just follow the convention when contributing to an existing project.


this can lead to bugs with values greater than 2^sizeof(ssize_t)

The range of indexable array elements is not only constrained by the unsigned type size_t, but also by the signed type ptrdiff_t, so you could always go with the latter instead of the non-ISO ssize_t.


...values greater than 10?


Do you even realize that these two loops are not equivalent? One of them starts at 8, then other one starts at 9 ...


Right, my bad. I tend to write loops in the former style, thinking of them as reversed(range(9)). This is another advantage of this style. I should have been more cautious when writing the second one.


That first example reads like a horrible pun.


now do the second example with an unsigned type.


The point is that a beginner does not need to care about signedness. When you get to that point, you can take the time to explain how to loop properly over it.


> I would not let that line pass code review.

At least it's not

    for (size_t i = 9; i >= 0; --i)
:-)


I agree. For this reason I wish the author would promote (no pun intended) the use of signed arithmetic.


Your for loop works, whereas the one in the parent comment would run forever, correct? Is there something I'm missing here?

edit: Ok, looking at it again the parent example is probably going to overflow or something right?


Your for loop works, whereas the one in the parent comment would run forever, correct?

The other way around: size_t is an unsigned type, so decrementing 0 will wrap around to SIZE_MAX, a value that is positive as well as greater than 9. This means counter to your intuition, the first loop will terminate and the second one won't.


> Is there something I'm missing here?

Yes (see other replies), and it's precisely the reason why this code shouldn't pass code review.

At first glance the first example looks like it should fail but in fact works. The second example I provided looks like it should work, but in fact loops infinitely.

The first one is tricky and nifty, but prone to bugs. Using signed counters is better way to go about it.


The example with ">= 0" is an infinite loop, a size_t will always be greater than or equal to zero; it's unsigned.


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


complaint

yeah I hear that often when talking about C11 :]


hah! fixed that for you.

Who would complain about something as wonderful as C11, outside it not being available for your compiler?


Who would complain about something as wonderful as C11

Beats me, but see rest of thread I guess :] In all fairness, sometimes it's the right tool for the job, sometimes it isn't


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.


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?


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


>The omission of assert() makes Go a non-starter.

A small syntactic sugar you can trivially implement yourself makes Go a non-starter?

Go doesn't include assert in the language because you're supposed to do better than assert. Assert easily allows lazy programmers to let their programs freely crash without properly handling error conditions. Go prevents you from compiling with unused variables, and that combined with the Go documentation goes a long way towards teaching new Go programmers how they're expected to work.

And neither Go nor Rust could possibly be good fits for SQLite. A Go hello world is bigger than all of SQLite while an idiomatic Rust one is on par, and neither's nearly as portable as the current C implementation, one that is both programatically and battle-tested like pretty much nothing else in the world.


> Assert easily allows lazy programmers to let their programs freely crash without properly handling error conditions.

This is what he meant by misuse. Properly-used assertions are meant to document and check conditions that were thought to be impossible by the developer. Not just unlikely, or illegal, but impossible. If a condition is possible, and you check it with assert, that's a bug.


I came to detest assert when I was working on a project with another guy, who used it generously, as a substitute for assertions in (non existing) unit tests. Nothing would annoy me more than working on my code, running it, and having all sorts of weird assertions pop up all over the place from this guy's code.


The biggest problem I've seen with assert[1] is putting functioning code inside one and then not knowing why your code no longer works with NDEBUG.

[1] http://en.cppreference.com/w/c/error/assert


Those are panic territory, which carries a different connotation than the word assert (which has very likely influenced in its very widespread misuse).


>Go prevents you from compiling with unused variables, and that combined with the Go documentation goes a long way towards teaching new Go programmers how they're expected to work.

Things like this make me not want to use a language. Want to comment a=b; to a=3;//b temporarily? Too bad, either assign b to 3 or comment out b too, and if b was the only variable to make use of c, same for c, and so on. Same obnoxious nonsense as Java not letting unreachable code exist, making me have to comment out the rest of the function body if I want to put in a return in the start to test something, which happens often enough that it's a pain.


You can always use the black hole variable _

That mild inconvenience (which I almost never face while writing Go code after getting accustomed to the language and getting my editor to run goimports on save) has a big RoI in safety and program quality, which are much loftier goals than short term code-writing convenience.


I doubt the RoI is much more than if these were just suppressible warnings instead of errors, which work out great in C#.


>which work out great in C#.

They're greatly ignored in the real world.


> (5) Rust has "immutable variables". Seriously? How can an object be both variable and immutable?

Variables have been called variables since the dawn of time, i.e., the lambda calculus, which doesn't even have assignment. The name derives from the idea that for every invocation of a function, a variable in its definition may be bound to a different value, hence it "varies" at runtime.


Furthermore, it's terminology from mathematics, where, just like the lambda calculus, mutation isn't a thing: https://en.wikipedia.org/wiki/Variable_(mathematics)


> Rust has "immutable variables". Seriously? How can an object be both variable and immutable?

"Immutable variables" is frequently used, true, but the official term is "immutable bindings".


Reassuring news. Thanks.


To elaborate slightly, there's three components here:

    let x = 5;
    let mut y = 6;
The let statement binds a variable to a value:

  * x and y are the variables
  * 5 and 6 are values
  * let does the binding
You can have an immutable binding, like x, or a mutable binding, like y. But most people turn "a bound variable" into "a variable" (or "an immutable variable") and "a mutably bound variable" into a "mutable variable".


Pragmatism over "Oooh Shiny", one of the reasons I have huge respect for the sqlite project ;).

Rust looks pretty decent but I'm still in the wait and see stage as well.


I was trying to teach myself some Rust and found the state of the documentation to be very frustrating. The core language is decently documented with the manual, but the standard library documentation was out of date in many places, most annoyingly in the first few hits on Google.

I found 5 different ways to read a file on Google, and only one of them still worked. Plus I saw the release notes on the newest version that the syntax that worked is now obsolete in favor of a new operator.


The docs should never be "out of date" as in not working, though some parts don't have more than type signatures yet. Working on it. Please file bugs if something is not working, or swing by #rust-beginners, we love to help!


What are you missing from the up-to-date API reference[1]?

[1] - https://doc.rust-lang.org/std/


I was trying to read a file one line at a time (reading only the first few lines of a very large file), and it turned out to be somewhat difficult as the built-in functions seemed to be really keen on operating on the entire file at once.


You still have to be a bit careful about finding old posts and old docs on the internet about Rust, given that many APIs have only been stable for a short while.

In general, unless you know your source is up to date, I'd recommend ignoring the internet at large completely when it comes to Rust APIs and just focusing on the official docs for the release that you're using. They're plenty good enough, though you do have to get used to navigating them.


Really? I read the documentation for file I/O directly out of the Rust online docs and put together a complete serialization module for a project I'm working on. It was actually quite straightforward.


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

I can see the latter, but why the former? Package management that has understanding of language dependencies is a huge productivity booster.

> (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".

A variable/binding is mutable, the object is not. I don't see the problem.


That's an interesting take. I don't think I've heard that complaint about go from anyone (other than you, I think, in a previous HN conversation). I've worked on code that had assert (Squid before the C++ rewrite, and it used assert correctly to the best of my knowledge), but I never considered it vital to the end result.

Have you read about panic (https://blog.golang.org/defer-panic-and-recover)? I'm not meaning to assign you homework, as I know you know more about this than I do, I'm just curious what about assert makes it mandatory for you...panic in go does require you to write your own error check (presumably just an if, for assert-like behavior, but you could do more complex error-handling).

All of your other comments are certainly valid reasons to choose C. Though I like cargo, and I suspect C would be well-served by something similar.


> 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++".


> "Life is too short for C++"

I've said for many years "Life is too long to write C++ for a living." And I haven't written C++ for many years.


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.


Also if anyone is talking about systems programming, it would be beneficial if you specify what that means.

I've seen people consider "systems" programming as relating to building web services. Traditionally systems programming would be more kernel level and utilities/daemons.

Rust might be OK to start using for the latter, but its very much not yet where I'd start using it for new things for either of the traditional setup.

I know everyone is gung-ho about rust, but for the traditional systems programming crowd, its very new. Given how much it changes I would NOT want to bet on using it for at least 5 years. You don't switch just because something is better on memory safety. That is a nice to have thing. But changing 40ish years of things isn't something that you do without planning.

Also, in my opinion Rust doesn't go far enough. I'd rather we move to things like Idris where I can prove much more than just memory safety. Rust is basically Ada/Oberon/Modula for the modern age. Nice, but ultimately not the first time this has happened.


Which is why I am more willing to bet on Swift and .NET Native getting more widely used than Rust.

Not to misrepresent the work the Rust guys are doing, it is great what they are doing and I have lots of fun dabbling on it.

But new system programming programming languages tend to be adopted when an OS vendor tells devs, either use it or go code elsewhere.

On OSes that have significant market share, devs tend to learn the new language instead of waving it away.


Rust is more focused on C compatibility and simple (dumb) operational semantics than Idris and other dependently typed languages. Rust will never require GC, for example; maybe Idris doesn't now but is that a focus for them?


Idris can compile to c, it requires no runtime. You're probably thinking of Haskell with its RTS.

Idris is closer to ocaml in that regard.

I cough may have already tried using idris in a kernel module. (for fun, not for serious)


I'm not thinking of Haskell. Idris compiles to C but there is an open question there, with regards to how memory will be handled now and in the future. Swift does not need a runtime either, since it uses automatic reference counting for everything; but it has not gone as far as Rust, which defaults to stack allocation everywhere.

This is not to say I doubt the ultimate approach of Idris: with dependent types you can do everything Rust is doing, and more. It's just a matter of project focus and ergonomics.


And how did it go?


It worked surprisingly enough, well enough to make me consider trying out some gpio stuff to play around with.

From there I'll let you know, I'm still learning Idris and dependent types so I'm a fairly boring test case. This was more of a: lets try it and see what goes kaboom test.


> Given how much it changes I would NOT want to bet on using it for at least 5 years.

Rust may be changing, but it's almost entirely _additions_. We've been stable for roughly 18 months now.


Sorry should have been more clear, my meaning was more around things like cargo and tooling rather than the language proper.

Don't take any of my criticism too harshly either, I do like and use Rust for side projects. But it is about 3 years away from where I'd consider using it for anything. I'm a bit more conservative than most here seem to be. Funny part is I'm the maverick in using new stuff compared to the people I work with.

Keep on trying to improve things either way!


Sure, "systems programming" has a variety of meanings. But, there are a lot of areas where C was the obvious choice in the past but Go might be a suitable replacement today. Areas where concurrency and safety is maybe more important than raw performance: Databases (and there are many now written in Go, including alternative types of databases like time series, key/value, etc.), servers, etc. I tend to consider those systems level, even if they are not kernel level or running on bare metal.

Obviously, if one defines "systems programming" more strictly than that, and only mean code that directly interacts with hardware or has hard realtime requirements (which also probably means it must interact directly with hardware), then Go does not make sense in that category. But, C has given up a lot of territory over the past several decades. When I first started programming, C was the language you used for writing almost any real application...if you weren't using assembly. That has changed a lot in the intervening years, and almost no one would think to use C for GUI apps today, for example.


Databases have been written in Java, too; but the combination of performance unpredictability (not something Go has addressed, since it is also garbage-collected) and a bad C compatibility story has proven to limit the reach of these projects.


> Databases have been written in Java, too;

Your comment seems to imply that java hasn't been successful for db development. HBase, Cassandra and ElasticSearch would all disagree.

Yes, there are situations where GC is not appropriate. But I think those situations aren't as common as they are made out to be.


Well, I'm thinking of RDBMSes, of which there are some written in Java -- but I think you would agree, they have never gained much traction relative to Postgres and MySQL (or Oracle in enterprise).

HBase and ElasticSearch, and even Cassandra can be seen as somewhat niche today. This doesn't mean they aren't doing something of value; and it doesn't mean using the same platform for OLTP and OLAP is the way of the future; but it does mean the competition in their space is more limited and less indicative. There are ground-up rewrites of Cassandra in non-GC'd languages out there, for what it's worth.


True. Even very successful database projects in Java have some issues with GC pauses.

Go seems to be, IMO, taking the things Java is best at and trying to make something better. Basically, how would you build Java today if you could do it again?


> Basically, how would you build Java today if you could do it again?

Take Modula-3 (1986), change the keywords to lowercase and re-brand it as "Cool Language X".

Still better Go than C.


Given that Go is quite similar to Oberon, I don't fully agree.

http://www.projectoberon.com/

http://people.inf.ethz.ch/wirth/ProjectOberon/index.html

http://www.astrobe.com/default.htm

http://wiki.osdev.org/Go_Bare_Bones

It just needs someone to port that Oberon code to Go, maybe people will then stop discussing how suitable Go is for systems programming.


As pjmlp said, Go was a partial clone of Oberon-2 per Pike. Modified to suite modern requirements and whatever other designers put in. Oberon's were used in numerous operating systems. Latest was A2 Bluebottle which seemed faster than my Linux desktop despite being a GC language and running in full virtualization on Linux. The original language in that family, Modula-2, was even hosted on a PDP-11 like C.

Therefore Go is closer to that space than people want to admit. A change of its runtime or compiler would let it do operating systems. Even Java (JX) and Haskell (House) do operating systems. I'm sure one could that is derived from and similar to a language designed for implementing OS's. :)


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


Having written a project that loads Go plugins to a C app (namely redis), it's not great but in recent Go versions it's not too bad as well.


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


Also Rust doesn't support compiling to certain architectures like the Xtensa ISA used in the popular ESP8266 and ESP32 wi-fi chips.


Also, Rusts standard library is simply too big for many embedded systems.

Last time I checked the compiler didn't support bare-metal and resource constrained builds without using crazy hacks. Although the situation may have improved since then.


I think it now amounts to adding a line to the file that tells it to not use the standard library. I've seen smaller projects that did it, and it was nothing like crazy hacks.


Fair enough, but it didn't used to be like that:

https://scialex.github.io/reenix.pdf


Which part of this paper is the part you're worried about?


The second half of the paper is mostly about challenges in using Rust. Some were related to the way the language handles data (i.e. challenges of sharing data among threads without using unsafe code), but there was also some that are related to the compiler and the standard libraries.

The authors mentions allocation in particular (to be honest I did not really understand his problem). He also writes about the standard library being too large while the tools don't support bare metal projects out of the box and that the compiler emits a lot of code that depends on the standard libraries.

Of course, the paper is more than a year old so all these issues can have been fixed by now. Maybe you can comment on that?


Yeah, that's why I asked! It's been a while since I read the paper, and skimming it, it wasn't clear which part you were asking about. Seems like mostly 3.2 and 3.3?

3.2.1 is about inheritance. That still may or may not be coming, exactly. It's just not a huge deal to more experienced Rust programmers, though we can and will be doing a better job of explaining alternative design strategies.

3.2.2 is about anonymous enums. The author is right that this is a little boilerplate-y today.

3.2.3 is about static data. The answer is the lazy_static crate, which is compatible with no_std; I use it in my own OS a lot. It works almost exactly how the author wants it to, though it doesn't literally use Option<T>.

3.3.1 is about allocation, which is where you said you didn't really understand. So let's back up a minute: Rust's standard library is built in layers: the foundation is libcore, and then libstd is layered on top. Libcore is suitable for OS development, and includes nothing about allocation at all. Libstd includes heap allocation. This is referencing Box, which is in libstd. If there isn't sufficient memory, the the implementation of heap allocation in libstd will abort. That's still true today. However, this is one of the reasons that libstd isn't meant for this kind of development; it's meant for general application development. So, while the author's point is still sort of true today, I'd argue that it's going about it wrong, and it's also not a limitation of Rust generally.

3.3.2 goes on about this some more.

There's some stuff about Cargo in 2.8.3. Many OS dev projects augment Cargo with a Makefile, but depending on context, you can use only Cargo today. My (still toy) kernel does, for example. This section is extremely unclear as to what problems they faced, so it's hard to tell what's changed between then and now.

Other than that, I'm not totally clear on the "standard library being too large" and "emits a lot of code that depends on the standard libraries" bits you're talking about. I don't have time to fully re-read the entire paper right now, if you could give me specifics, happy to elaborate further.

A lot has changed since April 2015, including the release of Rust 1.0 :)


Thanks for the answers!

> Other than that, I'm not totally clear on the "standard library being too large"

IIRC their Rust kernel hit some size barrier (for the bootloader?). The author tried to build a smaller standard lib to overcome this, which was a bit of work.


No problem!

I didn't see that from skimming; if it was bootloaders, well, they have a max size of 512 bytes (on x86) so virtually all bootloaders load in stages, with a tiny bootloader that loads the "real" bootloader.


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


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.


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!


It's not just that HN does a lot of webdev. It's that even in its element as a "systems language" it's virtually impossible to write 100% safe C/C++ code and guarantee that it will remain safe into the future, even for experts who are making every effort to do it right. There are just too many gotchas with "undefined behavior" and too many clever compilers out there waiting for you to make a mistake.

One only needs to look at something like the OpenSSL library to see the problem. You really need to hammer the hell out of C code with something like AFL to get at a reasonable majority of bugs - and you could hammer out every last bug one day and then the next day a compiler starts optimizing away your safety checks. This isn't a theoretical problem, this actually happens. Code rot is a very real problem in C++, to a far more massive extent than any other language.

http://blog.llvm.org/2011/05/what-every-c-programmer-should-...

http://www.kb.cert.org/vuls/id/162289

Personal opinion here, but with few exceptions C/C++ are inappropriate languages for starting new development at this point. I realize the tooling is not there yet but I would rather see something like Rust used in almost all performance-sensitive applications where C/C++ are currently used. Unless you can guarantee that you are operating in a trusted environment and will only ever operate on trusted data, C/C++ is just not the right language for the job.

Yes, it's fast, but at what cost? I would gladly give up a massive fraction of my performance for better security and portability - and that's why I program Java. Not that Java is perfect either, but at least I can be certain that the sands aren't shifting out underneath my programs.

I would actually say that porting the Linux kernel to Rust would be very high on my wish-list at this point. I am well aware of just how enormous that task would be and I might as well wish for a pony too, but it gives me heartburn to think of just how much C code is sitting there operating in the most untrusted of environments on the most untrusted of data. I have every faith in the kernel guys to do it right, but the reality is there is a lot of attack surface there and it's really easy to make a mistake in C/C++. It may not even be a mistake today, only when the compiler gets a little more clever.


> Yes, it's fast, but at what cost? I would gladly give up a massive fraction of my performance for better security and portability - and that's why I program Java.

While I agree with the sentiment, a problem with Java is that you're dependent on a runtime environment with a fairly consistent history of vulnerabilities, right? [0][1]

> Personal opinion here, but with few exceptions C/C++ are inappropriate languages for starting new development at this point.

Maybe, but now there's SaferCPlusPlus [2]. At least it may be a practical option for improving memory safety in many existing code bases.

[0] http://www.cvedetails.com/product/19117/Oracle-JRE.html?vend...

[1] http://www.cvedetails.com/product/1526/SUN-JRE.html?vendor_i...

[2] shameless plug: https://github.com/duneroadrunner/SaferCPlusPlus


I think the bottom line is that it simply takes too long to actually become fluent in 'C'. This makes it a horror for open source, where you have to draw on volunteers.

You simply can't just write 'C' without making sure all the details that are necessary to run safely are in scope at all times.

While I agree - the OpenSSL cases certainly show the weakness of the language, there's just no way I'm gonna hang all that on 'C'. Writing protocols and protocol drivers is a fairly tedious sort of skill to attain. We inevitably descend into a counterfactual ... "fantasy" ( sorry; don't mean anything insulting by that - besides I do it too - it is just the nature of counterfactuals ) in which 'C' ends up the villain, when it was a much richer set of failures in play.


I don't think anyone can demonstrate that it is virtually impossible to write 100% safe C code. Sure, you can always find people who don't know how to write a proper safety check. That doesn't mean nobody knows. You can always find people who ignore or don't know about best practices, but that doesn't mean everyone's like them. And you can find people who write goto fail; and ignore the warnings about unreachable code posted by any half-decent compiler or static analyzer, yet there are people who will pay attention to that kind of stuff. People scream UB, UB, C is evil because of UB, but goto fail is essentially a logic bug, something you could have implemented in any language. It doesn't need UB to happen.


> That doesn't mean nobody knows.

Yep. Have a look at the code coming from the OpenBSD crowd. Those folks really know how to wield C. It involves, first and foremost, writing readable and straightforward code, in an attempt to make any bugs obvious. The OpenBSD folks also insist on code review, which also helps.

And wrt tooling: C has some of the best tooling around of any language. GCC, Clang, and Visual C++ can all do some pretty decent static analysis, and then there are tools like lint and Frama-C, and tools like valgrind. Coverity also offers free static analysis for open-source projects. Make use of all the tools available to you. Testing is also important. Shoot for 100% code coverage (see SQLite3, for example, which has a massive test suite).

As you say, one of the requirements is to pay attention to warnings and fix them. In compiler parlance, "error" means "I can't compile this code" while "warning" means "I can compile it, but it's going to misbehave at runtime".

And here's something about undefined behavior: it's possible to know which behavior is undefined and to avoid it! Not every C program is riddled with undefined behavior.


I think that you have the formulation backwards. You claim that people can just write better, and should attain perfection.

> I don't think anyone can demonstrate that it is virtually impossible to write 100% safe C code.

I think most people come at the other way. Most people are aware that they are fallible and wants tools to help with that. Most people strive for perfection and none will ever actually attain it.

> I don't think anyone can demonstrate that it is virtually impossible to discover errors safely in C code.

There is a huge difference simply moving from C to C++ with exceptions. The type system in C++ can detect several classes of errors at compile time and prevent then grom going into the results.

Then for runtime problems if an underlying functions throws, it cannot simply be ignored. Any programmer can miss a single statement, or worse refactor a function with a void return to one that returns and error code (which then results in every caller ignoring the return value). However, it takes a special kind of malice to use something like carelessly catch(...) in C++ to disregard exceptions so that runtime errors are avoided. C++ with exceptions has more sane defaults because it fails fast and the failing itself doesn't need tests until it starts doing something meaningful.

Now imagine the advances in error detection moving to languages that catch additional classes of errors.


> which then results in every caller ignoring the return value

And a whole load of compiler warnings. Worse yet, people who ignore warnings might ignore them.

> Now imagine the advances in error detection moving to languages that catch additional classes of errors.

Languages don't catch errors, tools do. The C tooling has been and still is constantly improving.


Lint was created for C in 1979 as the language authors saw how easy it was to make errors, static analysis is still largely ignored by the majority of C developers nowadays.

https://www.bell-labs.com/usr/dmr/www/chist.html

I am yet to see it being use in enterprise C code.


In projects with centralized build scripts, like most projects, hopefully they have -Werror or its equivalent on by default. I was speaking about the case were a group has systematically ignored warnings and they are already beyond fixing. This is a depressingly common state for many shops. The best fix I have seen to enable as many warnings as possible and treat them as errors as early in the project lifecycle as possible. For whatever reason C++ shops are much more likely to do this than C shops in my experience.

If the compiler isn't the "language" enough for you, then please explain how to write a buffer overflow in Javascript?


So I see this argument as "should the tools catch these things?". I suppose that would make some people feel better. But the fact is, when you're in the seat, it's up to you to make sure you Do No Harm.

But please be aware - generalizing all failures and integrating them into the tool suite is a pretty daunting task. Perhaps the economics of it make sense. But if you're stuck writing 'C', especially on legacy code bases with legacy tools, you're stuck, and there's only the one thing to do...


That did sum up my argument well, same one extreme you are taking.

You don't need the compiler or exception to cover all your errors. If you know something would be too costly to integrate in these mechanisms then you are free to disregard it. I have written throwaway code that did gross things with pointers, memory and system specific resources. But if I want code to last and be maintainable I do my best to get the compiler to watch my back.

This also works well when interfacing with legacy C. If the new code can be written in composable and unit testable classes, then you can prove (only to the extent of the quality of your automated tests) that problems are in your code or in the legacy code as they arise. Then when you find problems in legacy code, try to break a piece out and replace it with another class, even a a big ugly one just so you can get some unit tests in there. Then you can break the big ugly class into smaller, cleaner, composable and well tested units.


This again. :) I think there's an angle your side of the discussion is missing on this. You might, with enough experience or team talent, be able to consistently write good code in C without defects. You might be able to do that up to millions of lines of code if your project becomes a Linux. However, the vast majority of projects will involve something along these lines:

1. The team is average or below since they're affordable or the work kind of sucks. This often happens in practice even with smart coders because the deadlines force them to move too fast with too little QA. Product might still have high impact, though, esp if it's widely-used product or service. The language itself preventing common problems is helpful here.

2. It's a FOSS project made by people that want to get stuff done without learning tons of rules for working around C's issues or stopping every common operation to prevent language itself from killing their project. I'd say vast majority of projects don't need whatever absolute advantages like max performance that C has over safer languages. Again, the language could be helpful.

3. Either of the above given the effects of time where new contributions come in that work against a codebase that fewer and fewer people understand due to organic growth. The language itself can be helpful with a combo of type-safety, programming in the large support, modules, etc. Better support for safer modifications of stuff you barely understand. Rarely a problem for Ada and Eiffel people if the team was even half-competent because the compiler simply demands it.

There's embedded people that can do one-off or steady projects however they like with enough time and tooling to get it right. ArkyBeagle appears to be in a category like that if my broken memory isn't fooling me. Then, there's vast majority of programmers either in the corporate crunch, scratching an itch barely caring, or fixing something they barely understand. Human nature will push defects in from all these directions. The tooling, if designed with human nature in mind, can prevent a lot of them automatically and aid efforts to catch the rest.

Hence, my opposing C language in favor of safer-by-default system languages. Especially those that avoid tedium of constantly watching out for dangers of most-common operations. Gotta work with human nature rather than against it. A hard lesson I learned after years of failed evangelism of high-assurance INFOSEC. Now, I exclusively look for ways to embed it seemlessly into stuff with other benefits listed. Much better responses on that. :)


Well, I guess you missed the Linux Security Summit:

http://arstechnica.com/security/2016/09/linux-kernel-securit...


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

As someone who went the "other direction" (Java -> Ruby -> Javascript) I can say that a lot of it has to do with the accessibility of the ecosystem rather than the language itself. This could absolutely just be my filter bubble, but I've noticed that the communities surrounding Ruby, Python, and Javascript seem to go above and beyond the call of duty when it comes to making libraries easy to use, documenting those libraries, building and refining the tools, and so on.

I know there are good tools out there for C development. I know there are good learning materials. I know there are communities out there dedicated to writing good C code (Shout-out to /r/c_programming on Reddit. Love those folks.) But I can't sort out the signal from the noise, because there isn't a lot of discussion about C programming happening in the online spaces I'm familiar with. As a counterexample, there was a _fantastic_ article on here the other day about "writing your own syscall" in Linux. Yes, it contains a lot of hand-holding and overexplanation, but that's useful for me because I haven't built up the mental model to parse a more terse explanation.

In fact, I think this is how having "the new hotness" change every couple years has been helpful _in some respects_- there's an incentive for lots of people to write blog posts, tutorials, and articles about how to properly use the latest and greatest tech, there's active development going on as people forward-port functionality (and therefore plenty of opportunity for devs to make meaningful contributions and have meaningful discussion about "how to write code using this language/library/framework"). For a short period, both the "old hands" and the newbies are in the same boat, and this is unbelievably useful for training up the next generation of developers.

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

Same here, friend. :) For what it's worth, I wish there were more of this attitude floating around the Internet.


It gets a lot of hate because the majority of developers are not embedded developers, kernel developers, or doing anything involving hardware. The other reason, IMO, is that to do anything that's actually kinda cool or fun in C you have to get pretty adept, so it's probably just written off as an old, boring language.

Personally I'm in my mid-20s and quite enjoy working in C. And for things like bit manipulation it's much easier than in higher level languages. I suspect at some point even the smallest MCUs will be able to run Rust or Go, but until that happens there is still a place for C/C++. Haters can hate but that won't change the fact that C is still the most widely supported language for embedded platforms (and Linux, the other elephant in the room).


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.


Well, the haters could always reimplement the whole infrastructure in their language of choice, wouldn't they?

It's been done at least once before for ideological reasons (and in C none-the-less) by the FSF. It should be even easier to give it a go in modern languages. I bet you can even get funding if you can write a compelling case that the wheel is actually broken!!!


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.


Undefined behaviour actually isn't the monster that most C language lawyers want you to believe it is. With tools like valgrind, address sanitizers and modern debugging toolchains, most of these issues can be caught. Compilers are also mature enough to issue warnings about the use of uninitialized variables, missing return statements or mismatched printf specifiers. Heck, Clang maybe has more than 250 -W options.


In theory, a good fraction of these can be caught. In practice, these issues keep coming up in production again and again and again.


Knowing your tools and compiler switches is key. The reward is that the final production code can be very lean and performant, without any runtime penalties to provide safety.

Most people who complain about the dangers of C probably have used it in an unprofessional setting without any additional tooling. It's a bit like saying that all RWD cars are dangerous just because you've once driven a '92 BMW, disregarding any technological advancements since.


> Most people who complain about the dangers of C probably have used it in an unprofessional setting without any additional tooling. It's a bit like saying that all RWD cars are dangerous just because you've once driven a '92 BMW, disregarding any technological advancements since.

Actually, most of the people I know who think C is a problem that needs fixing are longtime professional compiler developers and people who work on security critical codebases. In fact, I don't know any compiler engineers who don't have serious reservations about C and C++. Those people know more about tooling and instrumentation than virtually anybody. It's precisely that knowledge that leads one rapidly to the conclusion that there are serious flaws in C for secure software that can't just be papered over with tooling.

It's usually C++ enthusiasts who are the ones trying (unsuccessfully, IMO) to argue that undefined behavior isn't a problem in practice.


Are you sure you aren't mixing causes and consequences? I'd say that this is actually because undefined behaviour is hard (didn't say impossible) to get right at the human level that tools were, and still are, being developed.


UB and IB ( implementation-defined ) have not been a problem for me for a couple decades now. No advocacy here - I started using C because it was about all there was - but it's just a learning curve.

There was no direct cost to me because I was getting paid to learn this stuff on the job.


> With tools like valgrind, address sanitizers and modern debugging toolchains, most of these issues can be caught.

Most of these tools require support from an operating system. This is not the case when you do kernel programming. For some reason even existing tools are not popular among kernel programmers [0].

IMO, there are bugs that can be caught well a compile time without my effort, so why should I waste time on catching them at runtime?

I would better make love to compiler instead of having sex with debugger.

[0] http://lwn.net/2000/0914/a/lt-debugger.php3


>Compilers are also mature enough to issue warnings about the use of uninitialized variables

That depends on the compiler. It's not true with GCC: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=18501


Any user action that you didn't have a test case for can provoke catastrophic UB that valgrind never saw, so catching most issues has almost no value. This is why every OS and every app I have ever used were always unreliable shit.


Undefined/implementation-defined behavior are necessary if you want optimal performance (unless the compiler requires formal proofs of correctness).

>it is human nature to forget

High-performance programming is a job the average human does not do. A professional programmer should use spaced-repetition technology to rise above human nature, and use tools like valgrind for extra safety.


Porting is it's own thing - you must seperate implementation dependent things and ... "business rules" fairly strictly if you are to keep it portable. I'd also strongly suggest a really comprehensive test suite that beats the snot out of the implementation dependent portions.

Somebody mentioned "scripting languages" - use the ability of scripting languages to construct combinators to write your tests. They migth even emit 'C' code.


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


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.

Computers aren't just tools, they're also toys. People use computers for entertainment in varied and sundry ways. What is so wrong with somebody wanting to enjoy hacking around in the low level guts of a system? As long as no lives or livelihoods are at stake, what's the problem?


What's amusing to me is the amount of terribly unsafe code (that isn't C) that powers rockets, moon landers, and a variety of other safety-critical systems and yet isn't the subject of such persistent and severe criticisms. There's a reason C and C++ are targets. My (obviously controversial) opinion is it has at least as much to do with ego as a desire for safety.


As far as I know most space software these days, and embedded in general, is in (at least a sub/superset of) C.


Yes, but it wasn't always, and still isn't always.

Although your point is very good in that it weakens (further) the "safety is everything" argument. In my opinion. There is so much mission critical software today that is written in C and C++. That's one reason why "safety, safety, safety!" just isn't as persuasive to me as it perhaps is to others.


We have a winner. Kill your ego. It's the only way.


On the other hand a lot of the criticism of C and C++ is structured to exaggerate their deficiencies and minimize the proposed alternatives' by couching the comparison in contexts which favor the latter over the former by dint of language design. I'm not convinced that is a path to open and honest discussion, either.


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


But don't the security vulnerabilities come from poorly implemented code? These vulnerabilities are not inherent to C.


In what commonly used language other than C and C derivatives do you regularly see use-after-free leading to remote code execution?


C makes it trivial to implement poorly, though.

(Note: I'm playing devil's advocate here to some extent. My view is that safety is important, but lack of provable safety is not some terrible Demogorgon that we should hide in fear from. I think a lot of the concern over safety is valid, but in some contexts it's just overhyped.)


My view is that lack of provable safety should be resolved by defensive code (runtime checks). And then, you are safe (if safety is important in your code, which probably should by default in a professional setting).


I agree, it is solvable by defensive code. The vast majority of the time that code is perfectly sufficient. The number of people who don't die when the hundreds of thousands of things that don't go wrong when an embedded C-program doesn't crash or blow apart because of memory safety bugs daily demonstrates this. I don't think people understand just how much of our world is run, quite literally, by "not provably safe" code. It's not just C and C++, either.

Which is one reason why I don't buy the "memory safety" argument as a very strong one for adopting Rust. There are other much better reasons to do so for a certain class of programming, in my opinion.


Vulnerabilities like buffer overflow do not happen in languages with a string type. Humans are responsible if something bad happens, but without a safety net, the outcome is worse.


C has a perfectly useable (null-terminated) string type, and there is no good reason to ever have a buffer overrun in C.

I understand that this is... obscure for some reason and I'm not saying it never happens, but let's be realistic....


C has a char* type, which we call a string, but it is also the type of a pointer to a single char, which is not a string at all, and also something perfectly usable. "Ends with nul" is barely a part of C, it's more like a programmer's agreement. The language doesn't enforce it, require it, or check it. All it does is insert nul characters in literals, which is hardly enough to make a string type.

Thus if you have a to_upper(char*) function, you don't know what it takes or does without looking it up. Does it uppercase a single character or a whole string? How do you even tell what you were passed without potentially reading past the end of a buffer?

If I happen to have a pointer-to-char and pass it to a to_upper function that operates on strings, it will just write on invalid memory, because C can't distinguish between the two.


From the signature, I would say it expects a NUL-terminated sequence of characters (a C-string) and it would modify it in-place to upper case each character. C already has a standard C function:

    extern int toupper(int);
(via #include <ctypes.h>) that will upper case a single character. If, on the other hand, I saw:

    extern char *to_upper(const char *);
I would expect that to_upper() returns a new string (freeable via a call to free()) that is the upper case version of the given string.

> If I happen to have a pointer-to-char and pass it to a to_upper function that operates on strings, it will just write on invalid memory, because C can't distinguish between the two.

Um ... how do you "happen" to have a pointer-to-char? And unknowingly call to_upper()? I'm lost as to how this can happen ...


The signature doesn't tell you that. If my API said

    int frobnicate(char*)
and you make that kind of assumption, then your code may or may not work, depending on what the function does internally. You simply do not know whether I am operating on null-terminated char sequences or a single char.

>Um ... how do you "happen" to have a pointer-to-char?

    char* text = "some text";
    char* c = text[2]
There you go.

>And unknowingly call to_upper()?

Who said anything about unknowingly calling a function? It's "toupper", not "string_to_upper" or "char_to_upper". The function signature simply doesn't tell you what the function requires of its input.

PS: char* is also a pointer-to-byte in C.


Your response to me shows you don't program in C all that much. I ran your code example through a C compiler and got:

    a.c:2: warning: initialization makes pointer from integer without a cast
    a.c:2: error: initializer element is not constant
What you really want is:

    char * text = "some text";
    char * c    = &text[2];
which still doesn't prove your point because c is still pointing to a NUL-terminated string.

If fronnicate() really takes a single character, I might ask why the function requires a pointer to char for a single character instead of:

    int frobnicate(char);
but if you are going to really argue that point, so be it. Discard the fact that in idiomatic C, a char * is generally considered a NUL-terminated string (and here I'm talking ANSI C and not pre-ANSI C where char * was used where void * is used today).

You are also shifting the argument, because in your original comment I replied to, the function you gave was to_upper(). toupper() is an existing C function.

P.S. char * is a pointer-to-character, not a "pointer-to-byte", pedantically speaking. All the C standard says is that a 'char' is, at minimum, 8 bits in size. It can be larger. Yes, there are current systems that this is true.


A single typo doesn't tell you anything about my programming habits.

>which still doesn't prove your point because c is still pointing to a NUL-terminated string.

No, it's pointing at a char that happens to be part of a nul-terminated string. The semantic intent of that distinction is entirely lost because C fails to make a distinction. I could easily overwrite that nul, and it would no longer be the case. Then it's suddenly an array of chars, and everything pointing at it is now a new type of thing.

char* s = (char*) rand();

This also will point at a 'nul terminated string' with very high probability. Doesn't mean it is safe to call string functions on it...

>I might ask why the function requires a pointer to char for a single character instead of int frobnicate(char)

You could say the same about any pointer argument. Obviously pointers are useful for a reason. If frobnicate returned a char, I would just end up dereferencing a pointer to stick it back in the string it came from. Whether that is frobnicate's job or it's caller's job is a matter of API design, and should not be determined by C, especially when it makes no preference for any other kind of pointer.

>You are also shifting the argument, because in your original comment I replied to, the function you gave was to_upper

My arbitrary example function name doesn't matter one iota. Get over it, and stop being needlessly dense.


This is all true.

So don't do that.


Don't worry about me, I never make any mistakes. I'm a true C programmer: I believe that "implement a good string type" is an unsolved problem and that the last 50 years never happened.


Your first statement is pretty false, even in Rust (for example). Unless you mean something else by "buffer overflow" than I'm accustomed to.


You are right, "do not happen" sounds too much like "will never happen". See also Wikipedia's entry about that example[0]. My point is that if the programmer can't prove accesses are always within appropriate bounds, there should be a runtime check. That is simple. This is not "slow" (and even in the case it you need it fast and are ok to randomly crash, avoiding checks should be explicit). And some languages do it by default and make it really hard to mess with memory.

[0] https://en.wikipedia.org/wiki/Buffer_overflow#Choice_of_prog...


Well, yes, I agree in general bounds should be checked at runtime when it isn't possible to statically verify access at compile time.

I'm not sure how default access in C or C++ isn't explicitly avoiding checks. By definition "a[b]" is an unchecked dereference. It doesn't get more explicit than "by definition." Of course if by "explicit" you mean "syntax exists that demarcates unchecked access" then C and C++ will never satisfy. I'd argue that's a contrived and artificially narrow use of "explicit" meant, er, explicitly to exclude C and C++ from being acceptable by definition and therefore not terribly fair.



Yes (Rust's "unsafe" blocks serve the same purpose), and my point is you're narrowing the definition of "explicit" to exclude C or C++ by definition. And that isn't exactly a fair, in my view.


There is no doubt that C, by definition, opts out from performing bound checkings. But if bounds were always checked by default (implicitly), then you would have to opt-out explicitly, which is a safer approach, because all else being equal, in case of a programming mistake, the code ends up not being vulnerable to that specific kind of attack.


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.


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.


Turbo Pascal even allowed to mix in assembler right in your source code. That was super awesome at that time. No need to write separate assembly code, no need to link !

(not to say that Turbo Pascal was the only one to do that, just fond memories...)


I once wrote a mouse driver for MS-DOS like that.


Interesting. Though I used Turbo Pascal quite a lot earlier, I either don't remember that feature (being able to mix in assembly) or may have known of it then but forgotten it later. Getting a vague recollection - was it something like a function call of sorts (syntax-wise)? Start with a $something, then open parens, then some assembly statements, then close parens)?

If that was the way it was done in TP, the BBC micro (which was mentioned quite a bit in the recent HN thread about BASICs on personal computers of earlier years), also had a similar feature. I did use that one a bit. You had to open a square bracket in the middle of your BASIC program (though probably not in the middle of a BASIC statement), write your assembly code (6502 instruction set), and then close the square bracket, IIRC.

D (language) these days also has the ability to mix in assembly, though I haven't tried it yet.

Edited for typos and wording.


In TP you could use inline Assembly in several ways.

It could be just a block, a complete procedure/function with or without prolog.

Also it was quite comfortable to write, just as a plain macro Assembler with Intel syntax, not those asm functions with strange syntax used in gcc/clang.


C also allows for inline assembly.


Modern delphi carries that tradition.


I don't recall specifically about MSC but Turbo C had the same....

We considered having all the assembler in separate files better practice...


Even in embedded examples, I see a lot of inline ASM in C functions. So, what's separate files like? Do you just compile them separately, link them in as libraries, wrap them as a C function, and then call it? And what was the argument for this over just putting them inside functions of C source where necessary?


We'd generally wrap them as C functions. We'd frequently use the compiler to generate the assembly; have a prototype and all that.

Which is better depends.


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.


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.


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


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.


That would be perfect if you left off here. I worked with C compilers and libraries without ever writing a line of C outside my code generator. A lot of companies and people do that. Those not needing to integrate with C code outside of API's just need a FFI with a number of systems languages available.

Truth told: system programmers either never need C or need it so few times it's almost totally unnecessary. Others don't need it at all. So, it's "not necessary for vast majority of things application or system developers work on." ;)


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.


Comments like this perplex me. I am a full time C++ dev and I could just rewrite your comment woving the "++" to the other "C".

Really like that I can create a class that stores all the knowledge of one concept internally and if I wrote that correctly I never need to look inside it again. Even better, if I document the contracts of using a class I can carefully optimize it and have broad performance effects with small code changes.

Things like std::string are just so much easier to work with that their C counterpart and things like std::filesystem::path simplify (or use the boost one if you don't have C++17 yet) so many things and doesn't even have a C counterpart. I point these out as simple examples but in all the code bases I have worked on there are similar examples, like most games a class to represent 2d and 3d points, which are used to define AxisAlignedBoundingBoxes, which are needed for collision detection algorithms which them selves need several classes to describe.

Then I can build systems of a size and complexity I literally could not comprehend without those abstractions. And then the compiler enforces them for me so other people can use them safely as well. Why is it so much harder to do this in C if C is so much "simpler".


C++ has the issue that there are a lot of ways to hide magic, and a lot of hidden magic can blow up in unexpected ways if it interacts badly with other parts of the language that you do not understand.

The result is that you really need to stick to a subset of the language that has been chosen to work well together. Safely adding to that chosen subset is challenging. And it just takes one developer to create a major headache.

See, for example, https://google.github.io/styleguide/cppguide.html#Exceptions documenting why Google is not willing to allow even something as basic as exceptions to be used in C++ code.


Every language can hide stuff. Classes, templates and operator overloads are just pretty wrappers for functions with better designed conventions for the common cases. Anyone can write "hidden magic" in any language with anything like these abstractions.

It is particularly easy to make this hidden magic in C. For example, there is no way to express who has ownership of a pointer or when a function expects a pointer or an array. I have seen plenty of C libraries that document things like this, but for each that does there are 3 that don't. For each one does document things like that, they do it differently with different conventions but for the same reasons, but I cannot even use common idioms to be safe I must understand every part of each library I call. It is much more clear what is going own in C++ when a function accepts or returns a std::unique_ptr and I cannot screw it up without trying hard.

What seems more important to me is exposing the relevant parts of the software when needed. If I care about the business logic (Even if its nots a business app... HP and Mana can be considered the business logic of a game) it can be hard to tease that out when lots of "hidden magic" is shoved in my face. But when I need to handle a new file format that the business logic requires indirectly I don't want to mess with the business logic. Having more tools to cleanly express this helps. So have a type that handles this IO while some other type handles business logic is indeed changing magic into "hidden magic", but it is also enforcing separation of responsibilities. Something much harder to do when your only real tool of abstraction if functions.

The only people I have met in real life that stick to anything like the "hidden magic" argument are the same people who advocate for single large functions. These people like their function on the order of hundreds or thousands of lines so they can "see everything". You aren't doing that are you?


The problem is not so much hiding stuff. It is being surprised by the interactions between features. The "can't get there from here" like trying to printf to an iostream. If you're not careful, simply scanning through a large file is several times slower than other languages. Template magic makes seemingly reasonable type names blow up into insanely hard ones to figure out. There are non-obvious patterns that you have to know such as RAII.

And good luck if you want to make things portable! I remember at Google being asked how I checked in unit tests that broke integration tests. Turns out that GCC and Clang disagreed on whether a friend of a subclass can see protected elements in the parent class. The local language lawyer decided that gcc was right, sent off a bug report to Clang, and I had to make my little unit test class a friend of the parent class as well. Maybe I was unlucky, but why does this sort of thing not happen to me in other languages?

In other languages I am consistently able to read up on the language syntax and features, implement things within my knowledge, catch my bugs with unit tests, and have a basically working system. I've had this experience with C, Lua, Perl, Ruby, Python, PL/SQL, Java, JavaScript, etc.

But C++ finds ways to astound and surprise me. Perhaps if I was a full-time C++ developer I'd learn all of the corners and would simply be productive. But the last time that I wrote a non-trivial C++ program, there wound up being multiple things which worked right in unit tests but not the full program until I ran Valgrind and followed suggestions that I would have had no way of figuring out on my own.

Yes, I'm aware of how easy it is for third party libraries to be bad. From dangling pointers in C to monkey patching in Ruby there is a lot of crazy stuff that can be screwed up by third party developers. But C++ is the only language where I had trouble not screwing myself up.


Phrased this way your concerns seem much more valid.

As for C++ file IO, I think it sucks too that something as idiomatic a iostream iterators are pretty much garbage. I hope they are removed in STL2 and they keep the good parts of iostreams and ditch the bad. With tellg and seekg you get the same or better performance as the c lib functions unless you need to care about visual studio performance... but if you are using that compiler you never actually compared enough about performance to benchmark.

I feel I must point at that C features shouldn't be expected to interact with C++ features. Printf does what it does, it was not designed to live with objects and types. It is a holdover from the C days. It is totally unaware of non-trivial types and does really gross things when you give it the wrong type. Using some other function to write to something that can be streamed or writing and operator<< overload for the the thing you actually wanted to output seem like the simplest approaches.

As for finding implementation specific bugs, you claim not to have found them in other languages, then included Javascript on a list of things you have used. Javascript is the poster child for implemtation specific problems, to the point where there are several sites that put real effort into describing differences between different implementations. This whole listing seems odd. These languages either have 1 implementation (Lua) so cannot have differences or are so under-specified that of course the implementations have huge differences (Ruby, Python, Sql). Clearly I have had more problems with all these then you I find all languages terrible at this point, I just a few to be less terrible.

I think I may know why you are shooting yourself with C++, as you put it. If you find RAII non-obvious after you have worked with it then there is definitely a problem with how you are approaching something about C++.

RAII, or deterministic deconstruction in general, is probably the single strongest thing the C++ language brings to the table. With RAII you can implement your own memory safety model via any kind of shared pointer you can dream up. With RAII you can prevent race conditions by creating exception safe mutex guards. Write your own transaction handler by putting the roll-back code in the deconstructor. With RAII you can clean up any kind of resource in a deterministic and safe way that few other languages offer.

I had to some work with automated UI testing recently (a complex application and framework with C++, Java, Ruby and Python). My application leaked resources in a very gross way because we had to pass handles to our resource back out to users writing scripts that could manipulate them. This leaky resource was whole web browsers.

For a combination of technical and business reasons the only suitable tool for creating browser instances. If I could have relied on Java's finalizers to be called I could easily have close them there. We found several situations where they clearly failed and much documentation about the reason they failed (and apparently how the JVM could be fixed if the standard authors were so compelled). After a couple of weeks of research and failing to be able to explain the various segments of the Java community the "using" keyword was inadequate for this usage pattern, the smallest hack we could come up with was a silly watchdog timer that checked the processes on the machine and knew when the web browser manipulation API was used. This was almost a 1000 lines of code to get right to buy nothing but resource safety in the face of exceptions. It would have been 4 lines of C++ and two of those would have been curly brackets.

Of course I am biased, I pick a story from my experience to suit my argument as you have done yours. I am still not sure how one hurts themselves more with C++ than particularly and if you have access to C++11, C++14 or C++17 it seems a fair bit safer than Java or Ruby because of the precise guarantees and strong tools for safety the language lacked before. Still can't keep up with With Rust or Haskell in the safety department though.


It is not that RAII is non-obvious after you've worked with it. It is that you can read through how the language works somewhere like http://www.cplusplus.com/doc/tutorial/, start producing software, and not realize that you have to do RAII. You can even, as Google did, have experienced and competent people write a large and well-structured program and then only belatedly realize that you can't use certain features because they didn't structure the program right.

There is a lot of that in C++. If you get everything right, then wonderful. If you don't, then that is a problem.

On implementation bugs, I have found implementation bugs in lots of languages. But not generally as things that I stumble over while proceeding with what seems to me like it should be the obvious thing to try.

With C++ it isn't like that. I gave you an example where there is a disagreement between compilers. But, for example, what happens if I supply an ordering method that isn't consistent? In other languages I get stuff sorted slightly inconsistently. In C++ I get a core dump. Good luck figuring out why.

On the complaint that you have about Java, that falls in the category of things that I expect to have to deal with. Part of my mental model for a language has to be the memory management rules. C++ lets you set up whatever you want. Perl does deterministic destructors but therefore can't collect circular data structures without help. Java has true garbage collection so it collects circular data structures, but it can't guarantee that they are collected in a timely way. JavaScript does true garbage collection now, but back in the IE 4.0/5.0 days they separately collected garbage for native objects and JavaScript objects with the result that garbage never got collected if there was a cycle between native and JavaScript objects.

This is one of the basic facts that I know that I have to understand about any language I work with. It is like pass by value vs pass by reference, or the scoping rules. I immediately look for it, understand it, and then am not surprised at the consequences. I see other people use the language for a while without trying to understand that. I understand their pain. But I'm not caught by surprise.

However C++ keeps finding new ways to surprise me. In the past I reserved it for cases where I need to implement an algorithm and squeeze and order or two new magnitudes of precise memory layout and performance beyond what is available in scripting languages. I've resolved that the next time I need that, I'll try a new language. My past experiences with C++ haven't been worth it.


I think this says more about John Carmack's devine software engineering talent than it does about C++. I've seen some slick looking C++, and I've seen some flat out atrocious C (and vice-versa). Even Carmack decided to switch to C++ for iD Tech 5.


"I remember reading some of ID's engine code and admiring how well I could follow it and know what's going on."

Npte though that id hasn't really created an influential game in 15 years and arguably the game of the century (Minecraft) was programmed, badly from what I hear, in Java. People often say that "you can write good C code" without considering what you're giving up in terms of architecture and creativity.


Writing good C code also means knowing when to use another language on top. Scripting languages such as Lua are commonly used in the games industry.


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.


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.


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.


Yes - a thousand times yes!

The goto has gotten a bad rap over the years because of Dijkstra's paper. And that paper has unduly influenced a lot of incorrect thinking. There are valid use cases for goto, and this is certainly one of them.

I use it all the time like the example above. Particularly because it makes my life so much easier when developing and debugging embedded C code across various tool chains, some of which have less functionality than others.

[edit - correct typo on Ed's name]


Just curious, not a C developer by any means, but why wouldn't you use a function here instead of a goto? I'm confused how goto would reduce error/improve readability in that example.

Again, not criticizing, genuinely want to know.


Simply: a goto never returns while a function call returns to where it was called from.

So specifically in the example above, if you called failure-handling functions instead of using goto's then when the function returned you would continue execution on the next line after the function call. In the example above, that's clearly not what you want.

Now you could add some else's after the function calls to prevent execution from continuing. i.e. to get to the appropriate step in the free_* sequence at the bottom, but that starts to look messy. So I have to admit (not being a goto-lover), the above example reads very nicely.

It conforms to the "gotos might be okay if they only jump forward" rule of thumb I've heard.


I've also seen extensive use of gotos for exception handling in C. After the knee-jerk reaction ("...but but Dijkstra!") I came to appreciate it as a useful idiom.


To be fair, Dijkstra said "harmful", not "forbidden". He was also talking about encouraging structured procedural programming rather than a game of Who's Clever Enough to Follow the Spaghetti.

We do things that are harmful all the time, in limited appropriate situations. Cutting into your abdomen is harmful, but a skillfully used surgeon's scalpel can fix a bigger problem. That's not license to go roll around on a pile of jagged, rusty steel scrap. Missing sleep is harmful, but if you do it once in a while to keep your job or to escape a nighttime flash flood then it's helpful.

Dijkstra was intending to set the norm from which people should mindfully and occasionally deviate. The point wasn't to ban the use of labelled jumps entirely.


GKH somewhere explained that they accept gotos that don't go back (leads to mess) only gotos that jump forward in function. Nice rule.


This looks fine to me. I wonder if C/C++ could be improved by introducing a new keyword `bail` which is the same as `goto` but is only allowed to jump to the bottom of the function. That way, codebases can outlaw `goto` but keep `bail`.


Are you looking for "return"?

With C++ you can ensure that you have your destructors do the tidy up, e.g. a messy example

struct cleaner { cleaner(string *toCleanup) : m_x(toCleanup) { } ~cleaner() { delete m_x; m_x = nullptr; } };


Yup, C++ has autocleanup. Of course if you are using fopen() instead something more modern you'll need to fclose(). Adding a new keywords to C is a long shot. Perhaps compilers could detect non-cleanup use of goto and give it a warning.


A typical C++ codebase I'm working on these days would have scope guards implemented via macros, such that you can do e.g.:

    FILE* f = fopen(...);
    SCOPE_GUARD({ fclose(f); });
This is mainly used for one-off calls to some native API, where writing a proper RAII wrapper for the managed resource is not worth it.


In a previous job where I wrote C code, I had a macro named "bail" that pretty much did that: log an error message, then jump to the cleanup section of the function.


Wouldn't it be better to have some sort of linter tool do this? Why create a more specific language construct when you already have one?


I'd much rather see that as a state machine.


How common is it to add "exception macros"? Something like:

"error(handle, "could not open handle", free_something)"


Have you ever used goto in C? I feel like most people bash on C just because they heard Dijkstra said it, and that's it.

Most code I saw while teaching it were not good cases, but sometimes it's a very interesting technique that can make the code easier to understand and shorter. I think that use is beautiful.

But it doesn't mean there aren't bad use cases.


I think you meant "bash on goto" instead of "bash on C"?

Regardless, yes, a ton of people do bash on it because of Dijkstra - but at the time, he had a good point. In the industry of the time (as I understand it - I was a kid when he wrote that), there was a lot of "cowboy coding" out there, with goto's "gone wild" - jumping into the middle of everywhere and everything - and producing "spaghetti code".

But as you note, it can be very useful and make things easier to read (for instance, jumping out of deeply nested if-then constructs - though I could also argue a refactor might be the better solution).

I was once part of a discussion in a forum about state machines, and one guy posted a very beautifully done state machine that used no select-case construct, but rather goto statements, but done in a tight way that mimic'ed a select-case construct. I was very impressed at the time; it was some code for PIC Basic IIRC.

In time, I've changed my views from seeing goto as "always bad", to "can be very useful, in some situations - provided you know the risks of the tool".

In other words - think very carefully before you rush into using it; maybe there's a better or cleaner way.


The only valid use of goto I've seen is for breaking out of nested loops; a goto statement is much easier than unwinding a bunch of breaks based on arbitrary logic or flags to notify each parent loop that a break is needed.


It's also handy for error handling / cleanup: http://eli.thegreenplace.net/2009/04/27/using-goto-for-error...


The main argument against goto comes from standard C++ side because there are so many edge cases where goto and longjmp will cause your exceptions and end-of-life semantics to go awry.


If I remember correctly, C++ flat out bans all uses of goto that could affect constructors and destructors. For example, something like this won't compile:

    int main() {
        goto g;
        std::string s;
        g: return 0;
    }
while this will compile, but destructor is guaranteed to be called:

    int main() {
        {
            std::string s;
            goto g;
            return 1;
        }
        g: return 0;
    }
Now, longjmp is another matter. That thing is basically verboten in any sane C++ environment (and consequently, C libraries that use it across API boundary are very painful to use from C++; R hosting API is a great example of that).


It's been brought up that there are a few handy uses for goto. I think the reason it's so heavily preached against is because of its name. For someone just starting out, the word "goto" might sound like a handy tool that could be useful for all sorts of things.

But instead of a measured response to make it the last tool you reach for, it's been made into a pariah.


It's only more useful than structured code in a few niche situations, and even then, it doesn't usually provide massive benefits. It's a language construct that doesn't carry its weight.


The author provides the book as a free to download pdf at http://icube-icps.unistra.fr/img_auth.php/d/db/ModernC.pdf


I personally prefer Prehistoric C, which only has the two language keywords "ugh" and "grunt". Modern C has too many keywords for my taste.


> "grunt"

I'm sorry, you cannot use this keyword as it will cause a conflict with Javascript tools.


Most people use gulp now -- the reimplementation of the reimplementation of make in JavaScript because reasons.


So true. I'm helping people on a Node App and it was the first time I had to use it to "build" (build what?) the website I should write the API to, lol.


"Prehistoric" or not, the classical C as created by Kernighan and Ritchie is actually a perfect example of a sensibly minimalist design. Subsequent changes made to the language have spoiled the characteristic elegance of that design.


Well, function prototypes in the first ANSI standard were a fine addition, if you ask me. After that...


This is where it all started. In general, I would agree with you, but the immediate loss of the original elegance and simplicity of the design is obvious. (Also, I believe it was a deliberate design decision to only require that the return type be specified - if it is not 'int' - in a declaration of a function. Besides having resulted in easy-to-understand semantics and an uncluttered syntax, this opened a direct way of dealing with an unknown number and/or types of the arguments without needing any additional syntactical "niceties".)


Yes, the fact that calling conventions changed with prototypes in a way that was incompatible with and could not be duplicated by the existing calling conventions was a mistake.

I wrote a little about a very similar case with Objective-C and the default 'id' argument and return type here: http://blog.metaobject.com/2014/03/cargo-cult-typing-or-obje...


Nah, coming down from the trees was a bad idea:

http://www.dangermouse.net/esoteric/ook.html


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?


> Are their C api's mature and portable?

Rusts C api is completely mature and portable. In fact if you are writing a library that you want to have a C interface to, Rust is a fantastic choice.


This isn't really something that Go is meant to do, but it is possible with a little bit of hacking.

https://blog.heroku.com/see_python_see_python_go_go_python_g...


For Rust? Totally usable and robust.


> Old ones like Ada and Fortran of course.

Modula-2, FreePascal, D


For someone who studied basic C/C++ in university and is interested in hacking around in C, should I read this over K&R?


Don't skip K&R. Probably read both. They're both pretty short.

A really, really good one is Advanced Programming in the Unix Environment. But, it's pretty expensive.


APUE does not teach C, but the Unix API. Granted, it is an excellent resource for people interested in such OS family.

K&R is a great resource which covers a lot beyond the syntax, but is obviously dated from the standard's perspective.



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?


Arduino and other micro controllers. First of all it's really fun (YMMV). There is something about writing code that makes things happen in the physical world which is satisfying in ways that writing code that just affects bits on a computer isn't. Secondly it's one of the realms where C is still genuinely important. When you are working on problems where a few hundred bytes this way or that is difference between success and failure you really start to appreciate what C has to offer.


If you want to push this to the extreme, check out the MSP430 Launchpad and fiddle around with avr-gcc. Pick up a Bluetooth serial module (can be had on breakout board for ≈$40) and remote control something from any Android phone. Lots of fun to be had.

Just be aware, microcontroller C programming is pretty far out compared to regular systems programming. Lots of tasks involve writing bits to seemingly random memory-mapped registers to change the state of the controller... and forget about including your favorite libraries. You're lucky if the standard library fits on the chip. Its very similar to OS kernel development in that regard.


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

If you are into hardware as well, get microcontrollers and do some home improvement projects. The easiest way is to get Raspberry Pi or similar, but if you also want to learn a great deal about system programming, try to bring up MCU on your own, starting with bootloader.


If your a fan of the terminal I recommend writing a terminal program that scratches an itch of yours. Perhaps theres a shell script that runs too slowly? Or something in inscrutable awk that would be better as its own program.


Hmmm... Yes I'll have to give it some thought. I use Python for most of my scripting needs. I guess I've never had a need that's required better performance.


Even though it may not make technical sense to choose C, you may want to just use it to learn.


That's exactly why I wrote https://github.com/majewsky/xmpp-bridge in C. It feels so different to me to program in C than, say, Go or Python. I become so much more mindful of every step that I take.


Yup. I'm going to make some scripts. Thanks. :)


Python has an excellent C extension api. I'd suggest taking a C library that does something you want and writing Python bindings for it.


POSIX.


Or unix in general.

I learned C by implementing my own versions of popular unix commands, starting with echo then cat and so on...


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.


Is this the book you are talking about [1]? If so, it looks like the author has put it online for free.

[1] https://www.duckware.com/bugfreec/index.html


There you go! Thanks :)


I get a 500 error. Here's an archive link: http://web.archive.org/web/20161128093244/http://icube-icps....


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?


> Does it advocate good best practices?

That may come down to opinion. For example, type qualifiers are bound to the left.

Traditionally you would write:

    char *var;
They advocate keeping type on the left, name on the right, so:

    char* var;
A few things like that are covered under "Warning to experienced C programmers".

Personally, I prefer it, but have always done what everyone else expects, so there are no fights over styling.

> Does it talk about pitfalls?

Absolutely.

At a glance over, they talk about the unexpected way C treats truthy values (if it ain't 0, it's true), accidentally dereferencing to NULL, and even goes into goto, when it's good, and when it's bad.

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

Yes. They assume a C11 compiler, and state it in the introduction. At the moment, GCC and clang have some disagreements with how some C11 features should be treated, (GCC accepts a char or a char* for _Generic, clang requires it to be char. clang is more technically correct, but GCC is more flexible), and MSVC is still struggling to implement most of it. [0]

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

I probably need a week to read it more fully, but I'll quote from the end of the introduction:

> Last but not least comes ambition . It discusses my personal ideas for a future development of C. C as it is today has some rough edges and particularities that only have historical justification. I propose possible paths to improve on the lack of general constants, to simplify the memory model, and more generally to improve the modularity of the language. This level is clearly much more specialized than the others, most C programmers can probably live without it, but the curious ones among you could perhaps take up some of the ideas.

[0] https://msdn.microsoft.com/en-us/library/hh567368.aspx

Edit: escaping


Note that var1 and var2 are not the same type:

    char* var1, var2;
Traditional style makes this clear.


> We bind type modifiers and qualifiers to the left.

Good idea in theory but your example shows how bad it behaves in practice.


Usually declare variables one per line, so, type is still clear when:

char* var1;

char var2;


C does not in anyway forbid having two definitions on the same line hence

    char var2, *var1;
is valid C while in Java this would be illegal

    char var2, [] var1;
So the syntax is correct and all you are doing is to add style rules that make it less readable.


Having one var per line, with left bound typing seems more readable to me.

That said, I understand the differences in traditions.

I think that C's syntax is flexible that we can each go to our own, and decide which is more readable.


I tend to see typedef on a pointer type to address this.

    typedef char * pchar;

    pchar var1, var2; // Now they're both pointers
I'm not really a fan, but it can address the problem.


The language is that of Redmont, which I shall not utter here.


And then leave the poor beginner wondering why it's

char var1[10];

and not

char[10] var1;

The rule is simple once you understand it, variables are declared with the same syntax that is used to access it later.


I want to cry... remembering the old good time of C programming... ohhhh


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.


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


Let's not confuse low level understanding with the fine points of the C virtual machine, which drifts from whatever platform you are working on by the year.

What you really want is an understanding of how whatever code the candidate may write will map to the underlying hardware. Test for that.


do you have any pointers for reading about "the C virtual machine"? i'd love an accessible explanation of how the model of the hardware predented by C differs from actual modern chips, but those keywords make it hard to google for.


Well, like the sibling comment, I can only point to the standard. I'll give one example: the segmented memory model. In C, the difference between two pointers is a valid operation iff they point to the same object (or one slot past it). So it works if it's the same array or malloc() block, but something as simple as

  int a;
  int b;
  ptrdiff_t d = &b - &a;
is undefined. Modern computers, including most embedded platforms, have a flat memory model by now, and could implement the operation above without problem.

Another difference I know of is signed integer overflow. Most platforms use a 2's complement architecture, where signed overflow simply wraps around. In C, such an operation is undefined.

Yet another difference relates to pointer aliasing. On most platform, a pointer is just a pointer to a memory slot. In C, it is assumed for optimization purposes that pointers of different types cannot point to overlapping regions. This prevents practical stuff like type punning, for which you have to use unions.


The C virtual machine is described by the C standard, implementations of such do vary which is due to a combination of bugs, vague language and interpretations of the standard. But ask anyone to build a house given 100 pages of text and see what happens.


Hire a Javascript programmer and lemme know how that works out for you.

There's no point in testing for C knowledge if they're never, ever going to use it. Sure, they may know some C, but they're not going to have more than a surface, I-recognize-it-when-I-see-it understanding of it.


That's a problem if the developer never had a C influence. Depending on when you learned to code you may have never touched C.


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.


I looked into the table of contents & jumped into pages of it. It looks like good material on best practices & optimization rules & tips. Quickly, bookmarked it & definitely worth reading.

IMO, the title here is misleading, I don't think new feature is added to C to make it modern.


The reason Gustedt calls it Modern C has to do with how he organizes the contents, especially the last level: Ambition.

"This book is organized in levels. The starting level, encounter, will introduce you to the very basics of programming with C. By the end of it, even if you don’t have much experience in programming, you should be able to understand the structure of simple programs and start writing your own.

The acquaintance level details most principal concepts and features such as control structures, data types, operators and functions. It should give you a deeper understanding of the things that are going on when you run your programs. This knowledge should be sufficient for an introductory course in algorithms and other work at that level, with the notable caveat that pointers aren’t fully introduced yet at this level.

The cognition level goes to the heart of the C language. It fully explains pointers, familiarizes you with C’s memory model, and allows you to understand most of C’s library interface. Completing this level should enable you to write C code professionally, it therefore begins with an essential discussion about the writing and organization of C programs. I personally would expect anybody who graduated from an engineering school with a major related to computer science or programming in C to master this level. Don’t be satisfied with less. The experience level then goes into detail in specific topics, such as performance, reentrancy, atomicity, threads and type generic programming. These are probably best discovered as you go, that is when you encounter them in the real world. Nevertheless, as a whole they are necessary to round off the picture and to provide you with full expertise in C. Anybody with some years of professional programming in C or who heads a software project that uses C as its main programming language should master this level.

Last but not least comes ambition. It discusses my personal ideas for a future development of C. C as it is today has some rough edges and particularities that only have historical justification. I propose possible paths to improve on the lack of general constants, to simplify the memory model, and more generally to improve the modularity of the language. This level is clearly much more specialized than the others, most C programmers can probably live without it, but the curious ones among you could perhaps take up some of the ideas."


Eh? C11 isn't modern?


It is indeed. But its more a polishing of C99 imo. At least annex k is optional now iirc.


The C standards kind of have a main theme, eg numerics (aka eating Fortran's lunch) for C99 (_Complex, restrict, variable-length arrays, type-generic math functions, ...).

While C11 is indeed to some degree a polishing of C99, its theme is multi-threading.


Yep, I've not tried the c11 threads yet, pthreads tend to work for me and in kernel, well not like i'll be using c11 threads anyway. So its been a bit of a "maybe someday" task. :)


Pardon my noobness, but if I learned and became proficient in C and knew nothing else, would I have a marketable skill?

Is it possible for C to be a standalone skill, where ones job could be 100% programming in C, or do you need a lot of auxiliary knowledge outside of that?


> would I have a marketable skill?

Yes. Systems programming and Embedded are your best and most visible playing fields, but many large, legacy applications were written in C and continue to be maintained.

> Is it possible for C to be a standalone skill,

No. As others have pointed out, the language + standard library is very spartan and will only take you so far. This will only get you an entry level position, and only in teams that are big enough to have some senior people with spare capacity for mentoring, and a stream of small, self contained tasks for you do while in training.

To be able to work independently you need to have at least some basic knowledge of the whole toolchain: Compiling (you need to know to heart the different steps that are taken by the compiler, and at least its 20% most common cmdline flags), Building (make), program analysis (lint, valgrind), debugging (gdb, or whatever comes with the compiler you are using), 3rd-Party-Libraries (pick 2-3 of: glib, pthreads, antlr, curses, openssl, etc), Standards (MISRA, POSIX - which is at least as much about the API to Unix-like OS as it's about the C language).

From there, there are more tools to help you, but those are typically OS dependent and are not exclusive to C.


Probably not.

C doesn't exist in a vacuum: it has to run on something. And the standard library doesn't get you very far.

You'd need to know at least one OS API as well: POSIX is probably best, maybe Win32, an embedded OS/executive might work also, or perhaps even a bare-metal CPU or two.

C + POSIX covers a lot of the Open Source world, and increasingly more of the embedded market. C + VxWorks is possibly the next best combo.


You'd need a lot of auxiliary knowledge outside of that. Perhaps FPGA programming, perhaps even hardware development.


Yes and that's true of any marketable language.


Would any C-lovers recommend this book to people that already know programming (but not sys programming) wanting to learn C?

I personally know Java, (lil bit) Elixir, and Python.

EDIT: I'll also be reading K&R along side it.


Any idea if it will be available as a physical book?


If people aren't so into this book, can anyone suggest some other book beyond K&R?


Learn C the Hard Way was Zed Shaw's aspiration of a K&R Replacement[1].

If you like it, great, if you don't, you have company[2]

[0] https://learncodethehardway.org/c/

[1] https://web.archive.org/web/20141205223016/http://c.learncod...

[2] http://hentenaar.com/dont-learn-c-the-wrong-way


I didn't use C much. What kind of features of are in the book that make it modern?


Being based on a language revision that is only 5 years old instead of 17 or 27, for one.


it is as much to do with language style, usage, and (it's subjective view of) good C programming conventions, as it is to do with modern language features.


Hi, is there a epub version of this book? The pdf format is painful to read.


Seconded. If it is not feasible I would appreciate a narrow PDF (like A5) with minimal margins which should be good enough for reading on a Kindle.


Will this book be printed? I would love to get a paper copy.


where i can find Errata list of this book.


Another day for the HN crowd to express their distaste for C :)


Yeah, again and again. it's like blaming a hammer for the potential of breaking your finger when you use it, and proposing the use of a spoon, instead.


More like blaming a power-saw for the potential of destroying your table when you want to cut some bread.

If you are in for cutting a tree, go for it.


This C apologism is holding the industry back. Software development has changed many times over since C came out and it just isn't a good tool for tackling a lot of the issues that we have today. Just about every software project written in C has some serious bugs.

I personally judge a language by how well it lets you to define abstractions. In C's case, it doesn't let you do that very well.


"Just about every software project written has some serious bugs"

There, fixed it for you. Seriously, vulnerabilities and bugs are found everywhere not just in C.

I've been programming for 35 years, in so many languages I lost count, and every time I've seen the 'let's not use C because it'll lead to bugs' it was to be replaced by another thing that was ALSO leading to bugs, and/or become so bloated it was in itself... a bug.


> Seriously, vulnerabilities and bugs are found everywhere not just in C.

The black-and-white security fallacy again!

The simple fact (and this is a fact, not an opinion) is that the most severe security problems—remote code execution, in particular—are found way way way more in programs written in C and C++ than in other languages.


I strongly agree with your point in general, memory safety is a major problem with C/C++ to an extent that is not found in other languages. Even worse, these often become problems long after they are written when the compiler gets "smarter" and does a trick with your code. Call it "code rot" or whatever you want, but it happens a lot more and a lot faster on C/C++ than other languages.

However, the nature of where C/C++ code is used does lend itself to severe problems. When you have a JVM vulnerability, you shouldn't be able to get any farther than a regular user with only sandbox privileges. When you have a kernel vulnerability, by definition you have the keys to the kingdom.

Of course there are always holes that can be used to escalate, but that also comes back to the problems of having a primarily C/C++ ecosystem...


Per unit of code or as an absolute value? That's significant, but not even the whole picture. It's trivial to have a low (absolute and per-unit) number if the total number of units is low, for example. So by itself your fact is almost useless.


The big problem with C is the memory safety issues. It is extremely difficult to write C code and be safe from malicious input. If you use a memory safe language these difficult issues are erased.

I realise the compiler/runtime can't stop all bugs but fixing these simple errors goes a long way to producing robust secure software.


I am sorry, but there's nothing difficult about it. Tedious, yes. Difficult? No.


I hope you will agree that there are things where C is not the optimal choice (e.g. you wouldn't choose C for writing scripts). Many people argue that C is still suitable if you need maximal performance or do stuff that is very close to the hardware, like operating systems. Now, people who say that C shouldn't be used at all are just convinced that even in those applications there are now better choices than C. Safer languages like Rust exist that at least claim to fill the same niche as C.


A: "Let's not reuse syringe needles, it leads to spread of HIV".

B: "I've been using syringes for 35 years and clean needles won't get rid of HIV entirely".

Well no duh. But that isn't really a counter argument.

> become so bloated it was in itself

Whereas C is the pinnacle of expressiveness?


I don't think it sensible to single out C for having serious bug. It should be most software projects have serious bugs.


Your criticism lacks constructivity. What do you propose we replace C with?


Whatever doesn't have extremely tight constraints (CPU, memory, embedded…) should probably use garbage collection. Now your language can throw a nice exception (or otherwise cleanly abort the process) before any memory corruption (and subsequent vulnerabilities) can occur.

Otherwise, I'd say try something like Rust. And if Rust doesn't make the cut, maybe go write your own C-like dialect, with less undefined behaviour.


Depends on what you are writing. C isn't 100 percent replaceable right now but the more people bitch about it and support the alternatives, the better for the future.


Just curious, what are us C apologists supposed to be apologizing for:

- A lack of suitable replacement for C?

- C is too ubiquitous?

- C has been an integral part of most of the best software ever written?


FYI, "apologist" is often not used to literally mean "one who apologises", but rather "one who defends"[1], with a negative connotation (at least in my experience).

I would guess the "C apologism" in the original comment is referring to defenses of C that pretend C is safe "in practice" (or "for people who know what they're doing") or minimise/ignore the strong correlation between the use of C and serious problems/vulnerabilities.

[1]: https://en.wiktionary.org/wiki/apologist


Ada has been a suitable replacement for C since the early 90s when GNAT was released. Prior to that there were still plenty of suitable C replacements given a little work, like Pascal and Oberon.


Agree with the first two but the last isn't saying much. C has been an integral part of most software ever written, best, worst, and everything in between.


Rust?


C enables - in my opinion - better abstractions than languages which constrain you to the use of their suite of abstractions. I'd include C++ in that, and especially template-heavy C++.


While I am all about C/C++ (even if I hate it), it makes sense

HN is probably something like 99% web and app developers (I would argue that all are the former, but my definition may be a bit old). In those cases, you actively don't want to ever use C and anyone who does is kind of an idiot. It is the logic by which "systems project" means "script to run on a server" for a lot of people around here.

And that is fine. But just like this place's obsession with Rust would lead to derisive mockery by "real" systems people, so too is C hated here.

It also doesn't help that universities, at least in the US, seem hellbent on teaching along these lines. A few years ago Eclipse was "the thing you use when you are poor or doing Java". These days? I am seeing disturbing numbers of graduates who refuse to use Visual Studio for a project on Windows and insist on boostrapping together something that is only half functional. And talking to my academia friends (and even doing a fair amount of guest lecturing for them), I know where the indoctrination is coming from.


My university in the mid-90's already knew it was better to teach safe practices in C++ and Pascal dialects than keep teaching pure C.

We were supposed to be able to use C in the OS design classes from our C++ learnings.


And I think that is the way to go. I am personally a fan of having more sciencey programs use matlab and more "pure" CS use python as the first languages. Save C/C++ for the lower level stuff (as in OSes and hardware interfaces and optimization). Although, I would argue that nothing beats C++ for teaching OOP and data structures

My problem is the almost dogmatic indoctrination that goes along with it. My cousin's Intro to CS class was taught by a professor who spent about a third of the time basically parroting Stallman (he actually would regularly bring up blog posts and articles on the screen and read them to the class...) and screaming about how everyone needs to use either Vim or Jetbrains tools. That isn't teaching. That is pushing an agenda. And it just makes for worse students and hell for those of us who still spend some time with the interns and new hires.

Always explore new and better tools. But understand why. Sous-vide is awesome and has the potential to make some of the best food you will ever eat. But it also understand that even a "worse" method can be much more efficient or effective.


I would argue Smalltalk is a far superior language to teach the essence of OOP. Students in Smalltalk will be writing programs in the first hour while their C++ counterparts will still be struggling to understand #includes.

To quote Alan Kay: Actually I made up the term "object-oriented", and I can tell you I did not have C++ in mind.


I am definitely biased, but I think the includes are important and are almost part of the lesson. Teach someone how to write their own containers and classes, then show them that there is a library they can easily access that does a much better job than they ever will and just how easy it is to use.

And while there are better languages from an educational standpoint, having something that is usable/"real" is quite valuable.

But I definitely approach things from a more practical/industry oriented perspective. so grain of salt.


> HN is probably something like 99% web and app developers

Most generalizations about HN are merely projection based on sample bias, so please don't post such generalizations unless you have representative data.

We're thinking about adding that sentence to the site guidelines, because spurious general claims about HN are a cliché here.


Except that this is a discussion on how news and topics are received at HN. So making a projection based on available data is actually quite reasonable and if there is a sample bias, it is a valid one. Because it doesn't matter if there are nine hundred billion hardcore VHDL folk around here. The most popular discussions tend to be tech related to web technologies and the associated tools. If the consistently popular and top discussions on HN aren't adequate to make a generalization of the general makeup of the active HN users, then I don't know what is.

And, I thought it was pretty clear with the "probably something like" and "99%" that it was a number being pulled out of my arse for the sake of discussion.

That being said, actual data on the skillsets and interests of the more active members would be a very interesting bit of data. But it is also really hard to measure as lurkers don't contribute to discussion but will gladly vote in a poll.


You're not making a projection based on data, you're generalizing from things you personally happened to notice, which is practically the definition of sample bias.

It's extremely common to do this (I'm not picking on you personally!), but such generalizations have no objective value and lead to repetitive, low-quality discussion, which is why I think we're going to add a guideline asking people not to do it.


Again, the discussion was about why topics like C/C++ are dismissed/disliked around here and I presented an argument based upon observations in threads. That is actually a valid approach.

And if your issue is actually that I very clearly pulled a number out of my arse and went out of my way to make it clear that I had: Are you also going to make it mandatory for everyone to preface every non-cited statement with "in my opinion"?

Look, I get that this is your bread and butter and that it is important for HN to be presented as being THE place for tech and blah blah blah and being pigeonholed into "a site for web and app developers who like silicon valley" isn't necessarily what you want, but trying to pretend that a rule to not "generalize" as a way to not have people speculate and discuss why some topics just aren't particularly welcome here isn't the way to go. Just make it a rule, you aren't fooling anyone.

Also, if you REALLY wanted to improve discussions here: Make a rule against pseudo-technical naval gazing (is there a term for that? "Naval gazing" is more associated with philosophy and potheads) as far too many threads devolve into "We can't help but be introverted because our minds are designed that way and I need to be isolated to code because I need to optimize everything".


> While I am all about C/C++ (even if I hate it), it makes sense

There's no such thing as "C/C++". They're two totally different languages. (C++ actually has more in common with something like Haskell than C.)


Come on.

That nomenclature has been around for decades. We ALL know they're different languages.


If the handle of the hammer you're holding was a chainsaw that randomly turns on and off. And also sometimes explodes killing your entire family and pet dog. Don't blame the explodey-chainsaw-hammer, it's just a tool. Okay an idiotically dangerous too that nobody in their right mind should use, but still just a tool.


Eh, no need to overreact that much. Look. Hardware is hardware, and this is where tire meets the road so people can lay the foundation for the upper levels.

It is what it is. It's not user friendly because hardware just isn't, low levels aren't, and it has to just perform.

> 'idiotically dangerous'

Well, I don't want to sound harsh, but nobody forces anybody to do systems programming if they perceive C as such. There is JavaScript, python and plethora of other safe and cozy languages, but: the low level has to be done.


Hardware is hardware, but C is not. People need to stop pretending that it is.


Look at all his comments in this thread. He is just fishing for an argument/attention.


Oh, I see it's not that there's such a thing as a bad tool, or that C is unsafe.

Simply most of us don't have your towering intellect to appreciate it. Got it.


Maybe I was too harsh, I apologize. But c is far less dangerous than a kitchen knife.

Tools are tools, some are used with a great amount of care: jackhammers, routers, compression hammers, chainsaws etc.

Heck: driving a vehicle is probably the single most dangerous activity we do on a daily basis.


> If the handle of the hammer you're holding was a chainsaw

Except it isn't.

It's just a hammer, with a handle of a hammer.

> And also sometimes explodes killing

Except it doesn't.

At best, some people are reckless and end up causing problems due to their recklessness.

Enough with these silly hyperboles. Cushioned environments lead to less problems.


C is utterly predictable. Maybe you're the exploding chainsaw in this metaphor.


How detailed is your knowledge of undefined behavior?


We know when undefined behavior will occur(the specs are written very clearly), but not what will happen when it occurs. Our job as competent C programmers is to avoid undefined behavior. C isn't hard(perhaps tedious to do correctly) - it does exactly what you tell it to.


It does, but it's unnecessarily difficult to keep track of all the places undefined behaviour might occur and make sure you don't step into any of them under any conditions ever.

We shouldn't have to work like this anymore. C's been an amazing language, but it's getting time to gently, respectfully, move on. There's active and interesting development in alternatives which attempt to retain C's primary advantages while also allowing the compiler to keep you out of trouble as much as possible.

We have hugely powerful computers available to use as developers. We can contemplate designing and compiling languages with a complexity which would have completely defeated the systems available when C was developed. Why shouldn't we use some of that power to make our lives easier?


You'd be hard pressed to find a non-trivial C program that doesn't contain any undefined behavior.


Depends on what you mean by "non-trivial," I should think.


> it does exactly what you tell it to.

This is a tautology.

What do you expect to happen on signed overflow?


> sometimes explodes

thought you were talking about java bloatware for a second. Oh your OS doesn't have 1.2gb for HelloWorld.class? Enjoy your OutOfMemoryException!


I just think that C over the years has had more bashing than pragmatic advocacy for where it excels and as a result, there is a very knee jerk reaction to the language itself.

On the other hand, where the language gets promoted, I haven't seen anyone really promote C in a way that would sound modern in any sense, where people who have years of experience with C stick with C89 or use C99 in a C++ compatible fashion (i.e. without any C99 syntax which C++ has not adopted officially like restrict keyword or designated initializers). While that is fine for personal preferences, I think it does a disservice to people who have to learn the language and use the language for various justified reasons of their own and there isn't a unified response in how to really teach C to them.

I think the author really hits the hammer on the head with this part in the introduction.

"In contrast to the ubiquitous presence of C programs and systems, good knowledge of and about C is much more scarce. Even experienced C programmers often appear to be stuck in some degree of self-inflicted ignorance about the modern evolution of the C language. A likely reason for this is that C is seen as an "easy to learn" language, allowing a programmer with little experience to quickly write or copy snippets of code that at least appear to do what it’s supposed to. In a way, C fails to motivate its users to climb to higher levels of knowledge."

But on this book, from what I have read from past revisions, it's very well written and I have even learned some things from it I didn't know existed in C, like using the keyword static in array indices in parameter declarations. While it is not a perfect resource and I don't think anyone new to programming would be able to read this without guidance, it does some things extraordinarily well which I haven't really seen other C books touch on. Its treatment of undefined behavior is top notch and the way that it tries to explain the memory model of C is pretty good as well.

But had this book been written for another "antique" language, like Modern Fotran or Modern Cobol (which both had their last ISO standards most recently in 2008 and 2014 respectively, mind you), I doubt there would be this much polarization in the comments section.


And.. While they express such distaste the haters probably serve their web apps via NGINX.


... and put all their keys and values in Redis. And use curl for testing their revolutionary REST-API!


With modern day computing power and compilers there are better ways to tell the computer and explain your fellow programmer what you want the computer to do. In the end giving in the same (or even more efficient) CPU instructions.

Sniper rifles are improving every year. Yet a rifle from the 40's can kill people too. Both deadly in the hands of an expert and dangerous in the hands of an amateur. It's the same with C. You should be able to understand the computer on that level but you don't need C just to prove it.


Modern day CPUs are improving 3% a generation, and modern day compilers capped off looooong time ago. Better programming practices will return simply because people will want more and won't be able to get it.

This CPU power galore is just a short lived period before people want full phone VR on a battery for two hours or what have you. You can sort of see what's in demand now for $200k jobs and the tools there don't appear to be very high level or user friendly. If anything they all require few heavy courses in statistics.


> With modern day computing power and compilers there are better ways to tell the computer and explain your fellow programmer what you want the computer to do. In the end giving in the same (or even more efficient) CPU instructions.

It was already like that 10 years before C was invented, but their authors didn't want to invest too much resources creating an Extended Algol, PL/I or similar compiler.

ESPOL and NEWP were already available in the 60's.


In the end giving in the same (or even more efficient) CPU instructions.

We both know that is not always true and heavily depends on the type of software though.. Take https://github.com/micropython/micropython for instance: what higher level language but C would allow that to run with the same performance as it has now, on as much different devices? C++ would be a viable answer, but when used e.g. as C + templates it's basically still a form of C and anyway your complaints have been raised in nearly the exact same wordings about C++ laguage as well..


Turbo Pascal, Think Pascal, Quick Pascal, FreePascal, Modula-2, Ada, Delphi, MikroElektronika Pascal

The availability of compilers for a specific architecture is orthogonal to the language.


hmm got me there I guess. Never looked closely into those, can you summarize in what way thay are better than C? Less shoot-yourself-in-the-foot maybe?


Yes.

Pascal dialects already in the late 80's, early 90's, allowed for:

- type safe sub ranges

- proper strings

- type safe enumerations, enumerations as vector indexes

- type safe allocation (new vs malloc)

- type safe out parameters via references instead of pointers

- bounds checking and checked arithmetic (you can explicit disable them if required for performance)

- real modules via units

- pointer arithmetic is more explicit

As for MicroPython, MikroPascal from MikroElektronika supports all those tiny chips.

http://www.mikroe.com/mikropascal/


Any every other language out there. It is far easier for people to identify with what they don't like than it is to identify with what they do like.


HN prefer Javascript, it seems.


In a PDF no less. I for one do not miss C (or Objective-C for a long time) at all. I built my first commercial app in C starting in the mid 1985 (Mac). That's how much of an antique this language is.


And yet, the OS and the browser you used to type this comment was written in C or a C-derivative like C++.


Historical accident.

It could have been written in any other language that compiled to native code, if we had more options available.


Well, there are two types of people in this world

1. the type that do something

2. the type that claim they could do it better than the first group (this is by no means limited to programming, it is for example pretty common in politics)

So please show me an OS written in Go or Rust or Ruby or Python or Java that people can actually use for day-to-day tasks.


I've been working full time for years on a browser written in Rust. Which category am I in?

Yes, C++ is very entrenched. That is a problem. We should be fixing that.


There is such a thing as path dependence. C got ubiquitous because of reasons (UNIX?), and now we're stuck with it.

Now the historical accident theory is pretty obvious: it could have been an Algol, Fortran derivative instead of C, if only UNIX used that as a basis. C itself could have been designed differently, and if so would probably have different flaws and qualities.

Asking for a Rust/Go/Whatever OS is dishonest, because you know full well it will fail for reasons that have nothing to do with the underlying language's intrinsic qualities: even if that OS is great, nobody expect it to be so great we all have to switch away from Windows and Linux. Well, except maybe if we prove the correctness of the kernel, hence ensuring the absence of many vulnerabilities. Research is ongoing, I'd like to see where that goes.


C got ubiquitous well before Linux, at least. I'd date it back to the '80s on microcomputers ( mostly meaning DOS) .

Lots of Pascal, but Pascal really was an engineering constraint at the time if you were doing actual system programming. And back then, that was a serious consideration.


Not in Portugal, we were using mostly using Turbo Pascal.

In the 80's using C on MS-DOS, was only seen by those that had UNIX at work or universities and wanted to work home.

It wasn't even C, rather SmallC or any other K&R dialect.

In any case, everyone that cared about performance was using TASM and MASM, writing everything in Assembly.


If you pay me my monthly salary to prove you wrong, I will happily do it, should we discuss a work contract?


Those who did something include the developers of Burroughs MCP, who used Algol 60; the developers of MULTICS, who used PL/1; the developers of Symbolics Genera, who used Lisp; and the developers of Oberon, who used the language Oberon.

I go along with historical accident to account for Unix's success, though Richard Gabriel thought it was a case of Worse Is Better trumping The Right Thing.


The Oberon operating system is as old as linux, yet I'm pretty sure you use the latter more often than the former.


Being an UNIX clone available for free instead of having to pay for expensive Solaris, Aix, HP-UX, SGI workstations, while BSD was busy fighting AT&T helped.

UNIX is the VHS of operating systems.


ITT: Heated arguments and zealotry. In resume: "C is outdated, its ubiquitousness is just a historical accident"

"Better tools exist to do this job"

"C is not needed anymore" (Yet no contender has ever come close to it, hehehe --my2c)

There, saved you a ton of reading time.


A time will come when our entire concept of programming will shift due to advances in hardware unlike what we have today. Consider quantum computers or some biological machine etc.

Those who use C and assembly I imagine would be better equipped to understand the new paradigms. It's best to understand how to implement data structures in their most rudimentary form because implementing them on new platforms becomes easier.

In addition, higher-level idioms become easy to understand if the parts that make up the whole are understood. And underneath all those layers of translation and compilation we have raw assembly and the bare machine.


I agree!

I also believe efforts like LLVM are actively trying to 'bridge the gap' between both worlds (totally raw VS fully dynamic/scripted). Stuff like emscripten is enabling the "old farts" and the "newfags" to share common ground, and that's amazing... I just hope these youngsters keep learning stuff instead of just piling framework after framework after the new 'hot shit' gets released in a 6 month timeframe.. really, adhd is in full effect, specially in the webdev world, and imho that's hurtful.

o/



We've merged the discussions into this thread.


I mean, the problem cut C is not that it is old, but that it is dangerous.


Every language is, just about different things.

Array out of bounds is dangerous even in Excel VB.


It strikes me as odd you'd even go to the lengths of producing such a book. If you really wanted to protect people from the worst vagaries C the book should simply say "don't".


C is still the lingua franca of computing, like it or not.


Sounds like an Oxymoron. "Make sure your buffer flow exploits are up to the minute! Make sure your systematic lack of memory safety totally captures the zeitgeist!"




Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: