First variadic function...

Go To Last Post
10 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

After reading the text at:
http://www.gnu.org/software/libc/manual/html_node/How-Variadic.html#How-Variadic, I wrote my first variadic function. It compiles, and appears to work. the code is:

#include 
#include 
#include 

void foo(uint16_t type, ...);

int main(void) {
	uint16_t ctrl = 1;

	foo(1, 255);
	foo(2, 300);
	foo(3, 268435455);
	foo(4, -127);
	foo(5, -200);
	foo(6, -268435455);

	return(0);
}

void foo(uint16_t type, ...) {
	va_list argList;
	uint8_t utemp8;
	uint16_t utemp16;
	uint32_t utemp32;
	int8_t temp8;
	int16_t temp16;
	int32_t temp32;

	//initialize the argument list
	va_start(argList, type);

	/*A Priori knowledge:****************
	* type =
	* 1 --> second argument = uint8_t
	* 2 --> second argument = uint16_t
	* 3 --> second argument = uint32_t
	* 4 --> second argument = int8_t
	* 5 --> second argument = int16_t
	* 6 --> second argument = int32_t
	************************************/
	switch(type) {
		case (1) :
			utemp8 = (uint8_t)va_arg(argList, int);
			printf("utemp8: %d\n", utemp8);
			break;
		case (2) :
			utemp16 = (uint16_t)(uint16_t)va_arg(argList, int);
			printf("utemp16: %d\n", utemp16);
			break;
		case (3) :
			utemp32 = va_arg(argList, uint32_t);
			printf("utemp32: %ld\n", utemp32);
			break;
		case (4) :
			temp8 = (int8_t)va_arg(argList, int);
			printf("temp8: %d\n", temp8);
			break;
		case (5) :
			temp16 = (int16_t)va_arg(argList, int);
			printf("temp16: %d\n", temp16);
			break;
		case (6) :
			temp32 = va_arg(argList, int32_t);
			printf("temp32: %d\n", temp32);
			break;
		default :
			printf("stuffGlobal(): Unknown type!\n");
			break;
	}

	va_end(argList);
}

I am looking for [constructive] criticism.

A few particular quesitons:
1. Is there a better way than "a priori" knowledge to handle the function?
2. Should I handle a case where a user calls the function with the wrong type in the second argument, i.e., in a function call, the first var indicates that the 2nd arg is a uint32_t, but it is actually a uint8_t?
3. I got all kinds of promotion (and abort) warnings when I tried to specify different short types in the va_arg macro. I believe I understand why this is so after reading the above mentioned reference. Is my usage ("int") correct (it seems to work), or is there a better way?
4. Am I missing anything else and/or doing something unwise?

Thanks....

Science is not consensus. Science is numbers.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If you already know what parameter types are to be passed then why bother with va_args at all? Why not just:

void foo(uint8_t arg1, uint16_t arg2, uint32_t arg3, int8_t arg4, int16_t arg5, int32_t arg6) {
 ...
}

With your invocation examples you aren't passing variable arguments in the sense that you don't know how many there will be. Just a type identifier and a value. You might as well do what win32 does with lparam and wparam and effectively always pass a uint32_t then just interpret it differently. Something like:

typedef enum {
  UINT8,
  UINT16,
  UINT32,
  INT8,
  INT16,
  INT32
} parm_type;

void foo(parm_type ptype, uint32_t val) {
  switch(ptype) {
   case UINT8: // use (uint8_t)val; break;
   case UINT16: // use (uint16_t)val; break;
   case UINT32: // use (uint32_t)val; break;
   case INT8: // use (int8_t)val; break;
   case INT16: // use (int16_t)val; break;
   case INT32: // use (int32_t)val; break;
  }
}

int main(void) {
  foo(UINT8, 255);
  foo(UINT16, 300);
  foo(UINT32, 268435455);
  foo(INT8, -127);
  foo(INT16, -200);
  foo(INT32, -268435455);
}

No va_args needed there. You just overload the uint32_t to do duty in several ways. As I say when win32 calls the message handler for a Windows program it posts a WM_SOMETHING which says what kind of message it is (like a key down or a mouse move or a window maximize) and it always passes an LPARAM and a WPARAM and those two variables are interpreted in 100's (1,000's) of different ways depending on the message type. Sometime they are pointers. Some time they are integers. Some time they are 2 or more integers packed into one variable which need to be split with various uses of >> and &.

EDIT: to see an example of the Windows thing see:

http://www.toymaker.info/Games/h...

