2
votes

I have created a printf-like function which takes the following arguments:

  • One mandatory argument (custom error code)
  • Optional format string
  • Variable count of format arguments

The function prototype looks like:

int my_printf(int err_code, ...);

err_code also includes information whether the format string (and possible format tags) are given.

If they are given, I can extract the format string using the va_arg function and pass it to vfprintf together with the rest of the arguments.

The calls look like:

my_printf(-ERR_SIMPLE);
my_printf(-ERR_COMPLICATED, "Error: problem with %d", 123);

Unfortunately, I am unable to use the GCC attribute for format type-checking since it needs a string-index:

format (archetype, string-index, first-to-check)

Is it still possible to do the type-checking anyhow? Solutions using helper macros, helper functions, modifying the optional format string part etc are acceptable.

Update: I am unsure whether passing a modified va_list to vfprintf-like functions is OK. See comments. If possible, it would be better to avoid this and use macros or something else for the optional format string part.

1
How does your function even work at all? How do you strip the format string argument out of the valist? - Kerrek SB
@KerrekSB I get the format string with const char *fmt = va_arg(args, const char *);. Then I can simply call vfprintf(stdout, fmt, args); - LRipa
Isn't that UB? I'm pretty sure you can't pass a valist to a function if you've already started monkeying with it... - Kerrek SB
@KerrekSB I am not sure. It seems to work with GCC 4.4.5 on x86. I guess I should avoid that. The question is still valid (and updated), however. - LRipa
Well, if we're already in the territory or terrible hacks, I wouldn't worry too much about getting the attributes right :-) - Kerrek SB

1 Answers

2
votes

(Sorry, I hadn't seen that you had tried with __attribute__. My bad).

You might try experimenting with "argument counting" as described here:

http://locklessinc.com/articles/overloading/

I don't know if this can result in GCC selectively applying argument checking though; but I think it should.

UPDATE it appears to be working, with an added #define hack:

#include <stdio.h>

int printf_1(int err)
{
        printf("Got error %d\n", err);
        return 0;
}

int printf_2(int error, char *string)
{
        printf("Error %d and message %s\n", error, string);
        return 0;
}

int printf_3(int error, char *fmt, ...) __attribute__ ((format (printf, 2, 3)));

int printf_3(int error, char *fmt, ...)
{
        printf("Received full string err=%d, fmt=%s\n", error, fmt);
        return 0;
}

#define printf_4        printf_3
#define printf_5        printf_3
#define printf_6        printf_3
#define printf_7        printf_3
#define printf_8        printf_3
#define printf_9        printf_3
#define printf_10       printf_3


#define COUNT_PARMS2(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _, ...) _
#define COUNT_PARMS(...)\
        COUNT_PARMS2(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)

#define CAT(A, B) CAT2(A, B)
#define CAT2(A, B) A ## B

#define myprintf(...)\
        CAT(printf_, COUNT_PARMS(__VA_ARGS__))(__VA_ARGS__)

int main()
{
        myprintf(19);
        myprintf(19, "Hello");
        myprintf(19, "Hello '%s'", "world");
        // Warning!
        myprintf(19, "Hello '%s'", "world", 42);
        myprintf(19, 42);
        return 0;
}

I correctly receive (gcc 4.6.2):

$ gcc -W -Wall -o test test.c

test.c: In function ‘main’:
test.c:48:2: warning: too many arguments for format [-Wformat-extra-args]
test.c:49:2: warning: passing argument 2 of ‘printf_2’ makes pointer from integer without a cast [enabled by default]
test.c:9:5: note: expected ‘char *’ but argument is of type ‘int’