Chapter 3. C syntax

C’s grammar hides many a dark corner. They can usually be ignored, but not in this trivia quiz!

How many keywords does C have?

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.

What keyword is never used?

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.

What keyword was withdrawn?

Originally, entry was reserved. There were plans to support multiple entry points to a function that were ultimately abandoned.

How many keywords can you fit in a variable declaration?

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;

How do you write programs without any keywords?

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.

How do you write programs without semicolons?

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) {}
}

How do you write programs without curly braces?

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! }

Which commutative binary operator is not infix?

Addition and dereference, more commonly known as array subscripting: a[5] is the same as 5[a].

Which variables cannot take a prefix & operator?

Register ints, and bit fields. Incidentally, both these features should be used rarely, if at all.

Bonus questions

Miloslav Trmač poses difficult exercises:

  1. We have:

    ((int)0.1 == 0) == ((int)1.1 == 1)
    

    and:

    ((double)0.1 == 0) == ((double)1.1 == 1)
    

    For any type T that is not a pointer or an array, one might expect:

    ((T)0.1 == 0) == ((T)1.1 == 1)
    

    Why is this false in C99?

  2. 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!