I think the problem with learning C is that you need to learn stuff like make, autoconf, how the compiler + preprocessors work (what do all those flags even mean!?), how making "cross-platform" stuff works, how to pull in and use "libraries", C-isms, how to test, etc. C itself is a very small and simple language, but the tooling and patterns are old and mysterious.
In college I learned how to program embedded systems with C (and ASM). Once I knew how to debug, build, and push it onto the device, it was surprisingly easy and beautiful. There's no confusing and magical abstractions, it's just you and the hardware. You pull up the microcontroller's manual, and as long as you have an idea of what the lingo means, you can make it dance to your will.
Something I disliked was that with both microcontrollers I used, there was dark magic involved in building and uploading your binary. One of the devices provided examples for a specific IDE, so I imported that example and used it as a base. I could keep modifying it and keep adding files, but I never managed to setup a "new" project. The other device was similar, but instead of using an IDE it provided a Makefile, which was nicer.
Does anyone know any good resources on learning and writing real-world C? I've looked at C projects here and there over the years, with the most interesting being GNU Coreutils... But even if I can eventually understand what some code does, how do I learn why it does it that way?
I'd love for a guide that showed things like: "do X and Y because of this and that", "test X and Y by running the following", "write tests using XYZ", "debug X using Tool A, and Y using Tool B", "pull in this library by copying these files into these places, and use it by adding the following lines to the following files", "generate docs by pulling in this tool and set it up by doing the following", etc.
In the web world there's a massive set of problems, but it tends to be easy to find "boilerplate" generators that will get you up and running. And after you've tried out a few of them, you can usually pick up how people are mixing and matching different tools.
EDIT: Another comment linked to "Learn C The Hard Way" [0], and after browsing through the chapters it seems to cover a lot of the topics I'm interested in.
Javascript has the same problem, but I think its worse. At least there are platform standards in C (autotools in GNU, msbuild on Win).
Trying to figure out how Grunt/Gulp/Broccoli, LESS/SASS/Stylus/Jade, Coffeescript, Uglify, Bower, Browserify, Require.js, AMD/CommonJS, NPM etc all work together is a nightmare.
It's all too hard, so people added Yeoman, Brunch, or other things to generate application configs - but now you need to decide which skeleton/generator you want, which takes you down the JS and CSS framework rabbithole.
Of course the only sane response to all this is to write another build system that doesn't repeat everybody else's mistakes.
I'm a C guy, and I never "worry" that if I have to recompile a project I worked on 1 or 2 years ago that I'm going to have to fight the toolchain to get it going again. Make will be make, and it will work.
I have a huge concern when I do anything in Javascript about what happens in 1 or 2 years from now when I have to modify a project I built using some Yeoman scaffolding. Is it still going to work? Will those node modules still be there? Can I update them without it breaking everything?
It's really weird for me, coming from the world of C, CVS/Git, Make, Linux, etc. where it's almost _unthinkable_ to introduce changes that would break older versions. Hell even the world "old" generally means 5+ or 10+ years.
I don't understand. You can configure your node module to use specific versions of libraries just like a C project. Surely you've heard of problems people have with dynamic linkage and their programs not working? This is why distros spend so much time getting things to work with each other - I think you're just sweeping that under the rug.
The tools you're working with aren't different on this manner - it's not like your tool will magically recode itself to work differently.
Yep. I'm more comfortable with Make than I am with JSPM/NPM and System.js. The process to minify my javascript and CSS and then replace the paths in the HTML so my pages actually work seems to be needlessly complex.
I'm seriously considering whether I can use Make for my production website builds - the only issue is Windows support.
Try out webpack. Webpack makes handling all of your assets into something trivial. I converted around 3,000 lines of Gulp files into a 200 line webpack config. (Most of which ended up being alises, and this is a reasonably big project.)
For an example of what makes it so awesome:
var img = require('./foo.png')
// "/output-path/0dcbbaa701328a3c262cfd45869e351f.png"
Webpack will copy this file (foo.png) to your output folder, and rename it using the file hash, so it does cache-busting.
You don't need to use other build tools, you can just use the webpack CLI. I personally use npm scripts.
Webpack also allows you to setup aliases for modules, as well as load pretty much anything you can imagine. You wanna pull in a module that doesn't use CommonJS and instead exports a global? Webpack has global-loader. AMD is supported as well.
Oh, and this includes a sane development environment, with reload on save, as well as hot loading assets that support it. (Check out react-hot-loader: http://gaearon.github.io/react-hot-loader/, but it works with css as well.)
And you can require css files in your components, and then add the extract-text-webpack-plugin so it'll rip the css from the generated JS bundle!
Aaaaand it handles SourceMaps, so you don't have to worry about some plugins (looking at you gulp) not playing well together.
Finally, it also handles minification, either through a CLI option or in the config.
You have a couple of options with make under windows.
There are native versions of make. Where you are using dos shell commands to do stuff. MinGW provides Posix compatible native commands. (you can use ls, cat, etc commands). You can use msys which gives you a Posix compatible build env. Finally something like Cigwin provides both a Posix compatible build env and Posix compatible runtime as well.
I never found C's transition from source code to hardware to be confusing. I struggle to comprehend why so many people, some longtime professional programmers, have trouble understanding what a linker does.
On the other hand, I downloaded the CUDA SDK. I couldn't even figure out where the GPU compiled code even resided. I suppose I was just supposed to take it as it "just works" (and it did), but it all left me highly uncomfortable.
The OS is big and scary. When you're working directly with the metal, it's SUPER simple, there's no magical abstractions. But when you're using libraries and doing system calls you don't really know what's going on. Sure, you can dig into em sometimes, but it's more than a bit daunting.
I don't think you need to learn the build system until after you're comfortable with single-file programs, where "gcc yourfile.c" is enough. Then add compiler options, and get to know Makefiles after your programs grow large enough to require multiple files.
GNU coreutils (and in general, a lot of the GNU projects) are rather excessively complex and certainly not what I'd advocate "learning by example" from. Take a look at the BSDs' standard utilities for simpler, more straightforward code.
But even if I can eventually understand what some code does, how do I learn why it does it that way?
I believe that the best way to learn "why" is to ask "why not". You will see that a lot of programmers, IMHO unfortunately really don't know why and are just doing what they were taught to. If you don't do X, then either [1] it doesn't matter and you don't actually need to, or [2] it does and you realise the reason why, when you see how X makes things simpler/more efficient for either the programmer or the machine, or both.
Definitely. Not only is it worth doing the wrong thing first to understand why its wrong, its also often worth revisiting after you have more experience with the other alternatives.
Especially when it comes to programming paradigms and stuff like 'best practices'. Theres a lot of cargo-culting in programming culture, and you really shouldn't take it as dogma.
Personally, the hardest part for me was keeping track of the size of everything. Coming from a higher level language, keeping track of the bits and bytes takes some getting used to. Working with arrays in C is much tougher especially when the compiler will compile almost anything you give it, and even a small mistake is catastrophic.
Before C, I was pampered and took everything for granted.
Now I appreciated my life more after C and feel blessed every time my IDE gives me a warning.
My pet hate is #include files. The whole way that C handles multiple source files just seems archaic to me, having worked in higher-level languages. I wish C had a proper package system that was standard, so I don't have to mess around with things like include file path order (or my favorite, the C++ template definitions having to be in the header files thing I only recently learned about).
Interestingly, I first started with C (although I haven't written a line of C code for a long time), and when I first move to higher-level languages, I dislike the fact that I have no idea where the file I just imported is. Moreso when I'm playing with obscure/ new language: if I can just import whatever files I wanted (rather than at package level), it seems that would be much easier to hack on the language/std itself.
A sufficiently long include path can give you this problem anyway. I recently tripped over this when I created a "reason.h" and discovered that Windows had a file of the same name deep inside MFC.
I don't quite get this lament about being pampered in higher level languages. To me, it feels like someone saying they feel pampered for having indoor plumbing or running water.
Frankly, I don't use a lot of crazy libs or IDEs when writing C code. Most of my projects consist of one or two external libs and a few simple makefiles. I use Vim and clang/gcc for compiling and lldb/gdb for debugging.
As for compiler flags, the only ones I ever worry about are `-O`, `-g`, `-c`, CFLAGS and LDFLAGS.
What I've learned is that the way C includes other files/libs is extremely simple. The header files are simply a simple mapping of the code in the `.so` or `.a`. If it's a n `.so`, you can't make it a static executable and if it's `.a` you have to.
I've never worked directly with low-level microcontroller programming, and the extent of my electronics knowledge is some fiddling with arduino. When I did that I used ino (http://inotool.org/) to compile and upload code to the board.
EDIT:
Projects that have good C code include: http://suckless.org/ and the linux kernel (and other stuff by Linus Torvalds). Avoid anything with GNU (most of it's over-engineered)
I reccomend "Programming in the UNIX Environment" by Kernighan and Pike. Partly because it was written so soon after UNIX and C themselves, it has very little of the modern 'cruft' in it. It's at the level of "cc program.c -o program".
Make is fairly easy to learn, at least in its basic form. Autoconf is horrendous.
> Make is fairly easy to learn, at least in its basic form. Autoconf is horrendous.
I completely agree. Make is extremely flexible on it's own. I don't understand the need to abstract the build system to generate thousand-line makefiles that are impossible to hand edit.
As benwaffle says in adjacent comment, make is the 80% solution that works most of the time. Autoconf is the 100% solution that's supposed to work everywhere, no matter how weird or long-dead your UNIX is. In order to do that it does a vast number of compatibility tests. The result is complex enough that simple substitution of makefiles doesn't quite cut it.
Of course, that imposes the cost of 100% compatibility on every developer, when most would be happy to just build on today's Linux and call it a day.
> Autoconf is the 100% solution that's supposed to work everywhere
> Of course, that imposes the cost of 100% compatibility on every developer, when most would be happy to just build on today's Linux and call it a day.
The reality is that when you use programs built with autotools on systems that aren't mainstream, you'll have troubles. Because the scripts aren't right and were only tested by Linux developers on Linux.
And much of the time, I find it faster and easier to fix a broken makefile than to fix broken autohell.
> And much of the time, I find it faster and easier to fix a broken makefile than to fix broken autohell.
Exactly. 99% of the time, a broken makefile simply has an incorrect linker path or cflag. When it's not a path, the Makefile is structured in a way that makes sense and is easy to fix. If an autoconf project is broken, I just scrap it and don't even bother trying to build it.
The other issue with autoconf is that it's not standardized. So many of the autoconf projects I've seen have shell scripts (to install it or download deps) mixed in that only add more confusion. Some of them have a configure script. Some have a configure.in, so you have to generate the configure yourself.
100% of the makefiles I've seen have a build, install and clean task. Sure, it's not required, but everybody does it. You can't say the same for autoconf.
The idea is to make it work on all platforms, only requiring minimal POSIX compatibility. It also checks for any requirements you specify, and sets up stuff like make install, make dist-check, make check. It handles compiling your code into a library regardless of the os
unit tests are more effective than using a debugger. I use a debugger a couple times a month and unit tests with sprinkled asserts and debug prints in the code.
BTW: just want to say that you have a fascinating blog! I've just spent last hour only skimming through some of the articles and bookmarking them for later. (Link for the lazy: https://nickdesaulniers.github.io/)
I switched to CMake for all of my C and C++ projects and i never looked back. Takes care of a bunch of makefile issues for you once you get used to it.
In college I learned how to program embedded systems with C (and ASM). Once I knew how to debug, build, and push it onto the device, it was surprisingly easy and beautiful. There's no confusing and magical abstractions, it's just you and the hardware. You pull up the microcontroller's manual, and as long as you have an idea of what the lingo means, you can make it dance to your will.
Something I disliked was that with both microcontrollers I used, there was dark magic involved in building and uploading your binary. One of the devices provided examples for a specific IDE, so I imported that example and used it as a base. I could keep modifying it and keep adding files, but I never managed to setup a "new" project. The other device was similar, but instead of using an IDE it provided a Makefile, which was nicer.
Does anyone know any good resources on learning and writing real-world C? I've looked at C projects here and there over the years, with the most interesting being GNU Coreutils... But even if I can eventually understand what some code does, how do I learn why it does it that way?
I'd love for a guide that showed things like: "do X and Y because of this and that", "test X and Y by running the following", "write tests using XYZ", "debug X using Tool A, and Y using Tool B", "pull in this library by copying these files into these places, and use it by adding the following lines to the following files", "generate docs by pulling in this tool and set it up by doing the following", etc.
In the web world there's a massive set of problems, but it tends to be easy to find "boilerplate" generators that will get you up and running. And after you've tried out a few of them, you can usually pick up how people are mixing and matching different tools.
EDIT: Another comment linked to "Learn C The Hard Way" [0], and after browsing through the chapters it seems to cover a lot of the topics I'm interested in.
[0] http://c.learncodethehardway.org/book/