分段错误:当缓冲区>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
设为全局并不在“堆”上分配它。做到这一点的唯一方法是使用malloc
、calloc
等。
@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 程序中的堆栈分配的主要内容,如果未能解决你的问题,请参考以下文章