My personal C coding style as of late 2023
nullprogram.com
nullprogram.com
I'm guessing this is lacking an outer pair of parentheses (i.e. it's not `((size)sizeof(x))`) on the grounds that they're unnecessary. In terms of operator precedence, casting binds tightly, so if you write e.g. `sizeof(x) * 3`, it expands to `(size)sizeof(x) * 3`, which is equivalent to `((size)sizeof(x)) * 3`: the cast happens before the multiplication. Indeed, casting binds more tightly than anything that could appear on the right of sizeof(x) – with one exception which is completely trivial.
But just for fun, I'll point out the exception. It's this:
(size)sizeof(x)[y]
Indexing binds more tightly than casting, so the indexing happens before the cast.
In other words, it's equivalent to `(size)(sizeof(x)[y])`, not `((size)sizeof(x))[y]`.But you would never see that in a real program, since the size of something is not a pointer or array that can be indexed. Except that technically, C allows you to write integer[pointer], with the same meaning as pointer[integer]. Not that anyone ever writes code like that intentionally. But you could. And if you do, it will compile and do the wrong thing, thanks to the macro lacking the extra parentheses.
…On a more substantive note, I quite disagree with the claim that signed sizes are better. If you click through to the previous arena allocator post, the author says that unsigned sizes are a "source of defects" and in particular the code he presents would have a defect if you changed the signed types to unsigned. Which is true – but the code as presented also has a bug! Namely, it will corrupt memory if `count` is negative. You could argue that the code is correct as long as the arguments are valid, but it's very easy for overflow elsewhere in the code to make something accidentally go negative, so it's better for an allocator not to exacerbate the issue.
With unsigned integers, a negative count is not even representable, and a similar overflow elsewhere in the program would instead give you an extremely high positive count, which the code already checks for.
Personally I prefer to use unsigned integers but do as much as possible with bounds-checked wrappers that abort on overflow. Rarely does the performance difference actually matter.
> I could use _Bool, but I’d rather stick to a natural word size and stay away from its weird semantics.
This is even more subjective, but personally I like _Bool's semantics. They mean that if an expression works in an `if` statement:
if (flags & FLAG_ALLOCATED)
then you can extract that same expression into a boolean variable: _Bool need_free = flags & FLAG_ALLOCATED;
The issue is that `flags & FLAG_ALLOCATED` doesn't equal '0 if unset, 1 if set', but '0 if unset, some arbitrary nonzero value if set'. (Specifically it equals FLAG_ALLOCATED if set, which might be 1 by coincidence, but usually isn't.) This kind of punning is fine in an `if` statement, since any nonzero value will make the check pass. And it's fine as written with `_Bool`, since any nonzero integer will be converted to 1 when the expression is implicitly converted to `_Bool`. But if you replace `_Bool` with `int`, then this neither-0-nor-1 value will just stick around in the variable. Which can cause strange consequences. It means that if (need_free)
will pass, but if (need_free == true)
will fail. And if you have another pseudo-bool, then if (need_free == some_other_bool)
might fail even if both variables are considered 'true' (i.e. nonzero), if they happen to have different values._Bool solves this problem. Admittedly, the implicitness has downsides. If you're refactoring the code and you decide you don't really need a separate variable, you might try to replace all uses of `need_free` with its definition, not realizing that the implicit conversion to _Bool was doing useful work. So you might end up with incorrect code like:
if ((flags & FLAG_ALLOCATED) == true)
Also, if you are reading a struct from disk or otherwise stuffing it with arbitrary bytes, and the struct has a _Bool, then you risk undefined behavior if the corresponding byte becomes something other than 0 or 1 – because the compiler assumes that the implicit conversion to 0 or 1 has been done already.if (need_free == true)
Is such a horrible code smell to me. You have a perfectly good boolean. Why compare it to a second boolean to get a third boolean?
if (need_free)
or
if (!need_free)
for the opposite case is so much better.
I will admit that in my world this leaves
if (need_free == some_other_bool)
as something I don't have a particularly comfortable way of doing safely.
#define FLAG_63 (1ULL << 63)
long long flags = FLAG_63;
In this case, if (flags & FLAG_63) pass();
will pass, but typedef int BOOL;
BOOL set = flags & FLAG_63;
if (set) pass();
won't pass, due to truncation.Question: Would you argue that a datatype that holds the smallest (1-bit) datum should be as wide as the largest integer type just to handle such cases?
If so, that would be highly inefficient for storage purposes. Note that Win32 has 32-bit BOOL type, but internally NT uses 8-bit BOOLEAN type to store bools in structures.
> if (need_free == true)
> Is such a horrible code smell to me. You have a perfectly good boolean. Why compare it to a second boolean to get a third boolean?
> if (need_free)
You are probably interested if the `need_free` flag is set to true, and not if `need_free`. It is true that `if (need_free)` has the same behaviour, but it is some steps farther from what you are interested in. if (need_free == some_other_bool)
you could use: if (!need_free ^ some_other_bool)
if you're using _Bool.Actually, and this is probably surprising to many, this is equivalent to
(size)(sizeof ((x)[y]))
sizeof is not a function but a unary operator, and indexing (as well as function calling...) binds stronger than the sizeof operator. It is not a function, not even syntactically! Hence why I strongly prefer putting a space after the sizeof keyword, and to not use parens for the operand unless needed.https://en.cppreference.com/w/c/language/operator_precedence
So the "correct" way to define the macro is
#define sizeof(x) ((size)(sizeof (x)))
This particular part is not actually complicated: the postfix operators bind the most tightly, then the prefix ones, then the infix ones. (The last part is quite messy, though.)
So (int)x[y] parses the same way as, for example, *p++, which should be familliar to a C programmer.
Sure, that's not true for 16 bit targets. But are you really going to port a 5Mb program to 16 bits? It's not worth worrying about. Your code is highly unlikely to be portable to 16 bits anyway.
The problem is with `long`, which is 32 bits on some machines and 64 bits on others. This is just madness. Fortunately, `long long` is always 64 bits, so it makes sense to just abandon `long`.
So there it is:
char - 8 bits
short - 16 bits
int - 32 bits
long long - 64 bits
Done!(Sheesh, all the endless hours wasted on the size of an `int` in C.)
I therefore use typedefs called `byte` and `ubyte` wherever the data is 8-bit but not character data. I also use the aliases `ushort`, `uint` and `ulong` to cut down on typing. On the other hand, the types in <stdint.h> are often recognised by syntax colouring in editors where user-defined types aren't.
D's `char` type is unsigned. Done. No more problems.
It's just so much less error prone to define a uint32_t. That's guaranteed to be the same
Now, what about SIMD types?
What about 16bits floats?
Using the short size convention we have easy and logical answers.
The reason why new languages like Rust and Zig are using those conventions is not random, types naming (and stdlib) is a weak point of C (and C++).
Luckily they are not set in stone, we can choose different and reasonable conventions.
SIMD types in D are done with:
__vector(byte[16]), __vector(int[8])
and an alias (typedef for the C folk) for this is commonly used, like `byte16` and `int8`.It might be ridiculous, but it’s hardly confusing for a C programmer. But, yeah in and ideal world ‘long’ should just be defined as 64 bits
edit: Oh you're right
> Other models are very rare. For example, ILP64 (8/8/8: int, long, and pointer are 64-bit) only appeared in some early 64-bit Unix systems (e.g. UNICOS on Cray).
C's "long" should not be used in new code.
1. Consider the char and short types only if saving storage is important. Do not declare "char number_of_wheels" for a car, just because no car has anywhere near 127 wheels, unless it is really important to get it down to one byte.
2. Prefer signed types to unsigned types, when saving storage is not important. Unsigned types bend the rules of arithmetic around zero, and mixtures of signed and unsigned arithmetic add complexity and pitfalls. Do use unsigned for bitmasks and bitfields.
3. Two's complement is ubiquitous: feel free to assume that signed char gives you -128, and short gives you -32768, etc. ISO C now requires two's complement.
3. Use the lowest ranking type whose range is adequate, in light of the above rules: rule out the chars and shorts, and unsigned types, unless saving space or working with bits.
For instance, for a value that ranges from 0 to 65535, we would choose int. If it were important to save storage, then unsigned short.
The ISO C minimum required ranges are:
char 0..255, if unsigned; -128..127 if unsigned, therefore: 0..127
signed char -128..127
unsigned char 0..255
short -32768..32767
unsigned short 0..65535
int -32768..32767
unsigned int 0..65535
long -2147483648..2147483647
unsigned long 0..4294967295
long long 9223372036854775808..9223372036854775807
unsigned long long 0..18446744073709551615
If you're working with bitfields, and saving storage isn't important, start with unsigned int, and pick the type that holds all the bits required. For arrays of bitfields, prefer unsigned int; it's likely to be fast on a given target. It's good to leave that configurable the program. E.g. a good "bignum" library can easily be tuned to have "limbs" of different sizes: 16, 32 or 64 bit, and mostly hides that at the API level.If you're working with a numeric quantity, remove the unsigned types, shorts and chars, unless you need to save storage (and don't need negative values). Then pick the lowest ranking one that fits.
E.g. if saving storage, and don't need negative values, search in this order: char, signed char, unsigned char, short, unsigned short, long, unsigned long, long long, unsigned long long.
If saving storage, and negatives are required: signed char, short, int, long, long long.
If not saving storage: int, long, long long.
If the quantity is positive, and doesn't fit into long long, but does fit into unsigned long long, that's what it may have to be.
Therefore there is another use case : circular buffer indices.
the fact that you had to have tribal knowledge about all of this is why C shouldn't stay for the long term and we should phase out languages into ones with stronger more correct defaults.
would a new programmer use "long long"? would they notice immediately that things didn't work if they didn't use it?
Rust got it correct by labeling the bits with the type directly
In the C world, only the goofballs do things like use char or int8_t for the number of children in a family, or wheels on a car.
yet that is what Rust code looks like. Almost every Rust code sample I've ever seen sets off my bozon detector just for this reason.
But as you say, it's a personal style, and the author seems to be aware of that:
> I’m not saying everyone should write C this way, and when I contribute code to a project I follow their local style.
Because that's by far the most important rule to follow in any language.
I think the rest is less controversial, the 0 vs. NULL thing has been going on forever; I didn't check recently but I'd assume "const somestruct *foo" would still sometimes help out the compiler to optimize vs. the non-const version.
I think this is perfectly legitimate, in the same way that I don't use std libs directly but always behind wrappers or my own implementation.
The C std lib and default types are often what is keeping the language back.
And they should be used when you have no other choice.
Short name for scalar types is also pretty much the new standard for modern languages such as Zig.
They didn't adopt it for the same reason that it is a bad idea now - too many programs already contained at least one variable named after his types.
If the standard had adopted his convention, too many programs will break, which is why his convention is currently unsuitable for any existing project.
Only ones which don't have variables named `i8` or `b32` (which is common, but not for booleans).
I've seen many projects which used the pattern [a-z][1-9]+ as variables. Those programs with a variable called `i8` won't compile if the standard made a type called `i8`.
In particular, the standard reserves entire patterns to itself, so it cannot reserve the pattern of [a-z][0-9]+. They could, and did, reserve the pattern *int*_t for themselves.
Moreover, for those of us who write C fairly often, the mnemonics here are familiar.
Actually, as custom type systems go, this one is pretty elegant. Reminds me of Rust.
stdint.h
It's always been amazing to me how many different projects I've worked on (not that I've been in professional C for about 7 years now)) that include their own painstaking recreation of this file.
Reusing them and effectively translating them just to your own name is just annoying to the reader IMHO. I am reminded of a C++ project I worked on, where I questioned the extensive use of typedefs around collections of things, various forms of references and compound objects etc. I was informed by one of the more experienced C++ folks that it made the code easier to comprehend.
Later I saw the typedef cheat-sheet sellotaped to the side of his monitor...
How many of them started before stdint.h existed? AFAIK, it's a somewhat recent addition to the C language, and IIRC, for a long time even after it became part of the C standard, some popular C compilers still didn't have it.
And yes, Microsoft were the outlier and absolutely dragged their heels on stdint, but you could always grab a compliant implementation from one of the FOSS projects that produced one.
Oh I dunno. On one hand yeah learning a quirky system is an annoyance at times. On the other hand when you're coming from a language with a real type system dealing with custom types is standard operating procedure.
I've had to patch a lot of C over the years. I can't say I've ever been bothered by types. It's always the usual suspects; hard coded offsets peppered throughout the codebase, stack smashing, baby's first callback implementation, "parsing" that omits lexing/tokenizing, archaic business logic that may-or-may not have ever been correct.
Assuming you can trust those types to be what they look like, the code is readable.
I've worked with C for well over 30 years; custom typedefs are par for the course. Work with OpenMAX libs? You have OMX_U32. On Windows? You have DWORD. Using Glib? guint32 ...
But much C code is bringing in library headers which contain their author's own pet choices for these, which inevitably are not the same and the result is extremely confusing when you have that in play as well as the stdint.h ones.
The kernel contains a mixture of "pet" types like u32 and stdint ones, it's already confusing.
He also does make a "crazy" choice later to call his string class "s8" which clashes with his nomenclature here.
How?
I beg to disagree. In D:
byte - 8 bits
short - 16 bits
int - 32 bits
long - 64 bits
absolutely nobody is confused about this.Or, you know, we could just name them all by bit length and completely future-proof this system.
stdint already has that covered though: (u)int128_t
ubyte - 8 bits
ushort - 16 bits
uint - 32 bits
ulong - 64 bits
ucent - 128 bits
float - 32 bits
double - 64 bits
real - maximum precision hardware allows (80 bits on x87).But they are buggy (correct code cannot depend on the sign of `char`), which is usually the result of typedefing primitive types to save typing 3 characters on each use.
And manually writing out Win32 API prototypes instead of including windows.h might shave off some compile time, but it's like ignoring a well-maintained highway to trek through the woods. Just seems like a lot of these changes are about personal preference rather than sticking to what makes C code easy for everyone to work with.
It is not about saving keystrokes, it is about reducing sensory load when reading it.
Sorry, I know it may sound like I'm splitting hairs, but every single time when the argument of verbosity vs conciseness in programing languages comes around, this "keystrokes" argument is thrown and it is extremely flawed. The core belief that conciseness is only better for faster typing but that verbosity is somehow always better than conciseness for reading is just plain wrong and we should stop using it. And yes, verbosity has some advantages for reading comprehension, but so does conciseness, no side is a clear winner, it is all about the different compromises.
Where it does go to pieces is when two different programs both define u16, use them in header files, and then a third program tries to include both those header files at the same time. The big advantage of <stdint.h> is avoiding that failure mode.
The namespaced library type equivalent is something like libname_u32, at which point it's tempting to write uint32_t instead of the libname:: or libname_ prefix.
This seems a theoretical possibility _at best_, and a fairly strawman-y one at that. I doubt any competent C programmer would get "confused". Irritated at a different style, maybe.
Too, everything is hard to read before you learn to read it. -- Rich Hickey
Maybe I'm a beginner then. He lists a few cases where it's not worse than sticking to 8-bit bools, but no cases where it's actually an improvement. It still wastes memory sometimes, e.g. if you have adjacent booleans in a struct, or boolean variables in a function that spill out of registers onto the stack. Sure it's only a few bytes here and there, but why pessimize? What do you gain from using a larger size?
My specific bug bear here was a junior who insisted "saving space" by packing the structs and using a single 8 bit byte for the conditionals.
Their 'improved' code ground throughput on intel chips by a factor of 10 or so and generated BUS ERRORs on SPARC RISC architectures.
By packing the header of the structs they misaligned the array of data values such that the intel chips were silently fetching two 32 bit words (say) to get half a word from each to splice together to form a 32 bit data value (that was passed straddling a word boundary) to pipe into the ALU and then do something similar to repack on the other end - SPARC's quite sensibly were throwing a fit at non aligned data.
Point being - sometimes it makes sense to fit data to the architecture and not pack data to "save" space (this is all for throughput piped calculations not long term file storage in any case)
> Maybe I'm a beginner then. He lists a few cases where it's not worse than sticking to 8-bit bools, but no cases where it's actually an improvement. It still wastes memory sometimes
They key part of my response is sometimes "wasting memory" (to gain alignment) is a good thing.
If someone, a beginner, is concerned about percieved wasted memory then of course they will use "packed".
As for the guts of your comment, I agree with your sentiment but would exercise caution about expecting a compiler to do what you expect in practice - especially for cross architectural projects that are intended to be robust for a decade and more - code will be put through muliple compilers across multiple architectures and potentially many many flags will be appied that may conflict in unforseen ways with each other.
In general I supported the notion of sanity check routines that double check assumptions at runtime, if you want data aligned, require data to be big endian or small endian etc then have some runtime sanity checks that can verify this for specific executables on the target platform
Nuance is that each field should be at an address divisible by the fields size or wordline size, not some magic 32 constant. The entire struct should also be padded to a multiple of the largest fields size. In practice this usually means 32 bit alignment.
Architectures are generally optimized for aligned access (or disallow unaligned access), but what counts as "aligned" is different for each type.
A char type that is used for a bool can be accessed on any byte boundary because the alignment of a char is 1. The alignment of a 32-bit value is 4.
However, architectures are generally more optimized for 32-bit operations in registers. If you're dealing with a char in a register, the compiler will generally treat it as a 32-bit value, clearing the top bits. (This is one of those places where C's UB can bite you.)
However, there are architectures where 32-bit access is optimized.
Which almost noone ever does. It's very hard and almost never has any benefit. At that point you have way different problems than programming style choices...
if(foo(x,y, out1, out2) != WHATEVER_LIBRARY_OK) { ... }Otherwise there's thread_local mylibrary_errno, which might actually be the right thing for within a library, translating it to an enum return on the boundaries.
Well, I should probably just say "We're done here." and stop reading the rest of the article. "Signed sizes" are an extremely surprising abstraction break that are just asking for disaster.
> No const. It serves no practical role in optimization, and I cannot recall an instance where it caught, or would have caught, a mistake.
Should you even be writing C if you haven't hit this? People mix up "in buffers" and "out buffers" all the time. "const" flags this immediately.
> Declare all functions static except for entry points. Again, with everything compiled as a single translation unit there’s no reason to do otherwise.
And when you go trying to debug something and get at a variable or function that you can't find because everything is "static", you'll curse the one who wrote the code.
> Another change has been preferring structure returns instead of out parameters.
Which is a great way to accidentally return a pointer to your stack and open a big ass security hole. Passing in the output buffers makes clear the ownership semantics.
This guy seems like he mostly writes code for 64-bit systems. The coding advice is ... okay, I guess? Maybe? In that domain?
In a 32-bit embedded domain, some of these guidelines are a good way to get youself into a lot of trouble in a real hurry.
Bjarne Stroustrup wrote a detailed memo advocating for signed sizes:
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p14...
Modern C/C++ compilers can and will warn you (quite aggressively) if you mix signed and unsigned numbers without thinking about it.
A lot of the examples also seem weird. Eg, he gives a negative example of a function:
unsigned area(unsigned x, unsigned y) { return x * y; }
In this, he complains that you can still write buggy code: area(height1-height2, length1-length2);
He's right - that is potentially buggy, But, that code would be buggy whether the area function took signed or unsigned numbers as input. However, the signed version of this function is still worse imo because it could hide the logic bug for longer. If the area function should always return a positive number, I'd much rather that invalid input results in an area number like 4294967250 than a small negative number.Similarly, accidentally passing a negative index to a vec is much more dangerous with signed indexes because v[-2] will probably quietly work (but corrupt memory). However, v[4294967294] will segfault on the problematic line of code. That'll be much easier to find & debug.
And a lot of the examples he gives, you'd get nice clear compiler warnings in most modern compilers if you use unsigned integers. You won't get any warnings with signed integers. Your program will just misbehave. And thats much worse. I'd rather an easy to find bug than a hard to find bug any day of the week.
z=x+y; if(z < x || z < y) // overflow
And bounds checks are just a single comparisons against an upper bound (handles both over and underflow) size = x + y;
// or
size = x - y
if(size < bound) // good to go
Prior to C23 (stdckdint.h) its very error prone to check for signed overflow since you have to rearrange equations to make sure no operation could ever possibly overflow.(The static thing might depend on the tooling. I went static-by-default about 15 years ago, around the same time I went full size_t, and I've yet to have a problem with it.)
Codebases have their own conventions and design patterns. If you have that const is a needless formality.
Code should be being simple and clean first, constantly stating things that are obvious 90% of the time isn’t that.
https://dlang.org/blog/2023/10/02/crafting-self-evident-code...
(The article is crafted around D, but the principles apply to C as well.)
What happened to the conditional expressions? Move them to the interiors of doX() and doZ().
That was an interesting point. Not sure that it's always valid but I guess it depends where you want the abstraction to lay, and how it affects the mental construct around the code.e.g.
deleteRecords();
is not better than if let x = deadRecords()
deleteRecords(x);
Sure, it looks messier but there is value is showing upfront that you're pruning and not wiping.If the author wisely renames his function e.g. pruneDeadProjects(), yes. But merely moving the the condition within the function can be dangerous for context and be a leaky abstraction.
Like your idea of pruneDeadProjects()!
I'm not sold on the structs as return types thing. I prefer just a numeric error code as a return value, and out parameters for any other returns.
I think this is more-or-less a C/POSIX standard convention. E.g., `pthread_t` vs. `struct stat`.
Totally agree, I even wrote this as a blog post: https://www.lelanthran.com/chap9/content.html
The pattern I like to use for this is to expose class definitions, and declare each field with an underscore suffix to indicate it's private.
typedef struct Foo {
int x_; // implementation detail. don't touch.
} Foo;
Another pattern I've seen for this is to use truly opaque structs of byte arrays, that are then typecast into the proper struct in the implementation. // foo.h
#define FOO_SIZE_ 4
#define FOO_ALIGN_ 4
typedef struct Foo {
_Alignas(FOO_ALIGN_) char impl_[FOO_SIZE_];
} Foo;
// foo.c
#include "foo.h"
typedef struct FooImpl {
int x;
} FooImpl;
_Static_assert(FOO_SIZE_ == sizeof(FooImpl), "");
_Static_assert(FOO_ALIGN_ == _Alignof(FooImpl), "");
void Foo_bar(Foo const* self_opaque) {
FooImpl const* self = (FooImpl)self_opaque;
...
}
I'm not a big fan of this approach, but it does protect your clients against themselves.I agree with this. One of the things I dislike about SDL_net, etc, is they do exactly what you're describing. It's a pointer but they typedef it as if it's a value type.
I understand the intent but imo that's very icky.
I’ve started writing a bare metal OS for Arm64. It’s very early but I’ve done some similar things. I’m using pascal strings, I’ve also renamed the types (though I’m using “int8” style, not “i8”).
I quickly decided that I never intend to port real software to it, so I really don’t have to conform to standard C library functions or conventions. That’s given me more freedom to play around. C is old enough to have a lot of baggage from when every byte was precious, even in function names.
It’s nice to get away from that. Much like the contents of this post, that plus other small renamed just ended up feeling like a nice cleanup.
So you're just building it as just a hobby, won’t be big and professional like gnu?
[1] https://github.com/libjpeg-turbo/libjpeg-turbo/commit/52ded8...
> typedef double f64;
Assuming float is 32 bits and double is 64 bits sounds like a foot-gun. OpenCV defines a float16_t [0], CUDA implements half-precision floats [1], micro-controllers implement whatever they want.
C++23 introduces fixed width floating-point types [2], but not aware of any way to enforce this in C. What I would suggest it to have a macro to check data is not lost at compile time.
Generally I agree with others, it might be better to leave some of these things as default for readability, even if it is not concise.
[0] https://docs.opencv.org/4.x/df/dc9/classcv_1_1float16__t.htm...
[1] https://docs.nvidia.com/cuda/cuda-math-api/group__CUDA__MATH...
typedef _Float32 f32;
typedef _Float64 f64;
https://gcc.gnu.org/onlinedocs/gcc/Floating-Types.htmlEven Linux started as a personal project, but because of its quality and the need it met it quickly spread. So please write your code in such a way that experienced other C programmers can read it easily.
In isolation, I like some of his ideas, but some issues with C remain, and he is perhaps just to comfortable with C to jump ship and embrace Rust, which has many things he likes and more (e.g. no buffer overflows by design).
I adopted the style of writing all macros in lower case with the prefix "macro_", so i can grep through all macros. So macro_ becomes like a keyword. Same with enum.
I use almost the same naming scheme for i32,f32, etc., but i typedef for example size_t const to usz, and size_t to usz_ (or use macro mut(x) x ## _) . So all shorter type names are const by default. I use a single header of 35 sloc to do that.
I see that this way of coding can be confusing for other people, so i avoid it when writing code that other people have to work with. But for personal code its really enjoyable for me.
That breaks any macro that uses sizeof in its expansion, and subtly changes any code snippet you might bring into the code that uses sizeof, even if those macro are defined first.
Speaking of which, if you define a macro for a C keyword before including any standard header, the behavior is undefined.
It's an unparenthesized unary expression, which has a lower precedence than postfix. sizeof(x)[ptr] will turn into (size)sizeof(x)[ptr] which parses as (size) ( sizeof(x)[ptr] ).
This reminds me of "#define max ..." in Windows.h. Not as bad, but if you autoreplace `sizeof(ptrdiff_t)` with `sizeof(size)`, good luck, because it will output size of type of size variable, if it exists in the scope.
No const.
Please don't. `const` is incredibly valuable, not only to the reader, but to the compiler.
Take for example:
int Foo_bar(Foo const* self);
Just looking at this signature, I know that calling `bar()` will not modify the state of the object. This is incredibly valuable information to the reader.Furthermore, if I want to create a `Foo` constant, I can only call this function if it is `const`.
static Foo const a_foo = FOO_INIT(&some_params);
return Foo_bar(&a_foo); // Will not compile without 'const' in function
`const` is valuable to the compiler, since `a_foo` can be placed into ROM on some platforms like MCUs, saving precious RAM.static + const is valuable, but const parameters are merely a convention, there is no actual enforcement around them and due to aliasing the compiler generally can’t assume the parameter doesn’t actually change anyway.
No, but it can warn you!
The type is meant to capture programmer intention, and if you use `const` the compiler can warn you that your intention does not match the intention of the existing code (like, the intention of the author who wrote Foo_Bar).
That I can agree with TFA. However I agree with the GP that dismissing it entirely is a little misplaced. It serves as a hint/documentation and I think the article undersells the value of rodata (not the pointer use of const which is basically shit).
I mean I have seen at least a few SIGSEGV/aborts due to attempted writes to ro memory. Also like, one of the few modern justifications for C, embedded, const still has important link time meaning.
let x = foo()
... to ...
const x = foo()
...runs foo at compile time to get the value. I dunno I just thought it was neat.
I didn't say this. I said a `const` function tells the reader that the state of an object doesn't change.
Another reader correctly pointed out that there are ways to modify the state of a `const` parameters (indirection and const cast), but I would argue that such an API is poorly-designed.
To qualify my original comment, a reader only knows a function doesn't change an object's state if the API is well-designed.
int Foo_bar(const Foo * self);Not to be confused with `Foo *const`
Consistency, conciseness and clarity (you don't have to guess much about those, once you've understood the naming scheme)
My brain wants to read f32x4 as a 32 by 4 matrix of floats.
(That being said I'd definitely be interested in trying your convention in the context of something like Rust.)
And when working on some heavily optimized SIMD code on the CPU side, I tend to use the default types even less.
IMO including the number of bits in all primitive types is usually an overcorrection from trauma caused by C/C++'s historic loose definitions of primitive type sizes. However I don't write much CPU SIMD code and can definitely see how you'd develop your preference from that context.
Pointer-to-const on the other hand (as in "const Foo *x") is a bit of a fluff and it spreads like cancer. I agree with the author that const is a waste of time. And it breaks in situations like showcased by strstr().
I use pointer-to-const in function parameter lists though (most of the time it does not actually break like in strstr()): as documentation, and to be compatible with code that zealously attaches const everywhere where there (currently) is no need to mutate.
But overall my use of const is very very little and I generally do not waste my time (anymore) with it. I almost never have to use "const casts" so I suppose I can manage to keep it in check. In C++ it is a bit worse, when implementing interfaces, like const_iterator etc. That requires annotating constness much more religiously, and that can lead to quite a bit of cruft and repetition.
This is often not true, and even if you think so, you're often wrong. I can't recall all the consequences of the flaws in the system (promote/demote const), but it's not fun to deal with.
I've seen so many things wind up passed to a function or going through an interface eventually that's non-const (or lets not forget is "const'd for safety").
This is where some would say you should give up on practical grounds... if the mission is to determine which const scenarios can be ensured, you argue this is not practically possible and throw the whole thing out.
There's a really good chance you will either have to promote or cast away the const, which I hate.
I tend to agree regarding not using const. It's been a while, so I don't have an example off the top of my head, but it's incredibly easy to break the const mechanism and have to deal with these annoying flaws.
I've just seen this go really bad with any kind of code that has a split responsibility between teams. Eventually you will have to pass to a non-const interface, that you aren't supposed to change.
So perhaps it makes sense if you have control from the top down and can ensure that the constness is maintained, or completely not, if it ends up non-const (then you could also try to move the interface to const, if it truly is)...
... I also suspect in many projects you'd just have to come to the conclusion that nothing can be const'd, because it ends up non-const anyway. Thus leading to the conclusion "just don't use const".
P.S. I'm a bad boy that didn't read TA yet. This is just based on my past experience where we didn't really have the authority to change stuff in the stack... often times there was eg an MCU interface at the end that was non-const... guess we could contact the silica manufacturer... sure they'll get right on that.
The strstr() signature is probably the shortest example / explanation why. To implement strstr(), you have to hack the const away to create the return value. Alternatively, create a mutable_strstr() variant that does the exact same thing. This is the kind of boilerplate that we don't want in C (and that C is bad at generating automatically).
Think about it this way: Real const data doesn't exist. It always gets created (written) somewhere, and usually removed later. One way where this works cleanly is where the data is created at compile time, so the data can be "truly" const, and be put in .ro section, and automatically destroyed when the process terminates. But often, we have situations where some part of the code needs to mutate the data that is only consumed as read only by other parts of the code. One man's const data is another man's mutable data.
In C, the support for making this transition work fluently is just very limited (but I think it's not great in most other languages, either).
It seems to me the "hacking" is exactly the side-effect that is wanted. It's like the requirement in Rust to do certain kinds of things in an `unsafe { }` block (or using the `unsafe` package in Go): not that you want the compiler to prevent you from doing things completely, but that you want the compiler to prevent you from doing things by accident.
> One man's const data is another man's mutable data.
Yes; and the point of `const` for function parameters is to make sure that data isn't mutated unexpectedly.
Ever seen a ROM?
And the C library‘s hacks around not being able to overload functions (which is the only reason for strstr et al‘s weird signature) wouldn‘t stop me from using const. It can be really useful both for documentation and for correctness. Think memcpy, not strstr.
This introduces flaws in the type system. I wish I had a better breakdown on the impact of these concerns, but I'd rather not worry at all.
Anyway, if you don't use const, this goes away. Bear in mind the minor amount of "safety" it provides, because you can just ignore it later, as you arguably tend to be doing anyway when you pass a const to non-const or visa versa.
Inevitably, outside of really small insular project (and often times even then), there's something down the line that winds up being non-const that you don't want to change.
C developers of this mindset tend to just come to the conclusion that you will immediately break the type system, just give up on the whole game.
Edit adding at least on example:
Example: You define as const and remove the const later. If anything writes to the non-const, this is undefined behavior
Example: I believe the above is actually true for const promotion if you modify the non-const version... I think this is only after the call (edit. ie after it become const, really interest in the answer).
No Undefined behavior
/* I imagine this would be okay */
si_non_const = si_non_const + GetMagicValue();
/* Const is promoted here */
const int fparam = si_non_const;
/* Writing to fparam is undefined past here */
f_const(&fparam);
Undefined behavior
/* I imagine writing, after using as const is also not defined, but is fine at this point */
si_non_const = si_non_const + GetMagicValue();
/* Here we now have a constant value that will never be written to */ const int fparam = si_non_const;
f_const(&fparam);
/* I think this would also be UB, even though it's accessed through a different symbol */
si_non_const = si_non_const + GetMagicValue();
Interested in other opinion, maybe will think on later... would it be valid for the compiler to remove that last assignment?
Edit: Sorry, this is unreadable, if you put a space between the not undefined, and undefined it's easier
I only agree with "Declare all functions static except for entry points".
s8(s) is only for literal strings, it should be called s8_c instead and keep s8 for the default ctor.
The struct return part is okay, but I"ve never used. This is not Common Lisp.
Technically, it's illegal to #define over a language keyword.
I have seen many people redefine 'for' and 'while'. These people often argue that it is an improvement.
cpp
#define sizeof(x) (size)sizeof(x)
sizeof(UU)
^D # 1 "<stdin>"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "<stdin>"
(size)sizeof(UU) #define while if> #define int long
Because you're replacing the int keyword with something else.
The standard says:
> 17.6.4.3.1 [macro.names] paragraph 2: A translation unit shall not #define or #undef names lexically identical to keywords, to the identifiers listed in Table 3, or to the attribute-tokens described in 7.6.
I've always felt that C is unfairly maligned. Yes, it's very low level, it's meant to be. Yes, it lets you shoot yourself in the foot, but what language doesn't?
Most of the problems with C are really issues with the standard library, the Unix (now Posix) interfaces, and the string type.
None of these are actually part of C, but are part of how C is normally used. So those problems can be avoided, and use C for what it's good at.
Then they picked up a JS or Python class, were told high-level languages are easy and viola! they started to understand programming.
That's the reason people are spiteful of it. They had a terrible learning experience right out the gate.
Usually folks attach a debugger to capture a stack trace. Usually the debugger uses debug info to determine where the program is, and it's stack trace. Or it can walk frame pointers. Depends on if either are even used, which is a compile time decision.
Good lord.
In higher level languages, you can't shoot yourself in the foot nearly as easily in such a way as to trivially create a correctness problem and security vulnerability (like a buffer under/overflow). Languages like Java and C# make it pretty difficult to shoot yourself in the foot this way (though you still can in other ways, like with incorrect concurrency). Rust makes it a lot harder to shoot yourself in the foot across the board, especially on accident (i.e., without being aware that you're something dangerous and low-level, viz. `unsafe`).
Isn’t it a beauty of lower level languages that creating higher level abstractions provides more value?
edit: typo
But I don't like using 1 and 0 instead of booleans. Many standard C functions (fclose for example), return 0 on success. Better to be explicit here.
Some of my own style changes this year:
I try really hard to write functional code. Mainly try to keep functions pure, and write declarative code. I find that this makes the code easier to write(not necessarily read), and I'm less scared of bugs.
I also avoid malloc unless I absolutely need it. You can usually preallocate space on the stack or use a fixed length buffer, which pretty much avoids all fears of memory leaks or use after free type bugs. You will sometimes waste memory by allocating more than you need, but it's a lot more predictable.
This seems like a bad idea, because the whole point of an assert is that something shouldn't happen, but might due to a (future?) bug.
And so it’s a bad idea because…?
The whole idea is to notice a bug before it ships. Asserts are usually enabled in test and debug builds. So having an assert hit the “unreachable” path should be a good way to notice “hey, you’ve achieved the unexpected” in a bad way. You’re going to need to clarify in more detail why you think that’s a bad thing. I’m guessing because you would prefer this to be a real runtime check in non debug builds?
> #define lengthof(s) (countof(s) - 1)
It makes no sense to use the word "length" to mean one less than the number of items. You could call it maxindexof perhaps.
There may be good arguments for zero based indexing, but we have to also accept that there are downsides. One is that your code has to feature an artificial quantity obtained by subtracting one from a meaningful quantity.
If you pack too much information, you are taxing your brain more, you are slower to analyse the code and you make it easier to make mistakes.
Now I much prefer code that is as verbose as possible.
I like petunias! Now what? How does that help anyone?
I'm certain your opinion on petunias and your possible distaste for orchids will be welcomed in a flower-news type orange site. :-)
"ALL_CAPS" in C was not for constants, but for preprocessor macros. It's shouting in all-caps, because it means "Look out! There's a cpp macro expansion here!"
Related, please stop using "ALL_CAPS" for constants in other languages. Not only does shouting constants as the most prominent syntax in the code make no sense, but there are much better uses for shouting in a programming language.
(For an example of a good use of "ALL_CAPS": if your language ever acquires Scheme-like template-based hygienic macro transformers, "ALL_CAPS" (or "ALL-CAPS") is excellent for making template pattern variables stand out within the otherwise literal code blocks.)
I like this as a convention, but not necessarily as a grammar rule. Printing values is common enough that it shouldn't require shouting for constant attention, simple code shouldn't trigger sensory overload.
Changing this would be a huge undertake I'd be afraid of engaging, if I cared that much.
Constants are so innocent and useful. Why indirectly discourage their use by making their usage an eye-bleed?
do_thing(foo); // foo is variable
do_thing(FOO); // FOO is constant (i.e this call should always do the same thing)
foo = FOO; // I wanna name a variable the same as a constant#define DECLARE_STRING(variable_name, string) struct{size_t allocated; size_t used; char string[sizeof(c_string)];} variable_name ## internal = {.allocated = sizeof(c_string) - 1, .used = sizeof(c_string) - 1, .string = c_string}; MyString *variable_name = &variable_name ## internal
Its a lot of C99 magic, so it may not be what you want but it is possible.
The primitive type names should be native, but to "fix" C I prefer using the C preprocessor (I use it for namespace/name mangling too). This is not perfect, but should be already way more than enough.
With proper preprocessor usage (without going amok), one can write one compilation unit software roughly easily.
it seems this may be more an issue with being shy, than a coding issue :P
in my view, language is a Style, so coding in C is c-style coding.
when i started coding in python (small projects initially), my code cried-out Java Java (typing, packaging, naming, oop), and took nearly as long to write - I laughed my head of when I realized, just because I could doesn't mean I should.
Correct, the standard does not specify whether char is signed or unsigned, so it's implementation-specific.
> and may be more or less than 1 byte, right?
Wrong, char is specified as a single byte character, so the following will always be true:
sizeof(char) == 1;I stopped reading there. I wish this guy a happy coding (and non-coding) life, but I hope we never work together.
I don't agree with all stylistic choices in his code, but the level of experience and skills are far above most C developers.
U1, U2, U4, U8, I1, I2, etc
Also S for "slot" aka unsigned pointer sized integer (usize_t)
Another big point is formatting code to line-up instead of with an autoformatter. When you are doing something which is almost the same but slightly different it helps readability considerably. It is also a sign of a well-loved codebase, since I've never seen an autoformatter that can do it.
Maybe we could make formatters at least auto _detect_ that code is already aligned and to just leave that code alone. Some kind of "love heuristic"
Most of the time when I use fixed-width int types I’m trying to create guarantees for bitwise operators. From my perspective I feel like it therefore makes the most sense to name types on a per-bit level.
I also like that it makes all the type names the same width (notably U1/U2 vs u8/u16).
- I prefer functions over classes.
- no mangling of exported names, the binary is re-usable as API.
- in the long term, the C source code is more re-usable in other projects than C++ ones.
and more like this.
Then use functions?
> no mangling of exported names, the binary is re-usable as API.
Not once in my life have I seen someone use a binary as an API.
> in the long term, the C source code is more re-usable in other projects than C++ ones.
I don't even know what you mean by that.
ps. you know you can write C++ code that is functional and use free functions primarily instead of putting everything in classes?
I also prefer apples over brooms.
Sometimes you do not want, or need, all the batteries.
Simplicity beats complexity almost every time.
I think those definitions go to a header file. But how different will it be if he use existing types with an abbreviation system? And is this feature available with some IDE?
But one thing that makes it worth it is the removal of the struct tag space. I have a strong dislike for the struct tag boilerplate in C, but the alternative -- typedef boilerplate -- in C is unbearable to the point that I have a macro to define structs in C that does this automatically.
#define STRUCT(name) typedef struct name name; struct name
STRUCT(Foo) {
int x;
int y;
};
But macros often come with disadvantages. In this case it's that many IDEs have trouble finding the struct definitions from a usage site.He says so himself:
> I don’t intend to use these names in isolation, such as in code snippets (outside of this article). If I did, examples would require the typedefs to give readers the complete context. That’s not worth extra explanation. Even in the most recent articles I’ve used ptrdiff_t instead of size.
You require extra work to understand his basic types before reading even a short snippet, so he doesn't use it when he wants people to read short snippets.
Introducing additional stuff the programmer must remember that does not add any safety is pointless busywork.
A non-complete summary of his conventions:
1. typedef standard typenames to 3-char symbols,
2. remove qualifiers like const,
3. use macros to reduce the amount of typing the programmer does,
4. typedef all structs (and enums too, I assume)
5. A macro-ized string-typed with prefixed-length.
> Starting with the fundamentals, I’ve been using short names for primitive types. The resulting clarity was more than I had expected,
This isn't clear: `int8_t` is a lot clearer to a C programmer than `i8`, because a C programmer has already internalised the pattern of the stdint.h types. This is going to lead to subtle bugs as well: quick, according to his convention, what is the % specifier for `byte`?
You can use %c, but that gives you an ascii character (which is not what we think of when we say 'byte').
If you use PRIu8 the compiler might give warnings because `char` might be signed. The best option is to just not use `byte` and use `uint8_t` instead (or, in his system, `u8`).
Same with `b32` vs `i32` - it's a distinction without a difference and mixing these types won't give compiler warnings, while it is almost certainly an error on the part of the developer. Use `bool` if you don't like `_Bool`.
In general I try to take advantage of whatever typing C provides; I don't try to subvert it because I want the compiler to warn me when my intention doesn't match the code I wrote.
> No const. It serves no practical role in optimization, and I cannot recall an instance where it caught, or would have caught, a mistake.
I disagree with dropping `const`.
1. It's useful as an indicator to the caller that the returned value must/must not be freed. It's a convention I use that makes it easy to visually spot memory leaks.
Of the two functions below, it's clear to me which one needs the returned value `free()`ed and which one doesn't.
const char *replace_substring (const char *src, const char *pat, const char *replacement);
char *replace_substring (const char *src, const char *pat, const char *replacement);
2. It actually does catch a lot of problems, because the compiler warns me when I attempt to modify a value that some other code I wrote never intended to be modified. It's about intention, and when I know it is safe to modify the `const` value, then I have to explicitly cast away the const to compile my program. Anyone reading the program will know that the modification of the const-qualifed value is intentional, and therefore safe.> #define s8(s) (s8){(u8 *)s, lengthof(s)}
This is interesting. I will try this out in my next project. I do think that there'll be quite a few compiler warnings for sign-mismatch though. This is the second "I wonder what the sign is" question for programmers reading his code - it means that his code has to compile with the flags that he compiles it with (I assume he's passing a flag to force chars to a particular sign). You can't simply compile his code in another project unless you copy his flags, and those flags may conflict with the new projects flags.
I also wish that he'd showed a few examples of how having the length helps - what is presented in the post doesn't show any additional string safety over using nul-terminated strings. All those macros, including the one that creates the struct, could be written to operate on null-terminated strings. In essence, the length can be simply unused for everything! Where's the safety!?
> It’s also led to a style of defining a zero-initialized return value at the top of the function, i.e. ok is false, and then use it for all return statements. On error, it can bail out with an immediate return.
I use a similar pattern, but I use `goto cleanup` on all errors; you can't, as a general pattern, return early in a non-trivial C function without leaking resources. You can, as a general pattern, `goto cleanup` in every C function to clean up resources. I prefer the general pattern that I use everywhere rather than having to ensure that all resources acquired up to that particular return statement are released.
> rather than include windows.h, write the prototypes out by hand using custom types.
I think this is a very bad idea: you can't depend on the headers not changing after a compiler or library update. Sure, maybe in practice, all the Windows types and declarations don't change all that much, but I wouldn't want to be the developer trying to hunt down a bug because the interface to some function has changed and the compiler isn't giving me errors.
All in all, I dunno if I would look forward to working on a team with these conventions - the code is harder to read, doesn't work in isolation, needs custom flags, and introduces a string type without introducing any string safety with it.
Nice to see some evolutive convergence in C programmers, too
http://edit.rupy.se/?host=move.rupy.se&path=/file/game.cpp&s...
It's messy and in many ways ugly but it compiles and runs for an eternity.
and then you put the code in a header and everything explodes
The first section shows why some languages have no 'typedef': introducing another layer of aliases is just not a good idea. It's confusing, it changes the appearance to basically a new language. Just use the standard names, instead of redefining your language, like everyone else. This style is almost as bad as '#define begin {'.
Many of the other defs are obfuscations or language changes -- this coerces C to something else. I'd not like to read code written with this, as it heavily violates the principle of least astonishment (POLA).
(As a side note, I don't understand the #define for sizeof. The operator sizeof returns size_t -- it's size_t's definition, so what is this for?)
Their `size` type is signed. It's `ptrdiff_t`, not `size_t`.
That one is dubious. Char has magic aliasing properties that uint8_t might not have (iirc that was contentious in a GCC bug report) and it will be signed on some platforms and unsigned on others, which changes implicit integer conversions.
Missing from this is to embrace attribute((overloadable)) and attribute((cleanup)).
Overloadable is the sane, useful alternative to the thing standardised as _Generic. The C _Generic will let you define an overload set, with some weirdness around type conversions, provided you write the entire set out as a single _Generic expression, probably wrapped in a macro. If you want to dispatch on more than one argument, you nest _Generic expressions. If you want to declare different functions in different headers - maybe you want 'size(T)' defined on various types in the codebase - you can't. If you don't like the idea of thousands of lines of distracting nonsense in the preprocessed output, tough. Or - use overloadable, get open overload sets, minimal compile time cost, obvious intermediate IR, everything works. Prior art is all of C++, so talking decades of the tooling learning to deal with it.
Cleanup is either a replacement for raii, or a means to have debug builds yell at you when you miss a free. It looks like that got warped into a thing called 'defer' with different behaviour that didn't make it through the committee last time.
Other than that, ad hoc code generators work really well with C. Especially if you're willing to use some compiler extensions. Code generators + overloadable will give a fair approximation to templated data structures without going deep into the insanity of the preprocessor. If the overloadable functions are static inline forwarding things in a header they don't even mess up symbol names; you just get a straightforward translation to vector_float_size or whatever.
Personally I've given up on ISO C. I'd quite like to code in a dialect of C99 with a few of the GNU extensions and the equivalent of `fno-strict-aliasing`, but C with the pointer provenance modelling and an accretion of C++ features has no personal value. Currently still using clang with flags to make it behave like that but I'm conscious that's on borrowed time - the application performance friendly aliasing rules are the default and gaining popularity, and relying on opt-out flags is a means of opting into compiler bugs.
Semi-actively seeking something that will let me write assembly without the hassle of manual register allocation and calling conventions. Old style C with some of the warts bashed off would be good for that.
There's clearly life in the old dog yet!
I would have used isize instead, I think.
Everyone knows what a uint32_t is when they see it. The cognitive overhead (until it becomes second nature, obviously) just feels like a heavy price to pay in order to save yourself a few characters.
(Some other stuff in the proposed coding style still gets a thumbs up from me, though.)
I am not saying every style is good (some simply obfuscate things and/or make things overly verbose or unreadable) but rejecting a style solely based on it being "non-idiomatic" is not a good thing.
b32, size (ptrdiff_t), usize (size_t), nothing for ssize_t... what? Those are unidiomatic and also kind of weird. The macros... some are fine, some are weird.
If this makes the author more productive in C, it might behoove them to see if a higher level language like Rust would meet their needs.
Writing correct C is hard, so I’m not going to knock anyone who found stuff that helps them.
But pound defining shit to things you know via your Hungarian notion? Write some elisp. My Haskell programs don’t actually have Unicode lambda in them.
Pascal strings? Yeah, that’s probably the better call, but why not use C++ or Rust or something where a bunch of geniuses got it right already?
You might not be old enough then :-P many codebases typedef their own int types. See glib (gint, gshort, gint32, etc), SDL (Sint32, Uint32, etc) off the top of my head and there are many that define types like "int32" or "i32" like the linked article.
I don’t see the harm in following this for your own passion projects. You aren’t doing it for the world, you’re doing it for yourself.
Undefined behavior[1]
> #define assert(c) while (!(c)) __builtin_unreachable()
Undefined behavior[1]
> I’ll cast away the const if needed.
Undefined behavior[2]
> The assignments are separated by sequence points, giving them an explicit order.
I don't believe assignments are sequence points and only the function call is.
[1] https://en.cppreference.com/w/c/language/identifier#Reserved...
> Undefined behavior[2]
How so? As the page you linked mentions, simply casting 'const T *' to regular 'T *' is well-defined; it's only modifying a const object through the pointer that's UB (C17 6.7.3/7).
> I don't believe assignments are sequence points and only the function call is.
Assigments within expressions don't create sequence points. However, the expression of an expression statement is a full expression (i.e., not a subexpression of another expression), and there is a sequence point between each pair of full expressions (C17 6.8/4). In other words, the semicolons create sequence points.
> Undefined behavior[2]
To be clear, it's only UB if the object was defined const, which is the case given he wrote:
> One small exception: I still like it as a hint to place static tables in read-only memory closer to the code. I’ll cast away the const if needed.
So you are correct on this point. Funnily enough, such objects are relatively rare IME, so I had to double-check to see that he was advocating it specifically in the rare case where it must not be applied.
And people keep telling me that nobody uses the C preprocessor to define their own syntax any more!
Given that this particular undefined behavior usually causes crashes in practice, I expect the author is talking about casting away the const but not actually writing to the pointer. Which is legal.
(Haha, only serious.)
You missed the point of code style.