禁用可变参数列表
6.4.2 ID_forbidVariadicFunction
可变参数列表对参数的类型和数量缺乏有效的限定和控制,是公认的不安全因素。
示例:
string format(const char* fmt, ...); // Non-compliant
假设 format 函数与 sprintf 函数功能相似,由参数 fmt 设定格式,将其他参数转为字符串后依次替换 fmt 中的占位符并返回结果。设 @ 和 $ 为占位符,分别对应字符串和整数,如调用 format("@: $", "value", 123) 则返回字符串 "value: 123"。
如果用可变参数列表实现:
string format(const char* fmt, ...) {
va_list ap;
stringstream res;
va_start(ap, fmt);
for (auto* c = fmt; *c; c++) {
switch (*c) {
case '@': res << va_arg(ap, char*); break;
case '$': res << va_arg(ap, int); break;
default: res << *c; break;
}
}
va_end(ap);
return res.str();
}
例中 va_start、va_arg、va_end 是可变参数列表的标准支持,这种方法只能在运行时以 fmt 为依据获取后续参数,当实际参数与 fmt 不符时会造成严重问题,单纯地要求开发者小心谨慎是不可靠的,改用更安全的方法才是明智的选择。
在 C++ 代码中应使用“模板参数包”代替可变参数列表,如:
template <class T>
void get_argstrs(vector<string>& vs, const T& arg) {
ostringstream oss;
oss << arg;
vs.emplace_back(oss.str());
}
template <class T, class ...Args>
void get_argstrs(vector<string>& vs, const T& arg, const Args& ...rest) {
get_argstrs(vs, arg);
get_argstrs(vs, rest...); // Parameter pack expansion
}
template <class ...Args>
string format(const char* fmt, const Args& ...args) { // Compliant
string res;
vector<string> vs;
get_argstrs(vs, args...);
auto it = vs.begin();
for (auto* c = fmt; *c; c++) {
if ((*c == '@' || *c == '$') && it != vs.end())
res.append(*it++);
else
res.push_back(*c);
}
return res;
}
例中 ...args 是参数包,可以代替可变参数列表,get_argstrs 函数利用重载和递归将参数都转为 string 对象存入容器,再将 fmt 中的占位符依次替换成容器中的字符串。这种实现可以不区分 @ 和 $,参数的个数和类型可以由代码主动判断,如果参数不能转为字符串则不会通过编译,如果参数个数与占位符不符也容易作出处理。
从 C++17 开始,可利用“折叠表达式”简化 get_argstrs 函数的实现:
template <class ...Args>
void get_argstrs(vector<string>& vs, const Args& ...args) {
(
[&vs, &args]() {
ostringstream oss;
oss << args;
vs.emplace_back(oss.str());
}(), ...
); // Fold expression
}
例中 lambda 表达式和 ... 组成折叠表达式,可以免去重载和递归,化简参数包的展开。
相关
依据
ISO/IEC 14882:2003 5.2.2(7)-undefined
ISO/IEC 14882:2011 5.2.2(7)-implementation
ISO/IEC 14882:2011 14.5.3
ISO/IEC 14882:2017 8.1.6
参考
C++ Core Guidelines ES.34
C++ Core Guidelines F.55
MISRA C 2004 16.1
MISRA C 2012 17.1
MISRA C++ 2008 8-4-1
SEI CERT DCL50-CPP