[Webinar] Streamline your web hosting managementRegister Today

x
Solved

# Handling variable arguments.

Posted on 1999-11-16
Medium Priority
552 Views
I'm trying to find a solution to solve the following problem:
Input is the following:
1. a pointer to a function, possible form of the function is
double f(double)
double f(double,double)
double f(double,double,double)
double f(double,double,double,double)
... (to whatever)
2. an integer n telling me how many arguments the function need
3. n double numbers
4. an integer m telling me which double number I need to process

Output is:
f(...,value,...)
value is the processed double number

Summary:
The function should look like this
double Process(int n,int m,double f(?),...)
{
Process the mth argument
return f(...,value,...)
};

Can anyone help me?
I don't know if this involves assembly, but it does, please provide the possible solution for Win32 and Unix.
Thank you very much!
0
Question by:xu092098
• 20
• 14
• 12
• +4

LVL 86

Expert Comment

ID: 2211103
You can use va_arg, va_end, va_start to access variable-argument lists. If you'd like to pass different types of arguments, I'd suggest to use a 'printf()' like 'format string' that describes the types of the arguments in the variable list.

Here's a simple example from the VC++ docs:

/* VA.C: The program below illustrates passing a variable
* number of arguments using the following macros:
*      va_start            va_arg              va_end
*      va_list             va_dcl (UNIX only)
*/

#include <stdio.h>
#define ANSI            /* Comment out for UNIX version     */
#ifdef ANSI             /* ANSI compatible version          */
#include <stdarg.h>
int average( int first, ... );
#else                   /* UNIX compatible version          */
#include <varargs.h>
int average( va_list );
#endif

void main( void )
{
/* Call with 3 integers (-1 is used as terminator). */
printf( "Average is: %d\n", average( 2, 3, 4, -1 ) );

/* Call with 4 integers. */
printf( "Average is: %d\n", average( 5, 7, 9, 11, -1 ) );

/* Call with just -1 terminator. */
printf( "Average is: %d\n", average( -1 ) );
}

/* Returns the average of a variable list of integers. */
#ifdef ANSI             /* ANSI compatible version    */
int average( int first, ... )
{
int count = 0, sum = 0, i = first;
va_list marker;

va_start( marker, first );     /* Initialize variable arguments. */
while( i != -1 )
{
sum += i;
count++;
i = va_arg( marker, int);
}
va_end( marker );              /* Reset variable arguments.      */
return( sum ? (sum / count) : 0 );
}
#else       /* UNIX compatible version must use old-style definition.  */
int average( va_alist )
va_dcl
{
int i, count, sum;
va_list marker;

va_start( marker );            /* Initialize variable arguments. */
for( sum = count = 0; (i = va_arg( marker, int)) != -1; count++ )
sum += i;
va_end( marker );              /* Reset variable arguments.      */
return( sum ? (sum / count) : 0 );
}
#endif
0

LVL 86

Expert Comment

ID: 2211113
BTW: If you'd like an example on how to handle such a format string, please ask. The one I have here is just that large that I don't want to post it without request.

0

Author Comment

ID: 2211177
I know how to use va_xxx stuff. But the problem is this:
1. How do you get the pointer to the function if the function's type is unknow at compile time.
For example, for using va_arg, you need to provide the type of the variable, but the pointer to the function is an unknow type.
2. How do you call that function pointer?
For example, we now know it is a double f(double,double,double,double),
but I don't know how to call that in my function because I have the parameter array, but I can't just say:
f(var[0],var[1],var[2],var[3]), because at the compile time, I don't know how many arguments are there and surely, I can't write it down like that.
I hope I have made the question clear now.
0

LVL 9

Expert Comment

ID: 2211205
jkrs solution will only work if the function f(...) whatever passed can do the handling of the variable args (I think!) - let me know if this is incorrect.

This assumes that you can alter the signature of the passed functions.  If you can do that, then it would probably be better to have the passed functions just accept a vector or list or whatever of doubles and thus avoid the rather unpleasant (and non-type safe va_args stuff).

i.e. :

typedef double (*doublefunc)(const vector<double>&);

double Process(int n,int m,doublefunc df,const vector<double>& vals)
{
vector<double> copy = vals;
copy[m] = ............; // Process mth value
return df(vals)
};

0

Author Comment

ID: 2211252
The problem is I'm making something that is easy for others to use and it should look general. So I want to make it look normal and basic to use, for example, people just call it like they call any other functions, they don't need to define new types like vector or whatever.
I don't know if there's a solution, but I want to try.
Thank you very much! It's a challenge too, right?
0

LVL 9

Expert Comment

ID: 2211315
vector is a standard C++ type, defined in the STL, so you could reasonably expect users to use it.

So what you are asking for is a generic function type that can handle normal functions of the form:

double f(double,double,...,double etc.)

with no use of collections or variable argument lists?

A challenge? I don't think so, because I doubt that it can be done.

I think you will have to choose between some form  variable argument lists for client functions (very C, not type safe, unpleasant in use), C style arrays of parameters (not safe at run-time, messy to pass around) or an STL container of parameters.

i.e. your client functions would have to look like:

double f(...); // or
double f(size_t size, double* array); // or
double f(const vector<double>& vec);
0

LVL 22

Expert Comment

ID: 2211318
>> 1. How do you get the pointer to the function
>> if the function's type is unknow at
>> compile time.

xu, if the function uses variable arguments, like jkr suggested, then the type IS known at compile time.

if the function is

double Process(int n,int m,...)

Then the type, FunctionPointerType, is

typedef (double *FunctionPointerType)(int,int,...);
0

LVL 86

Expert Comment

ID: 2211356
nietod is correct. However, you could also choose to pass a function pointer as a 'void*' and casting it to its correct type using a 'description', like e.g. a 'format string' or a 'format tag' - BTW, that's exactly what MS does when implemeting message dispatching from a command target:

