Ask HN: Learn C in 2023?
Hello,
I always have the itch to learn C, juts because it was such an influental language and when you are digging around a lot in GNU/linux you will have contact with it one way or another.
What is the way you would suggest learning C in 2023? I am already familiar with other languages (rust..) but would like to have an introduction to the C basics and program some hands-on projects in C, so I can learn the pecularities of the language.
219 comments
[ 5.7 ms ] story [ 248 ms ] threadI would also suggest to enable all warnings your compiler allow for and sanitizers if possible. You will also want to learn to use a debugger that works on your operating system.
It ships with pretty high-level libraries (connecting to a Wi-Fi network is ~200 lines of C code), and you'll be running on top of a small operating system (FreeRTOS). But you still have full access to hardware and all the fun of working directly with it. The dev boards are quite cheap at $3-4 per piece, and have lots of peripherals (including Wi-Fi and Bluetooth).
Have a look for ESP-WROOM-32D at aliexpress, or whatever the platform of choice in your country is. Some of the newer boards are built on RISC-V, if that's your thing.
Their SDK also has first-class Linux support (and the other two OSs, obviously).
https://docs.espressif.com/projects/esp-idf/en/latest/esp32/
Depending upon their goals, something like at ATmega may be a better choice. ESP32 and RP2040 development places a lot of emphasis on using libraries. That's fine, but it doesn't really highlight how C is different from most other languages. In contrast, there is a lot of information on low level programming for the ATmega. The RP2040 would be my second choice. I believe the libraries are entirely open source, which means you can read the datasheet and can reference the library source to see how something is implemented in C.
Granted, the ESP32 is a lot more fun if the intent is to build something.
In other words, using that extension silently disables one of the most basic and effective security mitigations we have for C.
I don't think I've ever seen this criticism leveled elsewhere. Do people criticize JavaScript programming because the ECMAScript standard doesn't give you everything you could want?
Nobody is talking about the compilers. This, right here — the fact that you are conflating a language and one implementation — is the whole point and is indicative of the tight control the respective organizations keep over their languages.
The direction of the languages is entirely up to their private owners, and that exclusivity is enforced via intellectual property law. They are exactly as proprietary as RHEL or Unix.
I don't even know what you're referring to with IP law. The projects' names and logos?
No, the code of the implementation is permissively licensed.
Do I really have to explain, on HN of all places, the importance of distinguishing between implementation and interface?
Comparing this to Borland or Microsoft is bizarre: Microsoft and Borland actually do have proprietary C compilers that they do not make publicly available in source code form. Those compilers in turn have all kinds of non-standard, proprietary extensions.
The one thing that sets C apart from Rust and Go is the presence of an official ISO standard. But this isn't material to the definition of "proprietary" in the open source world. And, well, that ISO standard is itself copyrighted.
[1]: https://github.com/golang/go/blob/master/LICENSE
Edit: You shouldn't delete and re-write your comments. Here's the original comment I responded to:
> That's a dubious claim, and irrelevant besides. They are proprietary, in the standard meaning of the word, because a single private organization has exclusive control over each, but whatever word you choose to describe that is up to you.
> Google owns Go in a way that has never been true of Microsoft (or Borland or GNU or Apple...) when it comes to C.
Edit 2: For the edited comment: you're setting up a distinction that does not matter for programming languages that do not have formal specifications. There is no "interface" to distinguish against, and the claim that Rust (or Go, or Python, or any other language without an ISO spec) is tacitly reserving all rights to the design of the language itself is completely unsubstantiated.
The fact that you keep focusing on whether compilers are proprietary instead of whether Rust and Go are proprietary is starting to seem deliberately evasive. You're free to extol the virtues of open source software, and I agree with you, but it's utterly beside the point.
.rs is analogous to .doc. The "right" way to handle it is defined solely with respect to the reference implementation. I view that as a problem. You may disagree, and that's fine, but don't pretend this is a conversation about something else.
This is the overwhelmingly common state of affairs for programming languages: Python, PHP, Go, Rust, etc. do not have formal standards in the sense of C, but instead of a living standard in the form of their reference implementation. The reference implementation is permissively licensed, so the "standard," insofar as it can be said to exist, is also permissively licensed.
Edit: Again: this is just not what the definition of "proprietary" is, in the context of open source. There is a legitimate grievance that you're expressing about software standards and implementation diversity, but it has absolutely no bearing on whether the reference implementation is proprietary or not.
By analogy: I, an open source developer, create a new file format $FOO and release my reference implementation of a $FOO parser under a permissive software license. Am I somehow compelled to spend time writing a formal specification of $FOO to make it non-proprietary? It might be nice for me to do, and nothing stops anybody else from converting my living standard into a formal one, but I am absolutely not compelled to do it.
"You keep using that word. It don't think you know what it means." -- Inigo Montoya
This is just bizarre. If MIT/Apache-licensed Rust is proprietary, then anything could be? AFAIK there is no POSIX definition for epoll/io_uring. I suppose epoll is proprietary too?
proprietary: "2: something that is used, produced, or marketed under exclusive legal right of the inventor or maker"
Can Rust be forked, modified, used/sold and distributed with your changes? If yes that hardly seems like an exclusive legal right. Your problem seems to be that people trust the Rust project, and don't trust you or a central committee (yet). But that doesn't make a project proprietary. It makes it implementation defined, which has its own problems, but those are not the same problems of as those of proprietary software.
Rust is not MIT/Apache-licensed, rustc is.
Rust is licensed under terms that require explicit permission for use outside of a few very narrowly-defined circumstances.
I think you mean the Rust trademark, and sorry, but you don't get to use IBM or Google's mark willy-nilly either? I'm not sure it's a realistic expectation on your part to be able to market your changes under the other guy's mark.
Compare Google's practices here to Qt's[1], CMake's[2], or any other large open source project that is primarily maintained by a single company.
To be abundantly clear: I don't think this is a good way to run an open source project, but it is a legitimate way to run one. So long as the source code and standards (insofar as they exist) are themselves permissive, then the community details are just that.
[1]: https://wiki.qt.io/Qt_Contribution_Guidelines
[2]: https://github.com/Kitware/CMake/blob/master/CONTRIBUTING.rs...
I think it's the fact that if you took the Rust codebase and forked[1] it to accept new keywords, the resulting language that it accepts is most definitely not Rust.
If you did the same to a C compiler[2], the resulting compiler would still be called a C compiler by everyone.
[1] Assuming that the license allows it.
[2] One who's licence allowed this.
This argument does not make sense: if you fork GCC or LLVM and change the language frontend for C, the thing you are parsing is no longer standard C. This is exactly the same as is true for Rust, except that the reference is the the living standard rather than an ISO standard.
By the original commenter's logic, the modified C compiler would effectively be proprietary: it no longer has a public, liberally licensed specification that it conforms to. But this isn't the way anybody in the open source community uses the word "proprietary," because it precludes any open-source work that doesn't have a corresponding formal specification.
It doesn't make sense to you, certainly.
To the type of person who've used a dozen different C compilers, all of them containing extensions to the language, and all of them still advertised as "C compilers", the argument makes perfect sense.
I mean, the input to the GCC C compiler is, by default, C with extensions, and yet even you still called it a C frontend, not a "SomeOtherLanguage" frontend.
You may not agree that the argument is valid or sound, but you can't with a straight face say that it is an unreasonable position to take.
That's not the argument I'm advancing: I'm saying it does not make sense to call that modified C compiler "proprietary."
I don't care if you call it "C" or not, because everybody who writes C understands that compiler-specific extensions are commonplace. But the existence of non-formally-specified extensions doesn't somehow override the permissively licensed nature of the compiler.
Similarly, going back to the original argument: it does not make sense to call Rust "proprietary" because it only has a living specification, rather than a formal one. It's permissively licensed, and you can modify it to your heart's content. The only reason people wouldn't call your changes "Rust" is because the community doesn't share the same expectations re: vendor extensions that C's community does.
It doesn't have a "living specification", it has a reference implementation. There's a difference. HTML is a living specification without a reference implementation. Rust (and many other languages) have a reference implementation without a living specification.
> The only reason people wouldn't call your changes "Rust" is because the community doesn't share the same expectations
That's not true; you cannot call it Rust because Rust, the programming language, is trademarked.
It is protected, you understand? There are legal barriers to calling your NewLanguage "Rust", as explained by the foundation themselves over here: https://foundation.rust-lang.org/policies/logo-policy-and-me...
Writing your own language and calling it Rust is legally forbidden.[1] You are, of course, free to use the permissive copyright but the name is trademarked.
Now, with C, OTOH, as an ISO standard it has no trademark so this argument does not even arise.
[1] As far as I can tell, it would be legally forbidden even if your new implementation of Rust behaves identical to the reference implementation. The best you can do is call it Rust compatible.
The issue at bar is whether Rust the language is proprietary, especially as compared to C. Something is proprietary within the standard meaning of the term if it "is used, produced, or marketed under exclusive legal right of the inventor or maker." Merriam Webster, Online Edition, available at https://www.merriam-webster.com/dictionary/proprietary.
In this case, The Rust Foundation maintains and exercises exclusive legal rights over Rust that nobody exercises over C. If I make a C compiler with extensions, I can still call it C and I can still participate in C governance. C is an open standard. See https://www.open-std.org/. However, the same is not true for Rust, where governance happens entirely under the auspices of one vendor, who uses trademark law to ensure that no competitors can exist without its explicit approval. Specifically, any Rust variants other than those required for porting require explicit written permission. See https://foundation.rust-lang.org/policies/logo-policy-and-me.... Furthermore, even if I accepted my impotence in the governance process and even if I resolved to call my Rust variant something else (FeO?), competition in the market would still be chilled by The Rust Foundation's refusal to commit to fair, reasonable, and non-discriminatory (FRAND) licensing (something that is required as part of the ISO standardization process). If a competitor ever did arise, patenting the borrow checker mechanism would be all it would take to completely crush them.
I am glad that rustc is open source. I am glad that The Rust Foundation is a non-profit. I accept on faith that they want to do the right thing. However, they have deliberately created a monoculture that they enforce through IP law. Rust is proprietary, i.e., it is "something that is used, produced, or marketed under exclusive legal right of the inventor or maker." There are no open standards, there is no FRAND licensing obligation, there is no allowance for any Rust variants other than those explicitly permitted by the primary vendor of Rust compilers.
You brought up Python before, which is appropriate: Python explicitly is a "benevolent dictator for life" language. The difference with Rust is that the dictator is a legal personhood instead of a single living, breathing person. But dictatorships, even benevolent and well-intentioned ones, are ill-suited to the requirements of stability and predictability that are so vital to systems programming.
Imagine, God forbid, that The Rust Foundation goes defunct at some point in the next 100 years, and its IP bought up by the next generation's SCO or Novell or whatever, and they decide to patent the borrow checker and whatever other innovations are in Rust. Do you think that would be a bad thing?
Because it can only be bad if you agree that Rust is proprietary. If Rust were an open standard (including the borrow checker as part of the standardized compilation process and subject to mandatory FRAND licensing), then that outcome would be mitigated against.
It has absolutely nothing to do with how "proprietary" is used in the open source community. To make it as simple as possible: if your use of the word "proprietary" includes Python and Rust but excludes Borland C, you've failed to use to word in a way that is substantive and useful to everyone else in this community.
The implied risk of Rust's trademark terms is entirely ahistorical: C did not win as a systems programming language because it satisfied FRAND or any other standard for openness. It won because Worse Is Better, in spite of onerous licensing restrictions by companies with extremely expensive and proprietary C compilers.
I don't think this is why. I think it "won" because it was the best "high-level" language of the time and exploded to fill a huge niche, historically. In other words, historical luck.
I think that's also how JavaScript won.
Since it's always luck, and since the luckiest thing is rarely the "best" thing, it just looks like it's a selection for ironically worse quality from the outside.
[1]: https://cs.stanford.edu/people/eroberts/cs201/projects/2010-...
Apache 2 includes a patent grant? So I think most of this comment is clearly erroneous.
1) I'm not certain the Rust Foundation actually owns the underlying patent IP for the borrow checker.
2) But granting it does, imagine, the foundation or its successors files for a patent years after including that IP in Rust and years after granting two extremely permissive licenses (MIT/Apache 2) for the use of that IP, and tries to enforce their patent rights against your fork, FeO.
3) What exactly is their hope of winning a patent infringement case after granting those extremely permissive licenses?
Don't both MIT and Apache 2 provide a very strong defense against infringement where one is using and modifying the Rust code base (you fork creating FeO)? If you don't like Apache 2's explicit patent grant, Rust of course offers you the option to use MIT's implied grant.
"Any language used by the owner of the patent, or any conduct on his part exhibited to another from which that other may properly infer that the owner consents to his use of the patent in making or using it, or selling it, upon which the other acts, constitutes a license and a defense to an action for a tort." De Forest Radio Telephone Co. v. United States, 273 US 236, 241 (1927).
Can you show me an instance where anything like what you describe has occurred? If not, you should describe precisely how your hypothetical could occur? Because, from where I sit, your theories seem like pure FUD.
Can't you just fork the language, or, just re Rust, participate in the RFC process? Do we need more bureaucracy?
If you have lots of time: https://hal.inria.fr/hal-02383654
If you can't be bothered reading a whole book: https://matt.sh/howto-c
Exercises: https://www.codestepbystep.com/problem/list/c and https://exercism.org/tracks/c
Once you have syntax and basic algorithms down well, watch this, the only 2 hour YouTube video I'll ever recommend: https://m.youtube.com/watch?v=443UNeGrFoM
Both r/cprogramming and r/C_programming are active, also full of lazy students trying to get people to do their homework. If you come by, describe your problem well with code. Say you're learning for yourself, not for school.
Together C & C++ is a good Discord if you prefer live chat: https://discord.gg/tccpp
One thing that Apple did right in Xcode and where other IDEs lagged behind is easy access to those tools from the UI.
Edit: to stretch the metaphor, modern c++ is like a thick rubber pad on the bed of nails, but often on the job you are hot bunking and you don't know which c++ your predecessor used to make the bed... Or you are in a hurry and you aren't sure which one you grabbed.
In any situation where I have control of how the code is built, I personally prefer to run those various SANs instead of Valgrind, usually three parallel builds: one with -fsanitize=address,integer,undefined, one -fsanitize=thread if more than one thread is involved in the project, and one -fsanitize=memory too if Linux is available. That gets you almost all the benefits of Valgrind faster than Valgrind.
But Valgrind is still an exceptionally powerful tool to learn and use. Its chief advantage to me is to be able to work with an unmodified binary. Its chief downside is it can be slow, and occasionally you may run into issues with support for more obscure instructions like vcvtps2ph.
MSAN is really the star of the sanitizer world, and is worth spinning up a Linux VM for. It tracks the initialized-ness of all your data and catches any use (really, branch) on anything uninitialized. I like to think of ASAN as "did I get my pointers right?" and MSAN as "did I get everything else right?".
If you're not familiar with integer sanitizer, it's like UBSAN except it checks for things that are often mistakes rather than things that are actually undefined. The most common situation I've found is code that's hashing data will often pop up a warning about unsigned integer overflow, which I choose to suppress as finely grained as possible with something like __attribute__((no_sanitize("unsigned-integer-overflow", "unsigned-shift-base"))). That way you can still find unintentional unsigned integer problems (e.g. with size_t).
You may leave static analyzers for later though, since they're more likely to bother you with unimportant stuff - but not much later.
I usually use "-Wall -Wextra -Wpedantic", the GCC static analyser "-fanalyzer", cppcheck, and Clang scan-build.
That's good enough for my personal projects.
I have a couple of blog posts about this:
https://superjamie.github.io/2022/05/24/c-static-analysis-to...
https://superjamie.github.io/2022/11/04/c-static-analysis-to...
E.g., people like the book by Jens Gustedt. I'm ambivalent about it, but it sure machine-guns the important points.
[1] https://hal.inria.fr/hal-02383654/file/ModernC.pdf
K&R the 2nd edition of book teaches ANSI C. (It is still the best intro into the basics of C.)
For modern, practical C programing it is missing a lot though. It is a short, simple, from the original authors introduction to the language itself. There is nothing about how to test your code, how to set it up for long term maintainability, how to mitigate some of the security downfalls or even any of the features added since C89.
It's a good technical book. A great one for it's time and subject matter. But that caveat is important, it needs to be taken in context of the time. It shouldn't be the first, and absolutely not the only, book you read if you are writing modern production C in 2023.
I think I'd go further, there are places where it advocates downright unsafe practices. Eg. It's been a long time, but I thought I remember it introducing gets() unironically, without any caveat. For those that do not know, there is no safe use of that interface possible.
K&R C is an excellent learning resource primarily because it's extremely strong pedagogically. Read K&R and then move onto something else later when you're done with it. If you eventually get a job doing something with C, then you'll learn on the job what's important to the job you're doing.
I think it also presents the notion to neophytes that there is magic under there
A trivial example would be strtok vs strtok_r. There are some, like, say, asctime(), that operate against a global string buffer.
Sometimes interfaces "look like" they're returning a global buffer, for example readdir(), but that structure can hang off the DIR* struct ... Nonetheless there is readdir_r()
The one thing I impart the most from the beginning is to develop good instincts for secure interfaces. You should be able to look at function signatures and see alarm bells for unsafe practices. For someone skilled at C this is second nature, you can see bad practices from a distance, often at the function signature level.
I think it would be unwise to neglect this.
For the exact example of above, how would a developer learn not to use gets? The best place would be a section on red flags in API design in the book. If you don't have that you need to find it through experience. Lints, fuzzing, sanitizers can help, but once again, none of this is covered in K&R. Some of it just didn't exist at the time or if it did, it was very primitive compared to what we have today.
Developers could pick up bad habits and be productive for years with them before they get bit by them. I know I did. I look back at some of the practices I picked up when first learning and cringe a bit. Especially since I think a fair amount of that code is still in production.
If you continue using C, then you should eventually learn this stuff, but to overburden someone with a list of 100 things they must do right is the surest way to frustrate them to the point where they give up.
If OP eventually writes C code that goes into production for a company, it is the company's responsibility to have procedures and safeguards which ensure that OP writes reasonable code. Ideally, one such safeguard will be a senior colleague to introduce them to the thornier parts of C.
To expect a beginner to learn all of the finer points of a topic from the start is bad pedagogy.
On the other hand, K&R provides a set of exercises which are fun, graded, interesting, and challenging. By the time you finish them, you have a working knowledge of a large and useful subset of C. You can then proceed to reading another book (or consulting some other reference). Invariably, you will find that they disagree with K&R to some extent. Learning to spot these differences and begin to understand how C has evolved is itself an important lesson.
You've mentioned something like this in reply to a few comments in here. This is a strange argument. It isn't too much to ask of a book to lay down more of a foundation so you don't need a 1 on 1 tutor later to correct misconceptions.
I get people like K&R because it is short and easy to read. Some subjects aren't best served by short and easy to read. The subject was well served by it when C was new and novel. We live in a very different world.
Sure, it would be ideal if there were a new book on C which did a better job of explaining some of the developments since K&R 2ed was written. If that book exists, I'm not aware of it. All of the books I've seen recommended are either too long, or poorly written. K&R's strong advantage isn't that it's short, it's that it's very strong pedagogically.
A "how do I learn C?" thread comes up on HN about once a month. In each of these threads, people will steer people clear of K&R, quoting something or another about how K&R isn't appropriate for this day and age (note: you do not explain why you think it isn't appropriate!). The tacit assumption here is that you can only read one book, or that if you read a book which isn't "perfect", you are somehow tainting yourself with corrupted knowledge. I think this is nonsense. An important meta-skill for learning is the ability to discern between good and bad information. The only way to build that skill is by consulting multiple sources.
Note that in my actual reply to OP, I don't specifically advocate for reading K&R, but instead to take a multipronged approach where you primarily draw from whatever learning medium you're most comfortable with. Most people I know that are in their early 20s or younger prefer learning from videos (I'm a math teacher and have lots of experience with people this age).
However the book was last updated in 1988 and I prefer C99+ for almost every feature it adds. If I'm going to recommend just one book it will be something newer.
Read something else for safe C writing in a modern environment. Books like Modern C and Effective C are good starts.
I love C. As dangerous and as tedious it can be for large projects, I think it's my top choice for solo endeavors that don't require too many string manipulations. I don't think any language has come close to achieving the same balance of elegance and simplicity--maybe scheme? (zig comes close but has a substantial number of features that move it away from simplicity (e.g. payload capture))
Hard +1 on this!
I think Eskil Steenberg work in C is really top notch - the video is heavily recommended, as is an overview of his work in general, he does some awesome stuff with C.
[1] https://www.raylib.com/
I also recommend his book.
It's really out of date. It doesn't cover subsequent C standards; and it doesn't cover any of the best practices that have subsequently evolved to protect us from the things in C that are deeply awful (in retrospect) in the original. Or any of the evolved practical lore for writing good, portable, safe C code.
That's insane. K&R (2nd ed.) can easily be the only book you want to work through if you want to learn the basics of C.
In my opinion it’s a perfect project because:
- it is a good size. You can be done in 10-20 hours, or longer if that’s your pace (have fun!)
- a lot of examples and community support.
- you have a whole project without any overwhelming complexities: you’re just taking inputs, reading some data, running it through a switch statement, writing data, writing to a screen, then repeating.
- you have lots of “test code” aka games.
- it mostly lacks tedium: you’re not repeating ideas too often
- it’s fun!
RIP me who has spent 3 weeks on this
Great project for sure though!
Also look at C programs. I study old, small unix programs like "more".
I must also say that I see people asking about learning C on sites like Stack Overflow and getting discouraged saying that it's outdated and all. These people are doing a great disservice.
If you want to create your own language and want to experience what choices are subpar by modern standards - C is way to go.
If I had choice then I'd pick Rust anytime when messing with low level programming.
I don't understand fascination with C just because it's "tradition" "everywhere" or "gives you full control" - especially that, since it brought us countless security issues
PInvoke requires a working knowledge of C's concepts; you need to know the basics about pointers and how data is represented in order to do it correctly.
This statement comes from a lot of personal experience: I wrote a (currently closed source) adapter to call into SWMM from C#. (https://www.openswmm.org/SWMM51007/swmm5-c). (Hopefully I can open-source it at some point.)
In https://www.syncplicity.com/en, I wrote code in C# to interface with a closed-source driver to create a user-mode virtual disk drive. I also integrated with a different Virtual disk drive product that was written in C++ via PInvoke. I used PInvoke to create bindings between C# and Objective C for the Mac version as well.
The only times I've used such APIs was creating simple keylogger for tests and very simple cheat for a video game (tested on non-official servers).
C# nowadays seems mostly like web backend development, tools, game scripting or automation, at least that's my perception.
Anyway even if WinAPI, then why not Rust?
Because they said they already know rust and were asking about C…
Plenty of reasons:
First of all, most operating systems (and other lower-level APIs) you want to work with expose their APIs as C. These don't all have bindings in Rust. Even if you want to do them in Rust, you need to know enough C to be able to understand how to consume the API.
But then, there's plenty of legacy code in C. We aren't going to rewrite everything in Rust overnight.
More importantly, I think the "jury's still out" if Rust is better than C for embedded programming; or programming that's primarily direct memory manipulation. (And, if your embedded environment only gives you an API in C, you need to know enough C to write the API bindings for Rust before you can do embedded programming in Rust.)
Personally, I don't have any experience with unsafe Rust code. (I only do hobby projects in Rust.) In C# you can do C-style direct memory manipulation if you need to; but I'm not sure if Rust's "unsafe" is equivalent.
Rust is like a modern rifle, unloaded, with six safeties and three locked triggers in a gun safe. The ammunition will harmlessly self-destruct if you don’t focus properly before firing.
C is sort of like Rust except the rifle is on the couch, loaded, one in the chamber, all the safeties are off, the numerous open triggers are held by RNGs, and the business end is pointed right at your most prized possession.
It is fun, in a way. Use it in an environment that you don’t care too much about.
Some languages like Rust and Haskell do a little better by having the compiler keep track of most of them (if you use it right) but the consequence of misunderstanding them here is accidentally coding yourself into a corner which is extremely unpleasant.
There are other things people like to talk about like automatic memory management and package management and again I'm not convinced these really improve much. The pain of allocating memory in C means people often find ways to avoid doing it altogether which results in much more predictable behavior. Same with pulling in third party libraries. It's rare using more than a couple is actually beneficial but when the package manager is there the temptation is way too much for most people and the result is both unpleasant and very vulnerable to supply chain attacks.
I'm still not convinced any of this is the improvement people think it is.
If maintaining these invariants were so simple, why would the smartest people in the industry have so much trouble doing so? Humans cannot consistently write correct C, not even the ones who believe they can. We have decades of evidence.
> Most languages replace these with multi-dimensional string based hashmaps that have much more complex invariants to keep track of and similar consequences for failing to do so.
This take reflects a profound ignorance of the relative severity of different kinds of software defect.
When you get an array bound wrong in Java or Rust, you get an exception or a panic. Your program exits and restarts. You get a ticket for fixing the bug. Life goes on.
When you get an array bound wrong in C, you open the door for a nefarious actor to gain control over everything that your program can access.
These aren't similar consequences. They're not even on the same planet of consequences. C apologists are like apologists for asbestos and lead paint.
Modern C compilers/runtimes also have bounds checking (although I've never used it) like java if you want to use it.
Sure there's some improvement but it's not nearly as extreme as it's made out to be.
> Javascript for example has similar issues where if you're not careful about which keys in a hashmap you write to you can create situations that allow for all kinds of things including arbitrary code execution.
Not without eval() you can't.
> Modern C compilers/runtimes also have bounds checking
No, they don't. There are C-like languages (e.g. C++/CLI) that do, and you can use containers, but no, plain C cannot do bounds checking. There's no room in the ABI for it, at least not without CHERI or ASAN.
> although I've never used it
One of the most dangerous things in the world is giving powerful tools to people who don't know that they shouldn't use them.
I don’t think Javascript is the counterexample you want to use as a good language. It is like C where safety is traded for flexibility.
If people can’t write correct software in any language, the language designers can help, which is exactly what the Rust designers did.
It’s not great for everyone because now all the scar tissue that great C devs have around what near-fatal errors they’ve made previously has lost some of its value.
So? Humans cannot consistently write correct code in any language.
> When you get an array bound wrong in C, you open the door for a nefarious actor to gain control over everything that your program can access.
What's really interesting to me is how Heartbleed (the largest example of getting an array bound wrong) was orders of magnitude less expensive to fix than Log4j, and yet it was Log4j that resulted in gauranteed RCE, not Heartbleed.
> C apologists are like apologists for asbestos and lead paint.
Let's not go spilling hyperbole all down our shirt.
I get the feeling that a lot of people comparing C to things that kill have never worked in life-critical situations, the systems of which ran C for decades and decades, and never appeared to have lost very many lives in the process.
Military, munitions, healthcare machines ... I can't remember any of them losing lives because of a misfeature in the C programming language, and yet there you are, proudly proclaiming to the word that C is like Asbestos, which has taken peoples lives.
Get a grip.
The number of things a beginner doesn’t have to know or worry about to code something as essential as a function that reads a file, searches for a string, and interacts in a serious way with the network in Rust vs C is substantial.
Probably I’ll get downvoted again but it’s fine.
It’s useful to write that C code, and then try to break it (e.g., to force a segfault rather than a proper error-then-exit).
Is knowing all that necessary? No, as you are kind of implying. ;-)
C can be dangerous, yes. No one is denying that. C is also extremely stable, fast, and a "known-known". With proper discipline that any experienced developer of ANY language will have C can be very safe. Most, if not all, dangerous errors can be found and fixed simply by understanding how to use valgrind. With the advent of modern CI/CD it's never been easier to remember to valgrind your code.
C is probably the first language I would use in an environment I cared a lot about. For example, if binary size, availability of libraries, competency of programmer, etc would be in my list of concerns I would use C. I take particular offense to the "RNG" part of your analogy. C errors are not random at all. They are always the developer's fault and are often very predictable (use-after-free, poor array hygiene, etc). Despite the ever-evolving condition of post-C religions (Go, Rust, etc) there are only a handful of languages that fit the bill for a so-called serious systems project. C, C++, Java, and C#. You simply will not find a better library ecosystem and community of highly tenured developers to draw from for at least another decade in other languages.
If some people are too sensitive to take a little caricaturization of C in stride, maybe those people should examine those sensitivities.
Edit because I didn’t read your comment all the way through: uninitialized memory is RNG, functionally.
Confession time: I came to this thread to Ctrl-F for "Rust" to find the Rust-C fisticuffs. And this answer is like what I would expect if I said "ChatGPT, write me a reddit-style Rust defense that would rustle some jimmies."
I'm about equally capable at both Rust and C - I could pass a 1st year undergrad CS course using either, but I'm no better than that.
I know C has its place - its marketshare of embedded devices and gamedev is huuuge - and Rust... may eventually reach a similar place of importance, but isn't there yet.
I also know the very best C and Rust devs are pretty much geniuses - Ph.D. level understanding of math, that kind of thing - and would squash me in any type of fair competition.
Maybe in the year 2050, an attitude of condescending acceptance towards C might be appropriate. And also... maybe not, because C might not slip. Who knows?
And really, beyond this pretty crude but accurate-ish description, I think we're getting into unnecessary hair-splitting, among people who don't work with either and wouldn't understand the tradeoffs anyway.
https://www.youtube.com/watch?v=QpAhX-gsHMs
I think the best thing you can do is go find a unit testing library you like the look of, maybe some tools for address/leak sanitization, and spend a good amount of time learning CMake and creating a build/project workflow you're happy with. Coming from Rust you've got great tooling and good guardrails in place to keep you on the happy path, I think getting into that groove early in C where you can run experiments and break things in a sane environment is important.
If you like it you could buy the print version.
There are some things he writes that are a bit controversial but generally I think it is very thorough. Sometimes it felt a bit dry to read to me.
There are tons of useful/fun things you could do with C. Write some desktop apps with GTK, a web server with Mongoose (https://mongoose.ws), etc.
First, Code by Petzold to make sure you understand how a computer truly works. Then I recommend:
Understanding Microprocessors (Motorola)
8086/8088 Assembly Language Programming
Dissecting DOS
Practical Filesystem Design
FreeBSD Internals
The reason there are so many books is because it’s a big topic. The reason most are old is that things were once much simpler. Modern stuff builds on old, so if you get a good understanding of older stuff, you have an idea of what is happening. From there, I strongly recommend getting a good understanding of UEFI. UEFI will handle most of your system initialization no boot requirements, allowing you to focus on higher level stuff. Prior knowledge of systems will still be a requirement so that you can understand some of the assumptions and choices that UEFI makes.
OSDev is fun. I hope you have a good time.
>Understanding Microprocessors (Motorola)
>Dissecting DOS
Can you share or point me to where i can get the above from?
https://www.amazon.com/Dissecting-DOS-Code-Level-Operating-S...
The original companion floppy for that is available on archive.org:
https://archive.org/details/dissecting-dos-companion-floppy-...
Understanding Microprocessors is a small book, and currently unavailable anywhere I've seen except for eBay, but it's also normally quite inexpensive.
Highly recommend learning CMake or Meson instead.
If you're just getting started, there's no need to tie yourself into knots learning "how to set up a project the right way". It's a waste of time. Your time would initially be better spent just calling the C compiler directly. This will help you learn a bit about the compilation and linking phase and will expose you to the C compiler's flags---useful skills for debugging automated builds later. Once you get to the point where you are working on a larger project, then you will be in a better position to decide what build tools are most useful for you.
Yes, this is sane advice! Note that I'm not promoting autotools, I personally suffered it a lot before I got it for my builds, but is a tool that can be reliable across platforms. Yes I would leave the "how to set up a project" for the time when you really need it, no need to spend time on this if you're learning a language.
Eventually I'll be purging Autotools, make and all build systems, replacing it with a single batch file that wipes the slate then compiles. It only takes a few seconds to run, and removes a huge set of dependencies.
I hope to port the code to run under the Cosmopolitan library so I can be cross-platform and more.
[1] https://man7.org/tlpi/
Here's an idea: pick a project that interests you and start. For any question or issue you run into, there will be 1000 Youtube videos to choose from, and 10000 online tutorials. There will be an associated sub reddit to ask questions.
I'd guess it is because Sturgeon's Law[1] applies to most things on the internet. In fact there is so much crap when it specifically comes to instructional material on the internet that it reaches a point where we find that Sturgeon's Law was only a linear approximation to crappiness that is only valid with the relatively limited crap levels they were capable of producing 60 years ago when Sturgeon's Law was formulated.
The internet is capable of achieving crap harmonics, and we find that something like 90% of the stuff that doesn't seem to be crap is actually crap too.
The HN answers to this question are going to have a much lower crap level which will keep them well within the linear region of the Law. Many here recognize that having someplace that answers this question with only a 90% crap level is good and so upvote it.
[1] 90% of everything is crap
Likewise, if you're using any other language and you want to use a general library, you'll need to know enough C so that you can successfully encapsulate concepts like pointers; and copy your program's data into data structures that C can consume.
It's all much, much easier if you have some C experience.
In my case, I haven't done much C in 20 years. What really helped me "keep up" was occasionally doing Mac programming in Objective C. Because it's really easy to drop into C, it helped refresh me on a lot of the concepts. Even if you're doing Swift, you can mix in an class written in Objective C that's mostly straight C in the methods.
Currently I am trying to learn how to test C correctly, seems there are lots of approaches.
It began when I decided to learn low level development a few months ago. I completed my Chip 8 emulator. Next I bought Arduino a month ago and I was playing around with it. Now I am reading AVR Programming book so I decided to learn C properly. On the side I already made a small keyboard with a microcontroller with QMK firmware. Soldering and stuffs.
Next what I’m trying to learn is some electronics knowledge just enough to make stuffs.
This is way more fun than my web development job!
Currently the resources I use: - Learn C The Hard Way - BeeJ’s guide to C - random video tutorials from the internet - Make: Arduino - Make: AVR programming