C++多线程光线追踪器开发:栈溢出、堆数组赋值报错问题
栈溢出根因
std::array的内存是在当前作用域的栈上直接分配的,你定义的三维数组单份大小为225 * 400 * 3 * sizeof(int) ≈ 1.03MB。第一版函数里同时存在两份该尺寸的栈对象:一份是值传递的形参pc,一份是局部变量pic,仅这两个对象就占了2MB以上栈空间。
多线程环境下每个子线程的默认栈空间远小于主线程:Windows系统线程默认栈大小仅1MB,Linux默认8MB,再叠加函数调用栈帧、其他局部变量的开销,直接超过栈上限触发溢出是必然结果。
注意:你代码里的形参pc全程没有被读取使用,fml是值传递,函数内赋值完全不会影响外部变量,两个参数都是无意义的栈空间浪费,可以直接删除。
堆分配后赋值报错根因
第二版代码里pic是指向三维数组的指针,直接写pic[i][j]时,下标访问的偏移步长是整个三维数组的长度:pic[i]等价于*(pic + i),得到的是距离首地址偏移i个完整三维数组长度的内存,类型是一维的std::array<std::array<int,3>,400>对象,后续接[j]得到的类型和writeColour返回的std::array<int,3>类型不匹配,编译器自然找不到可用的赋值运算符重载,抛出no viable overload '='错误。
- 解决栈溢出:
- 所有大尺寸容器不要值传递,改成引用传递,不需要修改的参数加
const限定,避免无意义的拷贝占栈空间 - 大尺寸数组优先用
std::vector存储,其内存默认分配在堆上,不会占用栈空间,也不需要手动管理内存释放 - 子线程栈上不要分配超过16KB的对象,避免触发栈溢出
- 所有大尺寸容器不要值传递,改成引用传递,不需要修改的参数加
- 解决赋值报错:
先解引用指针拿到堆上数组的本体,再做下标访问即可,两种合法写法:// 写法1:直接解引用后访问 (*pic)[i][j] = writeColour(pixelColour, maxDepth); // 写法2:绑定数组引用,后续代码无需改动 auto& pic_ref = *pic; pic_ref[i][j] = writeColour(pixelColour, maxDepth);
如果要做多线程渲染,更推荐直接按行拆分任务,每个线程只负责处理若干行像素,不要每个线程单独存完整的一份图像数组,减少内存开销。如果用std::vector存像素,可以按如下方式定义,自动管理内存避免泄漏:
// 225行、400列的像素缓存,每个像素存3通道int值 std::vector<std::vector<std::array<int, 3>>> pic( 225, std::vector<std::array<int, 3>>(400) );
多线程传参时,大对象要用std::ref包装后传入线程函数,避免触发值拷贝。
内容的提问来源于stack exchange,提问作者nathan