switch (nSig)
{   default:
ASSERT(FALSE);
break;

case AfxSig_bD:
lResult = (this->*mmf.pfn_bD)(CDC::FromHandle((HDC)wParam));
break;

case AfxSig_bb:     // AfxSig_bb, AfxSig_bw, AfxSig_bh
lResult = (this->*mmf.pfn_bb)((BOOL)wParam);
break;

case AfxSig_bWww:   // really AfxSig_bWiw
lResult = (this->*mmf.pfn_bWww)(CWnd::FromHandle((HWND)wParam),
(short)LOWORD(lParam), HIWORD(lParam));
break;

case AfxSig_bWCDS:
lResult = (this->*mmf.pfn_bWCDS)(CWnd::FromHandle((HWND)lParam),
(COPYDATASTRUCT*) lParam);
break;

case AfxSig_bHELPINFO:
lResult = (this->*mmf.pfn_bHELPINFO)((HELPINFO*)lParam);
break;

// ...
}
0

LVL 9

Expert Comment

ID: 2211391
So how does he handle the case where there an unknown no. of args at run time:

i.e. how to write:

typedef (double *FunctionPointerType)(int,int,...);

void f(int n,int m,FunctionPointerType f,double* vals)
{
f(n,m, --- what goes here ---);
}

I don't think it can be done - and frankly why bother! The right solution would seem to be some sort of array of parameters:

typedef (double *FunctionPointerType)(int,int,double*);

void f(int n,int m,FunctionPointerType f,double* vals)
{
vals[m] = ....;
f(n,m,vals);
}

0

LVL 7

Expert Comment

ID: 2211415
double Process(int m, double (*)(double), double);
double Process(int m, double (*)(double, double),double,double);
etc.
(Excuse me for my function pointer syntax, I normally avoid them)
0

LVL 9

Expert Comment

ID: 2211423
> If there are only four variants why not use simple overloading?

I thought it was any amount?
0

LVL 22

Expert Comment

ID: 2211426
>> how does he handle the case where there an
>> unknown no. of args at run time
Is there a case like that?  Obviously that can't be handled.

>> I don't think it can be done - and frankly why
>> bother! The right solution would
>> seem to be some sort of array of parameters:
Agreed, but I don't know that that is the case we are looking at.
0

LVL 7

Expert Comment

ID: 2211500
>> 1. a pointer to a function, possible form of the function is ...
Four cases were listed in the question, which doesn't necesarily mean there are only four.

Passing an array of arguments seems an option too.

Maybe the problem should be looked at. What is interface you intend to give your users.
0

LVL 3

Expert Comment

ID: 2211555
Instead of trying to solve this in a ugly type-unsafe manner, I suggest you use STL.
What reason could there be to avoid using STL in favor of such a ugly construction? It is certainly not good OO design.
Luc
0

LVL 1

Accepted Solution

abk102299 earned 1200 total points
ID: 2211648
> I know how to use va_xxx stuff.
> But the problem is this:
> 1. How do you get the pointer
> to the function if the function's
> type is unknow at compile time.
>
> For example, for using va_arg,
> you need to provide the type
> of the variable, but the pointer
> to the function is an unknow type.

> 2. How do you call that function
> pointer?

> For example, we now know
> it is a double f
> (double,double,double,double),
> but I don't know how to
> call that in my function because
> I have the parameter array,
> but I can't just say:
> f(var[0],var[1],var[2],var[3]),
>  because at the compile time,
> I don't know how many arguments
> are there and surely, I can't
> write it down like that.
> I hope I have made the question
> clear now.

Ok, I'll give you some VC6 inline assembler solution. But you have to be kind of careful with this one because it is compiler dependent. The principles are rather common though. Of course the assembler code could be optimized but I made it simple to be more understandable. Here is the code, I hope it is clear enough:

#include <stdio.h>

double Process(int n, int m, void* f, ...)
{
double result(0);
_asm
{
mov            ecx, n
jcxz    zero            // if not urn 0
shl     ecx, 3            // ecx <- n * 8
mov     eax, ebp      // eax <- save double parameters beginning address
Back:      push    [eax+ecx]      // push double parameters for the f(...) call
push    [eax+ecx+4]
sub            ecx, 8
jne            back
call      f                  // call f
mov            ecx, n
shl     ecx, 3            // ecx <- n * 8 ( again )
add            esp, ecx      // free the stack after f(...) call
fstp      result            // save double rasult to the local result variable
Zero:
};
return result;
}

double dpr1(double d1)
{
printf("dpr1(%8.2f);\n", d1);
return d1;
}

double dpr2(double d1, double d2)
{
printf("dpr2(%8.2f,%8.2f);\n", d1, d2);
return d1+d2;
}

double dpr3(double d1, double d2, double d3)
{
printf("dpr3(%8.2f,%8.2f,%8.2f);\n", d1, d2, d3);
return d1+d2+d3;
}

int main()
{
printf("Process1: %8.2f\n",Process(1, 0, dpr1, 1.0));
printf("Process2: %8.2f\n",Process(2, 0, dpr2, 1.0, 2.0));
printf("Process3: %8.2f\n",Process(3, 0, dpr3, 1.0, 2.0, 3.0));
return 0;
}

0

LVL 1

Expert Comment

ID: 2211664
One more time, the Process function implementation without comments:

double Process(int n, int m, void* f, ...)
{
double result(0);
_asm
{
mov      ecx, n
jcxz    Zero
shl     ecx, 3
mov     eax, ebp
Back:      push    [eax+ecx]
push    [eax+ecx+4]
sub      ecx, 8
jne      Back
call      f
mov      ecx, n
shl     ecx, 3
fstp      result
Zero:
};
return result;
}
0

LVL 7

Expert Comment

ID: 2211739
And here are some other ways to pick from:

#include<iostream>
#include <typeinfo>

template <int N>
struct proc_args
{
double args[N];
};

// the type safe Process function
template<int N>
double Process(int m, proc_args<N>& args, double (*f)(int, proc_args<N>&))
{
cout << "N = " << N << endl;
cout << typeid(f).name() << endl;
cout << typeid(args).name() << endl;
f(m, args);
}

// the more conventioanl variant
template<int N>
double Process(int m, proc_args<N>& args, double (*f)(int, int, double[]))
{
cout << "N = " << N << endl;
cout << typeid(f).name() << endl;
cout << typeid(args).name() << endl;
f(N, m, args.args);
}

// these are fully type-safe
double f3(int, proc_args<3>&){ cout << "f3" << endl; }
double f4(int, proc_args<4>&){ cout << "f4" << endl; }

// more conventional
double fn(int n, int m, double da[])
{
cout << "fn" << ", n = " << n << ", m = " << m << endl;
}

