分段错误:当缓冲区>4M 时,Ubuntu 中 C 程序中的堆栈分配

Posted

技术标签:

【中文标题】分段错误:当缓冲区>4M 时,Ubuntu 中 C 程序中的堆栈分配【英文标题】:Segmentation fault: Stack allocation in a C program in Ubuntu when bufffer>4M 【发布时间】:2012-12-04 04:29:36 【问题描述】:

这是一个完成大学任务的小程序:

#include <unistd.h>

#ifndef BUFFERSIZE
#define BUFFERSIZE 1
#endif

main()

    char buffer[BUFFERSIZE];
    int i;
    int j = BUFFERSIZE;

    i = read(0, buffer, BUFFERSIZE);

    while (i>0)
    
        write(1, buffer, i);
        i = read(0, buffer, BUFFERSIZE);
    

    return 0;

还有一个替代方法是使用 stdio.h fread 和 fwrite 函数。

嗯。我用 25 个不同的缓冲区大小值编译了这两个版本的程序:1、2、4、...、2^i,i=0..30

这是我如何编译它的示例: gcc -DBUFERSIZE=8388608 prog_sys.c -o bin/psys.8M

问题:在我的机器上(Ubuntu Precise 64,最后有更多细节)所有版本的程序都可以正常工作: ./psys.1M

(数据是一个带有 3 行 ascii 文本的小文件。)

问题是:当缓冲区大小为 8MB 或更大时。两个版本(使用系统调用或 clib 函数)都会因这些缓冲区大小而崩溃(分段错误)。

我测试了很多东西。代码的第一个版本是这样的: (...) 主要的() 字符缓冲区[BUFFERSIZE]; 诠释我;

    i = read(0, buffer, BUFFERSIZE);
(...)

当我调用读取函数时,这会崩溃。但是对于这些版本:

main()

    char buffer[BUFFERSIZE]; // SEGMENTATION FAULT HERE
    int i;
    int j = BUFFERSIZE;

    i = read(0, buffer, BUFFERSIZE);


main()

    int j = BUFFERSIZE; // SEGMENTATION FAULT HERE
    char buffer[BUFFERSIZE];
    int i;

    i = read(0, buffer, BUFFERSIZE);

它们都在 main 的第一行崩溃 (SEGFAULT)。但是,如果我将缓冲区从 main 移到全局范围(因此,在堆中分配而不是在堆栈中),这可以正常工作:

char buffer[BUFFERSIZE]; //NOW GLOBAL AND WORKING FINE
main()

    int j = BUFFERSIZE;
    int i;

    i = read(0, buffer, BUFFERSIZE);

我使用的是 Ubuntu Precise 12.04 64 位和第一代 Intel i5 M 480。

#uname -a
Linux hostname 3.2.0-34-generic #53-Ubuntu SMP Thu Nov 15 10:48:16 UTC 2012 x86_64 x86_64 x86_64 GNU/Linux

我不知道堆栈的操作系统限制。有没有办法在堆栈中分配大数据,即使这不是一个好习惯?

【问题讨论】:

ulimit -s 对您的系统有什么作用? buffer 设为全局并在“堆”上分配它。做到这一点的唯一方法是使用malloccalloc 等。 @AllanDeamon:这意味着您的堆栈大小限制为 8192 KB(除非您采取措施更改此限制)。 ulimit -s newSizeInKB 这解决了我的问题。现在我应该如何处理这个问题? 我想你应该接受 Joseph Quinsey 的回答。您可以随时更改接受哪个答案。 【参考方案1】:

我认为这很简单:如果你试图让缓冲区太大,它就会溢出堆栈。

如果你使用malloc()来分配缓冲区,我打赌你不会有问题的。

请注意,有一个名为alloca() 的函数显式分配堆栈存储。使用alloca() 与声明堆栈变量几乎相同,只是它发生在程序运行时。阅读 alloca() 的手册页或对其进行讨论可能会帮助您了解您的程序出了什么问题。这是一个很好的讨论:

Why is the use of alloca() not considered good practice?

编辑:@jim mcnamara 在评论中向我们介绍了ulimit,这是一个可以检查用户限制的命令行工具。在这台计算机上(运行 Linux Mint 14,因此它应该与 Ubuntu 12.10 具有相同的限制)ulimit -s 命令显示堆栈大小限制:8192 K-bytes,它很好地跟踪了您描述的问题。

编辑:如果不完全清楚,我建议您致电malloc() 解决问题。另一个可接受的解决方案是静态分配内存,只要您的代码是单线程的,它就可以正常工作。

您应该只对小缓冲区使用堆栈分配,正是因为您不想炸毁堆栈。如果您的缓冲区很大,或者您的代码将多次递归调用一个函数,以便多次分配小缓冲区,您将破坏您的堆栈并且您的代码将崩溃。最糟糕的是你不会收到任何警告:要么没问题,要么你的程序已经崩溃了。

malloc() 唯一的缺点是它比较慢,所以你不想在时间关键的代码中调用malloc()。但这对于初始设置很好;一旦 malloc 完成,您分配的缓冲区的地址就是内存中的一个地址,就像您程序地址空间中的任何其他内存地址一样。

我特别建议不要编辑系统默认值以使堆栈大小变大,因为这会使您的程序的可移植性大大降低。如果您调用像malloc() 这样的标准C 库函数,您可以轻松地将您的代码移植到Windows、Mac、android 等;如果您开始调用系统函数来更改默认堆栈大小,那么移植会遇到更多问题。 (你现在可能没有移植这个的计划,但计划会改变!)

【讨论】:

哇,我不知道ulimit。谢谢你!我将编辑我的评论以提及它。 @MahmoudAl-Qudsi:是的,这不是正确的解决方案,但这只是一个学术实验,不打算用于实际案例。因为我必须尽可能避免更改 C 代码,所以 ulimit -s 在这种非常特殊的情况下是完美的解决方法。实际上,我将使用动态分配。谢谢【参考方案2】:

在 Linux 中堆栈大小经常受到限制。命令ulimit -s 将给出当前值,以千字节为单位。您可以更改(通常)文件/etc/security/limits.conf 中的默认值。您还可以根据权限,通过代码在每个进程的基础上更改它:

#include <sys/resource.h>
// ...
struct rlimit x;
if (getrlimit(RLIMIT_STACK, &x) < 0)
    perror("getrlimit");
x.rlim_cur = RLIM_INFINITY;
if (setrlimit(RLIMIT_STACK, &x) < 0)
    perror("setrlimit");

【讨论】:

以上是关于分段错误:当缓冲区>4M 时,Ubuntu 中 C 程序中的堆栈分配的主要内容,如果未能解决你的问题,请参考以下文章

在命名管道中获取分段错误(核心转储)错误

绘制 VBO 导致分段错误

C pthread:分段错误

Ubuntu更新后Python中的分段错误

一个简单的 qt 程序未运行并显示“分段错误”的错误消息

“程序收到信号SIGSEGV,分段错误”当试图使用递归获取3个字符组合的所有关键字时