Subject: RE: [hackers-il] Robust Argument Passing to C Functions

From: "Arik Baratz" <[email-masked]>
Date: Sun, 20 Jul 2003 21:46:06 +0300

> -----Original Message-----
> From: Shlomi Fish [mailto:[email-masked]]
[lots deleted]
> Needless to say, both methods require more code than just 
> passing them to
> the function call one by one.

Hi Shlomi

When C was designed, it was supposed to be as close to assembly language as possible. The argument list is pushed into the stack and the call is performed, and the callee knows their offsets hence it can use commands like 

mov eax,dword ptr [esp+6]

to get at those parameters.

In order to tweak this behaviour you have to either change it (modify the compiler) or use some abstraction that uses this method. Since the former is not very convenient and is a sure way to make porting a hell, you'll have to use the latter.

In order to use the latter, you need to either pass a fixed number of arguments (to statisfy the requirements that the call does not change), pass a different number of arguments, but use two different functions (a.k.a. overloading), or define optional arguments in some way.

The first of these 3 options - passing a fixed number of arguments - requires you to invent a data structure, like the hash structure you mentioned. Pass only one argument, always - with the understanding that the way to pass parameters is to insert the paramname,paramvalue pair into the hash. This has the obvious advantage of being a very flexible way to pass arguments to the program. If you drop the key uniqueness requirement of the hash, you can map the same property to several values, which increases the flexibility of the solution. The down side is, well, having to write it all. In some cases you'll discover that you have to - the interface is just plain to big and hairy to use plain parameter passing, and you have to invent a more flexible system. Even a simple data structure, like a pointer to a struct, is good enough for many of these things, and has the advantage that you can extend it without compiling old code if you do it right. If you have ever written Win32 API you know what I mean.

my_function(IN_STRUCT* pInput, OUT_STRUCT* pOutput)
{
	int attr1;
	attr1 = attr_get_int(pInput,"attr1");
	
	/* do stuff */
	
	attr_set_charptr(pOutput,"attr2",attr2);
}

attr_clean(pInput);
attr_set_int(pInput,"attr1",5);
my_function(IN_STRUCT* pInput, OUT_STRUCT* pOutput);

BTW, Python uses this method to pass any number of named arguments. You use the syntax name=value in the function call, but the function gets a dictionary (a.k.a. a hash) of attributes and values.

The second solution - overloading - is not a mechanism that exists in vanilla C. You have a good reference implementation of overloading - C++. You can just fake it, then, by running a preprocessor on your code and adding decoration to your functions. Decoration means that the name of the function is postfixed with a code that says how many argumens and what their types are. The two decorated functions are now named differently - when declared and when called - so there is no problem compiling both copies. Yes, both copies - you have to have two copies of the function for that to work. It is however possible for one to call the other, so it's not that problematic.

my_func(int a, int b)
{
	my_func(a,b,'x');
}

my_func(int a, int b, char c)
{
	/* do stuff */
}

in object code the entries look like:

my_func@I4I4
my_func@I4I4C1

Writing a precompiler on your source, however, is. (problematic, that is). One reason is that you have to write it. Ever written a preprocessor? It's not a task for the faint of heart. You have to harness the power of lex/yacc or similar tools, which ease the pain, but only to a degree. Not pretty. The second reason is that you are no longer writing C code. You have extended ANSI C for your devious purposes, and your code has become less maintainable. It is not less portable, however - provided the preprocessor is portable enough.

The third solution - inventing optional parameters - again can be done in two ways. One involves a preprocessor, but it is a less invasive one. All it has to do is go over your function definition, parse the 'optional parameter' syntax that you must decide upon (for example, putting the character '=' after the parameter name and addign a value), and then it has to go over the code and change the function calls to include the default value of the optional parameters if they do not exist. Not that difficult, and if you make it enough C++ - like you will not cause too much headache to anyone who reads your program. Disadvantage of a preprocessor notwithstanding.

The second way you can invent optional parameters is to use another C mechanism - the variable argument list. You can use a fixed structure in the beginning to tell the function what types of arguments are to follow, and then use those arguments just like printf() does. This requires that you match the argument list to the attribute list in the exact order, but it requires no preprocessing of any sort. The called function, however, has to do the nasty part of interpreting what's going on.

Since the compiler don't know what types to expect, it can't calculate fixed offsets to SP, so you really have to go out of your way to help. You have some helper macros in <stdarg.h> that do most of the hard work for you. You can have a function do like this:

my_func(char* attrs, ...)
{
	char* next;
	va_list	args;

	int attr1;
	char attr2;
	long attr3;

	va_start(args,attrs);
	next=attrs
	while (next)
	{
		next=strtok(next,",")
		if !strcmp(next,"attr1")
		{
			attr1 = va_arg(args,int);
			continue;
		}
		if !strcmp(next,"attr2")
		{
			attr2 = va_arg(args,char);
			continue;
		}
		if !strcmp(next,"attr3")
		{
			attr3 = va_arg(args,long);
			continue;
		}
		.
		.
		.
	}
	va_end(args);

	/* do stuff */
}

my_func("attr1,attr3",a1,a3);

Now I know I used strtok(); That's a privilege reserved for people writing sample program. I bet this won't compile, but I hope you get the gist of things.

That's it. If anyone has any more suggestions... I'd like to hear them.

-- Arik

**********************************************************************
This email and attachments have been scanned for
potential proprietary or sensitive information leakage. 

PortAuthority(TM)  Server 
Keeping Information Inside
Vidius, Inc. 
www.vidius.com
**********************************************************************

---

From: Shlomi Fish <[email-masked]>
Date: Sun, 20 Jul 2003 23:54:41 +0300 (IDT)

One suggestion that comes to mind are variable parameter lists where the
odd items are strings and the even items are values:

int myfunc(int required_arg,...)
{
}

myfunc(6,"table", mytable,"fields",myfields,"where", my_where_clause,
NULL);

a la Perl. Now, two questions:

1. Is it legal in C?
2. Does myfunc() have to determine the number of arguments in advance?

Regards,

	Shlomi Fish

----------------------------------------------------------------------
Shlomi Fish        [email-masked]
Home Page:         http://t2.technion.ac.il/~shlomif/

An apple a day will keep a doctor away. Two apples a day will keep two
doctors away.

	Falk Fish