void main()
{
proc_args<3> da3;
proc_args<4> da4;
Process(2, da3, f3);
Process(1, da4, f4); // type-safe
// Process(1, da3, f4); // illegal
Process(2, da3, fn); // conventional, no check on arg count
}
0

LVL 86

Expert Comment

ID: 2212939
Hmm, assembler solutions are charming, but wht's the code for a PA-RISC or a SPARC (UN*X has been mentioned in the Q)?
0

LVL 1

Expert Comment

ID: 2213033
Well, I hope the code for some unix compiler could be something very similar to the code above because a stack is also used there as a container for function parameters and return address. The technology of code generation is also quite simple - just write couple of C functions, generate assembler listing, analyze it and - bingo. Even for the code above there are several quite obvious modifications that could make it at least to look more portable. :)
0

LVL 7

Expert Comment

ID: 2213408
the best way to keep it portable is to keep the solution entirely C++. Depending on the specifications, if there are only four different functions to take care of, use overloading. Otherwise an array with the arguments would do fine. Why making it so vulnerable with assembler or C's variable arguments?
0

Author Comment

ID: 2213944
abk, that's exactly what I wanted. Thank you very much! But I don't know much about assembly and C/C++'s calling convention. What is that result(0), starting at 0 address? What if I need to write a similar function for member function. Are they the same? Is there any book I can read to know more about these?
Also, thank you all who gave your time and help so generously!
0

LVL 9

Expert Comment

ID: 2214050
> abk, that's exactly what I wanted

you may think so, but I doubt it is really what you want.  Show me a commercial library that relies on a technique like this.

You have already stated that you want it to work on Unix and on PCs, and now you want to get it to work with methods.  Each case may require a completely different solution - you will end up with one type of solution for each platform for each problem.  In otherwords you will have a maintenance nightmare, and if you don't understand whats going on, how can you ever hope to fix it.

And BTW, there is no standard calling convention for C/C++, its compiler dependant.

Do yourself a big favour and write the answer in standard C++ using the same techniques that everybody else uses for things like this.
0

Author Comment

ID: 2214090
JasonDarke,Thank you for your comments. But I guess I have to do this because I just want to write something that can be easily used by basic C users. That is, these people have little experience in C programming and they don't know much. So I want to wrap all the stuff and let them use my functions as if they are using common C routines.
I know it's really not a very good idea to do things like that, but I guess I have to deal with it because this world wasn't born for programmers, instead, it was born for the users. They pay and they use and they want the dogs running like a horse, carrying stuff like a bull, singing like a canary and eating like a gold fish.
This is really a great idea to build a website like this. With experts and books around us, there's nothing that can't be done!
0

LVL 9

Expert Comment

ID: 2214298
I suggest that if your users are expected to write C code, but can't be expected to use simple C arrays, then you are going to have problems whatever you do...
0

LVL 7

Expert Comment

ID: 2214320
The easiest way to handle this is to pass the parameters in an array. Both for you and the users.

For the user:

double MyFunction(int n, int m, double* arg)
{
for(i = 0; i < n; ++i)
// handle all arg[i]
// Do something special with arg[m]
}

void UseProcess()
{
double args[3];
// set values and do stuff
Process(3, 2, Myfunction);
}

0

LVL 1

Expert Comment

ID: 2214330
> But I don't know much about assembly
> and C/C++'s calling convention.

Well, with time if you are serious about programming then it's better to know more. But you really don't need to know much to start using the technique. As I told, try to generate compiler's assembler listing in doubtful cases and analyze it carefully.

> What is that result(0), starting

No-no-no! :) It's just a double variable copy-constructed from the value 0. You could write "double result = 0;" or whatever you like.

> What if I need to write a similar
> function for member function.
> Are they the same?

If you have a good enough reason for doing this why not? Sometimes inline assembler combined with expressive C++ interface is a very powerful way to do things.

> Is there any book I can read
> to know more about these?

Well, there are a lot of books and other documents - assemblers, compilers design, reverse engineering, etc. It's difficult to cover everything, just follow your goals.

In addition I'd like to give you a little improved version of asm-process. It can call some f(void) function now - why not? If you need any additional comments about it feel free to ask. Here is the code:

#include <assert.h>

double Process(int n, int m, void* f, ...)
{
assert(m < n && f != NULL);
double result = 0;
_asm
{
cmp            f,NULL
je            Zero
mov            ecx, n
jcxz    Callf
shl     ecx, 3
lea            eax, m
Back:      push    ss:[eax+ecx]
push    ss:[eax+ecx+4]
sub            ecx, 8
jne            Back
Callf:      call      f
mov            ecx, n
shl     ecx, 3
fstp      result
Zero:
};
return result;
}
0

Author Comment

ID: 2214407
Thank you,abk!
What if I have to call functions with my array. like:
double Process(int n,void*f,...)
double *parameter;
parameter=new double[n];
....//do something to fill the parameter
call f

}

What modification I need to make?
Do I just change it to:
....
mov eax,parameter
sub eax,8
back: push [eax+ecx] //what's the seg?
push [eax+ecx+4]//or it's fine?
....

Actually I changed to this form and it doesn't seem to work well. Only your codes work.
0

LVL 1

Expert Comment

ID: 2214436
KangaRoo,

> the best way to keep it portable
> is to keep the solution entirely C++.

This is also quite obvious. The problem is I don't see here any C++ solution (including yours, sorry ) entirely satisfying to the specifications. And I don't think I have to push xu towards the spec changing according to C/C++ limitations. :) He's a boss and he could have own reasons which are good enough.

> Depending on the specifications, if
> there are only four different
> functions to take care of,

I don't think so. According to the spec there are undefined and unlimited number of function types various by the count of parameters.

> Otherwise an array with the arguments
> would do fine. Why making
> it so vulnerable with
> assembler or C's variable arguments?

Generally an assembler code isn't such vulnerable as you would think. And I don't necessarily feel unsafe having couple of assembler lines in my stuff. You wouldn't believe in this but sometimes this is the most stable and robust part of project versus enormous amount of junk C++ code.
0

Author Comment

ID: 2214451
Thank you,abk!
What if I have to call functions with my array. like:
double Process(int n,void*f,...)
double *parameter;
parameter=new double[n];
....//do something to fill the parameter
call f

}

What modification I need to make?
Do I just change it to:
....
mov eax,parameter
sub eax,8
back: push [eax+ecx] //what's the seg?
push [eax+ecx+4]//or it's fine?
....

