How to know what’s the value of the constant INT_MAX
in
the C header <limits.h>
? There are many ways.
However, here’s how I like to do it: by running a script in C. Here it
is:
$ tcc -run <(printf '%s\n' '#include <'{stdio,limits}'.h>' 'int main() { printf("%u\n", INT_MAX); }')
2147483647
Now that you’ve seen this, let’s take this command line apart, going from the left to the right.
tcc
This part is easy, TCC (the “Tiny C Compiler”) is a C compiler.
-run
This flag allows TCC to run C programs without creating intermediate files, which is useful for a throwaway script like ours.
The script can accept other arguments as well. Here’s an example
where arguments 23
and 34
are passed to the C
program. with the output being the string 57
:
$ tcc -run <(printf '%s\n' '#include <'{stdio,stdlib}'.h>' 'int main(int argc, char **argv) { printf("%d\n", atoi(argv[1]) + atoi(argv[2])); }') 23 34
57
<(...)
This feature of Bash is called process substitution. In this case, it allows output of the command inside the parenthesis to be available using a filename.
Here’s an example:
$ echo <(echo hello)
/dev/fd/63
$ cat <(echo hello)
hello
As we can see from echo <(echo hello)
, from the
perspective of the receiving command process substitution expression
gets replaced by the string /dev/fd/N
where N
is the number of the descriptor Bash opened to capture the output of
echo hello
.
What’s special about /dev/fd/N
? This path provides us
access to the file descriptor N
. In the second example we
can see that the file /dev/fd/N
can be read by the
receiving process to get the standard output of echo hello
.
cat <(echo hello)
prints hello
.
printf '%s\n'
What’s printf
here? Turns
out that printf
is not only a name of a function in C, but
also of a utility. It allows us to do formatted output
(printf
allows to print formatted). The first
argument is the format string (%s\n
instructs to print each
argument on a single line), the rest are the arguments to be formatted.
Here’s an example:
$ printf 'the argument: %s\n' hello '' world
the argument: hello
the argument:
the argument: world
Each of the three arguments ("hello"
, ""
,
and "world"
) is printed on a separate line and prefixed by
the string "the argument"
. Even the empty string.
Why do we need this here? The answer is the C preprocessor.
As we’ll see later, in our C code there are two different C headers
which we need: stdio.h
for the printf
C
function and limits.h
for the definition of the macro
INT_MAX
. This requires two include directives, and there
can’t be multiple directives on one line. Which is why we use
printf
here to specify multiple lines in a single command
line, one per argument.
'#include <'{stdio,limits}'.h>'
What’s interesting here is the {stdio,limits}
part. What
is it? A yet another feature of Bash called brace
expansion. It allows us to add prefixes and suffixes to
multiple strings at once. Here’s an example:
$ printf '%s\n' dir/{a,b,c}.c
dir/a.c
dir/b.c
dir/c.c
As we can see, strings a,
b,
and
c
are prefixed by dir/
and suffixed by
.c
.
What does '#include <'{stdio,limits}'.h>'
do then?
That’s right, it prefixes strings stdio
and
limits
with the string #include <
and
suffixes them with the string .h>
. To verify this, let’s
the results of brace expansion on a separate line with
printf
:
$ printf '%s\n' '#include <'{stdio,limits.}'.h>'
#include <stdio.h>
#include <limits.h>
'int main() { printf("%u\n", INT_MAX); }'
This is the meat of the program we want to run, put into single
quotes to be presented as a single string to printf
.
Together with the two include directives this forms a complete C program.
This is it.
To summarize, let’s see the final program by replacing
tcc -run
with cat
:
$ cat <(printf '%s\n' '#include <'{stdio,limits}'.h>' 'int main(void) { printf("%d\n", INT_MAX); }')
#include <stdio.h>
#include <limits.h>
int main(void) { printf("%d\n", INT_MAX); }
This will print the value INT_MAX
. This is exactly what
we wanted.
For Plan 9
fans out there, here’s a way to do the same in the rc
shell.
rc
has analogues for process substitution and brace
expansion, so the command line will be quite similar:
; tcc -run <{printf '%s\n' '#include <'^(stdio limits)^'.h>' 'int main(void) { printf("%d\n", INT_MAX); }'}
2147483647
For programs that don’t require reading from the standard input (like the one we have), the command line can be simpler, requiring no process substitution:
$ printf '%s\n' '#include <'{stdio,limits}'.h>' 'int main() { printf("%u\n", INT_MAX); }' | tcc -run -
2147483647
This makes tcc
read the program from the standard input.
tcc -run /dev/stdin
would also work.