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.
This is one of those annoying little problems that is easily picked up by the vet command (https://pkg.go.dev/cmd/vet) when writing Go code. There are, of course, many linters that do the same thing in C, but it's nice to have an authoritative one built in as part of the official Go toolchain, so everyone's code undergoes the same basic checks.
Very nice collection. My favorite C feature is actually a gcc/clang feature : the __INCLUDE_LEVEL__ predefined macro. It made me code&maintain my C projects exactly twice as fast as before because file count dropped to half : https://github.com/milgra/headerlessc .
Is having two files really that much of a bother? I have my editor set switch between the .c(pp) and the .h with a keyboard shortcut and that seems easier than scrolling between declaration and definition when you want to change something.
> 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.
These days it can be chained with _Atomic to achieve the desired effect. That said, oftentimes you need more serious synchronization mechanisms your library would provide.
"Expert C Programming: Deep C Secrets" is a really good book to learn a lot of C tricks and quirks, plus some history. I read it a few years ago and loved it.
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!"
Compound Literals in C are great. They're no surprise to anyone coming from more sophisticated languages, but I've never seen them used in the C codebases I've worked on.
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
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.
MSVC supports C11 and C17, minus the C99 stuff that was made optional in C11.
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.
Never quite understood why compound literals are lvalues, but fine, whatever, I guess, it's so that you can write "&(struct Foo){};" instead of "struct Foo tmp; &tmp;"... which, on a tangential note, reminds me about Go: the proposals to make things like &5 and &true legal in Go were rejected because "the implied semantics would be unclear" even though &structFoo{} is legal and apparently has obvious semantics.
the c training course at a popular uk training company (the instruction set) had duff's device on something like page 5 of their c course - expunging it was one of the first things i did when i joined them. there were many others.
Designated init and compound literals were added in C99. I think there are two reasons for those features not being better known:
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
One non-obvious thing about named function types is that they can also be used to declare (but not define) functions:
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.
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.
There are three macros which I find indispensable and which I use in all my C projects, namely LEN, NEW and NEW_ARRAY. I keep them in a file named Util.h:
I wish there was a language "between" assembly and C: basically assembly with some quality-of-life improvements.
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:
[+] [-] ufo|3 years ago|reply
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-...
[+] [-] tom_|3 years ago|reply
[+] [-] rerdavies|3 years ago|reply
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.
[+] [-] gdprrrr|3 years ago|reply
[+] [-] GolangProject|3 years ago|reply
[+] [-] unknown|3 years ago|reply
[deleted]
[+] [-] milgra|3 years ago|reply
[+] [-] xigoi|3 years ago|reply
[+] [-] account42|3 years ago|reply
[+] [-] enriquto|3 years ago|reply
[+] [-] 3836293648|3 years ago|reply
[+] [-] gavinray|3 years ago|reply
[+] [-] LegionMammal978|3 years ago|reply
> 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.
[+] [-] Jorengarenar|3 years ago|reply
Eskil Steenberg talks about it at 12:42 in his talk Advanced C: The UB and optimizations that trick good programmers. [0]
[0]: https://youtu.be/w3_e9vZj7D8?t=762
[+] [-] unknown|3 years ago|reply
[deleted]
[+] [-] mid-kid|3 years ago|reply
[+] [-] plugin-baby|3 years ago|reply
[+] [-] tastysandwich|3 years ago|reply
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!"
[+] [-] AceJohnny2|3 years ago|reply
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
[+] [-] icedchai|3 years ago|reply
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.
[+] [-] pjmlp|3 years ago|reply
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.
[+] [-] unknown|3 years ago|reply
[deleted]
[+] [-] teo_zero|3 years ago|reply
[+] [-] Jorengarenar|3 years ago|reply
[+] [-] foobiekr|3 years ago|reply
I didn’t know C23 was getting rid of trigraphs. That’s probably a good thing and easy to clean up if needed.
[+] [-] Joker_vD|3 years ago|reply
[+] [-] dantle|3 years ago|reply
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).
[+] [-] gallier2|3 years ago|reply
[+] [-] nstbayless|3 years ago|reply
for (unsigned int i = N; i --> 0;) printf("%d\n", i);
This --> construction also works in JavaScript and so on.
[+] [-] mtklein|3 years ago|reply
[+] [-] titzer|3 years ago|reply
[+] [-] stonegray|3 years ago|reply
[+] [-] Jorengarenar|3 years ago|reply
[+] [-] unknown|3 years ago|reply
[deleted]
[+] [-] skribanto|3 years ago|reply
[+] [-] zabzonk|3 years ago|reply
[+] [-] zwieback|3 years ago|reply
Designated initializer is something I'll try to remember, seems handy.
[+] [-] flohofwoe|3 years ago|reply
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
[+] [-] AceJohnny2|3 years ago|reply
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.
[+] [-] suprjami|3 years ago|reply
[+] [-] jokoon|3 years ago|reply
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.
[+] [-] vocram|3 years ago|reply
[+] [-] int_19h|3 years ago|reply
[+] [-] ljosifov|3 years ago|reply
[+] [-] augustk|3 years ago|reply
[+] [-] Gibbon1|3 years ago|reply
[+] [-] Razengan|3 years ago|reply
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)
[+] [-] spc476|3 years ago|reply
[+] [-] xigoi|3 years ago|reply
https://c9x.me/compile/
[+] [-] Miserlou57|3 years ago|reply