传递大型栈分配的数组作为函数参数会导致堆栈溢出。

3

我在使用setrlimit调整堆栈大小后,在堆栈上分配了一个大型数组。当该数组在main()函数中声明并作为参数传递给一个方法时,会导致段错误。如果将数组声明为方法内部的局部变量,则代码可以运行而不会出现段错误。我在一台8GB RAM的Amdx86-64 Linux计算机上运行这段代码。

#include <iostream>
#include <sys/resource.h>

using usll = unsigned long long;
void sumArray0();
double sumArray1(double c[], usll dim); 

int main(int argc, char* argv[])
{
    const rlim_t stackSize = 3 * 1024UL * 1024UL * 1024UL;
    struct rlimit rl;
    int result;

    printf("The required value of stackSize is %lu\n", stackSize);  
    result = getrlimit(RLIMIT_STACK, &rl);

    if (result == 0)
    {
        if (rl.rlim_cur < stackSize)
        {
            rl.rlim_cur = stackSize;
            result = setrlimit(RLIMIT_STACK, &rl);

            if (result != 0)
            {
                fprintf(stderr, "setrlimit returned result = %d\n", result);
            }
            else
            {
                printf("The new value of stackSize is %lu\n", rl.rlim_cur);
            }
        }
    }

    // // This seg faults
    // const usll DIM = 20000UL * 18750UL;  
    // double c[DIM];

    // for (usll i{}; i<DIM; ++i)
    // {
    //     c[i] = 5.0e-6;
    // }
    // double total = sumArray1(c, DIM); // Seg fault occurs here

    sumArray0(); // This works

    std::cout << "Press enter to continue";
    std::cin.get();

    return 0;
}

void sumArray0()
{
    double total{};
    const usll DIM = 20000UL * 18750UL; 
    double c[DIM];

    for (usll i{}; i<DIM; ++i)
    {
        c[i] = 5.0e-6;
    }

    for (usll i{}; i<DIM; ++i)
    {
        total += c[i];
    }

    std::cout << "Sum of the elements of the vector is " << total << std::endl;
}

double sumArray1(double c[], usll dim)
{
    double total{};

    for (usll i{}; i<dim; ++i)
    {
        total += c[i];
    }

    return total;
}

我的问题是:
为什么在第一种情况下会出现堆栈溢出?
是因为调用sumArray1()方法时请求了一块新的内存块吗?
当将数组作为参数传递给该方法时,不是通过指针访问数组吗?
如此推荐,巨大的数组引起堆栈溢出,我总是使用std::vector并且永远不会在堆栈上分配大型数组,以防止出现上述问题。如果有人知道可以使调用sumArray1()方法正常工作的任何技巧、诀窍或解决方法,我会非常感激。

6
“然而,编写针对堆栈的代码时性能提升也可能非常显著。”您有任何证明吗?只要启用优化,向量应该与数组同样快。"堆栈"和"堆"都在RAM中,因此一个不会比另一个本质上更快。 - NathanOliver
4
“然而,编写栈相关代码时性能提升的确非常显著是真实存在的。但是,这种性能提升的原因在于避免分配内存,而不是因为使用了栈。” - Acorn
@walnut 抱歉,那是疏忽。已经编辑并将using声明移到了顶部。 - unbound37
2
@user11601099 在编译时不会分配任何内存。当程序启动时,它会为“堆栈”分配所需的空间。如果您有一个向量,它也会做同样的事情。原始数组不会比std::vector更快,一旦数据量足够大,它只会引起问题,就像您现在遇到的一样。 - NathanOliver
1
好的,那为什么不直接使用std::vector呢?毕竟你知道它肯定能工作。 - NathanOliver
显示剩余9条评论
1个回答

2
所以在我看来,您正在编译时没有启用优化,否则在 main 中的数组将被完全优化掉,因为使用它进行的计算在任何可观察的方式中都没有被使用。(这对任何事情都不好。即使是为了调试目的,您也应该使用 -Og。)
在评论中,您提到正在使用 -fPIC 标志进行编译。这会防止 gcc 内联 sumArray1 调用,因此无论优化标志如何,数组都无法被优化掉。您应该为可执行文件使用 -fpie(可能已经是默认值),而不是 -fPIC,后者是针对共享库的,带有这些性能惩罚,详情请参见 herehere
如果是这种情况,那么回答您的问题:问题不在于传递给函数,而在于在进入函数时分配了堆栈空间,因此在设置限制之前就已经分配了空间。
现在,“分配”在这里只是修改堆栈指针,但是在设置限制之前,main 可能会访问堆栈帧中的任何位置。特别是编译器可以随意重新排列堆栈帧中变量的位置,或者根据编译器设置添加堆栈保护等等。
在限制设置之前进行的任何此类访问都会导致段错误。
请注意,编译器也可以自由地内联函数。因此,即使在 sumArray0 中执行操作也可能会给您带来麻烦,如果编译器决定内联该函数调用,则数组将成为 main 的堆栈帧的一部分,并应用上述相同的问题。
编译器可能会意识到内联具有大型堆栈帧的函数是潜在危险的,并且不会这样做,但是这是您需要根据编译器文档检查的内容。
无论如何,编译器和操作系统都不希望程序使用大型堆栈帧。那不是它们的目的。堆/自由存储区专门用于处理比通常堆栈帧更大的内存分配。通常最好启用大型堆栈帧的警告并遵循它们。

如我在答案中所述,问题仅在于您正在使用大堆栈分配在同一函数内设置限制。如果您在另一个函数中进行分配并确保它不是内联的(例如通过使用某些特定于编译器的属性,或将其放入另一个共享对象文件或使用-fPIC),那么就不会有问题了。但是,通常用户应该在调用程序之前设置堆栈限制。我认为更改该限制不是程序的工作。它可以测试它并向用户报告,然后用户可以采取行动。 - walnut
1
@user11601099 如何告诉gcc不要内联函数? 您希望在运行setrlimit之后进行内存分配。只需像使用sumArray1一样使用另一个函数,并给它noinline属性即可。如果您正在使用-fPIC编译,则无论如何都不重要,因为默认情况下会禁止内联。 - walnut
感谢提供链接。 - unbound37
1
@DanielJour 对于使用常量表达式初始化的整型类型const变量,有一个明确的例外,使它们成为常量表达式,因此对于数组的编译时大小参数是有效的,参见[expr.const]/2.7.1。这个例外早于constexpr的引入。您还可以通过向g++添加-pedantic-errors标志来查看,这将导致它为可变长度数组发出错误。 - walnut
1
@DanielJour 如果 ReadFromUser(); 不是一个常量表达式(考虑到其命名,它可能确实不是),那么您的示例就是一个可变长度数组;但是 20000UL * 18750UL 是一个常量表达式,使得 OP 的用法不是可变长度数组且符合标准。 - walnut
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接