Edit: The second program demonstrates how to pass a struct by value.
Here is an example how to define a printf
custom conversion for systems that use the gnu C standard library. The program first prints the glibc version in case the printf API changes (as it has in the past):
#include <stdio.h>
#include <printf.h>
#include <gnu/libc-version.h> // for glibc version information
struct data
{
int d;
char str;
};
int
print_struct_ptr (FILE *stream,
const struct printf_info *info,
const void *const *args)
{
const struct data *aStruct;
int len;
aStruct = *((const struct data **) (args[0]));
/* Simply print the members to the stream */
len = fprintf (stream, "[struct data at %p: d=%d, str=%c]\n",
aStruct, aStruct->d, aStruct->str);
return len;
}
int
print_struct_arginfo_sz (const struct printf_info *info, size_t n,
int *argtypes, int *size)
{
/* We always take exactly one argument and this is a pointer to the
structure.. */
if (n > 0)
argtypes[0] = PA_POINTER;
return 1;
}
int main(void)
{
struct data myStruct = {3, 'A'};
printf("glibc version: %s\n", gnu_get_libc_version());
register_printf_specifier('M', print_struct_ptr, print_struct_arginfo_sz);
return printf("%M\n", &myStruct) > 0 ? 0 : -1;
}
This prints your data structure. (By the way, it is odd to name a character member str
— is that supposed to be a pointer? I left it as-is for now.)
The first thing to observe is that I pass a pointer to your struct. This makes it easier to register a customized conversion because printf
knows how to extract a pointer from the variable argument list. That the argument is a pointer is indicated by inserting PA_POINTER
in print_struct_arginfo_sz()
. The other info fields are not needed here.
Because we pass a built-in type we need to define only two fairly simple callback functions. The first one, print_struct()
, indeed prints the structure to which the passed pointer points simply by accessing its data members through the pointer. The second function, as mentioned, informs printf()
what data type to extract (a pointer).
Finally, the register_printf_specifier()
call in main()
connects the dots: It registers these two callbacks (the function pointers are passed as arguments), connecting them with the format specifier 'M'
.
Here is a sample session. The compiler warns about the "unknown" conversion specifier and, because the conversion is not recognized as such, about an argument that has no conversion specifier. This is one of the rare cases where such warnings can be ignored.
$ gcc -Wall -o printf-customization printf-customization.c && ./printf-customization
printf-customization.c: In function ‘main’:
printf-customization.c:43:19: warning: unknown conversion type character ‘M’ in format [-Wformat=]
return printf("%M\n", &myStruct) > 0 ? 0 : -1;
^
printf-customization.c:43:17: warning: too many arguments for format [-Wformat-extra-args]
return printf("%M\n", &myStruct) > 0 ? 0 : -1;
^~~~~~
glibc version: 2.24
[struct data at 0xbf9b6448: d=3, str=A]
For completeness, here is a version where the struct is passed by value. I struggled with the correct interpretation of the args
argument passed to the print function — it is a pointer to the first element in an array of pointers to the actual arguments. Apparently, args
is a pointer-to-pointer-to-pointer even for printf arguments passed by value, that is, args[0]
still is a pointer-to-pointer, and not a simple pointer to struct. I'm not sure why that is so. Inspecting the printf
sources might yield insights but I genuinely lack time and motivation. Here is the complete, again lengthy program.
#include <stdio.h>
#include <printf.h>
#include <string.h>
#include <gnu/libc-version.h> // for glibc version information
// We can be gcc specific here and use variadic macros.
#if DEBUG
#define dbg(fmt, ...) fprintf(stderr, fmt, __VA_ARGS__)
#else
# define dbg(fmt, ...)
#endif
#define err(...) fprintf(stderr, __VA_ARGS__)
/// Our custom type we want to print
struct T { int i; char c; };
/// Callback to extract an struct T from va_list and copy into dest
void getTFromVaList(void *dest, va_list *valist)
{
struct T t = va_arg(*valist, struct T);
// debug: This looks good.
dbg("extracted struct T (%d, %c) to mem %p\n", t.i, t.c, dest);
memcpy(dest, &t, sizeof(t));
}
/// Global variable to communicate conversion type,
/// will be set in main before printf call
static int TArgType;
/// Callback that prints arguments pointed to by pointers
/// in the args array
int printT (FILE *stream,
const struct printf_info *info,
const void *const *args)
{
struct T *tPtr = **(struct T ***)args;
int len;
len = fprintf (stream, "[struct T at %p: t.i=%d, t.c=%c]",
tPtr, tPtr->i, tPtr->c);
if(len <= 0) { err("fprintf to stream failed\n"); }
return len;
}
/// set the argument type connected to specifier T
int fillTArgType( const struct printf_info *info,
size_t n,
int *argtypes,
int *size )
{
// debug: Looks good
dbg("printf_info spec: %c, struct T arg type=%d\n", (char)info->spec, TArgType);
if (n != 1) { err("Weird: n != 1\n"); }
argtypes[0] = TArgType;
*size = sizeof(struct T);
// return the number of arguments required for T, which is 1.
return 1;
}
int main(void)
{
struct T t = { 23, 'A'};
printf("glibc version: %s\n", gnu_get_libc_version());
// Set global variable used in fillTArgType
TArgType = register_printf_type(getTFromVaList);
dbg("T arg type: %d\n", TArgType);
// register specifier
register_printf_specifier('T', printT, fillTArgType);
// try it out
int diag = printf("%T\n", t);
return diag > 0 ? 0 : -1; // 0 bytes printed is failure, too.
}
Sample session:
$ gcc -DDEBUG -Wall -o printf-custom-struct printf-custom-struct.c && ./printf-custom-struct && echo $?
printf-custom-struct.c: In function ‘main’:
printf-custom-struct.c:75:23: warning: unknown conversion type character ‘T’ in format [-Wformat=]
int diag = printf("%T\n", t);
^
printf-custom-struct.c:75:21: warning: too many arguments for format [-Wformat-extra-args]
int diag = printf("%T\n", t);
^~~~~~
glibc version: 2.24
T arg type: 8
printf_info spec: T, struct T arg type=8
extracted struct T (23, A) to mem 0xbfabfdb0
[struct T at 0xbfabfdb0: t.i=23, t.c=A]
0