Functions with variable lenght arguments in Arduino

Ever wondered if functions with variable length arguments are possible in Arduino?

I mean, something like:

printf("%d", 5);
printf("%d %d", 5, 7);

Well, it is. Full blog post here. Code below:

#include <stdarg.h>
#include <stdio.h>

/* this function will take the number of values to average
   followed by all of the numbers to average */
float average ( int num, ... )
{
    va_list arguments;                     
    float sum = 0;

    /* Initializing arguments to store all values after num */
    va_start ( arguments, num );           
    /* Sum all the inputs; we still rely on the function caller to tell us how
     * many there are */
    for ( int x = 0; x < num; x++ )        
    {
        sum += va_arg ( arguments, double ); 
    }
    va_end ( arguments );                  // Cleans up the list

    return sum / num;                      
}


void setup() {
  Serial.begin(9600);
}

void loop() {
  float avg = average( 3, 12.2, 22.3, 4.5 );
  Serial.println(avg);
  avg = average( 5, 3.3, 2.2, 1.1, 5.5, 3.3 );
  Serial.println(avg);
  
  delay(1000);
}

Very usefull !!

would add the following line in the sample code - if (num <=0) return 0; // or NAN or infinity or …

Another application could look like

void pulses(num, ....)
{
  va_list arguments;
  va_start ( arguments, num );  
  int pin = va_arg ( arguments, int); 
  for ( int x = 1; x < num; x++ )        
  {
    digitalWrite(pin, HIGH);
    int del = va_arg ( arguments, int); 
    delay(del);
    digitalWrite(pin, LOW);
    delay(500-del);
  }
}

pulses(4,100,100,200,300);

Any numbers on the performance?

Neat! Always wanted to learn this trick but too scared to start! I looked a little deeper, the va_start() is defined as _builtin_va_start() so it's a macro but I can't fine this _builtin_va_start anywhere inside arduino folder. I bet they used the address of the arguments and num somewhere inside _builtin_va_start. Just don't feel comfortable using it without knowing what it does :grin:

It can be handy, and the implementation should be straightforward, economical, and fast. I’ll show another that I wrote to concatenate multiple strings to assemble log entries, along with a (desktop machine) test program:

#include <stdarg.h>
/*----------------------------------------------------------------------------*/
/* Concatenate strings from NULL-terminated list to a buffer                  */
/*----------------------------------------------------------------------------*/
char *mcat(char *b,...)
{  char *r = b, *s = b;                /* Pointers to buffer                  */
   va_list ap;                         /* Parameter list reference            */
   while (*s) ++s;                     /* Find buffer string terminator       */
   va_start(ap,b);                     /* Initialize ap                       */
   while (b = va_arg(ap,char *))       /* If the next parameter is non-NULL   */
      while (*b) *s++ = *b++;          /* Concatenate string to buffer        */
   va_end(ap);                         /* Done with parameter list            */
   *s = '\0';                          /* Terminate the concatenated string   */
   return r;                           /* Return pointer to buffer            */
}                                      /*  end: mcat() function               */
/*----------------------------------------------------------------------------*/

#ifdef MCAT_TEST
/*----------------------------------------------------------------------------*/
/* Test program - dummy fusion reactor log line                               */
/*----------------------------------------------------------------------------*/
#include <stdio.h>
int main(void)
{  char b[81] = "\0";
   mcat(b,"YY-MM-DD HH:MM:SS.SSS"," 123"," 456"," 789",NULL);
   mcat(b," # ","Annotation",NULL);
   puts(b);
   return 0;
}
#endif

Just tested... a word of warning (for myself and anyone reading): don't overlook the last argument: NULL. If you forget to add a NULL as last argument, the mcat function won't be able to determine where the argument list ends, thus overwriting memory and causing nasty uP resets or freezes.

From cplusplus.com:

Notice also that va_arg does not determine either whether the retrieved argument is the last argument passed to the function (or even if it is an element past the end of that list). The function should be designed in such a way that the amount of parameters can be inferred in some way by the values of either the named parameters or the additional arguments already read.

Yuppers. I guess it might be worth warning that the memory pointed to by the first parameter [u]must[/u] be large enough to contain all of the strings that are to be concatenated - else something beyond it will be overwritten - just like they would be if you were using strcat().

This is neat and been part of C from day one IIRC, but this is is expressly forbidden by all (or at least most) safety/critical code specs, for example MISRA

Rule 69 (required): Functions with variable numbers of arguments shall not be used. [Unspecified 15; Undefined 25,45,61,70–76; Koenig 62] There are a lot of potential problems with this feature and it shall not be used. This precludes the use of stdarg.h, va_arg, va_start and va_end, and the ellipsis notation in function prototypes.

So don't use it for you heart-lung machine controller :)


Rob

No worry, mate - it's only being used in the control code for a (small) nuclear reactor. :o)

I haven't read the small print recently, but I'd be surprised if the Atmel AVR data sheet didn't have a similar disclaimer about the use of its processors for ANY safety-critical application.

I'd be surprised if the Atmel AVR data sheet didn't have a similar disclaimer...

I thought I'd have a look

Unless specifically provided otherwise, Atmel products are not suitable for, and shall not be used in, automotive applications. Atmel’s products are not intended, authorized, or warranted for use as components in applications intended to support or sustain life.

So a reactor controller should be OK, as long as it's small (the reactor that is, the controller can be any size).


Rob

Let's hope so. The reactor (containment vessel) itself has shrunk somewhat since I put up the web page at http://www.iedu.com/Solar/Electricity/Fusion.html (to approx 5"/125mm). The controller (Mega + shields) will be roughly the same size. The whole purpose of the controller is to ensure that it behaves itself and to collect experimental data.

The old saying is that "You pays yer money and you take your chances." Not having the usual budget for building nukes, I decided the Arduino would have to do.

That reactor looks quite cool, might do a bit of reading on them.

If you like variable function arguments. Check out variadic macros and templates

Unfortunatly the IDE does not support the latest version of C++ If enough people like these maybe we can push for a switch in the IDE preferences for C++ version selection. I think it would be a relatively simple task to update the arduino core code ( if any updates are even needed ).

My understanding is that if the source filetype is .c, it will be compiled as C. Don't change the IDE for me - I'm a confirmed C bigot. :-)

I would like to be able to look at the generated assembler code and the linker memory maps tho...

changing the C++ version would not affect the compilation of C code.

EDIT: Unless the C standard has evolved too, c99 is still valid I'm pretty sure.