Actually I changed to this form and it doesn't seem to work well. Only your codes work.
0

Author Comment

ID: 2214454
oops,
I thought to refresh the screen and it post my message again.
Sorry
0

Author Comment

ID: 2214486
abk,Here is my latest code:
============================
#include <stdio.h>
#include <stdarg.h>

double CallDlFun(int n,void*f,double *arg)
{
double result(0);
_asm{
mov ecx,n
jcxz zero
shl ecx,3
mov eax,arg
sub eax,8
back:   push [eax+ecx]
push [eax+ecx+4]
sub ecx,8
jne back
call f
mov ecx,n
shl ecx,3
fstp result
zero:
};
return result;
};

void Process(int n,void*f,...)
{
double *par;
par=new double[n];
va_list ap;
va_start(ap,f);
for (int i=0;i<n;i++)
{
par[i]=va_arg(ap,double);
printf("parameter %f\n",par[i]);
};
va_end(ap);
CallDlFun(n,f,par);
delete par;
};

double d1(double a)
{
printf("d1 %f\n",a);
return a;
};
double d2(double a,double b)
{
printf("d2 %f , %f\n",a,b);
return b;
};
double d4(double a,double b,double c,double d)
{
printf("d4 %f %f %f %f\n",a,b,c,d);
return a;
};
void main()
{
Process(1,d1,1.0);
Process(2,d2,1.0,2.0);
Process(4,d4,1.0,2.0,3.0,4.0);
char c;
scanf("%c",&c);
};

==================
What I'm trying to do is to handle functions written by user without modification to those functions.
All they need to do is call my Process like I did in the main.

But currently it's not working. I traced it and found that after I pushed all the arguments, and call f, the esp register is somehow changed by 4 bytes smaller. Maybe it push something else in it? I really don't know. Or maybe there's some double word alignment requirement?
I'm still trying, and hope it'll work.

I was trying to develop a set of classes to do mathematical stuff, like integrals, finding-root, etc. And the people who use my stuff is going to be some physics students. They want to write simple function they are interested and use integral or whatever. The reason we don't use matlab is because it involves a lot of computation which matlab will take a long time.
So I need to write something to support them without changing their functions.

Gotta go, I'll write to you later!
0

LVL 1

Expert Comment

ID: 2214539
> What if I have to call functions with my array. like:
> double Process(int n,void*f,...)
> double *parameter;

Ok.

> parameter=new double[n];

Bad idea, sorry. It's possible to follow this, but you need to know what are initial values for the parameter. So let's forget about new/delete for a while ( maybe we don't need them at all) and try a little different approach. As for segments in 32 mode inline you usually don't care about them. I just used ss: prefix to make accent we deal with a stack here. The code follows:

double Process(int n, int m, void* f, ...)
{
assert(m < n && f != NULL);
double result = 0;
double *parameter;
if (f != NULL)
{
_asm
{
mov            ecx, n
jcxz    Callf
shl     ecx, 3
lea            eax, m
Back:      push    ss:[eax+ecx]
push    ss:[eax+ecx+4]
sub            ecx, 8
jne            Back
Callf:      mov            parameter, esp
}

for (int i = 0; i < n; i++)
{
printf("\t[%d] = %8.2f\n",i,parameter[i]);
}

_asm
{
call      f
mov            ecx, n
shl     ecx, 3
fstp      result
};
}
return result;
}
0

LVL 7

Expert Comment

ID: 2214540
>> The problem is I don't see here any C++ solution

Am I missing something? I didn't put a single non C++ feature in my code. If you dislike function pointers, use classes and overload operator():

template<class F, int N>
double Process(int m, proc_args<N>& args, F f);

User creates:
struct MyFunc
{
double operator()(int,int,double*);     // or
template<int N>
double operator()(int m, proc_args<I>& args);
};
0

LVL 7

Expert Comment

ID: 2214582
>> ... but sometimes this is the most stable
>> and robust part of project ...
I bet its usually also a very small, and carefully deigned part.

Assembler just isn't portable, and the specs mentioned
>> ... solution for Win32 and Unix
Can you implement the assembler for every machine running Unix?

Why on earth make things difficult implementing a piece of assembler for a range of target machines. And can xu implement and maintain them?
0

LVL 1

Expert Comment

ID: 2214591
> But currently it's not working.
> I traced it and found that after
> I pushed all the arguments,
> and call f, the esp register is
> somehow changed by 4 bytes smaller.
> Maybe it push something else in it?
> I really don't know.

Congratulations, what you just discovered is part of the call command specification - return address is pushed in the stack. To use assembler you probably have to know this. And as I told you, be very very careful with this stuff.

> Or maybe there's some double word
> alignment requirement?

> I'm still trying, and hope it'll work.

Of course it will.

0

LVL 1

Expert Comment

ID: 2214645
KangaRoo,

> Can you implement the assembler for
> every machine running Unix?

No, I cannot. If this is the goal then the assembler solution just isn't acceptable. What I keep telling you that customer only knows WHAT is the goal. As for me this is just a curious task, sorry to hurt your feelings.

0

LVL 7

Expert Comment

ID: 2214659
>> They want to write simple function they are
>> interested and use integral or whatever

If you design your code as template users will be able to

template<class Function>
double integrate(double lower, double upper, Function f)
f(upper);
}

// User A:

struct MyFuncA
{
double operator()(double)
{// do stuff
}
};

int userA()
{
double result = integrate(1.0, 2.0, MyFuncA());
}

// User B:

double MyFuncB(double)
{ // do stuff
}

int userB()
{
double result = integrate(1.0, 2.0, MyFuncB);
}

// User C, makes a mistake and will get an error at compile time.

double MyFuncC(double, double)
{ // do stuff
}

int userC()
{
double result = integrate(1.0, 2.0, MyFuncC); // oops
}

0

LVL 7

Expert Comment

ID: 2214687
abk, you didn't hurt my feelings.
Originally xu specified Windows and Unix so for that reason I was surprised he accepted the assembler as solution.

There are various good and portable C++ solutions to this problem, and especially if it is xu's intentention to create a complete library of math function a carefull study of all the possible approaches would be in order.
0

LVL 1

Expert Comment

ID: 2214737
xu,

I'm sorry to say but I just found a bug in my code ( also in yours :). It's related to the parameter pushing order.
Now my function is:

