C |可变参数函数的实现及其不安全性
可变参数函数头的一般格式:
最右边是省略号“...”,省略号“...”的左边必须至少有一形参,必须由靠近省略号“...”的形参来识别省略号“...”表示的形参数量,可以是以下类型:
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 传递一个哨兵值参数
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-