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 passing
double 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+008
\tsystem("pause");
\treturn 0;
}
/<cstdarg>/<iostream>

在頭文件stdarg定義了四個宏:va_list, va_arg, va_start, and va_end,可結合上面的實例並結合棧表示如下:

C++|可變參數函數的實現及其不安全性

看彙編代碼:

C++|可變參數函數的實現及其不安全性

對於findAverage(6, 1.0, 2, 3, 4, 5, 6),並沒有得到我們期望的結果,這就是因為其無法完成數據類型的檢查,自然也無法做隱式數據類型轉換,看彙編代碼:

C++|可變參數函數的實現及其不安全性

在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。

C++|可變參數函數的實現及其不安全性

以上就是可變參數的優勢和不安全性。

因為是用宏實現的,所以沒有類型檢查,且參數數量需要通過其它參數去判斷。

2 傳遞一個哨兵值參數

#include <iostream>
#include <cstdarg> // needed to use ellipsis

// The ellipsis must be the last parameter
double findAverage(int first, ...)
{
\t// We have to deal with the first number specially
\tdouble sum = first;

\t// We access the ellipsis through a va_list, so let's declare one
\tva_list list;

\t// We initialize the va_list using va_start. The first parameter is
\t// the list to initialize. The second parameter is the last non-ellipsis
\t// parameter.
\tva_start(list, first);

\tint count = 1;
\t// Loop indefinitely
\twhile (1)
\t{
\t\t// We use va_arg to get parameters out of our ellipsis
\t\t// The first parameter is the va_list we're using
\t\t// The second parameter is the type of the parameter
\t\tint arg = va_arg(list, int);

\t\t// If this parameter is our sentinel value, stop looping
\t\tif (arg == -1)
\t\t\tbreak;

\t\tsum += arg;
\t\tcount++;
\t}

\t// Cleanup the va_list when we're done.
\tva_end(list);

\treturn sum / count;
}

int main()
{
\tstd::cout << findAverage(1, 2, 3, 4, 5, -1) << '\\n';
\tstd::cout << findAverage(1, 2, 3, 4, 5, 6, -1) << '\\n';
}

/<cstdarg>/<iostream>

3 傳遞一個字符串參數

#include <iostream>
#include <string>
#include <cstdarg> // needed to use ellipsis

// The ellipsis must be the last parameter
double findAverage(std::string decoder, ...)
{
\tdouble sum = 0;

\t// We access the ellipsis through a va_list, so let's declare one
\tva_list list;

\t// We initialize the va_list using va_start. The first parameter is
\t// the list to initialize. The second parameter is the last non-ellipsis
\t// parameter.
\tva_start(list, decoder);

\tint count = 0;
\t// Loop indefinitely
\twhile (1)
\t{
\t\tchar codetype = decoder[count];
\t\tswitch (codetype)
\t\t{
\t\tdefault:
\t\tcase '\\0':
\t\t\t// Cleanup the va_list when we're done.
\t\t\tva_end(list);
\t\t\treturn sum / count;

\t\tcase 'i':
\t\t\tsum += va_arg(list, int);
\t\t\tcount++;
\t\t\tbreak;

\t\tcase 'd':
\t\t\tsum += va_arg(list, double);
\t\t\tcount++;
\t\t\tbreak;
\t\t}
\t}
}
\t

int main()
{
\tstd::cout << findAverage("iiiii", 1, 2, 3, 4, 5) << '\\n';
\tstd::cout << findAverage("iiiiii", 1, 2, 3, 4, 5, 6) << '\\n';
\tstd::cout << findAverage("iiddi", 1, 2, 3.5, 4.5, 5) << '\\n';
}
/<cstdarg>/<string>/<iostream>

針對以上的不安全性,一般互聯網公司偏向於盡量不用可變參數的函數,而用動態數組來代替。以上三種方式,第一種、第三種相對來說較安全。

-End-


分享到:


相關文章: