C hasn't seen a lot of relevant changes since C89 as far as I'm concerned. The most important for me is definitely declare-anywhere (which had been widely supported prior to standardization in C99), it removes most of the friction when quickly trying out new stuff.
Here are some things I almost always do to improve ergonomics (some of this is debatable and none of this should be exposed in a library, at least prefix things)
Each .c file includes "common.h" as the first thing.
Each .h file has #pragma once at the beginning.
ARRAY_COUNT(a) is used to get the capacity of a C array without using defines (which is brittle). Unfortunately it's imperfect because code breaks when you change arrays to dynamically allocated buffers and you forget to switch to using dynamic capacity values. This is one case where I'd like to see some standards update that allows us to improve safety.
STRUCT(x) is used to declare struct x with typedef -- I've grown to hate tag namespaces for their boilerplate. I simply uppercase types, and the problem (that struct tags were invented to solve) is gone.
Probably I'll make something like this very soon to get started
Later obviously more sophisticated allocation is needed, but the point here is how macros can be employed to improve safety and ergonomics. This is how I do polymorphism in C basically, not much more is needed than abstracting over size and maybe alignment for almost everything. In some cases, manually set up v-tables make sense from an architectural perspective.
I also often make a very simple "logging" module that does basically printf logging but with \n automatically added and optionally printing out __FILE__ and __LINE__ but without requiring to re-type this all the time. Something like
Other than that, I like to not think about the language but about what the machine is going to do. How to decrease size of working set and generally speed up the program. How to speed up compilation (don't expose all the internals in the .h files). Things like that.
There are very few container data structures actually needed, most of the time it's just C-arrays, dynamically allocated buffers (pointer + capacity), and some simple queues / synchronization primitives. Also linked lists. A favorite of mine are chunk-lists. All of these are very simple to implement -- there is hardly a point of making them in a super-general library, it's ok to code them from scratch for all but the smallest projects.
I like it like that. It's also done like that in some codebases that I'm often skimming, e.g. the Linux kernel. I don't worry about the standard in this case. As I've written more and more code, my willingness to do extra bullshit chores when there is no practical reason has been steadily decreasing.
Should you happen to choose an identifier that is already used by the host environment, you'll notice and can fix it. (It has literally never happened to me).
The intention here is that the prefixed variants are "private" i.e. they should not, or only rarely, be used directly. Instead, I use the macro layers to automatically fill in the right values and to improve safety. The above allows me to write
My_Foo *foo = xmalloc(My_Foo);
As you can see, there is very little boilerplate. Prefixing the implementation function makes it less likely to accidentally use them when doing code completion. The prefix nicely documents the relation between the macro and the inline function, and I don't have to bother coming up with a different name or naming scheme.
It's Undefined Behavior by the C standard. So GCC (or other compiler) could use that for optimization, and omit the relevant lines or otherwise mess things up in weird ways. There's no guarantee that you'll notice, and no need for the identifier to be used by the host environment for it to cause a problem.
That said, I'm not aware of any current compiler that does this. It'd be weird.
Here are some things I almost always do to improve ergonomics (some of this is debatable and none of this should be exposed in a library, at least prefix things)
Each .c file includes "common.h" as the first thing. Each .h file has #pragma once at the beginning.ARRAY_COUNT(a) is used to get the capacity of a C array without using defines (which is brittle). Unfortunately it's imperfect because code breaks when you change arrays to dynamically allocated buffers and you forget to switch to using dynamic capacity values. This is one case where I'd like to see some standards update that allows us to improve safety.
STRUCT(x) is used to declare struct x with typedef -- I've grown to hate tag namespaces for their boilerplate. I simply uppercase types, and the problem (that struct tags were invented to solve) is gone.
Probably I'll make something like this very soon to get started
Later obviously more sophisticated allocation is needed, but the point here is how macros can be employed to improve safety and ergonomics. This is how I do polymorphism in C basically, not much more is needed than abstracting over size and maybe alignment for almost everything. In some cases, manually set up v-tables make sense from an architectural perspective.I also often make a very simple "logging" module that does basically printf logging but with \n automatically added and optionally printing out __FILE__ and __LINE__ but without requiring to re-type this all the time. Something like
Other than that, I like to not think about the language but about what the machine is going to do. How to decrease size of working set and generally speed up the program. How to speed up compilation (don't expose all the internals in the .h files). Things like that.There are very few container data structures actually needed, most of the time it's just C-arrays, dynamically allocated buffers (pointer + capacity), and some simple queues / synchronization primitives. Also linked lists. A favorite of mine are chunk-lists. All of these are very simple to implement -- there is hardly a point of making them in a super-general library, it's ok to code them from scratch for all but the smallest projects.