今天来分享一个很久之前的bug。
把项目中的代码使用另一种方式来表述,大致是这样的:
#include <iostream> #include <cstring> #include <cstdlib> void fun(std::size_t size){ char buffer[size]; std::memset(buffer, 0, size); // 防止优化掉 std::cout << buffer << std::endl; } int main(int argc, char* argv[]){ if (argc < 2) { std::cerr << "Usage: ./a.out <size>\n"; return 1; } std::size_t size = std::stoul(argv[1]); fun(size); return 0; }这段代码一直运行的很好,直至某一天,传入了一个合理的值(超过默认栈大小),之后程序开始崩溃,出现 segmentation fault。原因是:代码试图在栈上分配过大的内存。
当然了,修复方式也很简单,无非是以下几种:
• std::vector v(size)
• std::string s(size, 0)
• std::unique_ptr<char[]>(new char[size])
• 等等
我当时的疑惑是:这段代码一开始是怎么成功编译的?
根据C标准,栈上分配的对象(局部数组)的大小必须在编译期已知。但示例中`char buffer[size];`明显不符合标准,后面查了相关资料,**这种写法属于VLA(边长数组),这是 C99 的特性,不属于 C**。
那么问题来了:C++ 不支持变长数组但GCC 和 Clang却能编译这段代码,这是因为GCC 与 Clang 默认启用了 C99 扩展。
可以采用以下方式来避免此种错误:我们可以使用-Werror=vla来避免,即编译命令中加上即可,这样报错如下:
<source>:10:10: error: variable length array 'buffer' is used [-Werror=vla] 10 | char buffer[size]; | ^~~~~~当然了,也有更严格的限制:-pedantic。它告诉编译器严格按照 C++ 标准,不允许任何扩展。
输出如下:
<source>:10:10: warning: ISO C++ forbids variable length array 'buffer' [-Wvla] 10 | char buffer[size]; | ^~~~~~