C |可变参数函数的实现及其不安全性

可变参数函数头的一般格式:

return_type function_name(argument_list, ...)

最右边是省略号“...”,省略号“...”的左边必须至少有一形参,必须由靠近省略号“...”的形参来识别省略号“...”表示的形参数量,可以是以下类型:

a 表示长度的int类型;

b 表示哨兵类型的int类型;

c 表示参数类型及数量的字符串,通过解析字符串的字符来解析以后的参数数量及类型;

1 传递一个表示长度的int类型参数

#include <iostream>#include <cstdarg> // needed to use ellipsis // The ellipsis must be the last parameter// count is how many additional arguments we're passingdouble findAverage(int count, ...){ double sum = 0;  // We access the ellipsis through a va_list, so let's declare one va_list list;  // We initialize the va_list using va_start. The first parameter is // the list to initialize. The second parameter is the last non-ellipsis // parameter. va_start(list, count);  // Loop through all the ellipsis arguments for (int arg=0; arg < count; ++arg) // We use va_arg to get parameters out of our ellipsis // The first parameter is the va_list we're using // The second parameter is the type of the parameter sum += va_arg(list, int);  // Cleanup the va_list when we're done. va_end(list);  return sum / count;} int main(){ std::cout << findAverage(6, 1, 2, 3, 4, 5, 6) << '\n'; // 3.5 std::cout << findAverage(6, 1.0, 2, 3, 4, 5, 6) << '\n'; // 1.79766e+008system('pause');return 0;}

在头文件stdarg定义了四个宏:va_list, va_arg, va_start, and va_end,可结合上面的实例并结合栈表示如下:

看汇编代码:

对于findAverage(6, 1.0, 2, 3, 4, 5, 6),并没有得到我们期望的结果,这就是因为其无法完成数据类型的检查,自然也无法做隐式数据类型转换,看汇编代码:

在2和6之间压入了3FF00000h和0

00401630 push 2

00401632 push 3FF00000h

00401637 push 0

00401639 push 6

按照IEEE754浮点数算数标准,double浮点数字长64位,一位是符号位,指数长度11,指数偏移量1023,尾数长度52;1.0指数位是0,偏移1023就是10个1,其它位都是0:

0 0 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

分成两个32位,后面的32位是0,前面的二进制位用16进制表示成整数就是:3FF00000h,除以6后就得到1.79766e+008。

以上就是可变参数的优势和不安全性。

因为是用宏实现的,所以没有类型检查,且参数数量需要通过其它参数去判断。

2 传递一个哨兵值参数

#include <iostream>#include <cstdarg> // needed to use ellipsis // The ellipsis must be the last parameterdouble findAverage(int first, ...){// We have to deal with the first number speciallydouble sum = first; // We access the ellipsis through a va_list, so let's declare oneva_list list; // We initialize the va_list using va_start. The first parameter is// the list to initialize. The second parameter is the last non-ellipsis// parameter.va_start(list, first); int count = 1;// Loop indefinitelywhile (1){// We use va_arg to get parameters out of our ellipsis// The first parameter is the va_list we're using// The second parameter is the type of the parameterint arg = va_arg(list, int); // If this parameter is our sentinel value, stop loopingif (arg == -1)break; sum += arg;count++;} // Cleanup the va_list when we're done.va_end(list); return sum / count;} int main(){std::cout << findAverage(1, 2, 3, 4, 5, -1) << '\n';std::cout << findAverage(1, 2, 3, 4, 5, 6, -1) << '\n';}

3 传递一个字符串参数

#include <iostream>#include <string>#include <cstdarg> // needed to use ellipsis // The ellipsis must be the last parameterdouble findAverage(std::string decoder, ...){double sum = 0; // We access the ellipsis through a va_list, so let's declare oneva_list list; // We initialize the va_list using va_start. The first parameter is// the list to initialize. The second parameter is the last non-ellipsis// parameter.va_start(list, decoder); int count = 0;// Loop indefinitelywhile (1){char codetype = decoder[count];switch (codetype){default:case '\0':// Cleanup the va_list when we're done.va_end(list);return sum / count; case 'i':sum += va_arg(list, int);count++;break; case 'd':sum += va_arg(list, double);count++;break;}}} int main(){std::cout << findAverage('iiiii', 1, 2, 3, 4, 5) << '\n';std::cout << findAverage('iiiiii', 1, 2, 3, 4, 5, 6) << '\n';std::cout << findAverage('iiddi', 1, 2, 3.5, 4.5, 5) << '\n';}

针对以上的不安全性,一般互联网公司偏向于尽量不用可变参数的函数,而用动态数组或variadic模板来代替。以上三种方式,第一种、第三种相对来说较安全。

-End-

(0)

相关推荐