Notice for example that whe windows sends WM_COMMAND (one of your buttons or menu entries clicked) it then uses a switch() on the LOWORD(wParam) where LOWORD and HIWORD aer two macros to make it easy to split values out of wider variables. When it sends WM_KEYDOWN the code switches on the whole of wParam. When it sends WM_MOUSEMOVE the x,y of where the mouse is are sent are two halves of lParam with x in the LOWORD and y in the HIWORD and so on.

Last Edited: Tue. Feb 26, 2013 - 08:26 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

That is a very good point, especially given the code posted. However, I may wish to add additional arguments (i.e., "subtypes"), for some "types" and not for others. For example, for a UINT8 type, I may wish to have two additional arguments, but for a UINT16 type, only one. This example was simplified as a first cut.

To your point, for the function as is, could I simply use a void pointer, and dereference it according to the first argument?

Science is not consensus. Science is numbers.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

To your point, for the function as is, could I simply use a void pointer, and dereference it according to the first argument?


A pointer could be a way to do it but, like I say you could simply always pass a uint32_t which would have room to hold one uint32_t/int32_t, a couple of uint16_t/int16_t, four uint8_t/int8_t, two uint8_t/int_8 and a single uint16_t/int16_t. You could then do something like this:

#define BYTE0(x) (x & 0xFF)
#define BYTE1(x) ((x & 0xFF00) >> 8)
#define BYTE2(x) ((x & 0xFF0000) >> 16)
#define BYTE3(x) ((x & 0xFF000000) >> 24)
#define WORD0(x) (x & 0xFFFF)
#define WORD1(x) ((x & 0xFFFF) >> 16)

void foo(uint8_t message, uint32_t lParam) {
  switch(message) {
   case ONEU8: c = (uint8_t)BYTE0(lParam); break;
   case TWOU8: c = (uint8_t)BYTE1(lParam); 
               d = (uint8_t)BYTE0(lParam); break;
   case ONEI16ANDTWOI8:
               w = (int16_t)WORD1(lParam);
               e = (int8_t)BYTE1(lParam);
               f = (int8_t)BYTE0(lParam); break;
  }
}

