2

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

#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 < data

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

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

我测试了很多东西。代码的第一个版本是这样的: (...) main() { char buffer[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 位和英特尔 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

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

4

2 回答 2

3

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

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

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

为什么使用 alloca() 不被认为是好的做法?

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

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

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

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

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

于 2012-12-17T03:56:20.460 回答
1

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");
于 2012-12-17T04:30:44.753 回答