4

Is there a standard C/C++ function that, given a printf format string, returns the number of arguments it expects? E.g.:

num_printf_args("%d %s") == 2;
num_printf_args("%.1f%%") == 1;
num_printf_args("%*d") == 2;

Just counting the number of % in the format string would be a first approximation, which works in the first example, but obviously not in the second and third ones.

I know gcc can do this, since at compile time it complains when the number of arguments (and also their type) actually passed to printf does not match the format string.

Wim
  • 11,091
  • 41
  • 58
  • No such function in ISO/IEC 9899:1999 (the language standard). Not completely sure about :2011, but I don't think so. It's an extension implemented by GCC. – DevSolar Jun 21 '16 at 08:19
  • 1
    Why would it be useful without information about argument types? – n. m. could be an AI Jun 21 '16 at 08:22
  • It's not part of the language, but a specific gcc extension. See `format` in [gcc's Function Attributes](https://gcc.gnu.org/onlinedocs/gcc-3.2/gcc/Function-Attributes.html). – Jongware Jun 21 '16 at 08:25
  • 1
    The way I understand it, you have *fixed* format strings from which `printf` will return *the number of characters output* and you have *variadic* functions that will accept a variable number of arguments, but no `printf` that returns the number of *conversions* expected. That's not to say you couldn't create a *wrapper* that parses the content of a format string and returns the number of conversions expected and also provide type-checking for those arguments if provided your list. – David C. Rankin Jun 21 '16 at 08:37
  • @n.m. *Why would it be useful without information about argument types?* Counting the number of expected arguments is effectively a prerequisite to determine what the arguments should be. You have to know *where* they are before you can tell *what* they are, and knowing *where* they are implies knowledge of the number. – Andrew Henle Jun 21 '16 at 09:20
  • As to why I need this: I use printf-style formatting in my application's logging function, where I keep a circular log of the last one million or so entries which I write out when the application crashes. Because printf is kinda slow, rather than calling sprintf and dumping the formatted text in the buffer; instead my circular log contains just the format strings and arguments and I do the formatting only when I need the actual output, but not for the (potentially billions) of entries that are overwritten and thrown away. I want the number of arguments to make sure I copy enough of them. – Wim Jun 21 '16 at 09:45
  • How would you copy arguments without knowing their types? – n. m. could be an AI Jun 21 '16 at 11:47
  • @AndrewHenle You cannot know *where* the second argument is without knowing *what* the first one is, but you don't need to know how many arguments are there in total. To access *all* arguments you need to know *all* types, which of course implies knowing the number. – n. m. could be an AI Jun 21 '16 at 11:51
  • @n.m. *You cannot know where the second argument is without knowing what the first one is.* True, but I'm referring to the format string, which specifies the *expected* arguments. Not the arguments themselves. The question started as solely about the format string. – Andrew Henle Jun 21 '16 at 12:01
  • @Wim *my circular log contains just the format strings and arguments and I do the formatting only when I need the actual output* If the argument you save is something like a `char *` string, by the time you evaluate it to produce the output, it may be invalid. And you can't know if the argument might be invalid until you parse the format string and do everything but format the output. I suspect there may also be other ways to invalidate arguments that refer to local variables. – Andrew Henle Jun 21 '16 at 12:05
  • Sure there are some limitations: I just assume all arguments are 64-bit and const, so in practice it works for `int` and `const char*` but not `char *` as you say. Still, I can do near-zero overhead logging of a massive amount of events so it's helped me out a great deal so far. – Wim Jun 21 '16 at 12:14

2 Answers2

6

No standard function to do this.

However, it would be easy to implement.

Count the number of % that are not followed by another %. The add 1 for every one of those counted % immediately followed by a *.

When counting runs of % characters, don't allow overlaps. So "%%%d" should result in a value of 1 (not 0, and certainly not 2 or 3).

EDIT - text below added following comment from user694733

This will suffice for format strings along the lines of the three examples you gave. However, as noted by user694733 in comments, this is not the whole story.

In general terms, a format specifier follows the prototype %[flags][width][.precision][length]specifier. The approach above is a starting point, and will work for format strings have have no flags, a possible * in width specifier, no * in the precision specifier, and overlooks length and specifier fields. All of those need to be considered, for a general format string, and the string parsed accordingly. The effort to do that depends on how robust you need your count to be - for example, more effort if you need to detect an invalid format string.

Peter
  • 35,646
  • 4
  • 32
  • 74
  • 2
    It's not that simple. You need to also consider the case of double `*` and flags; for example `%+*.*f`. You need to essentially implement the complete format string parser. – user694733 Jun 21 '16 at 08:48
  • @user694733. For the examples the OP gave, my response will suffice, but I agree - other examples where it won't/ I'll add a comment about the limits, and other things that need to be done shortly. – Peter Jun 21 '16 at 11:05
  • For non-compiler support of determining the number of arguments, implementing this by self can't be safe. It could create security holes because almost all compilers do stack memory usage to implement `printf()` like functions, and it could allow stack overflow attacks. – 0xAA55 Nov 07 '21 at 08:44
3

I'm not sure why you need this, however your problem might be solved in this way (accepted answer from this question: Variadic macro trick).

As suggested in comments, you can wrap the call of printf into a macro that will first count the number of arguments required and then proceed to whatever you want to do.

Code from blog post:

#define VA_NUM_ARGS(...) VA_NUM_ARGS_IMPL(__VA_ARGS__, 5,4,3,2,1)
#define VA_NUM_ARGS_IMPL(_1,_2,_3,_4,_5,N,...) N

// to verify, run the preprocessor alone (g++ -E):
VA_NUM_ARGS(x,y,z)
Community
  • 1
  • 1
n0p
  • 3,399
  • 2
  • 29
  • 50