double Process(int n, int m, void* f, ...)
{
assert(m < n && f != NULL);
double result = 0;
double *parameter;
if (f != NULL)
{
_asm
{
mov            ecx, n
jcxz    Callf
shl     ecx, 3
lea            eax, m
add            eax, 4 + SIZE m
Back:      sub            ecx, 8
push    ss:[eax+ecx+4]
push    ss:[eax+ecx]
jne            Back
Callf:      mov            parameter, esp
}

for (int i = 0; i < n; i++)
{
printf("\t[%d] = %8.2f\n",i,parameter[i]);
}

_asm
{
call      f
mov            ecx, n
shl     ecx, 3
fstp      result
};
}
return result;
}

And yours:

double CallDlFun(int n,void*f,double *arg)
{
double result(0);
_asm{
mov ecx,n
jcxz zero
shl ecx,3
mov eax,arg
sub eax,8
back:
push [eax+ecx+4]
push [eax+ecx]
sub ecx,8
jne back
call f
mov ecx,n
shl ecx,3
fstp result
zero:
};
return result;
};

double Process(int n,int m,void*f,...)
{
double *par;
par=new double[n];
va_list ap;
va_start(ap,f);
for (int i=0;i<n;i++)
{
par[i]=va_arg(ap,double);
printf("parameter %f\n",par[i]);
};
va_end(ap);
CallDlFun(n,f,par);
delete par;
return 0;
};

0

LVL 7

Expert Comment

ID: 2214785
>> sometimes this is the most stable part
emphasizing the word sometimes ;)
0

LVL 1

Expert Comment

ID: 2214880
jasonclarke,

>> abk, that's exactly what I wanted
>
> you may think so, but I doubt
> it is really what you want.
> Show me a commercial library
> that relies on a technique like this.

There definitely are some satellites flying in the sky that rely on a technique like this.:) As for commercial libraries I wouldn't make such a big deal with them. I can give you some quite impressive ( as for me ) practical example about commercial libraries. There is a solution that relies on collection template. And I recently made some simple performance comparison as for such template implementation in the MFC and Rogue Wave Tools.h++, which are quite popular. I could send you the source sample and here are just result counter values:

As for MS the program outputs the following info:

Totals:
^^^^^^
constructors             7
assignments              4
destructors              7

As for RW the output looks as below:

Totals:
^^^^^^
constructors            4611
assignments            323
destructors            4611

And I'm very far from the doubts as for RW developer qualification level - they are good enough. They just tried to follow the C++ conceptual point exactly. But sometimes the prize is just to high.

> You have already stated that you want
> it to work on Unix and on PCs,
> and now you want to get it to work
> with methods. Each case may require
> a completely different solution -
> you will end up with one type
> of solution for each platform
> for each problem. In otherwords
> you will have a maintenance nightmare,
> and if you don't understand
> whats going on, how can you ever
> hope to fix it.

These are serious reasons that have to be considered, of course.

> And BTW, there is no standard calling convention
> for C/C++, its compiler dependant.

Giving the solution I've mentioned compiler dependance.

0

LVL 1

Expert Comment

ID: 2214904
KangaRoo,

>> sometimes this is the most stable part
> emphasizing the word sometimes ;)

What is a problem? Are you impressed by the existence of some bug there? I'm ashamed to say it's not the first bug in my life and I'm afraid it's not the last one. :)
0

Author Comment

ID: 2215133
Hi, guys,
really impressed by all these comments.
The reason why I didn't use C++ solution for this one is that users don't follow my need, I follow them.
And users can surely write functions like:
double Gaussian(x,a,r);
for a Gaussian distribution:
g(x)=a*exp(-x*x/r^2)
And the forms they prefer put in are totally unpreditable.
They can even demand things like this:
for a function Gaussian(x,y,a,r1,r2)
integral on x and give them a function of y.
They don't care how I do my job, they just want to write down a function in their favorite ways and call my function to do the stuff they want. So it should be simple and direct, no matter how complicated my job will be. This is actually what's happening in this world. For example, customers don't care how bad the idea of compatibility is, they want win32 OS to be compatible with DOS and Win16. Microsoft surely provided a great solution, but basically speaking, all those virtual machine stuff is just not so good, since as far as I see, many games fail to run in it. And surely WinNT has more problems.
I'm glad that my problem raised a mini discussion. And I learned a lot from this one.
Thank you again for all your time, help and kindness!
0

LVL 1

Expert Comment

ID: 2215290
Hi xu,

Thank you for this problem it was kind of interesting to play with the toy. The people resisting to this approach certainly have serious arguments on their side but I'm not the person who's going to question your choice. As for me it's almost as safe as the known printf function is. But let me ask you the following question: How are you going to guarantee that ALL double parameters of your Process function are really double numerics? I mean if they call: Process(5, Gaussian, x,y,a,r1,0.0) it may by OK, but if they call: Process(5, Gaussian, x,y,a,0,0) then two last parameters will be just two integers in the stack and all system will fail. What do you think about this?

0

LVL 7

Expert Comment

ID: 2216224
abk, an single test one one ineffecient product (RW) doesn't proove that following C++ concepts produces inefficient programs. C++ has few features that allow more efficient code then C (I'll say nothing about assembler). As for efficiency, one of the methods I showed allows the user function to be inlined in the Process function.
Anyway that is a useless discussion, efficiency is usually better served with good algorithms and design then language features.

The var-args aproach makes it easier for users in one way; they are not 'forced' to correctly prototype their function. But this 'ease' of used is paid for, they will have to be very precise in calling your library functions with all the parameters placed and counted correctly. Basically the effort they have to make is moved, from precise definition of their functions to precise invocation of the library functions. Generally the latter is repeated more often....
Yes, we all still use printf, and we still make mistakes. Mistakes that only show up when program crashes. Luckely I have the experience to know what caused such crash, but will the users of your library have such skill? Even we would have to become familiar with it's crash-behaviour.

xu, the last problem you brought us has to do precisely with that. As far as I know, you can't do typechecking on var-args and desingning a typechecking system around var-args is basically redoing the job the compiler normally does. A C++ function
f(double);
will be called with a double as argument when called:
f(1);
but
g(...);
will get an integer from
g(1);
0

LVL 1

Expert Comment

ID: 2217129
KangaRoo,

