some makefile based build codebase can not build compile_commands.json at all, I had to use the old ctags way to jump around, as clangd could not understand the code(e.g. it can find header definitions, but could not find function implementations in other non-header source files)
I would love to stick with clangd if it can do whatever ctags can as far as source indexing goes, it seems can not.
VLA are closer to a deprecated feature than a recent feature: they were mandatory in C99, and made optional in C11.
Would you consider the fixed width integer types (uint8_t, etc) a recent C feature worth using? The older type's size being implementation defined seems like a major pain to me, but I haven't used C much.
https://pubs.opengroup.org/onlinepubs/009695399/basedefs/std...
C89 (aka ANSI C) was "standard" C for years. C99 was released, and largely ignored until the mid-2000's, and then gradually began to grow. As of about 2015, C99 was probably the "standard" C dialect, but there is still a lot of C89 out there, especially where you have niche toolchains.
I wouldn't seriously consider C11 unless you're certain that the toolchain supports it, and there's some feature that you really need. And that's pretty unlikely.
One thing I'd add to the list: stdint.h. Don't use int, long, etc. Just use uint32_t, uint64_t and friends. Completely eliminates the ambiguity and you don't have to remember which is which.
I would recommend against this. You're trading off one form of incorrect behavior (UB) for another form of incorrect behavior (wraparound on overflow is incorrect >99% of the time), with the added potential for mistakes in the emulation step.
Instead, do this:
* If you consider that wraparound on overflow is in fact the correct and desired behavior, use unsigned arithmetic.
* If you want to check for overflowing conditions, use your compiler's builtins for checked operations (C23 finally adds ckd_* ops, but it's too new to recommend use of yet).
I have, in fact, done this.
("It's not portable" is a reason that will cease to be true shortly--as mentioned, C23 added checked overflow functions.)
> Your compiler already has an implementation
That's a big assumption that is not true on all compilers I want to target.
So in practice, today, it's not portable and won't be for a while (even when it's released into compilers, it'll take time for those new versions to trickle into the ecosystem), which is a compelling argument against using it.
also, which way you use to do 'destructors' in C?
libname_module_function();
So for my bc [1], I do this: bc_parse_if(...);
That parses an `if` statement.For destructors, I want them to be passed and stored as first-class entities, so I defined them like this:
typedef void (*BcDestructor)(void* item);
Then things like my resizable arrays store the destructor for the items in the array, and when items are removed (popped, whatever), the destructor is run on the item if the destructor is not NULL.Works like a charm.
Edit: There is one danger to the destructors: you can pass the wrong type of pointer. But if this happens, you'll probably get a lot of Valgrind errors and a spectacular crash.
Typically, they're two-part. First you have the project/company prefix, and then the ADT (abstract data type) prefix. So, eg. "foo_hash_" is a prefix from company/project "foo" and is a part of the "hash" ADT interface.
As a consequence, C people like abbreviations. Which is fine in moderation.
Private functions don't need the prefixes, of course.
Public variables are a little problematic if you ever port to Windows (DLLs), so try to avoid them.
I find that gcc is a version or two behind clang for new compiler diagnostics and static analysis features (purely anecdotal so please take it with pinch of salt).
* +1 to sanitizers.
* +1 to clangd and bear.
* +1 to namespacing.
* Make sure you have a code formatter. I use clang-format, but any should work.
* Do not use recent C features unless you need them. VLA's in particular are a bad choice. An example of something you might need is `alignof()` and `max_align_t`, which are basically necessary if writing your own allocator(s).
* Write destructors for your types. If they own data, their destructor should free that data. This has helped me avoid countless memory leaks.
* Finally (and this will probably be controversial), use unsigned arithmetic as much as you can! The reason for this is because unsigned arithmetic has far fewer cases of UB, and with a little work, you can simulate two's-complement signed arithmetic in C without UB.
Source: I am a C programmer with a public project that has had very few memory bugs or UB bugs found in releases. In particular, unsigned arithmetic is what got me farther than most, since most will use Valgrind/sanitizers.