Argument lists

C++ inherits from C the idea of argument lists. To do this, you use the ellipses syntax (...) as the last parameter to indicate that the caller can provide zero or more parameters. The compiler will check how the function is called and will allocate space on the stack for these extra parameters. To access the extra parameters, your code must include the <cstdarg> header file, which has macros that you can use to extract the extra parameters off the stack.

This is inherently type-unsafe because the compiler cannot check that the parameters that the function will get off the stack at runtime will be the same type as the parameters put on the stack by the calling code. For example, the following is an implementation of a function that will sum integers:

    int sum(int first, ...) 
{
int sum = 0;
va_list args;
va_start(args, first);
int i = first;
while (i != -1)
{
sum += i;
i = va_arg(args, int);
}
va_end(args);
return sum;
}

The definition of the function must have at least one parameter so that the macros work; in this case the parameter is called first. It is important that your code leaves the stack in a consistent state and this is carried out using a variable of the va_list type. This variable is initialized at the beginning of the function by calling the va_start macro and the stack is restored to its previous state at the end of the function by calling the va_end macro.

The code in this function simply iterates through the argument list, and maintains a sum, and the loop finishes when the parameter has a value of -1. There are no macros to give information about how many parameters there are on the stack, nor are there any macros to give an indication of the type of the parameter on the stack. Your code has to assume the type of the variable and provide the desired type in the va_arg macro. In this example, va_arg is called, assuming that every parameter on the stack is an int.

Once all parameters have been read off the stack, the code calls va_end before returning the sum. The function can be called like this:

    cout << sum(-1) << endl;                       // 0 
cout << sum(-6, -5, -4, -3, -2, -1) << endl; // -20 !!!
cout << sum(10, 20, 30, -1) << endl; // 60

Since -1 is used to indicate the end of the list, it means that to sum a zero number of parameters, you have to pass at least one parameter, that is -1. In addition, the second line shows that you have a problem if you are passing a list of negative numbers (in this case -1 cannot be a parameter). This problem could be addressed in this implementation by choosing another marker value.

Another implementation could eschew the use of a marker for the end of the list, and instead use the first, required, argument to give the count of the parameters that follow:

    int sum(int count, ...) 
{
int sum = 0;
va_list args;
va_start(args, count);
while(count--)
{
int i = va_arg(args, int);
sum += i;
}
va_end(args);
return sum;
}

This time, the first value is the number of arguments that follow, and so the routine will extract this exact number of integers off the stack and sum them. The code is called like this:

    cout << sum(0) << endl;                         // 0 
cout << sum(6, -6, -5, -4, -3, -2, -1) << endl; // -21
cout << sum(3, 10, 20, 30) << endl; // 60

There is no convention for how to handle the issue of determining how many parameters have been passed.

The routine assumes that every item on the stack is an int, but there is no information about this in the prototype of the function, so the compiler cannot do type checking on the parameters actually used to call the function. If the caller provides a parameter of a different type, the wrong number of bytes could be read off the stack, making the results of all the other calls to va_arg invalid. Consider this:

    cout << sum(3, 10., 20, 30) << endl;

It is easy to press both, the comma and period keys, at the same time, and this has happened after typing the 10 parameter. The period means that the 10 is a double, and so the compiler puts a double value on the stack. When the function reads values off the stack with the va_arg macro, it will read the 8-byte double as two 4-byte int values and for code produced by Visual C++ this results in a total sum of 1076101140. This illustrates the type unsafe aspect of argument lists: you cannot get the compiler to do type checks of the parameters passed to the function.

If your function has different types passed to it then you have to implement some mechanism to determine what those parameters are. A good example of argument lists is the C printf function:

    int printf(const char *format, ...);

The required parameter of this function is a format string, and importantly this has an ordered list of variable parameters and their types. The format string provides the information that is not available via the <cstdarg> macros: the number of variable parameters and the type of each one. The implementation of the printf function will iterate through the format string and when it comes across a format specifier for a parameter (a character sequence starting with %) it will read the expected type off the stack with va_arg. It should be clear that C-style argument lists are not as flexible as they appear on first sight; moreover, they can be quite dangerous.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset