禁用可变参数列表
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