int main(void) {
  foo(ONEU8, 227);
  foo(TWOU8, (227<<8) | 129);
  foo(ONEI16ANDTWOI8, (-12345 << 16) | (-39 <<8) | 147;
}

As you can see this is very like the win32 lParam/wParam and the LOWORD()/HIWORD() macros.

Talking of pointers. In Windows if you send a message to a text box to say "tell me what text the user has typed in" the text box will send a message back to your message handler with one of lParam/wParam (can't remember which) holding a pointer to a (wide character) string array. Similarly if you want to send a message to a text label to say "show 'Hello world'" you post it a message somthign like WM_SETTEXT with wParam holding a pointer to the "Hello world" text you want to set. etc.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You are probably right that this behavior would be better for the application I have outlined. However, I really want to understand variadic functions, so I am continuing to pursue this.

I have the following code, which incorporates a few of your comments, and also has a void pointer.

I am seeing some strange behavior.

#include 
#include 
#include 

typedef struct solution {
	uint8_t foo1;
	uint8_t foo2;
	uint16_t foo3;	//UBX-NAV-STATUS
	int32_t foo4;
} solution_t;

//Macros for varidic type arg
#define GET					0
#define	SET					1

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//Typdefs/Enums~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
typedef enum {
	FOO1,		//uint8_t
	FOO2,		//uint8_t
	FOO3,		//uint16_t
	FOO4		//int32_t
} solnSubVar_t;


void accessSolutionVar(solnSubVar_t var,  uint8_t action, ...);

solution_t currSolution = {0};

int main(void) {
	uint8_t *val;
	uint16_t *val16;
	int32_t  *val32;

	accessSolutionVar(FOO1, GET, (void *)val);
	printf("Before: currSolution.foo1: %d\n", *val);
	accessSolutionVar(FOO1, SET, 12);
	accessSolutionVar(FOO1, GET, (void *)val);
	printf("After: currSolution.foo1: %d\n", *val);

	printf("\n\n");

	accessSolutionVar(FOO3, GET, (void *)val16);
	printf("Before: currSolution.foo3: %d\n", *val16);
	accessSolutionVar(FOO3, SET, 300);
	accessSolutionVar(FOO3, GET, (void *)val16);
	printf("After: currSolution.foo3: %d\n", *val16);

	printf("\n\n");
	accessSolutionVar(FOO4, GET, val32);
	printf("Before: currSolution.foo4: %ld\n", *val32);
	accessSolutionVar(FOO4, SET, 268435454);
	accessSolutionVar(FOO4, GET, val32);
	printf("After: currSolution.foo4: %ld\n",*val32);

	return(0);
}

void accessSolutionVar(solnSubVar_t var,  uint8_t action, ...) {
	va_list argList;
	uint32_t setVal = 0;
	void * getPtr = 0;

	//Initialize the argument list
	va_start(argList, action);
	if(action == SET) {
		setVal = (uint32_t)va_arg(argList, uint32_t);
	}
	else if(action == GET) {
		getPtr = va_arg(argList, void *);
	}
	else {
		printf("ERROR - accessSolutionVar(): invalid action argument.\n");
		return;
	}

	switch(var) {
		case FOO1 :
			if(action == SET) {
				currSolution.foo1 = (uint8_t)(setVal & 0x000000FF);
			}
			else {
				*(uint8_t *)getPtr = currSolution.foo1;
			}
			break;
		case FOO2 :
			if(action == SET) {
				currSolution.foo2 = (uint8_t)(setVal & 0x000000FF);
			}
			else {
				*(uint8_t *)getPtr = currSolution.foo2;
			}
			break;
		case FOO3 :
			if(action == SET) {
				currSolution.foo3 = (uint16_t)(setVal & 0x0000FFFF);
			}
			else {
				*(uint16_t *)getPtr = currSolution.foo3;
			}
			break;
		case FOO4 :
			if(action == SET) {
				currSolution.foo4 = (int32_t)setVal;
			}
			else {
				*(int32_t *)getPtr = currSolution.foo4;
			}
			break;
		default :
			printf("ERROR - accessSolutionVar: invalid type argument.\n");
			break;
	}
}

In this test, the 8 bit and 16 bit variables work fine, but the 32 bit test causes a segmentation fault.

If I change the code a little, it works fine. Note, I changed the 32 bit pointer (in main) to an actual 32 bit number, and passed its address to the function.

int main(void) {
	uint8_t *val;
	uint16_t *val16;
	int32_t  val32;

	accessSolutionVar(FOO1, GET, (void *)val);
	printf("Before: currSolution.changeFlag: %d\n", *val);
	accessSolutionVar(FOO1, SET, 12);
	accessSolutionVar(FOO1, GET, (void *)val);
	printf("After: currSolution.changeFlag: %d\n", *val);

	printf("\n\n");

	accessSolutionVar(FOO3, GET, (void *)val16);
	printf("Before: currSolution.speed: %d\n", *val16);
	accessSolutionVar(FOO3, SET, 300);
	accessSolutionVar(FOO3, GET, (void *)val16);
	printf("After: currSolution.changeFlag: %d\n", *val16);

	printf("\n\n");
	accessSolutionVar(FOO4, GET, &val32);
	printf("Before: currSolution.changeFlag: %ld\n", val32);
	accessSolutionVar(FOO4, SET, 268435454);
	accessSolutionVar(FOO4, GET, &val32);
	printf("After: currSolution.changeFlag: %ld\n",val32);

	return(0);
}

I am running the test code on a pc (not an AVR) -- could this be the reason I am seeing this seg fault?

As I understand it, all addresses in an AVR are 16 bits, whether it points to a char or a 32 bit signed integer. Why would these two cases behave differently?

Edit: When I try this on an actual AVR, the behavior is similar, but not identical. On the AVR, the 8 case works normally with a pointer declared in the calling function, but neither the 16 nor the 32 bit work. Again, if I change the calling function to declare an actual 16 (or 32) bit number, and pass the address to the function, it works normally (as expected). I am confused...

Science is not consensus. Science is numbers.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

But it's still not a variadic function, every invocation has three parameters.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I get that. Right now, I'm trying to figure out why it behaves differently when I pass a declared int32_t pointer versus an address of a declared int32_t.

Science is not consensus. Science is numbers.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

hobbss wrote:
I am running the test code on a pc (not an AVR) -- could this be the reason I am seeing this seg fault?
I do not know what compiler you are using on the PC, but I am pretty sure that most modern compilers have options to enable pedantic error/warning checks. Use them!

For example, /Wall /WX for Microsoft's; -Wall -Werror for gcc. gcc also has some additional ones. For example, here is what I use: -Wextra -Wstrict-prototypes -Wundef -Wall -Werror.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
In this test, the 8 bit and 16 bit variables work fine,
Pure luck. None of your test pointers is initialized, and therefore all point to random locations.

Quote:
but the 32 bit test causes a segmentation fault.
And that happens with less luck when using random pointers.

Stefan Ernst

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ezharkov -- good tip. I sometimes use mingcw and sometimes cygwin gcc.

stefan: you are [as always] correct. I had a serious mental lapse yesterday with regards to pointers. I actually woke up at 4am today with an epiphany about the pointers -- of course they need to be initialized. Is it truly luck with the 8 bit case, or is it due to the fact that the variable size is smaller than the address size of the pointer (16 bit)?

Science is not consensus. Science is numbers.