1

I am new to C programming. I was curious about whether I can make my own user-defined format specifier. But, unfortunately I don't know where to start from. Can someone please guide me. I read from somewhere that the header files of C are also written in C. So is it possible like copying the code from there and modifying it will do the work? I was saying like say I have defined a structure:

struct data
{
    int d;
    char str;
};

Now we have typedef ed it to say data:

typedef struct data data;

what I want is when I will be writing the print statement then, if I have made a format specifier for data as %q then:

printf("%q",x);   // Where x is a data type variable.

It will print the int part and then the String part on it's own.

gsamaras
  • 71,951
  • 46
  • 188
  • 305
  • 5
    You can't, unless you make a wrapper around printf. Might be a better idea to come up with your custom print function in that case. – Lundin Jan 04 '22 at 15:19
  • `char *fmt = build_format_string(); printf(fmt, x)`; Be careful about matching the types with the format string. (The printf will probably be in a switch with different types.) – William Pursell Jan 04 '22 at 15:21
  • The header file does not contain the implementation but just the function signature (where the first argument to `printf` is a simple pointer to const char without any knowledge of the conversion specifiers, and the other ones are unknown). The *implementation* is in the C standard library. You could, in theory, pull an implementation for a version of printf (e.g. from the the gnu standard C library, https://github.com/lattera/glibc/tree/master/stdio-common), modify it and make it part of your project. But I'd consider getting that to compile and link properly an expert task and overkill. – Peter - Reinstate Monica Jan 04 '22 at 15:28
  • @Peter-ReinstateMonica thanks a lot. I will try this for sure. – Surya Majumder Jan 04 '22 at 15:30
  • Thanks a lot @WilliamPursell as well for your help. Thanks a lot. – Surya Majumder Jan 04 '22 at 15:30
  • 3
    There is a GNU extension that allows it, but it is not standard C. Check out the comments on the accepted answer [here](https://stackoverflow.com/questions/9260170/is-this-possible-to-customize-printf). – alex01011 Jan 04 '22 at 15:31
  • 2
    @alex Interesting! That is pretty much what the OP asked for, I believe. Another example for the open source trope that if you have a reasonable demand for a use case, chances are that somebody has already done that... – Peter - Reinstate Monica Jan 04 '22 at 15:33
  • Yes exactly @alex01011 this is almost what I want. Too bad that it is only Linux specific. But still at least I can use it. Thanks a lot. – Surya Majumder Jan 04 '22 at 15:37
  • I believe that the answer you’re looking for is probably “use Python”, unfortunately. Other techniques will not be portable but since there’s only a handful of ABIs in widespread use, it’s quite reasonable to apply platform-specific techniques to modify the variadic argument list before passing it to vfprintf.l & co. – Kuba hasn't forgotten Monica Jan 04 '22 at 16:00

2 Answers2

2

That's not possible in Standard C.

I would suggest to create a function that takes the struct as an argument and in its body it prints all the fields of the struct.

Then instead of:

printf("%q",x);   // Where x is a data type variable.

you can use your function to print, like this:

print_struct(x);

Read more in Print the structure fields and values in C and Using functions to print structures.


If you want to print all the fields of the struct, then I suggest reading this Run through a structure and print all the values?

gsamaras
  • 71,951
  • 46
  • 188
  • 305
  • thanks a lot. I will definitely try this but won't changing codes from header file do it? I mean they might also have functions or even my own created functions after editing? Please help. – Surya Majumder Jan 04 '22 at 15:23
  • 1
    That will surely work but the OP probably envisioned something along the lines `for (i=0; i – Peter - Reinstate Monica Jan 04 '22 at 15:23
  • Yes @Peter-ReinstateMonica. I actually don't want to modify the printf() function. Is it possible? Please help. – Surya Majumder Jan 04 '22 at 15:25
  • @Peter-ReinstateMonica ah didn't think of that. I updated my answer [un-through-a-structure-and-print-all-the-values](https://stackoverflow.com/questions/6074288/run-through-a-structure-and-print-all-the-values). – gsamaras Jan 04 '22 at 15:26
  • 1
    Oh ok thanks @gsamaras for your help. I will definitely try this. Thanks a lot. – Surya Majumder Jan 04 '22 at 15:28
  • Errrr... how is `printf(fmt, x);` supposed to work?? Obviously you can programmatically assemble format strings, but you still cannot use conversion specifiers other than the predefined ones (except with the GNU extension suggested by alex in the comments), and passing a struct to printf is UB, or am I missing something? – Peter - Reinstate Monica Jan 04 '22 at 15:38
  • @Peter-ReinstateMonica good catch, answer fixed! – gsamaras Jan 04 '22 at 16:11
2

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
Peter - Reinstate Monica
  • 15,048
  • 4
  • 37
  • 62