related to this `make` post I've been very vaguely thinking of writing a "things that are useful to know about C even if you're never going to write a C program in your life" zine because I feel like I get a surprising amount of mileage out of understanding C at a very basic level even though I have never really written a C or C++ program and do not plan to
but I have to actually get the terminal zine published before I start thinking about whether I want to do that or not
@b0rk In John Barnes' odd fairytale _One For the Morning Glory_ the royal library has books titled _Highly Unpleasant Things It Is Sometimes Useful to Know_ and _Things It Is Not Good to Know at All_.
It seems like this organizing principle might be applied to the general subject of C programming.
@b0rk This knowledge really useful in a bunch of places, yes!
- Operating system APIs are all documented as C/C++ APIs
- All (or almost all?) books on low-level software performance assume you can read C
I'm thinking about the latter a lot at the moment since I'm trying to write a book that -doesn't- require that, but would be nice to have something I can point people at.
@itamarst you're making me wonder what the "minimal" set of C it's useful to understand is
for example I feel like understanding everything that's going on in this extrmely small program is a good start, but probably there are a couple of other things that are useful too?
@b0rk @itamarst not C specific but understanding calling conventions might also be useful since it’s what most FFI libraries will use to call C code.
I’ve seen folks struggle with pointers a lot but for non-C folks the one place that’s useful to them are “result” pointers: often a C function will take a pointer as an argument and it will initialize the data at that pointer instead of returning a value; this surprises users of higher level languages.
Also checking global, special variables to detect errors. Especially with system calls.
Sorry for the info dump. I’m a big fan of your work and I think this is a fantastic idea. 😇
The scoping of what to teach is a tricky problem yeah.
I think my success criteria (and there are other ones I'm sure) would be "are you able to read Kerrisk's The Linux Programming Interface book" which probably does a involve a bunch of additional syntax, just opening a random page (e.g. setting flags with |= operator).
The other question is... what can you assume the people you're writing for already know?
Someone with decent knowledge of Python you'd need to teach some syntax and surface semantics (straightforward, mostly), data types (straightforward, mostly), and pointers (complicated).
But "getting memory management right" is unnecessary as you said, and "here's how you make .h files for your library" is unnecessary, "what does static and friends mean, what are compilation units etc" is almost certainly unnecessary.
@itamarst oh man explaining pointers sounds like a huge weird project. recently I realized that the way pointers are used in C is a bit weird compared to in other languages (where instead of having a function return a value in a "normal" way, instead you'll just have a void* function which you pass a pointer to the struct that you want it to populate with the return value)
that's maybe just a consequence of C not having a garbage collector but it's still kind of weird
That example requires understanding more than just the C programming language. It also requires understanding some system calls (section 2 of the manpages) and library functions (section 3).
The C language involves simple constructs like int, pointers, structures, if, else, while, return, etc.
Casual programmers learning C would need to understand a minimal subset of system or library calls, e.g. simple usage of printf(3) but not sigaction(2). Signals are quite complicated.
@b0rk CPPFLAGS is for the preprocessor, though I guess it's strictly correct to use instead of CFLAGS for -I in particular! I just think it might be confusing to refer to CPPFLAGS rather than the more common CFLAGS/CXXFLAGS.
I think one confusing binary toolchain thing that got me when I first learned is that I didn't understand that cc was both the c compiler *and* the linker depending on args, and that often tooling would be really confusing about what it actually is calling when it is "calling the linker" (-fuse-ld referring to an actual linker) *because* a lot of the time, including with rust, "calling the linker" means "calling the compiler driver in link mode".
@leftpaddotpy ugh yeah this is kind of a weird example! the reason I ended up using CPPFLAGS instead of CXXFLAGS is that the makefile hardcodes CXXFLAGS to something else so that was the only way I could figure out to pass flags to the compiler without editing the makefile?
but maybe there's another way to do it that I'm missing
@b0rk The makefile is supposed to use some other assignment operator in that case, I think += possibly? It's supposed to be the case that you can add your own flags, but it's never quite ideal especially with crusty makefiles, so sometimes you have to do what you have to do.
In fact what it's ideally supposed to do is call pkg-config --cflags somedep and automatically resolve it from the .pc file shipped by the package (PKG_CONFIG_PATH to override those). Alas.
For this regrettable reason of bad build systems, nixpkgs chooses to replace the c compiler with its own little bad shell script, cc-wrapper, which is what processes those NIX_CFLAGS_COMPILE things and injects them into the actual compiler args. This is usually really confusing but at least if it fucks up you actually have a much nicer time than a normal compiler because you can NIX_DEBUG=4 (6? i forget what the just spammy enough one is) or so and get the compiler args dumped right out to stderr without putting up any fights.
@b0rk it's *supposed* to be a pretty simple thing of "find the transitive dependency closure of the pkg-config package names you handed it and dump out the concatenation of the cflags/ldflags to use those packages as requested". idk i definitely should read the docs lmao!
@ariadne develops it and seems to be working on some really awesome looking modernization to hopefully reduce the amount of fiddling with cflags and overall crimes people put in their pkg-config files (aws-sdk-cpp for a long time was setting -std=c++17 in their requested cflags!! which is busted because you might want a newer standard yourself).
@b0rk @leftpaddotpy looking at the essay again, it seems like you consistently use environment variables to set makefile variables:
CFLAGS='...' make
But you can also set makefile variables as command line arguments:
make CFLAGS='...'
And this works kinda like CSS cascades. A variable value given on the command line overrides what was written in the makefile, but a value given in the environment only applies if there wasn't any value written in the makefile.
@b0rk For what it's worth, LD_LIBRARY_PATH problems are usually caused by projects misunderstanding the dynamic loader and screwing up something like omitting soname (-install_name on macOS) or otherwise having wrong rpath. Rust currently doesn't do this well with --crate-type=dylib, though that's admittedly rarely used.
I should write my own post about this stuff, how to use otool -L, install_name_tool, and similar to fix these rpath related load problems. Then maybe I can stop accidentally becoming the macOS domain expert 🙃.
Recently after several years of refusing to engage with autotools because of distaste for it (as an occasional begrudging C/C++ developer/packager) I finally spent 20 minutes reading the intro section to the autotools manual and found it really valuable in understanding how the project thinks about itself and *why* it's the way it is. I still don't like it but I understand better what it is trying to do. https://docs.jade.fyi/gnu/autoconf/autoconf.html#The-GNU-Build-System
@b0rk "Really unusual" here means things like the Linux kernel.
You can see what all the extra options are by picking a program that fits all in one .c file, like qf, and compiling it by hand with -v added to the options — for qf something like this should do it:
gcc -c -g -O -o qf.o qf.c
gcc -v -o qf qf.o -lncurses
this will spew out a lot of output, look for the "collect2" line (collect2 is *another* wrapper around ld but it doesn't do much of anything nowadays). (2/2)
@b0rk A small tidbit which has been *immensely* helpful when compiling makefile-based programs is the `--output-sync` option of make (see attached pic).
Compiler warnings are already hard to read, no need to mix them up with multiple `make` threads!
I think it'd be a good addition to the tips in section 4 of the article.
@b0rk Usually, I invoke make as `make -j10 --output-sync=target`. It allows me to see all of the compiler output of one failed "thread" at a time in the terminal.
Instead of interleaving warnings/errors for multiple in-progress builds, I can see them all at once once they've finished :)
@b0rk For fun, you can use make for scripting! Just put
#!/usr/bin/make -f
And then just write a normal Makefile syntax file. It's like an easy way to make a shell script with loads of subcommands that sometimes run.
No, it's not a good idea, but I've seen it done!
https://github.com/lgersman/make-recipes-in-different-scripting-languages-demoDid you kow that Make recipes can be written in almost any scripting language ? You don't need to know about shell scripting. Just use NodeJS, Typescript , Python or whatever you want ! - lgers...
Did you kow that Make recipes can be written in almost any scripting language ? You don't need to know about shell scripting. Just use NodeJS, Typescript , Python or whatever you want ! - lgers...
@b0rk I am a C programmer, and know make rather well... But I struggle to explain it to people.
This is an excellent guide, and really nails a lot of the things that someone like me typically misses when trying to teach someone.
Thanks for writing it and thanks for sharing!