Pointer Question

Way in over my head trying to pass a variable number of arguments ( pointers ) to a routine.

Arguments are passed in pairs, the variable type and then its address. When passing variables of type int things work, but byte and long types fail.

Usage

      byte TestByte = 10;
      va_func(1, 0, &TestByte); // Fails

      int TestInt = 20;
      va_func(1, 1, &TestInt); // Works

      long TestLong = 30;
      va_func(1, 2, &TestLong ); // Fails

Routine

      va_func(int ArgCount, ... ); 
      { 
        va_list Args;  

        va_start(Args, ArgCount);           

          for (int I = 0; I < ArgCount; I += 2)        
          { 
/*
*           Argument type
*/
            int VarType;
            VarType = va_arg(Args, int)
            printf("Type %d\n", VarType ); 
/*
*           Argument address
*/
            long VarAddress;
            VarAddress = va_arg(Args, int)
            printf("Address %d\n", VarAddress); 
/*
*           Argument Value
*/
            void *Ptr;
            Ptr = (void *)VarAddress;
            printf("Value %d\n", *Ptr);


            .....Code to process arguments goes here, returning a new value (NewValue) 

            *Ptr = NewValue;

          }

        va_end(Args);

      }
va_func(int ArgCount, ... );

That will never work for anything. You've not given enough information. A partial program with elipses, no context, and an unclear error. What is the expected behavior, or what is the behavior that is not expected?

you have to cast your pointer to the type you want based on the value of VarType. You are also pulling the VarAddress as an 'int' which is wrong since it is a pointer.

void setup() {
  Serial.begin(9600);
  Serial.println("Alive");


  byte TestByte = 10;
  va_func(1, 0, &TestByte); // Fails
  Serial.println( TestByte );
  int TestInt = 20;
  va_func(1, 1, &TestInt); // Works
  Serial.println( TestInt );

  long TestLong = 30;
  va_func(1, 2, &TestLong ); // Fails
  Serial.println( TestLong );

}

void loop() {
  // put your main code here, to run repeatedly:

}


void va_func(int ArgCount, ... )
{
  va_list Args;

  va_start(Args, ArgCount);

  for (int I = 0; I < ArgCount; I += 2)
  {
    /*
                Argument type
    */
    int VarType;
    VarType = va_arg(Args, int);
    printf("Type %d\n", VarType );
    /*
                Argument address
    */
    void * VarAddress;
    VarAddress = va_arg(Args, void *);
    printf("Address %ld\n", VarAddress);
    /*
                Argument Value
    */

    //    .....Code to process arguments goes here, returning a new value (NewValue)
    long NewValue = 100;
    switch (VarType) {
      case 0:   // byte
        *(byte*)VarAddress = (byte)NewValue;
        break;

      case 1:   // int
        *(int*)VarAddress = (int)NewValue;
        break;

      case 2:   // byte
        *(long*)VarAddress = (long)NewValue;
        break;
    }

  }
  va_end(Args);
}

You have to know, and tell the va_arg() macro, the type of the next argument. It appears you are passing an integer argument to indicate the type but you don't use that data to tell va_arg() what to expect. Like this:

enum VarTypes {ByteType, IntType, LongType};


void setup()
{
  Serial.begin(115200);
  while (!Serial);


  byte TestByte = 10;
  va_func(1, 0, &TestByte); // Fails


  int TestInt = 20;
  va_func(1, 1, &TestInt); // Works


  long TestLong = 30;
  va_func(1, 2, &TestLong ); // Fails
}


void loop() {}


