C/C++主流操作系统上程序的最大堆栈大小

8nuwlpux  于 2023-04-19  发布在  C/C++
关注(0)|答案(7)|浏览(226)

我想在一个100 X 100的数组上做DFS。(假设数组的元素代表图形节点)所以假设最坏的情况,递归函数调用的深度可以达到10000,每次调用占用20个字节。那么这是否可行,是否有可能发生堆栈溢出?
C/C++中栈的最大大小是多少?
请为两者指定gcc
1)Windows上的cygwin
2)Unix
一般限制是什么?

guicsvcw

guicsvcw1#

在Visual Studio中,默认的堆栈大小是1 MB,我认为,因此递归深度为10,000,每个堆栈帧最多可以是100字节,这对于DFS算法来说应该足够了。
大多数编译器,包括Visual Studio,都允许你指定堆栈大小。在一些(所有?)Linux版本中,堆栈大小不是可执行文件的一部分,而是操作系统中的一个环境变量。然后你可以使用ulimit -s检查堆栈大小,并使用例如ulimit -s 16384将其设置为一个新值。
下面是gcc的默认堆栈大小的link
无递归的DFS:

std::stack<Node> dfs;
dfs.push(start);
do {
    Node top = dfs.top();
    if (top is what we are looking for) {
       break;
    }
    dfs.pop();
    for (outgoing nodes from top) {
        dfs.push(outgoing node);
    }
} while (!dfs.empty())
wydwbb8l

wydwbb8l2#

线程的堆栈通常较小。您可以在链接时更改默认值,也可以在运行时更改。作为参考,一些默认值如下:

***glibc i386,x86_64:**7.4 MB
***Tru64 5.1:**5.2 MB
***Cygwin:**1.8 MB
***Solaris 7..10:**1 MB
***MacOS X 10.5:**460 KB
***AIX 5:**98 KB
***OpenBSD 4.0:**64 KB
***HP-UX 11:**16 KB

64jmpszr

64jmpszr3#

依赖于平台、依赖于工具链、依赖于ulimit、依赖于参数......它根本没有指定,并且有许多静态和动态属性可以影响它。

2vuwiymt

2vuwiymt4#

是的,有可能发生堆栈溢出。C和C++标准并没有规定堆栈深度之类的事情,这些通常是环境问题。
大多数像样的开发环境和/或操作系统都允许您在链接或加载时定制进程的堆栈大小。
您应该指定要使用的操作系统和开发环境,以获得更有针对性的帮助。
例如,在Ubuntu Karmic Koala下,gcc的默认值是2 M reserved和4K committed,但当您链接程序时可以更改。使用ld--stack选项来完成此操作。

92dk7w1h

92dk7w1h5#

我只是在工作中用完了堆栈,这是一个数据库,它正在运行一些线程,基本上是以前的开发人员在堆栈上抛出了一个大数组,反正堆栈很低。该软件是使用Microsoft Visual Studio 2015编译的。
即使线程已经耗尽了堆栈,它也会默默地失败并继续,只有当它访问堆栈上的数据内容时才会发生堆栈溢出。
我能给予的最好的建议是不要在堆栈上声明数组--特别是在复杂的应用程序中,特别是在线程中,而应该使用堆。)
另外要记住,声明栈的时候可能不会立即失败,而只会在访问的时候失败。我的猜测是编译器在windows下声明栈是“乐观的”,也就是说,它会假设栈已经被声明,并且足够大,直到它开始使用它,然后发现栈不存在。
不同的操作系统可能有不同的堆栈声明策略。

qlvxas9a

qlvxas9a6#

(2020年9月26日)
2009年10月24日,as @pixelbeat first pointed out hereBruno Haible empirically discovered the following default thread stack sizes用于几个系统。他说在多线程程序中,“默认线程堆栈大小是”如下。我在“实际”大小列中添加,因为@Peter.Cordes在我的答案下面的评论中指出,然而,下面显示的奇数测试数字不包括所有线程堆栈,如果我运行ulimit -s来查看我的Linux计算机配置的“最大堆栈大小”,它输出8192 kB,正好是8 MB,* 不是 * 下表中列出的奇数7.4 MB。对于我的x86-64计算机,使用gcc编译器和glibc。所以,你可以在下表中的数字上加一点,以获得给定线程的实际完整堆栈大小。
还要注意,下面的“测试”列单位都是MB和KB(基数为1000的数字),而不是MiB和KiB(基数为1024的数字)。

Thread stack sizes

