Lesser known tricks, quirks and features of C
blog.joren.ga
blog.joren.ga
Mazda cars used to have a bug where they used printf(str) instead of printf("%s", str) and their media system would crash if you tried to play the "99% Invisible" podcast in them. All because the "% In" was parsed as a "%n" with some extra modifiers. https://99percentinvisible.org/episode/the-roman-mars-mazda-...
The %n functionality also makes printf accidentally Turing-complete even with a well-formed set of arguments. A game of tic-tac-toe written in the format string is a winner of the 27th IOCCC.
- sez wiki.
A not so fun fact:
Because the %n format is inherently insecure, it's disabled by default.
- MSVC reference.
No it doesn't. Printf has no way to loop so it's not Turing complete. Even if you did what the IOCCC entry did with putting it into a loop it still wouldn't be Turing complete as it would not have an infinite memory.
It's definitely a bit fuzzy. I'm sure lots of philosophy papers have been written on when exactly it is or isn't appropriate to consider a finite computational system as a finite approximation to a Turing-complete system. In realistic everyday cases, however, it's usually clear enough what should and shouldn't count as such.
Adding another state to a FSA adds more memory.
There is no difference between a hobbled Turing machine and a FSA. Turing machines aren't a useful concept in the real world and that is okay.
Yes, but it also changes the state transition logic. You can't just 'add 100 more states' to an FSA in the same way that you can 'add 100 more stack slots' to a bounded pushdown automaton.
As I said previously, these are somewhat fuzzy distinctions, and I'm not saying that they're easy to make mathematically precise. They do however seem clear enough in most cases of practical interest. There are many real-world computing systems that would be Turing-complete if they had unbounded memory. There are others that are not Turing-complete for more fundamental reasons than memory limitations. Again, I acknowledge that 'more fundamental' is not a mathematically precise concept.
-Werror=format-security> The ISO C90 functions abort, abs, acos, asin, atan2, atan, calloc, ceil, cosh, cos, exit, exp, fabs, floor, fmod, fprintf, fputs, free, frexp, fscanf, isalnum, isalpha, iscntrl, isdigit, isgraph, islower, isprint, ispunct, isspace, isupper, isxdigit, tolower, toupper, labs, ldexp, log10, log, malloc, memchr, memcmp, memcpy, memset, modf, pow, printf, putchar, puts, realloc, scanf, sinh, sin, snprintf, sprintf, sqrt, sscanf, strcat, strchr, strcmp, strcpy, strcspn, strlen, strncat, strncmp, strncpy, strpbrk, strrchr, strspn, strstr, tanh, tan, vfprintf, vprintf and vsprintf are all recognized as built-in functions unless -fno-builtin is specified (or -fno-builtin-function is specified for an individual function).
Builtin here doesn't mean that GCC won't ever emit calls to library functions, only that it reserves not to and allows itselfs to make assumptions about how the functions work, including diagnosing misuse.
The library functions themselves might also be marked with __attribute__(format(...)) as the sibling comment notes but that is not necessarily required for GCC to check the format strings.
Also, in personal projects, I almost always used custom wrapper functions for printf/fprintf/sprintf for various reasons, so that default wouldn’t be of much use, unless maybe I could enable it for the custom functions.
For loading translated strings, I'm missing some library function to verify whether two format strings are argument-compatible.
Aren't those usually resolved to string literals by preprocessor such that the compiler still could emit a warning?
(I have no recommendations. When I've seen this stuff done properly, on the occasions I've managed not to avoid doing it, it's always been using some in-house system.)
You can with the $ syntax. Never seen it used though. Maybe it isn't very portable.
I think the real reason you rarely see it, is it is only used with internationalisation–the idea being if you translate the format string, the translator may need to reorder the parameters for a natural translation, given differences in word order in different languages. However, a lot of software isn't internationalised, or if it is, the internationalisation is in end-user facing text, which nowadays usually ends up in a GUI or web UI, so printf has less to do with it. And the kind of lower-level tools/components for which people still often use C are less likely to be internationalised, since they are targeted at a technical audience who are expected to be able to read some level of English.
I like printf format strings, but as a way of handling localizable strings I don't think they are the best.
A good example of this is pluralisation. We've all done things like:
printf("%d file(s) copied\n", count);
which is acceptable but kind of ugly. Some people want to make it nicer: printf("%d file%s copied\n", count, count != 1 ? "s" : "");
Which is fine for English, but doesn't work at all for other languages. The problem is not just that the plural ending is something other than `s` – if it was just that, it wouldn't be too hard. The problem is that the `count != 1` bit only works for English. For example, while 0 is plural in English, in French it is singular. Many other languages are much more complex. The GNU gettext manual has a chapter which goes into this in great detail – https://www.gnu.org/software/gettext/manual/html_node/Plural...printf() has zero hope of coping with this complexity. gettext provides a special function to handle this, ngettext(), which is passed the number as a separate argument, so it can select which plural form to use. And then the translated message files contain a header defining how many plural forms that language has, and the rules to choose which one to use. And for some languages it is crazy complex. Arabic is the most extreme, for which the manual gives this plural rule:
Plural-Forms: nplurals=6; \
plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 \
: n%100>=11 ? 4 : 5;One practical reason I can think of is because not everyone compiles their own code.
You must most definitely look for and enable such flags as they become available in your own projects. (eg I was rooting for -Wlifetime but it did not land for various reasons)
But when you compile other people's code, your breaking your local build doesn't help anyone. Best you can do is to submit a bug report, which may or may not be ignored.
std::format specifically only works for constant† format strings. Not because they can't make it work with a dynamic format, std::vformat is exactly that, but most of the time you don't want and shouldn't use a dynamic format and the choice to refuse dynamic formats in std::format means fewer people are going to end up shooting themselves in the foot.
Because it requires constant formats, std::format also gets to guarantee compile time errors. Too many or not enough arguments? Program won't build. Wrong types? Program won't build. This shifts some nasty errors hard left.
† Not necessarily a literal, any constant expression, so it just needs to have some concrete value when it's compiled.
> This qualifier tells the compiler that a variable may be accessed by other means than the current code (e.g. by code run in another thread or it's MMIO device), thus to not optimize away reads and writes to this resource.
It's dangerous to mention cross-thread data access as a use case for volatile. In standard C, modifying any non-atomic value on one thread, while accessing it on another thread without synchronization, is always UB. Volatile variables do not get any exemption from this rule. In practice, the symptoms of such a data race include the modification not being visible on the other thread, or the modified value getting torn between its old and new states.
Eskil Steenberg talks about it at 12:42 in his talk Advanced C: The UB and optimizations that trick good programmers. [0]
What use case do you have in mind where this theoretical possibility would cause issues?
I was a grad when I read it and remember annoying my older coworkers for a few weeks with little gotchas I picked up. "hey what do you think THIS example prints?" "Stop sending me these!"
In essence it was a RISC machine saying "I can't deal with unaligned data" and a sign that code was (say) storing a 32 or 64 bit int that straddled a word boundary and the hardware was not coping on a MOV.
Why did that matter and why was it good thing?
It forced programmers to think about data alignment and resulted in code that run faster on CISC Intel chips.
The dirty secret about Intel chips was they "just did it" w/out complaint - and it slowed them down significantly on pipelined computations if they were constantly double handling unaligned data to get it from memory (across a word boundary) to bus (aligned for transit) to memry again (across a word boundary).
What with C also allowing structures as return values, another rarely-used feature, they're really useful for allowing a richer API than the historical `int foo(...)` that so many people are used to seeing.
C has so much legacy that it's really hard for even decades-old (C99!) feature to impose themselves. Or perhaps that's MSVC's lagging support that's to blame :p
Anyway given the option, one should always favour C++ over C, if they care about secure code, which while not perfect it is much better than any C compiler will do.
Eh. I work in embedded, where C reigns supreme. C++ has its own issues in the area, namely that you need to construct your own sub-dialect that removes some features of C++ to make it fit embedded constraints. Commonly, it's C++-but-no-exceptions, sometimes C++-but-no-templates, and others.
That said, I'll grant that us embedded developers are effectively "traumatized" and have difficulty accepting new approaches because we're too focused on certain paradigms that are no longer relevant (See my previous about returning structures, which has encountered responses like "but then it might do an extra memcpy()!!11")
I can understand exceptions, but what constraints require you to ban templates? If its just code size then it seems a bit arbitrary to ban them completely.
AFAIK most users of C++ do ban some features in their projects so I don't see why that specifically is holding embedded back. Disabling exceptions specifically is something that is not unheard of outside embedded either.
Sure they do now. They were particularly slow at implementing a bunch of C99 stuff, some of it landing only in VS 2019
https://learn.microsoft.com/en-us/cpp/overview/visual-cpp-la...
MSVC has long lagged in C support because they focused on that subset of C standards that were required for C++.
https://herbsutter.com/2012/05/03/reader-qa-what-about-vc-an...
I remember working on a commercial project in the mid-2000's that still had #ifdefs for K&R C prototypes (meaning, pre-ANSI C.) This was a recent-ish project at the time, started in 2000. Were people going to go back in time and compile it on an old architecture? I doubt it.
C moves slow.
// traditional syntax:
boxes[products[myorder.product].box].weight
// index[array]:
myorder.product[products].box[boxes].weightI didn’t know C23 was getting rid of trigraphs. That’s probably a good thing and easy to clean up if needed.
The stock verbiage about how modern compilers ignore "register" because they can do better but it may be useful on simpler ones, has been around in this exact form 20 years ago already. And one curious thing is that even back then, such statements would never list specific compilers where "register" still did something useful.
So far as I can tell, "register" was in actual use back when many C compilers were still single-pass, or at least didn't have a full-fledged AST, and thus their ability to do things like escape analysis was limited. With that in mind, "register" was basically a promise to such a compiler to not take the address of a local in the function body (this is the only standard way in which it affects C semantics!). But we haven't had such compilers for a very long time now, even when targeting embedded - the compilers themselves run on full-power hardware, so there's no reason for them to take shortcuts.
So if you want to make absolutely sure that a variable can always be in a register then you should consider adding the register specifier to stop other programmers from taking the address of that variable.
OTOH I didn't recall this bit, but apparently you can also apply it to arrays. Which keeps them indexable, but you can't take addresses of elements nor of the whole array anymore. Now I'm not sure if the compiler can do anything useful with this, but it would allow it to play with alignment and padding - e.g. storing "register bool x[10]" as 10 words rather than 10 bytes. Is there any architecture on which that would be beneficial, though?
I realize that's a gnu extension - but it's super useful!
https://cigix.me/c17#6.5.2.5.p5
> If the compound literal occurs outside the body of a function, the object has static storage duration; otherwise, it has automatic storage duration associated with the enclosing block.
Note that the struct values thus created have longer lifetimes than temporary C++ objects created directly inside the argument list of a function call.
func(&(bla_t){ .x = 0, .y = 1 });1. %n in printf would be handy when writing CLIs dealing w/ multiple lines or precise counts of backspaces.
2. Using enums as a form of static_assert() is a great idea (triggering a div by zero compiler error).
%n is for bytes, not user-perceived characters.
#define STATIC_ASSERT(COND) extern char static_assert_cond_[(COND)?1:-1] /* C99 or earlier */
#define STATIC_ASSERT(COND) _Static_assert(COND) /* C11 or later */
As both are declarations, I don't think you'll end up in a situation where one is valid and the other isn't - but I could be wrong, and I suspect it would rarely matter in practice anyway.for (unsigned int i = N; i --> 0;) printf("%d\n", i);
This --> construction also works in JavaScript and so on.
for (unsigned int i = N; i--;){}
unsigned int i = N;
while(i--){ ... }
Also I think I'm missing the tricky part. Couldn't this be a bog-standard for loop? for (unsigned int i = N - 1; i > 0; i--){ ... }
The "downto" pseudooperator definitely scores some points for coolness and aesthetics, but there's no immediately obvious use case for me.And we cannot change the comparison to `>=` in the later, because unsigned is always bigger or equal 0, thus we would get infinite loop.
unsigned char x = 0;
do {
printf("%d\n", x);
} while (++x); unsigned int x = 0;
const unsigned int max = ~x;
while (x <= max) {
calc(x++);
}
Or, #include <limits.h>
const unsigned int max = UINT_MAX; unsigned int x = 0;
while (x < ~x) {
calc(x++);
}
Or going down: unsigned int x = ~0;
while (x > ~x) {
calc(x--);
} while (1) {
calc(x++);
}
which is an infinite loop, since the expression x <= max is always true while (N --> 0) { ... }
and know it will execute N times no matter the details of the type of N.Designated initializer is something I'll try to remember, seems handy.
I wish there was some effort to create a modern version while preserving the clarity and conciseness of Kernighan and Ritchie.
Designated initializers in particular are extremely useful. I once halted a factory line for days because of a mistake they would have avoided.
1) C++ 'forked' their C subset before C99 (ca. "C95"), and while C++20 finally got its own version of designated init, this has so many restrictions compared to C99 that it is basically pointless.
2) MSVC hasn't supported any important C99 features until around 2016
I started reading the C BNF and I have to admit that I was not prepared at all. It's not as easy as it sounds.
I cannot imagine how difficult it must be to maintain a modern C++ compiler.
There is a reason why modern languages use keywords like "func", "def", "fn", "var", "let" to discern between different types of declarations, for example. I dont think many languages are LL(k) (please correct me if im wrong), but C is as far away from that as it gets, for small k.
It's one of those weird ideas I've had kicking about for years but never did anything about, and yet I keep coming back to it.
> It should be possible to create a binary notation for SKI calculus where arbitrary bitstrings will be valid
What if it's not ? How will your genetic petri dish spot and eliminate invalid programs ? > one could randomly mutate and recombine arbitrary programs
What if non-halting programs get generated ?In this vein I've seen magnificent images of 1D cellular automatons that use the surrounding pattern to decide on the local rule for next gen.
Though if the space is small enough I imagine you could get past that. It's a bit of a gnarly point, hard to tell how this would turn out without trying I suppose.
As for the halting problem there's of course no clever solution there other than limiting CPU time. So I guess pick a reasonable limit that makes sense for whatever you're trying to do.
typedef void func(int);
func f;
void f(int) {}
I don't think I've ever seen a practical use for this in C, though. In C++, where this also works, and extends to member functions, this can be very occasionally useful in conjunction with decltype to assert that a function has signature identical to some other function - e.g. when you're intercepting and detouring some shared library calls: int foo();
decltype(foo) bar;
I suppose with typeof() in C23 this might also become more interesting. typedef void Step(whatever...);
Step add,sub,mul,div,
load,store,
etc...; typedef int (*CallbackPtr)(int,int);
void foo(..., CallbackPtr callback);
always reads less clearly to me than typedef int Callback(int,int);
void foo(..., Callback* callback);
I find this especially useful in C++ where the callback type could conceivably be some other type like std::function. Seeing that * helps me know at a glance it's probably just a plain old function pointer.Though I think maybe clearest of all is to not use a typedef, provided it doesn't cause other readability problems:
void foo(..., int (*callback)(int,int));
(Not meaning to steal your thunder here... just wanted to write out an example in case anyone else was curious.) int n = 3, m = 4;
int (*matrix_NxM)[n][m] = malloc(sizeof *matrix_NxM); // `n` and `m` are variables with dimensions known at runtime, not compile time
if (matrix_NxM) {
// (*matrix_NxM)[i][j] = ...;
free(matrix_NxM);
}
Well, that makes much easier a few things I'm doing atm, really glad I read it. #ifndef UTIL_H
#define UTIL_H
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define LEN(arr) (sizeof (arr) / sizeof (arr)[0])
#define NEW_ARRAY(ptr, n) \
(ptr) = malloc((n) * sizeof (ptr)[0]); \
if ((ptr) == NULL) { \
fprintf(stderr, "Memory allocation failed: %s\n", strerror(errno)); \
exit(EXIT_FAILURE); \
}
#define NEW(ptr) NEW_ARRAY(ptr, 1)
#endif
With these in place, working with arrays and dynamic memory is safer, less verbose and readability is improved.No, because C doesn't have templates. The best you can do for a "vector" in C is macros like above, that also realloc, or write an API around structs for each type.
(ptr) = malloc((n) * sizeof (ptr)[0]);
with (ptr) = GC_MALLOC((n) * sizeof (ptr)[0]);
in the macro and don't have to worry about calling free. // will barf if fed a pointer
#define sizeof_array(arr) \
(sizeof(arr) / sizeof((arr)[0]) \
+ sizeof(typeof(int[1 - 2 * \
!!__builtin_types_compatible_p(typeof(arr), \
typeof(&arr[0]))])) * 0)Shortcuts to reduce redundant chores (like those multiple instructions to load one 64-bit number into an ARM register) but minimal "magic" or unintended consequences as in C. Things like maybe a function call syntax like:
CALL someFunc(R1: thingForRegister1, @R7: pushR7ThenPopOnReturn, R42: [memoryAddressForR42])
and the function might be defined as:
someFunc(R1 as localNameForR1, R7 as oneMoreThing, R42? as optionalArgument)
and so on. (but anyone could come up with better ideas than me)
PRINTF "main_task: sf=%p",ebp
PRINTF " sf: 1=%d 2=%d 3=%d 4=%d",dword [ebp+8],dword [ebp+12],eax,ebx
I'm using NASM and it wasn't hard to write the macro to do so. int (*ap3)[900000] = malloc(sizeof *ap3);
is nicer than int *a = malloc(900000 * sizeof *a);
Notice that, in the former case, the array elements must be accessed as (*ap3)[i] whereas in the latter case the usual method a[i] is fine. int (*arr)[n][m] = malloc(sizeof *arr);
which you have to access with (*arr)[i][j]
I prefer doing int (*arr)[m] = malloc(n*sizeof(*arr));
though this separates m and n to be one on left side while the other on the right side, it allows me to index directly arr[i][j]The goal is to have a pointer to the array, and not a pointer to the first element of the array.
Whether this is "nicer" or not, and whether this is what you need in your application, are out of the scope of the fine article.
> It's possible, because C cares less than more about whitespace
Idiomatically we'd say 'couldn't care less'. I guess we should be glad it wasn't the diabolical and illogical 'could care less'
In contrast, the author is suggesting a comparative only.
And, on careful re-reading, I suspect the author is having a play on syntax & semantics here -- the context of the quote is:
> You may ask, since when C has such operator and the answer is: since never. --> is not an operator, but two separate operators -- and > written in a way they look like one. It's possible, because C cares less than more about whitespace.
Given that '--' is decrement (kind of 'lessen') and > is greater than (kind of 'more'). Perhaps I am reading too much into that.
(I feel 'couldn't care less' is perhaps more common in northern America than elsewhere, and while TFA has a Gabon TLD, appears to be resident in Poland, so automatically receives a lot of leeway in their use of idiomatic English.)
'cares not so much' / 'doesn't care so much' might also work in your context.
int x;
with: intx; *z = *x / *y;
Or here: address = mask & &object;
Or here: a = b - --c;This isn't correct since int can't be less than 16-bits. Fields are placed on the nearest natural alignment for the target platform, which might not support unaligned access.
The bitfield stuff in C would be fantastic if it weren't fundamentally broken. E.g. some Microsoft compilers in the past interpreted bit fields as signed...always. In V8 we had a work around with templates to avoid bitfields altogether. Fail.
> The bitfield stuff in C would be fantastic if it weren't fundamentally broken
Bitfields in C can be manageable. Each compiler has it's own set of rules for how it prefers to arrange and pack them. Despite them not always being intuitive, I use them regularly since they are so succinct. If you are concerned about faithful and predictable ordering you generally just have a test program which uses known values to verify your layout as part of your build configuration or test battery.
> ... some Microsoft compilers ...
I've used many C compilers but I have always avoided microsoft ones, going so far to carry a floppy disc with my own when working in the lab at school.
For the use case of specifying a more efficient representation of a fiction confined to the program, then no harm, no foul. But the use case of specifying a hardware- or network- or ABI-specified data layout, then you need those bits in exactly the right spot, and the compiler should have no freedom whatsoever. (I'm thinking of the case of network protocol packets and hardware memory-mapped registers).
The compiler itself is not taking a liberal approach to bit field management, it is only working within the restriction of the type (I am speaking for GCC here, I can't vouch for others). But if you think of them as an interface to store packed binary data freely without limitations I can understand why they seem frustrating. They are much more intuitive when you consider them as being restricted to the type.
So yeah, the placement in memory might be `xxxxx000 00000000 yyyyyyy0` but it could also be `yyyyyyy0 00000000 xxxxx000` or `yyyyyyy0 00000000 00000000 xxxxx000` or anything else.
Bitfields are very misunderstood and really only safe to treat as an ADT with access through their named API, not their bit placement ABI. People misuse them a lot.
Oh man, I think we all have been this young and naive at some point.
I have spent time working with compilers for this purpose (having realized I did not want to attempt parsing source and generating the AST) and decided it is much easier to let them do the work. That being said, it can still be more than a handful (both GCC and Clang have their eccentricities) and depending on how you are using it you still might be in over your head.
When you start a project like this and end up failing because you simply do not have the depth of knowledge or time to see it to completion it often feels a bit demoralizing from the loss of investment. Truthfully though, having started many such ventures (emulators for 6502 and 80386 to name a few), you get all the benefit of experience from working on a difficult problems without the misery of debugging and model checking until everything until is more/less perfect. It's great fun, you learn a lot, and you should never avoid trying simply because it might be too much to handle.
> There are some tricks, quirks and features (some quite fundamental to the language!)
Given the title of the article, one ought to assume the opposite ;)