> abk, an single test one inefficient product (RW)

Would you like to inspect the code?

> doesn't proove that following C++ concepts
> produces inefficient programs.

I'm not against C++, I use it as major programming language these days. I'm just a little skeptical about the idea of using commercial libraries code as a paragon in all cases. Blind following any single set of concepts produces inefficiency. Programmers knew that even years before C++ was created.

> C++ has few features that allow more efficient code then C
> (I'll say nothing about assembler).

Would you mind to list the features?

> As for efficiency, one of the methods I showed
> allows the user function to be inlined in the Process function.
> Anyway that is a useless discussion, efficiency is usually better
> served with good algorithms and design then language features.

Sure thing. And a limited assembler usage (in appropriate cases) may even bring along some algorithmical freedom (not referring to en efficiency).

> The var-args approach ...
>[...]

O yes! That's why this case doesn't seem to be appropriate for my solution. Not because of assembly but because of ellipsis notation in the function header specified from the beginning:  double Process(int n,int m,double f(?),...); And I feel a little guilty as an author of the solution. So let me also join your CPP solutions club and suggest the code below.

xu,

I'd like to suggest one more solution that doesn't fit to your spec ( as ellipsis and unlimited parameter number ) but I think it is good enough for your problem. The idea is very simple - just set up some reasonable maximum value for the count of parameters. The code demonstrates solution for 7 parameters. You can make it 16,32,etc if you need. Writing a simple code-generator you could make it 128 and more, but who's going to need it? There are couple of specific features:

1) Users enjoy writing their favorite c-like functions.
2) In addition they enjoy type safety and early ( compilation time ) errors detection.
3) They don't need to provide the count of parameter for the Process any more.
4) There is no "switch" statement in the code. The implementation is based on so called pointers to member usage.

Here is the code:
0

LVL 1

Expert Comment

ID: 2217133
#include <stdio.h>

/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
// client function types
typedef double (*f0_t)();
typedef double (*f1_t)(double);
typedef double (*f2_t)(double,double);
typedef double (*f3_t)(double,double,double);
typedef double (*f4_t)(double,double,double,double);
typedef double (*f5_t)(double,double,double,double,double);
typedef double (*f6_t)(double,double,double,double,double,double);
typedef double (*f7_t)(double,double,double,double,double,double,double);

/////////////////////////////////////////////////////////////////////////////
// Process functions
double Process(f0_t);
double Process(f1_t,double);
double Process(f2_t,double,double);
double Process(f3_t,double,double,double);
double Process(f4_t,double,double,double,double);
double Process(f5_t,double,double,double,double,double);
double Process(f6_t,double,double,double,double,double,double);
double Process(f7_t,double,double,double,double,double,double,double);

/////////////////////////////////////////////////////////////////////////////
// implementation file
template <size_t N> class CObj
{
public:
CObj(f0_t f0) : m_nCount(N), m_call(call_f0), m_f0(f0) {}
CObj(f1_t f1) : m_nCount(N), m_call(call_f1), m_f1(f1) {}
CObj(f2_t f2) : m_nCount(N), m_call(call_f2), m_f2(f2) {}
CObj(f3_t f3) : m_nCount(N), m_call(call_f3), m_f3(f3) {}
CObj(f4_t f4) : m_nCount(N), m_call(call_f4), m_f4(f4) {}
CObj(f5_t f5) : m_nCount(N), m_call(call_f5), m_f5(f5) {}
CObj(f6_t f6) : m_nCount(N), m_call(call_f6), m_f6(f6) {}
CObj(f7_t f7) : m_nCount(N), m_call(call_f7), m_f7(f7) {}

double Process();
double Call() { return (this->*m_call)(); }

double call_f0();
double call_f1();
double call_f2();
double call_f3();
double call_f4();
double call_f5();
double call_f6();
double call_f7();

// data members
const size_t      m_nCount;
double                  m_p[N+1];
double      (CObj::*m_call)();

union
{
f0_t            m_f0;
f1_t            m_f1;
f2_t            m_f2;
f3_t            m_f3;
f4_t            m_f4;
f5_t            m_f5;
f6_t            m_f6;
f7_t            m_f7;
};
};

/////////////////////////////////////////////////////////////////////////////
//
template <size_t N> double CObj<N>::Process()
{
for (size_t i = 0; i < m_nCount; i++)
{
m_p[i] *= 2;
printf("\t[%d]=%8.2f\n",i,m_p[i]);
}
return Call();
}

template <size_t N> double CObj<N>::call_f0()
{ return m_f0(); }

template <size_t N> double CObj<N>::call_f1()
{ return m_f1(m_p[0]); }

template <size_t N> double CObj<N>::call_f2()
{ return m_f2(m_p[0],m_p[1]); }

template <size_t N> double CObj<N>::call_f3()
{ return m_f3(m_p[0],m_p[1],m_p[2]); }

template <size_t N> double CObj<N>::call_f4()
{ return m_f4(m_p[0],m_p[1],m_p[2],m_p[3]); }

template <size_t N> double CObj<N>::call_f5()
{ return m_f5(m_p[0],m_p[1],m_p[2],m_p[3],m_p[4]); }

template <size_t N> double CObj<N>::call_f6()
{ return m_f6(m_p[0],m_p[1],m_p[2],m_p[3],m_p[4],m_p[5]); }

template <size_t N> double CObj<N>::call_f7()
{ return m_f7(m_p[0],m_p[1],m_p[2],m_p[3],m_p[4],m_p[5],m_p[6]); }

/////////////////////////////////////////////////////////////////////////////
// Process functions implementation
double Process(f0_t f0)
{
CObj<0> obj(f0);
return obj.Process();
}

double Process(f1_t f1,double p1)
{
CObj<1> obj(f1);
obj.m_p[0] = p1;
return obj.Process();
}

double Process(f2_t f2,double p1,double p2)
{
CObj<2> obj(f2);
obj.m_p[0] = p1;
obj.m_p[1] = p2;
return obj.Process();
}

double Process(f3_t f3,double p1,double p2,double p3)
{
CObj<3> obj(f3);
obj.m_p[0] = p1;
obj.m_p[1] = p2;
obj.m_p[2] = p3;
return obj.Process();
}