System and std library  Tested  Actual
----------------------  ------  ------
- glibc i386, x86_64    7.4 MB  8 MiB (8192 KiB, as shown by `ulimit -s`)
- Tru64 5.1             5.2 MB  ?
- Cygwin                1.8 MB  ?
- Solaris 7..10           1 MB  ?
- MacOS X 10.5          460 KB  ?
- AIX 5                  98 KB  ?
- OpenBSD 4.0            64 KB  ?
- HP-UX 11               16 KB  ?

Bruno Haible还指出:
在多线程程序中,32 KB的内存超过了您可以安全地在堆栈上分配的内存
他说:
sigaltstack的默认堆栈大小SIGSTKSZ为

  • 在某些平台上只有16 KB:IRIX,OSF/1,Haiku.
  • 在某些平台上只有8 KB:glibc、NetBSD、OpenBSD、HP-UX、Solaris。
  • 在某些平台上只有4 KB:艾克斯

布鲁诺
他写了下面这个简单的Linux C程序来根据经验确定上述值。你可以在你的系统上运行它来快速查看你的最大线程堆栈大小是多少,或者你可以在GDBOnline上在线运行它:https://onlinegdb.com/rkO9JnaHD

**解释:**它只是创建一个新线程,检查 * 线程堆栈大小 * 而不是 * 程序堆栈大小 ,如果它们不同,那么它会让该线程重复分配128字节的内存 * 在堆栈上(而不是堆上),使用Linux alloca() call,然后它会将0写入这个新内存块的第一个字节,然后它会打印出它分配了多少字节。它会重复这个过程,每次都在堆栈上分配128字节 *,直到程序崩溃并出现Segmentation fault (core dumped)错误。最后打印的值是您的系统允许的估计最大线程堆栈大小。
**重要提示:alloca()在堆栈上分配 *:**尽管这看起来像是在堆上动态分配内存,类似于malloc()调用,但alloca()不会动态分配到堆上。相反,alloca()是一个专门的Linux函数,用于“伪动态”(我不确定我该怎么称呼它,所以我选择了这个术语)直接分配到堆栈上,就像静态分配内存一样。alloca()使用和返回的堆栈内存的作用域是 * 函数级 *,因此,“当调用alloca()的 * 函数 * 返回到其调用者时,将自动释放”。这就是为什么它的静态作用域不会退出,并且alloca()分配的内存不会在每次for循环迭代完成,并到达for循环范围的末尾。有关详细信息,请参阅man 3 alloca。以下是相关引用(强调已添加):

描述
alloca()函数在调用者的栈帧中分配 size 字节的空间,当调用alloca()函数返回给调用者时,会自动释放这个临时空间。
返回值
alloca()函数返回一个指针,指向分配空间的开始位置。如果分配导致堆栈溢出,则程序行为未定义。

以下是Bruno Haible在2009年10月24日的节目,copied directly from the GNU mailing list here

同样,您可以run it live online here

// By Bruno Haible
// 24 Oct. 2009
// Source: https://lists.gnu.org/archive/html/bug-coreutils/2009-10/msg00262.html

// =============== Program for determining the default thread stack size =========

#include <alloca.h>
#include <pthread.h>
#include <stdio.h>
void* threadfunc (void*p) {
  int n = 0;
  for (;;) {
    printf("Allocated %d bytes\n", n);
    fflush(stdout);
    n += 128;
    *((volatile char *) alloca(128)) = 0;
  }
}

int main()
{
  pthread_t thread;
  pthread_create(&thread, NULL, threadfunc, NULL);
  for (;;) {}
}

当我使用上面的链接在GDBOnline上运行它时,我每次运行它都会得到完全相同的结果,作为C和C++17程序。运行大约需要10秒左右。下面是输出的最后几行:

Allocated 7449856 bytes
Allocated 7449984 bytes
Allocated 7450112 bytes
Allocated 7450240 bytes
Allocated 7450368 bytes
Allocated 7450496 bytes
Allocated 7450624 bytes
Allocated 7450752 bytes
Allocated 7450880 bytes
Segmentation fault (core dumped)

因此,这个系统的线程堆栈大小约为7.45 MB,正如Bruno在上面提到的(7.4 MB)。
我对程序做了一些修改,主要是为了清晰,但也是为了提高效率,还有一点是为了学习。

我的修改摘要:

