C’s grammar hides many a dark corner. They can usually be ignored, but not in this trivia quiz!
For a while it was 32, a deliciously auspicious number for a computer programming language.
auto break case char const continue default do double else enum extern float for goto if int long register return short signed sizeof static struct switch typedef union unsigned void volatile while
Unfortunately, C99 introduced more keywords, but not
enough to total 64. Worse still, some of them have awkward
names. The new keywords are: inline
restrict _Bool _Complex _Imaginary.
The keyword auto. Within
functions, auto variables are local to the stack frame, in
contrast to static variables. But since this is the default,
auto is useless.
One could argue there are other redundant keywords: for
example, one can always replace an if statement with the
ternary operator, or for loops with while loops, but we can
lose clarity doing so. The auto
keyword may be dropped with no loss.
In GNU C, auto is used to
forward-declare a nested function.
Originally, entry was
reserved. There were plans to support multiple entry points
to a function that were ultimately abandoned.
If the variable is a struct or union we can have an arbitrary number of members. Pointers can also be extended to any desired length:
int *const ... *const foo;
Otherwise, my best is:
static const volatile signed long long int bar;
The default type is int:
#include <stdio.h>
i, j;
main() {
i = 8 > 7 ? 6 : 5 - 4;
j = 3 + 2 | 1;
printf("%d %d\n", i, j);
}
Observe "a;" is valid globally, but not within a function definition.
One solution is to use an empty while loop:
#include <stdio.h>
#include <stdlib.h>
int main() { while(puts("Hello, World!"), exit(0), 0) {} }
We can do more by abusing the standard library:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
int main() {
while(errno = 0, 0) {}
while(printf("%d\n", errno * errno * errno), ++errno < 10) {}
while(exit(0), 0) {}
}
Use digraphs:
int main() <% return 0; %>
If we also ban % then switch
to trigraphs:
int main() ??< return 0; ??>
GCC requires the --trigraphs option to compile this example.
Digraphs are handled by the tokenizer, while trigraphs are substituted by a preprocessor, so the following program:
#include <stdio.h>
int main() ??< return puts("<% Brace yourself! ??>"); %>
prints:
<% Brace yourself! }
Addition and dereference, more commonly known as array
subscripting: a[5] is the same
as 5[a].
Register ints, and bit fields. Incidentally, both these features should be used rarely, if at all.
Miloslav Trmač poses difficult exercises:
-
We have:
((int)0.1 == 0) == ((int)1.1 == 1)
and:
((double)0.1 == 0) == ((double)1.1 == 1)
For any type
Tthat is not a pointer or an array, one might expect:((T)0.1 == 0) == ((T)1.1 == 1)
Why is this false in C99?
-
Explain why the result of:
1 ? (int *)0 : (void *)0
differs to the result of:
1 ? (int *)0 : (void *)1
To avoid spoiling his fun I’ll refrain from answering these here. Read his article!