double Process(f4_t f4,double p1,double p2,double p3,double p4)
{
CObj<4> obj(f4);
obj.m_p[0] = p1;
obj.m_p[1] = p2;
obj.m_p[2] = p3;
obj.m_p[3] = p4;
return obj.Process();
}

double Process(f5_t f5,double p1,double p2,double p3,double p4,double p5)
{
CObj<5> obj(f5);
obj.m_p[0] = p1;
obj.m_p[1] = p2;
obj.m_p[2] = p3;
obj.m_p[3] = p4;
obj.m_p[4] = p5;
return obj.Process();
}

double Process(f6_t f6,double p1,double p2,double p3,double p4,double p5,double p6)
{
CObj<6> obj(f6);
obj.m_p[0] = p1;
obj.m_p[1] = p2;
obj.m_p[2] = p3;
obj.m_p[3] = p4;
obj.m_p[4] = p5;
obj.m_p[5] = p6;
return obj.Process();
}

double Process(f7_t f7,double p1,double p2,double p3,double p4,double p5,double p6,double p7)
{
CObj<7> obj(f7);
obj.m_p[0] = p1;
obj.m_p[1] = p2;
obj.m_p[2] = p3;
obj.m_p[3] = p4;
obj.m_p[4] = p5;
obj.m_p[5] = p6;
obj.m_p[6] = p7;
return obj.Process();
}

/////////////////////////////////////////////////////////////////////////////
// Demo
double dpr1(double d1)
{
printf("dpr1(%8.2f);\n", d1);
return d1;
}

double dpr2(double d1, double d2)
{
printf("dpr2(%8.2f,%8.2f);\n", d1, d2);
return d1+d2;
}

double dpr3(double d1, double d2, double d3)
{
printf("dpr3(%8.2f,%8.2f,%8.2f);\n", d1, d2, d3);
return d1+d2+d3;
}

int main()
{
printf("Process1: %8.2f\n",Process(dpr1, 1));
printf("Process2: %8.2f\n",Process(dpr2, 1, 2));
printf("Process3: %8.2f\n",Process(dpr3, 1, 2, 3));
return 0;
}
0

LVL 7

Expert Comment

ID: 2217212
I agree, blindly following any concept is 'stupid' (and not only in programming)

>> Would you mind to list the features?
templates
inline functions
inline assembly ;)
0

LVL 1

Expert Comment

ID: 2217357
>> Would you mind to list the features?

> templates
> inline functions
> inline assembly ;)

Hmm. The last two may be also considered as C features. I see nothing CPP-specific about them. As for templates they may be also extremely inefficient. And most of their current implementations are too far from perfect. And I'm not happy with a class implementation located in a header file.

0

LVL 7

Expert Comment

ID: 2217426
I thought inline (functions/assembly) were not ANSI C?

True, incorrect use of templates can bloat code size. Well, bad use of anything may cause severe inefficiency. And current implementation are indeed inperfect, otherwise your last argument would not hold true. Personally, I have no objections against implementation in a header, or #included in a header.
0

LVL 1

Expert Comment

ID: 2217503
> I thought inline (functions/assembly) were not ANSI C?

Yes, of course you are right from formal point of view. But inlines are not directly related to OO C-language extension that is a core of the C++ IMHO.
0

Author Comment

ID: 2218671
Thank you,abk and Kangaroo,
I understand most of your comments and I still kinda prefer the asembly solution. It's short and cute. Maybe it doesn't work for both, but I still can do separate stuff for different platforms.
For interesting problems like this, it's really great to have all these different solutions.
Using a code generator to generate the lengthy codes for different types isn't too bad an idea. Actually, there might be a way to do dynamic generation and compilation/interpretation. Well, that's also a sacrifice on the speed side which is one of the original reasons we started using C/C++ instead of matlab.
But I've been wondering, why C library provides the variable argument input method (va_list,va_start,...), but it doesn't support the variable argument output. I don't know much about the history, but I wish they had this stuff. Maybe people can simply make some reliable assembly code for that and put into a standard library. I don't know much about C compiler, but I assume the C libraries on different platforms were developed with different assembly codes for the platforms. So what's the big deal of having a different code solution for different platform. Well, it may involve the stardardization process, but are things changing all the time? TCP/IP protocols can change, why the standard C libraries can't.
I'm just a graduate student, my comments may be stupid in the eyes all of you pros, but that's why I came here, the experts-exchange.
Thank you again for your help!

0

LVL 7

Expert Comment

ID: 2219400
>> the C libraries on different platforms were developed with different
>> assembly codes for the platforms
C library functions may be written in C, which makes them portable. Some parts will usually be assembly and need to be speciall written for each OS. Besides, very few compiler manufacturers support multiple platforms (I know of one: gcc)

The main disadvantages of using assembly is that you have to apply modifications to the functions for each platform. If you change a function, you actually have to change it for each platform you are targetting. Each function your library presents has to be maintained for each platform. It multiplies all the work involved in devolopping and maintaing the library by the number of platforms you are targetting.

Your comments are not stupid and the approach seems motivated from the will to serve your clients well.
I (we) just doubt the usefulness of the var-args effort. You are facing a big task with this, while your clients recieve a (probably) small benefit; being able to use their MySpecialFunction(double, double, double, double, double); directly into YourMathProcess(int, int, ...) functions. The price that they have to pay is giving meticulous and precise attention to calling your lib functions. As with errors in using printf, they will probably get totally irrelevant looking run time errors (forgetting an argument in printf sometimes resulted in 'Floating point formats not linked' with BC 3.1). Most alternatives will require them to write a simple wrapper around their precious functions. They will have to do that anyway if their MySpecialFunc has non-double arguments like MySpecialFunction(int, char*, double, double[], int, double);
0

LVL 3

Expert Comment

ID: 2220314
In short, its real bad design... and for what?
Easy of functioncalling? In a good design you might have to type a bit more code, but you get many benefits from it: reusabillity, easy maintanance, strong typechecking, etc...
I for sure wouldn't buy your library if I knew you used this kind of techniques...
Look at STL, many times code could be shorter if they chosen dirty tricks like you do... but it wouldn't nearly be as powerfull and usefull.
But he, have fun! I noticed there is a great difference in developing software as hobby, and as a job. I spend 70% of my time documenting, motivating and testing my design and code, actual coding is only 30% of my time... Things like testabillity, reusabillity and maintainabillity become the key issues then.
But it's weekend now :-) See, you all on monday.
Luc
0

