How to pass a variable list of parameters to vprintf in a portable way?

1k Views Asked by At

I have a code that receives binary array of 32-bit values from a device and prints them with vsprintf, like this:

void print_stuff(int32_t *p, const char *format)
{
    vprintf( format, (va_list)p);
}

(this is simplified; it's ensured that the values match the format, etc.)

Basically this relies on that in normal x86 va_list is just a pointer (or array). This compiles without warnings.

Now I need to port this to ARM (arm-linux-gnueabihf) and x64, and it does not even compile. GCC 4-something for ARM says "error: conversion to non-scalar type requested"

How to make a va_list from a binary array in a portable way? Or at least for 32-bit and 64-bit archs separately - is possible without any "native call interface" libraries? If this is impossible, then is there any other standard or GNU library function suitable for this task?

Example of code that calls this:

#include <stdio.h>
#include <stdarg.h>
#include <stdint.h>
#ifndef __GNUC__
#error Only Linux, other platforms/compilers not needed
#endif

int main() {
uint32_t data[10];
int i;
const char *fmt = "%x %x %x %x\n";
// Simulation of reading the data
for(i = 0; i < 10, i++) data[i] = 0x100 + i;
print_stuff(data, fmt);
return 0;
}
2

There are 2 best solutions below

1
Jonathan Leffler On BEST ANSWER

Transferring salient comments into an approximation to an answer.

You will have to review and revise the calling code to do the job properly. Or you give up your simple call to vprintf() and work a lot harder.

If you are passing an array to your printing function, you are not passing a va_list. There are all sorts of things that happen to work on some particular implementation that don't work on all implementations. What you're finding out is that your current system worked in its niche habitat, but now it needs to move out of its niche, it doesn't work any more. Welcome to the world of 'undefined behaviour'. Code that uses undefined behaviour isn't required to work; it isn't required to fail, either.

Even with your revised question, there is no (portable) way to do it. Period. How many entries are in the array? What do the format strings look like? Is the number of items to be printed fixed? Basically, you may be reduced to:

int n_fields = count_fields(format);
switch (n_fields)
{
case 1: printf(format, p[0]); break;
case 2: printf(format, p[0], p[1]); break;
…
}

That's portable and reliable. If the number of items in the array is fixed (your code shows 4 entries used), you don't need to do the counting or the switch, of course.

You might lookup libffi; it may be able to help you.

1
dbush On

Since the format is pre-defined, you know exactly how many parameters to pass in. So pass to your function exactly the parameters you need instead of passing an array. Then you change your function to use ... as a parameter and get a va_list from that.

So your function would look like this:

void print_stuff(const char *format, ...)
{
    va_list args;

    va_start(args, format);
    vprintf(format, args);
    va_end(args);
}

And you would call it like this:

const char *fmt = "%x %x %x %x\n";
...
print_stuff(fmt, data[0], data[1], data[2], data[3]);