It certainly could do though. In C, using an uninitialised variable does not mean "whatever that memory happened to have in it before" (although that is a potential result). Instead, it's undefined behaviour, so the compiler can do what it likes.
For example, it could well unconditionally initialise that memory to 123. Alternatively, it could notice that the whole snippet has undefined behaviour so simply replace it with no instructions, so it doesn't print anything at all. It could even optimise away the return that presumably follows that code in a function, so it ends up crashing or doing something random. It could even optimise away the instructions before that snippet, if it can prove that they would only be executed if followed by undefined behaviour – essentially the undefined behaviour can travel back in time!
I'm not enough of a specification lawyer to say that this is definitely true, but the reasoning and example given there seems sound to me.
[1] https://devblogs.microsoft.com/oldnewthing/20140627-00/?p=63...
Without undefined behavior, the compiler emits code that has the behavior defined by the code —- the ordering may be altered, but not the behavior.
A compiler's analysis can go backward in time. That is to say, the compiler can build a model of what happens in some section of code over time, and analyze it whichever way it wants.
You cannot go back in time from execution time to translation time, but the translator can follow the code as if it were executing it at translation time.
Static objects are always initialized, so the situation cannot arise.
That leaves dynamic ones, like uninitialized struct members in a malloced structure.
Accessing uninitialized dynamic memory means isn't undefined behavior in C. It results in whatever value is implied by the uninitialized bits. If the type in question has no trap representations, then it cannot fail.
switch(k) {
if (0) case 0: x = 1;
if (0) case 1: x = 2;
if (0) default: x = 3;
}
which is a switch where you don't have to write break at the end of every clause. #define brkcase if (0) case
That might be worth using. Compilers won't love the control flow but they'll probably delete it effectively. #define brkcase break;case
kinda defeats the purpose of the macro even.Incidentally, what happens if you use your brkcase as the first case?
I don't find either particularly exciting - a macro that would append break to the current case feels better
I'll confess, I've used this construct to mean "omit the first line of the next case label but otherwise fall through".
If you think of the case label as merely a label and not a delimiter between statements all of this makes sense.
case 1 ... 10:
Is valid C? I have been programming in C for years, what standard is this from? #include <stdio.h> // compile & run: gcc -Wall countdown.c -o countdown && ./countdown
int n = 10; int main(int argc, char *argv[]) { printf("%d\n", n) && --n && main(n, NULL); }
Python version: import sys # run: python3 countdown.py 10
def main(n:int): sys.stdout.write(f"{n}\n") and n-1 and main(n-1)
main(int(sys.argv[1]))
Shell version: # run ./countdown.sh 10
echo $1 && (($1-1)) && $0 $(($1-1)) import sys # run: python3 countdown.py 10
def main(n:int): print(n) or n-1 and main(n-1)
main(int(sys.argv[1]))
This also works and is definitely more Pythonic: _ = [print(n) for n in range(10,0,-1)] 4[arr] // same as arr[4] arr[i][j]
j[i[arr]]
These are the simplifications you'd do. You only need to know that a[x][y] is equivalent to (a[x])[y], and that a[x] is the same as x[a]. arr[i][j]
(arr[i])[j]
(i[arr])[j]
j[i[arr]]https://stackoverflow.com/questions/34559705/ternary-conditi...
Duff is trying to optimise MMIO, you wouldn't do anything close to this today even in C, not least because your MMIO is no longer similarly fast to your CPU instruction pace and for non-trivial amounts of data you have DMA (which Duff's hardware did not). In a modern language you also wouldn't treat "MMIO" as just pointer indirection, to make this stay working in C they have kept adding hacks to the type system rather than say OK, apparently this is an intrinsic, we should bake it into the freestanding mode of the stdlib.
Edited to add:
For my money the successor to Tom Duff's "Device" is WUFFS' "iterate loops" mechanism where you may specify how to partially unroll N steps of the loop, promising that this has equivalent results to running the main loop body N times but potentially faster. This makes it really easy for vectorisation to see what you're trying to do, while still handling those annoying corner cases where M % N != 0 correctly because that's the job of the tool, not the human.
That's just a special case of being able to intermingle switch with arbitrary syntax, which is what TFA does, before it jumps to computed gotos.
While you can't use SIMD you can still benefit from instruction-level parallelism.
It's potentially better in some scenarios where you want to minimize instruction cache usage and there are few iterations of the loop.
#include <stdio.h>
#include <stddef.h>
int main()
{
{
int *p = NULL;
if (p)
{
what:
printf("a = %d\n", *p);
return 0;
}
int a = 123;
p = &a;
goto what;
}
}I've seen this in the wild, particularly with macros.
#define assert(c) if (!c) ...
if (foo) assert(...);
else bar(); // oops!Let's stop getting silly with C, too many CVEs!
---
Serious comment:
It's a rather cool article actually. Not something I'd do daily but it's kind of sort of useful to know these techniques.
Metaprogramming custom control structures in C by Simon Tatham
I will never love anything as much as I love C, but C development jobs lie in really weird fields I’m not interested in, and I’m fairly certain I am not talented enough. I have seen C wizardry up close that I know I simply cannot do. However, one of the more useful exercises I ever did was implement basic things like a file system, command line utilities like ls/mkdir etc. Sometimes they are surprisingly complex, sometimes no.
After you program in C for a while certain conventions meant to be extra careful kind of bubble up in languages in a way that seems weird to other people. for example I knew a guy that’d auto reject C PR’s if they didn’t use the syntax if (1==x) rather than if (x==1). The former will not compile if you accidentally use variable assignment instead of equality operator (which everyone has done at some point).
This tendency bites me a lot in some programming cultures, people (ime) tend to find this style of programming as overly defensive.
No need for Yoda notation. clang will warn of this by default and gcc will do so if you compile with -Wall, which should also be your default.
I've seen that one and personally dislike that mindset: Making the code less readable to compensate for a disinterest in using actual static analysis tooling.
if (987654321987654321==x)
if (find_optimal(frobnicator(x)*8)==1)
while these are subjectively more readable:if (x==987654321987654321)
if (1==find_optimal(frobnicator(x)*8))C evolved a lot and many foot guns are not a problem anymore. For example for
if (x = 1)
you nowaday get a warning. https://godbolt.org/z/79acPPro6
Implicit int, calling functions without prototypes, etc. are hard errors. And so on.
https://docs.gtk.org/glib/data-structures.html
It could be fun to do a lab summary after the lists and hashes introduction.
Have a wonderful day, =)
I have written C at least a few times per year for over 30 years. About ten years of that was OS development on Solaris and its derivatives.
Articles like this show crazy things you can do in C. I’ve never found the need to do things like this and have never seen them in the wild.
The places that wizardry is required are places like integer and buffer overflow, locking, overall structure of large codebases, build infrastructure, algorithms, etc. Many of these are concerns in most languages.
> auto reject C PR’s if they didn’t use the syntax if (1==x) rather than if (x==1)
When I was a student in the 90s advice like this would have been helpful. Compiler warnings and static analyzers are so much better now that tricks like this are not needed.
That's not so much of a footgun anymore - the common C compilers will warn you about that so there's not much point in defending against it.
Same with literal format string parameters to printf functions: the compiler is very good at warning about mismatched types.