LVL 1

Expert Comment

ID: 2220349
xu,

> and I still kinda prefer the asembly
> solution. It's short and cute.

Sorry, but it seems you've ignored the question: What are you going to do with possible type errors in your var-arguments list? You just cannot go ahead leaving the question like this unanswered. Just imagine, it's not going to look as your library runtime error (crash or something). It's going to look exactly as a bug in user defined functions. Your customers will spend hours looking for a black cat in dark room. :) After that they will blame you for the trouble. Not everything short and cute is eatable after all.

BTW, I reviewed my C++ approach  and striped almost all C++ crap out of it. Why wouldn't you generate as many functions as you like and forget about the problem? The simpler the better. Here is the code:
0

LVL 1

Expert Comment

ID: 2220351
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
// function types
typedef double (*f0_t)();
typedef double (*f1_t)(double);
typedef double (*f2_t)(double,double);
typedef double (*f3_t)(double,double,double);
typedef double (*f4_t)(double,double,double,double);
typedef double (*f5_t)(double,double,double,double,double);
typedef double (*f6_t)(double,double,double,double,double,double);
typedef double (*f7_t)(double,double,double,double,double,double,double);

/////////////////////////////////////////////////////////////////////////////
// Process functions
double Process(size_t m,f0_t);
double Process(size_t m,f1_t,double);
double Process(size_t m,f2_t,double,double);
double Process(size_t m,f3_t,double,double,double);
double Process(size_t m,f4_t,double,double,double,double);
double Process(size_t m,f5_t,double,double,double,double,double);
double Process(size_t m,f6_t,double,double,double,double,double,double);
double Process(size_t m,f7_t,double,double,double,double,double,double,double);

/////////////////////////////////////////////////////////////////////////////
// implementation
static void proc(size_t n, size_t m, double*const d[])
{
for (size_t i = 0; i < n; i++)
{
*d[i] *= 2;
printf("\t[%d]=%8.2f\n",i,*d[i]);
}
}

double Process(size_t m,f0_t f0)
{
proc(0, m, NULL);
return f0();
}

double Process(size_t m,f1_t f1,double p1)
{
double* dp[1]={&p1};
proc(1, m, dp);
return f1(p1);
}

double Process(size_t m,f2_t f2,double p1,double p2)
{
double* dp[2]={&p1,&p2};
proc(2, m, dp);
return f2(p1,p2);
}

double Process(size_t m,f3_t f3,double p1,double p2,double p3)
{
double* dp[3]={&p1,&p2,&p3};
proc(3, m, dp);
return f3(p1,p2,p3);
}

double Process(size_t m,f4_t f4,double p1,double p2,double p3,double p4)
{
double* dp[4]={&p1,&p2,&p3,&p4};
proc(4, m, dp);
return f4(p1,p2,p3,p4);
}

double Process(size_t m,f5_t f5,double p1,double p2,double p3,double p4,double p5)
{
double* dp[5]={&p1,&p2,&p3,&p4,&p5};
proc(5, m, dp);
return f5(p1,p2,p3,p4,p5);
}

double Process(size_t m,f6_t f6,double p1,double p2,double p3,double p4,double p5,double p6)
{
double* dp[6]={&p1,&p2,&p3,&p4,&p5,&p6};
proc(6, m, dp);
return f6(p1,p2,p3,p4,p5,p6);
}

double Process(size_t m,f7_t f7,double p1,double p2,double p3,double p4,double p5,double p6,double p7)
{
double* dp[7]={&p1,&p2,&p3,&p4,&p5,&p6,&p7};
proc(7, m, dp);
return f7(p1,p2,p3,p4,p5,p6,p7);
}

/////////////////////////////////////////////////////////////////////////////
// Demo
double dpr1(double d1)
{
printf("dpr1(%8.2f);\n", d1);
return d1;
}

double dpr2(double d1, double d2)
{
printf("dpr2(%8.2f,%8.2f);\n", d1, d2);
return d1+d2;
}

double dpr3(double d1, double d2, double d3)
{
printf("dpr3(%8.2f,%8.2f,%8.2f);\n", d1, d2, d3);
return d1+d2+d3;
}

int main()
{
printf("Process1: %8.2f\n",Process(0, dpr1, 1));
printf("Process2: %8.2f\n",Process(0, dpr2, 1, 2));
printf("Process3: %8.2f\n",Process(0, dpr3, 1, 2, 3));
return 0;
}
0

Author Comment

ID: 2220446
abk,
Sorry about the type thing. I don't have any solution for that, maybe just ask the customer to type 1.0 instead of 1 or ask them to use variables. I'll make a function wrapper for the customer functions and do most of the stuff with C++ for the integration and other stuff. I called the function with more variables than it needs and it seems to work fine.
Testing is an issue, I'll put more stuff in the wrapper class to make sure everything works fine.  For things like type, I'll put some TRACE lines, so that they know what's going on.
As to the commercial issues, I will just put the library as a shareware. It's not really that ugly, it's just not standard.
0

LVL 1

Expert Comment

ID: 2220577
> maybe just ask the customer to type 1.0

IMHO these are VERY serious limitations without any good reason for doing this.
0

Author Comment

ID: 2220598
I can't think of any way to solve this one. Does that mean I have to give up all these stuff just because of this small pit?
0

LVL 1

Expert Comment

ID: 2221166
Sometimes it happens this way. Just consider some other options. For example, let's criticize the generation approach. Tell me, please, whatever bad could you ( or anybody else here ) see about it form customer's point of view or from any other point?
0

## Featured Post

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Errors will happen. It is a fact of life for the programmer. How and when errors are detected have a great impact on quality and cost of a product. It is better to detect errors at compile time, when possible and practical. Errors that make their waâ€¦
Looking for a way to avoid searching through large data sets for data that doesn't exist? A Bloom Filter might be what you need. This data structure is a probabilistic filter that allows you to avoid unnecessary searches when you know the data definâ€¦
The goal of the video will be to teach the user the concept of local variables and scope. An example of a locally defined variable will be given as well as an explanation of what scope is in C++. The local variable and concept of scope will be relatâ€¦
The viewer will learn how to use the return statement in functions in C++. The video will also teach the user how to pass data to a function and have the function return data back for further processing.
###### Suggested Courses
Course of the Month11 days, 11 hours left to enroll