An Abstrusing printf() expression
While reading some random code, I happen to encounter a printf开发者_JS百科()
expression which is a bit strange to me, the statement is like this
void PrintDiceFace(int n){
printf("%d 0 %d\n%d %d %3$d\n%2$d 0 %1$d\n\n",n>50,51%n%2,n>53,n%2);
}
This is actually obfuscated version of snippet which actually prints the face of the electronic dice. For example.
Please explain this printf()
statement in detail.
http://en.wikipedia.org/wiki/Dice
POSIX/SUS printf()
allows a number followed by $
after the %
in order to indicate that a specific argument from the varargs should be picked.
printf("%2$s, %1$s!\n", "world", "Hello");
The format results in:
<num1> 0 <num2>
<num3> <num4> <num3>
<num2> 0 <num1>
with all numbers being either 0 or 1 depending on the result of the operations later on.
The only unusual thing in this format string is the use of %<N>$
- which says that for this parameter, arg<N>
of the function (instead of 'the next' in order) should be used. Example, if you specify:
printf("%d %1$x\n", 10);
it prints you '10 a'.
It prints three rows of integers, like in the more simple:
printf("%d %d %d\n%d %d %d\n%d %d %d\n", 0, 0, 0, 0, 1, 0, 0, 0, 0);
Which would print
0 0 0
0 1 0
0 0 0
%2$d means "use the 2nd argument instead of the argument corresponding to my place"
Now, each column in each row has a specific condition on which "0" or "1" is printed at that spot. Because of the symmetricity of a dice, all you are really interested in is the following positions (marked by x):
x 0 x
x x -
- 0 -
The other positions can be derived off of them (-) or are always 0 (0).
It's an example of 'clever coding', which is fun to do but frustrating to read.
Let's start with the format ("%d 0 %d\n%d %d %3$d\n%2$d 0 %1$d\n\n"
):
- %d means a numeric (decimal number as integer) output
- %3d means you allow at least 3 chars as placeholder for the output (so for instance "2" will be padded " 2". You can use %03d to do the same but padding with leading zeros to obtain "00")
- %3$d actually means that instead of using the element in the list in the appropriate order, you directly specify the element (so here it would be
n > 53
) - etc...
For the funny looking params:
- n > 50 will be the 1st and 7th %d
- 51 % n % 2 will be the 2nd and 6th %d
- n > 53 will be the 3rd and 5th %d
- n %2 will be the 4th %d
See man 3 printf
This code is nonconformant and broken. If %N$
-type argument index specifiers are used, they must be used for all arguments, not selectively for some and not for others.
A fixed version:
printf("%1$d 0 %2$d\n%3$d %4$d %3$d\n%2$d 0 %1$d\n\n",n>50,51%n%2,n>53,n%2);
The relevant citation is here: http://www.opengroup.org/onlinepubs/9699919799/functions/fprintf.html
The format can contain either numbered argument conversion specifications (that is, "%n$" and "*m$"), or unnumbered argument conversion specifications (that is, % and * ), but not both. The only exception to this is that %% can be mixed with the "%n$" form. The results of mixing numbered and unnumbered argument specifications in a format string are undefined. When numbered argument specifications are used, specifying the Nth argument requires that all the leading arguments, from the first to the (N-1)th, are specified in the format string.
The fact that this code "worked" is a side-effect of a particular implementation of the printf
function (probably the GNU one). There's no reason to expect it to work on other systems or even on future versions of the same system unless it's documented to work on that system.
Even with my fix, this code is still POSIX-specific; it's not portable to C implementations on non-POSIX systems. Instead of ugly hacks the author should have just duplicated the arguments as needed:
printf("%d 0 %d\n%d %d %d\n%d 0 %d\n\n",n>50,51%n%2,n>53,n%2,n>53,51%n%2,n>50);
精彩评论