1.[学习]我传入BYTES_TO_ALLOCATE_EACH_LOOP作为threadfunc()的参数,只是为了练习传入和使用C中的泛型void*参数。
1.注意:这也是the pthread_create() function所需的函数原型,用于传递给pthread_create()的回调函数(在我的例子中是threadfunc())。请参阅:https://www.man7.org/linux/man-pages/man3/pthread_create.3.html
1.[效率]我让主线程休眠,而不是浪费地旋转。
1.我添加了更详细的变量名,比如BYTES_TO_ALLOCATE_EACH_LOOPbytes_allocated
1.[清晰度]我改变了这个:

*((volatile char *) alloca(128)) = 0;

对此:

volatile uint8_t * byte_buff = 
         (volatile uint8_t *)alloca(BYTES_TO_ALLOCATE_EACH_LOOP);
 byte_buff[0] = 0;

这是我修改后的测试程序,它做的事情和Bruno的完全一样,甚至有相同的结果:

你可以使用run it online heredownload it from my repo here。如果你选择从我的仓库本地运行它,这里是我用于测试的build和run命令:
1.编译并运行它作为一个C程序:

mkdir -p bin && \
 gcc -Wall -Werror -g3 -O3 -std=c11 -pthread -o bin/tmp \
 onlinegdb--empirically_determine_max_thread_stack_size_GS_version.c && \
 time bin/tmp

1.将其作为C++程序构建并运行:

mkdir -p bin && \
 g++ -Wall -Werror -g3 -O3 -std=c++17 -pthread -o bin/tmp \
 onlinegdb--empirically_determine_max_thread_stack_size_GS_version.c && \
 time bin/tmp

在线程堆栈大小约为7.4 MB的快速计算机上本地运行所需时间〈0.5秒。
程序如下:

// =============== Program for determining the default thread stack size =========

// Modified by Gabriel Staples, 26 Sept. 2020

// Originally by Bruno Haible
// 24 Oct. 2009
// Source: https://lists.gnu.org/archive/html/bug-coreutils/2009-10/msg00262.html

#include <alloca.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <unistd.h> // sleep

/// Thread function to repeatedly allocate memory within a thread, printing
/// the total memory allocated each time, until the program crashes. The last
/// value printed before the crash indicates how big a thread's stack size is.
///
/// Note: passing in a `uint32_t` as a `void *` type here is for practice,
/// to learn how to pass in ANY type to a func by using a `void *` parameter.
/// This is also the required function prototype, as required by the
/// `pthread_create()` function, for the callback function (this function)
/// passed to `pthread_create()`. See:
/// https://www.man7.org/linux/man-pages/man3/pthread_create.3.html
void* threadfunc(void* bytes_to_allocate_each_loop)
{
    const uint32_t BYTES_TO_ALLOCATE_EACH_LOOP =
            *(uint32_t*)bytes_to_allocate_each_loop;

    uint32_t bytes_allocated = 0;
    while (true)
    {
        printf("bytes_allocated = %u\n", bytes_allocated);
        fflush(stdout);
        // NB: it appears that you don't necessarily need `volatile` here,
        // but you DO definitely need to actually use (ex: write to) the
        // memory allocated by `alloca()`, as we do below, or else the
        // `alloca()` call does seem to get optimized out on some systems,
        // making this whole program just run infinitely forever without
        // ever hitting the expected segmentation fault.
        volatile uint8_t * byte_buff =
                (volatile uint8_t *)alloca(BYTES_TO_ALLOCATE_EACH_LOOP);
        byte_buff[0] = 0;
        bytes_allocated += BYTES_TO_ALLOCATE_EACH_LOOP;
    }
}

int main()
{
    const uint32_t BYTES_TO_ALLOCATE_EACH_LOOP = 128;

    pthread_t thread;
    pthread_create(&thread, NULL, threadfunc,
                   (void*)(&BYTES_TO_ALLOCATE_EACH_LOOP));
    while (true)
    {
        const unsigned int SLEEP_SEC = 10000;
        sleep(SLEEP_SEC);
    }

    return 0;
}

示例输出(与Bruno Haible的原始程序相同的结果):

bytes_allocated = 7450240                                                                                                                                                        
bytes_allocated = 7450368                                                                                                                                                        
bytes_allocated = 7450496                                                                                                                                                        
bytes_allocated = 7450624                                                                                                                                                        
bytes_allocated = 7450752                                                                                                                                                        
bytes_allocated = 7450880                                                                                                                                                        
Segmentation fault (core dumped)
khbbv19g

khbbv19g7#

我不知道你在矩形数组上做深度优先搜索是什么意思,但我假设你知道你在做什么。
如果堆栈限制是一个问题,您应该能够将递归解决方案转换为迭代解决方案,该解决方案将中间值推送到从堆分配的堆栈上。

相关问题