Home > Software design >  Correct use of param-no in GNU C's printf format specification
Correct use of param-no in GNU C's printf format specification

Time:01-12

I am working on a helper function using printf format strings, so I started to examine printf format specifications in more detail, and found that GNU's manual allows the use of a param-no: https://www.gnu.org/software/libc/manual/2.36/html_mono/libc.html#Output-Conversion-Syntax.

I have never used this feature before (as I did not find it useful), however, I would like my function to process all possible format specifications, so I started experimenting with it:

#include <stdio.h>

int main()
{
    double u = 1.23456789;
    double d = 9.87654321;
    
    // Example A                                1.  2. 3.
    printf( "A) Testing param-no: >%3$*.*f<\n", 12, 3, u );
    // Compiler warning: Missing $ operand in format
    // Works.
    
//  // Example B                                1.  2. 3.
//  printf( "B) Testing param-no: >%1$*.*f<\n", 12, 3, u );
//  // Compiler warning: Missing $ operand in format
//  // Prints spaces in an infinite loop.
    
    // Example C                                1.  2. 3.
    printf( "C) Testing param-no: >%3$*.*f<\n", u, 12, 3 );
    // Compiler warning: Missing $ operand in format
    // Works.
    
//  // Example D                                1.  2. 3.
//  printf( "D) Testing param-no: >%1$*.*f<\n", u, 12, 3 );
//  // Compiler warning: Missing $ operand in format
//  // Prints spaces in an infinite loop.
    
    // Example E                                           1.  2. 3. 4. 5. 6.
    printf( "E) Testing param-no: >%3$*.*f<, >%6$*.*f<\n", 12, 3, u, 5, 4, d );
    // Compiler warning: Missing $ operand in format
    // Wrong output: "Testing param-no: >       0.000<, >1.2346<"
    
    // Example F                                           1. 2.  3. 4. 5. 6.
    printf( "F) Testing param-no: >%3$*.*f<, >%6$*.*f<\n", u, 12, 3, d, 5, 4 );
    // Compiler warning: Missing $ operand in format
    // Wrong output: "Testing param-no: >       0.000<, >1.2346<"
    
    // Example G                                    1.  2. 3.
    printf( "G) Testing param-no: >%3$*1$.*2$f<\n", 12, 3, u );
    // Works.
    
    // Example H                                                   1.  2. 3. 4. 5. 6.
    printf( "H) Testing param-no: >%3$*1$.*2$f<, >%6$*4$.*5$f<\n", 12, 3, u, 5, 4, d );
    // Works.
    
    // Example I                                                   1. 2.  3. 4. 5. 6.
    printf( "I) Testing param-no: >%1$*2$.*3$f<, >%4$*5$.*6$f<\n", u, 12, 3, d, 5, 4 );
    // Works.
    
    // Example J                                                   1. 2.  3. 4. 5. 6.
    printf( "J) Testing param-no: >%4$*5$.*6$f<, >%1$*2$.*3$f<\n", u, 12, 3, d, 5, 4 );
    // Works.
    
    // Example K                                                   1. 2.  3. 4. 5. 6.
    printf( "K) Testing param-no: >%1$*3$.*6$f<, >%5$*2$.*4$f<\n", d, 12, 5, 3, u, 4 );
    // Works.
    
    return 0;
}

Based on these results, I find the following points of the related documentation misleading:

  • The second general form of the conversion specification, % [ param-no $ ] flags width . * [ param-no $ ] type conversion suggests that you can give an asterisk (*) as the precision and an optional param-no followed by a dollar ($), like in Example A. However, this generates a compiler warning, and either works as in Example A, or makes the program print spaces in an infinite (or very long, I did not wait until the end of it) loop as in Example D.

  • Related to the previous point, as I was reading the description of the width and precision parts: "You can also specify a field width of '*'. This means that the next argument in the argument list (before the actual value to be printed) is used as the field width." and "You can also specify a precision of '*'. This means that the next argument in the argument list (before the actual value to be printed) is used as the precision.", I thought that the width and precision values will be fetched from the appropriate position, which is calculated by taking the position of the actual paramater (e.g. u in any of the above examples), and reducing it by 1 or 2. However, this approach leads to a compiler warning and wrong output as in Example E.

  • Presenting the two general forms of the conversion specification suggests that there is no third form, and you may conclude that setting the width with one of the variadic arguments is not allowed because only the precision part is replaced by an asterisk (*) in the second general form. However, setting both the width and the precision at the same time works as in e.g. Example H.

I suspect the correct description would be the following:

All conversion specifications in a printf template string should have one of the following forms, and all of them should have the same form:

% flags [ width-digits | * ] [ . precision-digits | . * ] type conversion

or

% param-no-a $ flags [ width-digits | * param-no-b $ ] [ . precision-digits | . * param-no-c $ ] type conversion

The mandatory param-no-a indicates the position of the actual parameter, while the optional param-no-b and param-no-c indicate the position of the width and precision values.

Is the documentation I linked the correct one?

Do I understand the use of param-no correctly?

CodePudding user response:

Indeed, the glibc documentation seems to be lacking. Use man 3p fprintf https://pubs.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 format:

printf("%3$*.*f", 12, 3, 1.23);

is just invalid.

  • Related