18

The C23 standard draft n3220 says the following about the conversion specifier n for fscanf in statement 7.23.6.2p12 (emphasis mine)

n No input is consumed. The corresponding argument shall be a pointer of a signed integer type. The number of characters read from the input stream so far by this call to the fscanf function is stored into the integer object pointed to by the argument. Execution of a %n directive does not increment the assignment count returned at the completion of execution of the fscanf function. No argument is converted, but one is consumed. If the conversion specification includes an assignment-suppressing character or a field width, the behavior is undefined.

I don't see any mention that the corresponding argument for the conversion specification %n (no length modifier) shall be a pointer to int.

Hence the following program should be well defined, since long long int is a signed integer type.

#include <stdio.h>

int main(void){
    long long n = -1;
    char c;
    int num_asn = sscanf("...h", "...%n%c", &n, &c);
    printf("num_asn = %i\n", num_asn);
    printf("n       = %lli\n", n);   
    printf("c       = %c\n", c);   
}

I compiled this program with gcc main.c -std=c23 and it linked with the cygwin C library.

I expected the following output:

num_asn = 1
n       = 3
c       = h

Instead I received the following output

num_asn = 1
n       = -4294967293
c       = h

When I change n (in my program) to have type int , I receive the expected output.

Example 4, in statement 7.23.6.2p21, in the C23 standard draft n3220, uses a pointer to int for conversion specification %n but doesnt say why:

#include <stdio.h>
/* ... */
int d1, d2, n1, n2, i;
i = sscanf("123", "%d%n%n%d", &d1, &n1, &n2, &d2);

Where in the C23 draft (or in any revision of the C standard) does it mention the corresponding argument for the conversion specification %n shall be a pointer to int for fscanf

1
  • 1
    There'd be no way for sscanf to infer the size of the pointed-to integer type, so there does have to be a single valid size. The asm calling convention for variadic functions applies the "usual argument promotions" (which doesn't do anything to pointers), then passes them unchanged. No extra parameters that specify anything about types. Your experiment successfully demonstrated that the (default) size is smaller than long long (64-bit) on your system. I would've used %llx to print (uint64_t)n, to get an output like 0xFFFFFFFF00000003 clearly showing a little-endian narrow write. Commented Apr 21 at 6:21

2 Answers 2

19

Where in the C23 draft (or in any C standard) does it mention the corresponding argument for the conversion specification %n shall be a pointer to int

The exact type is specified by the length modifier as specified in section 7.23.6.2p11. It states that the hh, h, l, ll, j, t, and z modifiers can be applied to the n format specifier to specify that the argument points to a char, short, long, long long, intmax_t, ptrdiff_t, or size_t respectively.

Starting with C23, the documentation for the d, i, o, u, and x format specifiers were updated (and b was added) to explicitly state that a missing length modifier means the argument is a int * or unsigned int *. Prior to that, they were the same as n in that this wasn't explicitly stated.

Given that 7.23.6.2p11 states that length modifiers apply to n going back to at least C11 and that the documentation for d, i, o, u, and x were updated, this is likely an oversight.

Had your code used %lln as the full format specifier you would have gotten the expected results.

Sign up to request clarification or add additional context in comments.

5 Comments

To be clear, with or without a modifier, %n remains a pointer to a signed integer type. Too bad size_t is not directly possible, only the mysterious corresponding signed integer type to size_t.
@chux : "Too bad" <- remember the printf()-family functions return an int, anyway. So how would the size_t help you?
Since much sizing in C uses size_t, something like scanf("...%zn", ..., &sz) would allow direct saving into a size_t sz. Return value of printf() is not really a concern for this scanf() post.
dbush, are the size modifiers for %n a new thing, or is this C99 already?
It is spec'd in C99 and alluded to in C89.
9

In the standard, many of the other conversion specifiers state one of the following:

Unless a length modifier is specified, the corresponding argument shall be a pointer to int.

Unless a length modifier is specified, the corresponding argument shall be a pointer to unsigned int.

The fact that such as sentence is missing from the description of the n specifier is, as far as I can tell, a defect in the standard.

The statement in the standard about the n specifier

The corresponding argument shall be a pointer of a signed integer type.

is insufficient, because as correctly pointed out in the question, a long long int is also a "signed integer type".

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.