void va_func(int ArgCount, ... )
{
  va_list Args;


  va_start(Args, ArgCount);


  /*
              Argument type
  */
  int VarType = va_arg(Args, int);
  Serial.print("Type ");
  Serial.println(VarType);
  /*
              Argument pointer
  */
  switch (VarType)
  {
    case ByteType:
      {
        byte *BP = va_arg(Args, byte *);
        Serial.println((int)BP, HEX);
        Serial.println(*BP);
      }
      break;
    case IntType:
      {
        int *IP = va_arg(Args, int *);
        Serial.println((int)IP, HEX);
        Serial.println(*IP);
      }
      break;


    case LongType:
      {
        long *LP = va_arg(Args, long *);
        Serial.println((int)LP, HEX);
        Serial.println(*LP);
      }
      break;
  }


  va_end(Args);
}

The results are:

Type 0
8FB
10
Type 1
8F9
20
Type 2
8F5
30

Using variadic functions like that is extremely error prone.

Try using variadic templates instead, it allows the compiler to check the number of arguments and their types, so you don't have to rely on the programmer to supply the right parameters.

#include <stdint.h>
#include <stdio.h>

using byte = uint8_t;

template <class T>
void printArgument(const T &t) {  // Generic type T
    puts(__PRETTY_FUNCTION__);
    printf("Type: unkown\n");
    printf("Address: %p\n", &t);
}

template <>
void printArgument(const byte &b) {  // Specialization for byte
    printf("Type: byte\n");
    printf("Address: %p\n", &b);
    printf("Value: %#02x\n", b);
}

template <>
void printArgument(const int &i) {  // Specialization for int
    printf("Type: int\n");
    printf("Address: %p\n", &i);
    printf("Value: %d\n", i);
}

template <>
void printArgument(const long &l) {  // Specialization for long
    printf("Type: long\n");
    printf("Address: %p\n", &l);
    printf("Value: %ld\n", l);
}

void va_func() {}  // Base case (no arguments)

template <class T, class... Others>
void va_func(const T &t, const Others &... others) {  // Generic type T
    printArgument(t);
    va_func(others...);  // Call itself recursively with remaining arguments
}

int main() {
    byte b = 0x55;
    int i  = 42;
    long l = 999999;
    struct {} s;  // Unknown type
    va_func(b);
    va_func(i);
    va_func(l);
    va_func(s);
    printf("\n");
    va_func(b, i, l);
}

This prints:

Type: byte
Address: 0x7ffd2ee8559a
Value: 0x55
Type: int
Address: 0x7ffd2ee8559c
Value: 42
Type: long
Address: 0x7ffd2ee855a0
Value: 999999
void printArgument(const T&) [with T = main()::<anonymous struct>]
Type: unkown
Address: 0x7ffd2ee8559b

Type: byte
Address: 0x7ffd2ee8559a
Value: 0x55
Type: int
Address: 0x7ffd2ee8559c
Value: 42
Type: long
Address: 0x7ffd2ee855a0
Value: 999999

Pieter

PieterP:
Try using variadic templates instead, ....

Nice example, thanks. I'll file it away as a "template" to use should I need to implement a variadic function in a future project.

Although, in the in the vein of constructive criticism, I would make the recommendation that if you're going to present an example of a technique, said technique should be the only new topic presented. For the vast majority of Arduino users (even "advanced" ones), the following aren't very standard:

stdint.h & stdio.h, using byte = uint8_t; (just include Arduino.h)
puts(PRETTY_FUNCTION)
printf()

So, they're superfluous and distracting. If you're presenting variadic templates as the new concept, then just present those and use standard Arduino techniques for the rest.

One guy's opinion, FWIW.

gfvalvo:
Although, in the in the vein of constructive criticism, I would make the recommendation that if you're going to present an example of a technique, said technique should be the only new topic presented. For the vast majority of Arduino users (even "advanced" ones), the following aren't very standard:

stdint.h & stdio.h, using byte = uint8_t; (just include Arduino.h)
puts(PRETTY_FUNCTION)
printf()

Yeah, you're right, I used printf because that's what the OP used in his code, but I don't know about any Arduinos that support it out of the box, so I ran it on my computer instead, and needed the right headers and type aliases.