C Style: My favorite C programming practices (2014)
github.com
github.com
Suffice to say: my opinions on this topic have shifted significantly. A decade+ more of programming-in-the-large, and I no longer pay much heed to written-in-prose style guides. Instead, I've found mechanistic "style" enforcement and close-to-live-feedback much more effective for maintaining code quality over time.
A subtext is that I wrote this during a period of work - solo programmer, small company - on a green-field power system microcontroller project; MODBUS comms, CSV data wrangling. I'd opted for C primarily for the appeal of having a codebase I could keep in my head (dependencies included!). There was much in-the-field development, debugging and redeployments, so it was really valuable to have a thin stack, and an easy build process.
So, other than one vendored third-party package, I had total control over that codebase's style. And so, I had the space to consider and evolve my C programming style, reflecting on what I considered was working best for that code.
My personal C code style has since shifted significantly, as well - much more towards older, more-conventional styles.
Still, opinionated, idiosyncratic documents like this - if nothing else - can serve as fun discussion prompts. I'm appreciating all the discussion here!
If speed is a primary concern, you can't tack it on at the end, it needs to be built in architecturally. Benchmarks applied after meeting goals of read/maintainability are only benchmarking the limits of that approach and focus.
They can't capture the results of trying and benchmarking several different fundamental approaches made at the outset in order to best choose the initial direction. In this case "optimisation" is almost happening first.
Sometimes the fastest approach may not be particularly maintainable, and that may be just fine if that component is not expected to require maintaining, eg, a pure C bare-metal in a bespoke and one-off embedded environment.
I do think it is much better to prioritize readability; then measure where the code has to be sped up, and then do changes, but try HARD to first find a better algorithm, and if that does not work, and more processor, or equipment is not viable or still does not work, go for less readable code, which is microoptimized
The difference was ridiculous - we were actually porting a prototype algorithm from a powerful TI device with hardware floating point. It turned out viable to simply compile the same algorithm with software emulation of floating point - the Cortex M0 could keep up.
Having said all that though: the 8051 solution was so much physically smaller that the ARM just wouldn't have been viable in some products (this was more significant because having the analogue circuitry on-chip limited how small the feature size for the digital part of the silicon could be).
Obviously that was quite a while ago! But even at the time, I was amazed how much difference the simpler chip made actually made to the size of the solution. The ARM would have been a total deal breaker for that first project, it would just have been too big. I could certainly believe people are still programming for applications like that where a modern CPU doesn't get a look in.
But it's true that outside the top-level "don't make dumb design decisions" decision points, application code in the embedded world is reasonably insulated form this kind of nonsense. But that's because the folks you're standing on did the work for you.
if we can believe the datasheet, it's basically a pic12f clone (with 55 'powerful' instructions, most single-cycle) with 512 instructions of memory, a 4-level hardware stack, and 32 bytes of ram, with an internal 20 megahertz clock, 20 milliamps per pin at 5 volts, burning half a microamp in halt mode and 700 microamps at full speed at 3 volts
and it costs less than most discrete transistors. in fact, although that page is the sop-8 version, you can get it in a sot23-6 package too
there are definitely a lot of things you can do with this chip if you're willing to optimize your code. but you aren't going to start with a 30-kilobyte firmware image and optimize it until it fits
yeah it's not an nrf52840 and you probably can't do ble on it. but the ny8a051h costs 1.58¢, and an nrf52840 costs 245¢, 154 times as much, and only runs three times as fast on the kinds of things you'd mostly use the ny8a051h for. it does have a lot more than 154 times as much ram tho
for 11.83¢ you can get a ch32v003 https://www.lcsc.com/product-detail/Microcontroller-Units-MC... which is a 48 megahertz risc-v processor with 2 kilobytes of ram, 16 kilobytes of flash, a 10-bit 1.7 megahertz adc, and an on-chip op-amp. so for 5% of the cost of the nrf52840 you get 50% of the cpu speed, 1.6% of the ram, and 0% of the bluetooth
for 70¢, less than a third the price of the nrf52840, you can get an ice40ul-640 https://www.lcsc.com/product-detail/Programmable-Logic-Devic... which i'm pretty sure can do bluetooth. though it might be saner to hook it up to one of the microcontrollers mentioned above (or maybe something with a few more pins), you can probably fit olof kindgren's serv implementation of risc-v https://github.com/olofk/serv into about a third of it and probably get over a mips out of it. but the total amount of block ram is 7 kilobytes. the compensating virtue is that you have another 400 or so luts and flip-flops to do certain kinds of data processing a lot faster and more predictably than a cpu can. 19 billion bit operations per second and pin-to-pin latency of 9 nanoseconds
so my summary is that there's a lot of that kind of embedded work going on, maybe more than ever, and you can do things today that were impossible only a few years ago
on the other hand, something like a 32-bit multiplication or a floating-point subtraction is going to cost a lot of instructions, if you can afford it at all
I'll be more impressed when I see specific advice about what kinds of "optimizations" are premature. Or, to address your reply specifically, what counts as "doing something dumb" vs. what is a "micro-optimization". And, the truth is, you can't really answer those questions without a specific project and programming language in mind.
But, what I do end up seeing across domains and programming languages is that people sacrifice efficiency (which is objective and measurable, even if "micro") for a vague idea of what they consider to be "readable" (today--ask them again in six months). What I'm specifically thinking of is people writing in programming languages with eager collection types that have `map`, `filter`, etc methods, and they'll chain four or five of them together because it's "more readable" than a for-loop. The difference in readability is absolutely negligible to any programmer, but they choose to make four extra heap-allocated, temporary, arrays/lists and iterate over the N elements four or five times instead of once because it looks slightly more elegant (and I agree that it does). Is it a "micro-optimization" to just opt for the for-loop so that I don't have to benchmark how shitty the performance is in the future when we're iterating over more elements than we thought we'd ever need to? Or is it not doing something dumb? To me, it seems ridiculous to intentionally choose a sub-optimal solution when the optimal one is just as easy to write and 99% (or more) as easy to read/understand.
Architecting for performance means picking your data structures, data flow, and algorithms with some thought towards efficiency for the application you have in mind. Details will vary a lot depending on context. But as many folks have said, this sort of thing can't be done after the fact.
As for "doing something dumb", I've often seem fellow engineers do things like repeatedly insert into sorted data structures in a loop instead of just inserting into an unsorted structure and then sorting after the inserts. If you think about it for just a minute, it should be obvious why that's not smart (for most cases.) Stuff like that.
What do I mean by "micro-optimizations"? Taking a clearly written function and spending a lot of time making it as efficient _as_possible_ (possibly at the expense of clarity) without first doing some performance analysis to see if it matters.
Nobody's saying to pick suboptimal solutions at all.
That's a great example that I've seen in the wild as well!
> Nobody's saying to pick suboptimal solutions at all.
No, I realize that. And most of my comment wasn't intended as some kind of direct disagreement to yours. It was mostly just some observations. One of which is that advice about writing efficient code is usually too vague to be useful, and the other is that people take the "don't optimize without measuring" advice to mean something ridiculous in the opposite extreme that reads more like "just write whatever garbage looks pretty to you because any forethought about what makes sense to the computer is premature optimization". I wasn't trying to say that's what you were advocating for, though.
One thing that I'll strongly quibble with: "Use double rather than float, unless you have a specific reason otherwise".
As a graphics programmer, I've found that single precision will do just fine in the vast majority of cases. I've also found that it's often better to try to make my code work well in the single precision while keeping an eye out for precision loss. Then I can either rewrite my math to try to avoid the precision loss, or selectively use double precision just in the parts where its needed. I think that using double precision from the start is a big hammer that's often unneeded. And using single precision buys you double the number of floats moving through your cache and memory bandwidth compared to using double precision.
Switch has many valid uses. However, I also often see switch used in places where functional decomposition would've been much better (maintainable / testable / extensible). So I think there's still value in advocating for those switch alternatives, such as that rule's text covers. Not that I agree with everything there either. But, useful for discussion!
OTOH, double precision is often just a panacea. If you don't know the precision requirements of your algorithm, how do you know that double precision will work either? Some types of errors will compound without anti-drifting protection in ways that are exponential, where the extra mantissa bits from a double will only get you a constant factor of additional time.
There are also current platforms where double will land you in very significant performance problems, not just a minor hit. GPUs are a particularly fun one -- there are currently popular GPUs where double precision math runs at 1/32 the rate of single precision.
And when the cure fails to be adequate, well, it becomes a band-aid, a temporary measure in search of a real solution.
surely nanoseconds is the truth.
Couple decades ago Microsoft did that too, VT_DATE in old OLE Automation keeping FP64 value inside. Luckily, their newer APIs and frameworks are using uint64 with 100-nanoseconds ticks.
Integers are always an option, of course, but in this context it's hard to beat the convenience of just storing seconds in a floating point number.
Related: https://randomascii.wordpress.com/2012/02/13/dont-store-that...
Sure, my code could maybe run on an embedded platform someday. But the person importing it probably has an editor that can do a search and replace...
What I was thinking too. There's something in here to offend everyone, and that's probably a good thing.
Try pasting a long URL into a comment describing a method/problem/solution and you’ll see immediately that it doesn’t fit 77 chars and you cannot wrap it. Then due to your hard limit you’ll invent something like “// see explained.txt:123 for explanation” or maybe “https://shrt.url/f0ob4r” it.
There’s nothing wrong with breaking limits if you do that reasonably, cause most limits have edge cases. It’s (Rule -> Goal X) most of the times, but sometimes it’s (Rule -> Issue). Make it (Solution (breaks Rule) -> Goal X), not (Solution (obeys Rule) -> not (Goal X)).
Another argument for shorters lines is that it is much harder for us to read any text when lines get too long. There's a reason why we read and write documents in portrait mode, not landscape.
But in sum, I don't think there's a need for creating a hard limit at the 80 character mark. Most code is not indented more than three or four times anyways, and most if not all languages allow you to insert newlines to make long expressions wrap. However, if you occasionally do need to go longer, I think that's completely fine and certainly better than having to bend around an arcane character limit.
The 80 char rule has little to do with old monitors. Has to do with ergonomics, and is why any good edited and typeset book will have between 60 and 80 characters per line.
While I understand that this is an anecdotal preference, to me it doesn’t feel like the 80 column standard fits any modern dev workspace perfectly, tbh. (By modern I don’t mean “shiny”, just what we have now in hw/sw.)
But then again, of course there is a reason why terminals (or punchcards) were made that way - presumably because of reading / writing ergonomics (besides technical reasons).
For example Java often prefers long explicitly verbose names for class, fields, methods, variables.
Another approach is to use short names as much as possible. `mkdir` instead of `create_directory`, `i` instead of `person_index` and so on.
I think that max line length greatly depends on the chosen identifier naming style. So it makes sense to use 100 or 120 for Java and it makes sense to use 72 for Golang.
C code often use short naming style, so 72 or 80 should be fine.
For mkdir all-in, meh. It’s okay for mkdirp or rimraf, cause these basically became new words. But once you add more libs or code to a project it becomes a cryptic mess of six-letter nonsense. English code reads best, Java just overdoes it by adding patterns into the mix.
Local variables sure, be short and terse. But that's common in most languages.
They are talking about URLs in comments.
However, the item to not use unsigned types is vastly stupid! Signed types have far more instances of UB, and in the face of 00UB [1], that is untenable.
It is correct that mixing signed and unsigned is really bad; don't do this.
Instead, use unsigned types for everything, including signed math. Yes, you can simulate two's complement with unsigned types, and you can do it without UB.
On my part, all of my stuff uses unsigned, and when I get a signed type from the outside, the first thing I do is convert it safely, so I don't mix the two.
This does mean you have to be careful in some ways. For example, when casting a "signed" type to a larger "signed" type, you need to explicitly check the sign bit and fill the extension with that bit.
And yes, you need to use functions for math, which can be ugly. But you can make them static inline in a header so that they will be inlined.
The result is that my code isn't subject to 00UB nearly as much.
If you start to fuzz test with UBSan and -fsanitize=integer, you will realize that the choice of integer types doesn't matter much. Unsigned types have the benefit that overflowing the left end of the allowed range (zero) has a much better chance of being detected.
This is absolutely false.
Say you want to check if a mathematical operation will overflow. How do you do it with signed types?
Answer: you can't. The compiler will delete any form of check you make because it's UB.
(There might be really clever forms that avoid UB, but I haven't found them.)
The problem with UB isn't UB, it's the compiler. If the compilers didn't take advantage of UB, then you would be right, but they do, so you're wrong.
However, what if you did that same check with unsigned types? The compiler has to allow it.
Even more importantly, you can implement crashes on overflow if you wish, to find those bugs, and I have done so. You can also implement it so the operation returns a bit saying whether it overflowed or not.
You can't do that with signed types.
> If you start to fuzz test with UBSan and -fsanitize=integer, you will realize that the choice of integer types doesn't matter much.
I do this, and this is exactly why I think it matters. Every time they report UB is a chance for the compiler to maliciously destroy your hard work.
What? Of course you can. If you want to add ints a and b:
if (b >= 0 ? a > INT_MAX - b : a < INT_MIN - b)
printf("overflow\n");Now do it for multiplication.
With respect, this is nonsense. With UB, the compiler might remove the line of code entirely. With overflow/underflow/truncation, the results are well-defined and the compiler is not allowed to simply remove the offending line.
I would still advocate for large signed types over unsigned types for most domain-level measurements. Even if you think you "can't" have a negative balance or distance field, use a signed integer type so that underflows are more correct.
Although validating bounds would be strictly better, in many large contexts you can't tie validation to the representation, such as across most isolation boundaries (IPC, network, ...). For example, you see signed integer types much more often in service APIs and IDLs, and I think that's usually the right call.
> I would still advocate for large signed types over unsigned types for most domain-level measurements. Even if you think you "can't" have a negative balance or distance field, use a signed integer type so that underflows are more correct.
I agree with this, but I think I would personally still use unsigned types simulating two's complement that gives the correct underflow semantics. Yeah, I'm a hard egg.
I used to agree with this but I have moved away from compound literals entirely except for global statics/const definitions.
Having a variable and explicit:
foo.x = whatever;
foo.y = something_else;
Leads to better debug experience imo, can set breakpoints and single step each assignment and have a name to put a watch on.One advantage of initialization via compound literals is that you can make the target immutable, and you won't accidentially get any uninitialized junk in unlisted struct members, e.g.:
const vec3 vec = { .x = 1.0, .y = 2.0 };
...vec.z will be default-initialized to zero, and vec doesn't need to be mutable.
This is one of the silliest practices to still be enforced or even considered in 2024. “Readers” should get a modern IDE/text editor and/or modern hardware.
Annoyingly lots of modern website have very wonky breakpoints / detection and will serve nonsense mobile UIs on what I think is reasonable window widths e.g. if you consider bootstrap's "xl" to be desktop then an UWQHD display (3440x1440) won't get a desktop layout in 3 (to say nothing of 4) columns layouts, nor may smaller laptops (especially if they're zoomed somewhat).
Is that true for an average developer, really? Yes, we read lots of manuals, snippets, stackoverflows. But code? One does mostly write code.
And when we do read code, it may lack good naming, structure, comments, clarity, may be unnecessarily complex or hacky. Where does it wrap is the thing one would care about only in perfect code, if at all. Most editors can smart-wrap and clearly indicate it anyway.
No, every developer almost certainly reads a lot more code than they write. You can't modify code to add a feature without reading and understanding the code first. The code you add is often very short compared to the code you need to read to understand what to modify.
Other people disagree with you and it's best to not assume they are idiots.
The 80 column rule may seem arbitrary, but it really helps analysis. I avoid open source code that ignores it, and I'll ding code that violates it during code review.
If I had code marching off the screen, or rudely wrapped around so it violated spacing, I'd have to reduce the number of splits I used to see it, and that directly impacts my ability to see code in context. Modern IDEs don't reduce the need to see things in context. It's not a matter of organizing things in drop-down menus, smart tabs, font changes, or magic "refactor" commands. Verifying function contracts in most extant software -- which lacks modern tooling like model checking -- requires verifying these things by hand until these contracts can be codified by static assertions. This, in turn, requires examining function calls often 5-6 calls deep to ensure that the de facto specifications being built up don't miss assumptions made in code deep in the bowels of under-documented libraries. I'd be terribly upset if I had to try to do this in code that not only missed modern tooling but that was written by a developer who mistakenly believed that "80 columns is for geezers." I freely admit that, at 43, I probably count as a "geezer" to many young developers. But, that doesn't change the utility of this rule. Violations of contracts in software account for a large percentage of errors in software AND security vulnerabilities. Most of these violations are subtle and easy to miss unless you can see the call stack in context. No developer can keep hundreds of details from code that they did not write in their head with perfect clarity. It's incredibly nice to have uniform style and uniform maximum line lengths. By convention, 80 columns has shown itself to be the most stable of these limits.
Even FAANG companies like Google follow this rule.
Google also uses 100
Either way, if Google and other companies can do what they do in 80 columns, I think it's a fair constraint. What we get out of this constraint is the ability to put a lot of context on the screen.
I remember reading this for the first time as a teenager: "if you need more than 3 levels of indentation, you’re screwed anyway, and should fix your program". Twenty years later, it seems like solid advice to me.
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/lin...
- Someone using a Braille display
- Someone with a vision impairment (i.e. high scaling factor; common occurence during ageing)
- A group of people that doesn't sit close to the display
- Someone with a low-DPI (or small) display due to the normal workplace being unavailable
While you could, of course, disregard all these scenarios, the sheer amount of people profiting from or requiring a character limit on lines is usually grounds for a restrictive policy regarding this topic.
You might consider it silly, but as long as there is no reliable way to convert between these "styles of presentation" you will find that many people prefer to err on the safe side.I'm more like: Always use tabs, never use space. Code doesn't need to be "aligned" it's not some ASCIIart masterpiece...
One tab means one indentation level and if your taste is to have tabs of pi chars wide, nice! But it won't mess my code
Read the type for all the below right-to-left, substituting the word "pointer" for "*".
int long long unsigned wibble; // unsigned long long int
double const *long_number; // pointer to a const double
double volatile * const immutable_pointer; // immutable pointer to a volatile double
They all read correctly now, when read right-to-left. It's not just "const" you do this for, as per the advice. Do it for all qualifiers.> They all read correctly now, when read right-to-left.
... suppose I'm someone who reads from left-to-right, should I flip the order to make it correct for me?
C declarations can become unfriendly by being too complex and disordered.
I’m neither of them, but chances are that’s because you can’t make them left-to-right all the time.
double const *foo; // foo is a pointer to a const double
double *const foo; // foo is a const pointer to a double
compile and do what the comment says; these do not compile: * const double foo; // a pointer to a const double named “foo”
foo * const double; // foo is a pointer to a const double const char *const ptr;
The first const applies to the char, but the second one to the pointer itself. Being consistent: char const *const ptr;
The first const applies to the item to its left---char. The second const applies to the item to its left---the pointer. To recap: char *ptr1; // modifiable pointer to modifiable data
char const *ptr2; // modifiable pointer to const data
char *const ptr3; // const pointer to modifiable data
char const *const ptr4; // const pointer to const dataIt works in simple cases, but I find the consistent thing to do is read it as "dereference".
double volatile *(const immutable_pointer);
// immutable_pointer is immutable, when you dereference it you'll get a volatile doubleThe declaration of the pointer ip,
int *ip;
is intended as a mnemonic; it says that the expression *ip is an int. The syntax of the declaration for a variable mimics the syntax of expressions in which the variable might appear. This reasoning applies to function declarations as well.K&R C
by contrast, this document is largely motherhood and apple pie — and where it isn't (e.g., when it advocates titlecasing struct types or never typedeffing primitive types), i often think it's wrong. 'Write assertions to meaningfully crash your program before it does something stupid, ... to prevent a security vulnerability' is especially wrong; one of the major features of the standard assert() macro is that it's turned off in release builds!
the named-arguments macro hack is an example of the kind of thing i was most hoping to find in here
if you write something like this, don't dilute whatever value it may have with your opinions about tabs vs. spaces, line length, what natural language to write your comments and identifiers in, include guards, how many blank lines to put between functions, etc. these have been debated to death, and you're unlikely to have any brilliant insights about them that other people will be happy to have read
I'm sorry, I just cannot do this. I start to feel somewhat guity after 300 characters but 80 feels like an Atari 800.
Tabs are always correct, IF spaces are never used instead. One tab, for one level of indent. Adjust to preference.
Alas, I don't think there's a standard way of specifying...
// kate: space-indent off; indent-width 8; tab-width 8; mixedindent off; indent-mode tab;
Similarly, // comments should be preferred, but /* comments */ are acceptable at the top of large function blocks for large blobs of comments. Judicious / sparing use as the key idea to make it worth the exceptions if commenting out large blocks during tests or refactors.
Just one (personal) stuff : Stick to 80 columns... Sorry, no ! :)
Why not? Do people really care about porting their toy project to another compiler? If portability is a goal, avoid extensions, but not all projects need to be portable.
I'm writing an Operating System, and I do not care it if compiles on Clang or MSVC. GCC has been around for decades, it is a safe bet.
I'm happy to use compiler extensions, but I'll use __asm__ instead of asm, and use __extension__ as needed. I've done some neat but truly upsetting things with compiler extensions in my hobby code, especially once I combine them with macros. I'm particularly "proud" of the mutex macros in a toy OS of mine, which wrap a statement inside for loops and switch statements for automatic release of the mutex, unless it's requested that it stay locked. There, I originally used compiler extensions to release the mutex on scope exit, but switched to non-compiler-extension code for the actual functions, and just using the extensions for checking that the code using the macros didn't break the "contracts" on what is allowed in those statements, and how they can be exited.
It's the same reason that I'm always explicit about the size of integers, using stdint, even if I know that an int is 32-bit on a particular platform.
Can’t a modern compiler do that already? Didn’t google but seems an obvious compiler feature at the very least behind a warning flag.
https://clang.llvm.org/extra/clang-tidy/checks/misc/include-...
Absolute agreement
* Always develop and compile with all warnings (and more) on
* #include the definition of everything you use
* Provide include guards for all headers to prevent double inclusion
* Always comment `#endif`s of large conditional sections
* Declare variables as late as possible
* Be consistent in your variable names across functions
* Minimize the scope of variables
* Use `assert` everywhere your program would fail otherwise
* Repeat `assert` calls; don't `&&` them together
* C isn't object-oriented, and you shouldn't pretend it is
Strong agreement with some obvious exceptions * Use `//` comments everywhere, never `/* ... */`
* Comment non-standard-library `#include`s to say what symbols you use from them
* No global or static variables if you can help it (you probably can)
* Minimize what you expose; declare top-level names static where you can
* Use `double` rather than `float`, unless you have a specific reason otherwise
* Avoid non-pure or non-trivial function calls in expressions
* Simple constant expressions can be easier to read than variables
* Initialize strings as arrays, and use sizeof for byte size
* Where possible, use `sizeof` on the variable; not the type
* Document your struct invariants, and provide invariant checkers
* Avoid `void *` because it harms type safety
* If you have a `void *`, assign it to a typed variable as soon as possible
* Only use pointers in structs for nullity, dynamic arrays or incomplete types
* Avoid getters and setters
Agreed but you need a few more words * Don't be afraid of short variable names [if the scope fits on a screen]
* Explicitly compare values; don't rely on truthiness
[unless values themselves are boolean]
* Use parentheses for expressions where the operator precedence isn't obvious
[but `&foo->bar` *is* obvious]
* Separate functions and struct definitions with two lines
[can use comments instead]
* If a macro is specific to a function, `#define` it in the body [and `#undef` ASAP]
* Only typedef structs; never basic types or pointers
[or make them distinct enough, but ISO C stole a `_t` suffix]
I do so or I see why but that's really a problem of C and its ecosystem instead * Use GCC's and Clang's `-M` to automatically generate object file dependencies
* Avoid unified headers
* Immutability saves lives: use `const` everywhere you can
* Use `bool` from `stdbool.h` whenever you have a boolean value
* Avoid unsigned types because the integer conversion rules are complicated
* Prefer compound literals to superfluous variables
* Never use array syntax for function arguments definitions
* Don't use variable-length arrays
* Use C11's anonymous structs and unions rather mutually-exclusive fields
* Give structs TitleCase names, and typedef them
* Never begin names with `_` or end them with `_t`: they're reserved for standards
* Only use pointer arguments for nullity, arrays or modifications
* Prefer to return a value rather than modifying pointers
* Always use designated initializers in struct literals
I do so but am not sure * Write to the most modern standard you can [we have no choice for many cases]
* Program in American English [only applicable for native speakers]
I see why but I think you are mislead * Don't write argument names in function prototypes if they just repeat the type
[such case is very, very rare]
* Use `+= 1` and `-= 1` over `++` and `--`
[`++`/`--` should be read as succ/pred and should be exclusively used for pointers]
* Don't use `switch`, and avoid complicated conditionals
[switch is okay once you have enabled enough warnings]
* Only upper-case a macro if will act differently than a function call
[agreed in principle, but should define "differently" more broadly]
* Always prefer array indexing over pointer arithmetic
[and then you will be biten by index variable types, remember `ptrdiff_t`]
That's really just a personal preference * We can't get tabs right, so use spaces everywhere
[as long as mechanically enforcable, the choice itself is irrelevant]
* Always put `const` on the right and read types right-to-left [too eyesore]
* Use one line per variable definition; don't bunch same types together
[will agree with some significant exceptions though]
* Never change state within an expression (e.g. with assignments or `++`)
[absolutely avoid functions, but `++` has its uses]
* Always use brackets, even for single-statement block
[rather a read-write trade-off; this may make some codes harder to read]
* Never use or provide macros that wrap control structures like `for`
[the example is very tame in comparison to actually problematic macros]
* Don't typecast unless you have to (you probably don't)
[while many typecasts can be easily removed, excess doesn't do actual harm]
* Give enums `UPPERCASE_SNAKE` names, and lowercase their values
[I would rather avoid enums for various reasons]
* Use structs to name functions' optional arguments
[maybe the author tried to say "avoid too many arguments" instead?]
* If you're providing allocation and free functions only for a struct member,
allocate memory for the whole struct
[that complicates using struct as a value]
Just no. * Never have more than 79 characters per line
[100 or 120 do work equally well, you do need some limit though]
* Define a constant for the size of every enum
[would imply that all enum values are sequential, and that's not true!]It's not mechanically enforceable (in practice), that's the point. Forbidding tabs altogether is the most practical and actionable path.
[1] https://clang.llvm.org/docs/ClangFormatStyleOptions.html#use...
I can't imagine doing this on embedded systems.
So if you're inclined to declare 2 global variables, instead define a struct with those two values and your functions take a pointer to that struct, and whatever code calls into yours can then decide whether they want to define that struct as having global scope. It just makes for more modular code.
I don't really care that much about American vs. British English, except that it should be consistent within the code base. But I do think programming in English is generally a best practice that applies even if you aren't a native speaker.
I agree with most of your (dis)agreements, though.