【问题标题】:Understanding memory allocation, test program crashing了解内存分配,测试程序崩溃
【发布时间】:2023-03-07 19:09:02
【问题描述】:

我即将读完 K&R,这就是我所知道的所有 C。我所有的编译都是使用 MinGW 从 Windows 命令行完成的,而且我不了解高级调试方法(因此下面的第二个程序中的“ghetto debug”注释)。

我正在尝试制作一些小型测试程序,以帮助我更好地了解内存分配的工作原理。这些前几个程序不使用 malloc 或 free,我只是想看看如何为函数本地的标准数组分配和取消分配内存。这个想法是我观察我正在运行的进程 RAM 使用情况,看看它是否与我理解的相符。对于下面的第一个程序,它确实按我的预期工作。 alloc_one_meg() 函数分配并初始化 250,000 个 4 字节整数,但该 MB 会在函数返回后立即释放。因此,如果我连续调用该函数 1000000 次,我永远不会看到我的 RAM 使用量超过 1MB。而且,它有效。

#include <stdio.h>
#include <stdlib.h>

void alloc_one_meg() {
    int megabyte[250000];
    int i;
    for (i=0; i<250000; i++) {
        megabyte[i] = rand();
    }
}

main()
{
    int i;
    for (i=0; i<1000000; i++) {
        alloc_one_meg();
    }
}

对于下面的第二个程序,其想法是不允许函数退出,同时运行同一函数的 1000 个副本,这是我通过递归实现的。我的理论是,该程序在递归完成后将其全部释放之前会消耗 1GB 的 RAM。但是,它没有通过递归的第二个循环(请参阅我的 ghetto 调试评论)。该程序崩溃,并显示一条非常不具信息性的(对我而言)消息(Windows 弹出窗口说 ____.exe 遇到问题)。通常我总是可以用我的 ghetto 调试方法弄清事情的真相……但它在这里不起作用。我难住了。这段代码有什么问题?谢谢!

#include <stdio.h>
#include <stdlib.h>

int j=0;

void alloc_one_meg() {
    int megabyte[250000];
    int i;
    for (i=0; i<250000; i++) {
        megabyte[i] = rand();
    }
    j++;
    printf("Loop %d\n", j); // ghetto debug
    if (j<1000) {
        alloc_one_meg();
    }
}

main()
{
    alloc_one_meg();
}

后续问题已发布here

【问题讨论】:

  • 现在你有一个堆栈溢出崩溃,你对内存有了更好的理解。有趣的是事情是这样发展的。
  • 是的,我是编程新手,但对测试的想法并不陌生。我是一名工程师和重度软件用户,我非常清楚打破事物是理解它们的最佳方式! :-)

标签: c


【解决方案1】:

你遇到了堆栈溢出。

本地自动存储变量(如megabyte)分配在堆栈上,空间有限。 malloc 在堆上分配,这允许更大的分配。

您可以在这里阅读更多内容:

http://en.wikipedia.org/wiki/Stack_overflow

(我要注意的是,C语言并没有指定内存的分配位置——栈和堆是实现细节)

【讨论】:

  • 太酷了,我在 stackoverflow.com 上了解了堆栈溢出。我应该在meta上发布这个!感谢您的解释和链接。
  • malloc() 没有更大的空间——查看 dumpbin 的输出和为“堆”保留的数量。
【解决方案2】:

Windows 程序中的堆栈大小通常约为 1 MB,因此在第二次递归时,堆栈会溢出。您不应该在堆栈上分配如此大的数组,使用mallocfree 在堆上分配和释放内存(对于这种大小的数组,无法绕过malloc):

void alloc_one_meg() {
    int *megabyte = malloc(sizeof(int) * 250000); // allocate space for 250000
                                                  // ints on the heap
    int i;
    for (i=0; i<250000; i++) {
        megabyte[i] = rand();
    }
    j++;
    printf("Loop %d\n", j); // ghetto debug
    if (j<1000) {
        alloc_one_meg();
    }

    free(megabyte); // DO NOT FORGET THIS
}

也就是说,您实际上可以更改程序的堆栈大小并使其更大(尽管我只是将其作为教育练习,而不是在生产代码中)。对于 Visual Studio,您可以使用 use the /F compiler option,在 Linux 上您可以使用 setrlimit(3)。不过,我不确定 MinGW 可以使用什么。

【讨论】:

  • 他说他不想使用malloc/free。
  • 这一系列测试的部分更大目的是帮助我理解 malloc 和 free 的目的。我想先在没有它们的情况下运行一些测试,然后再用它们进行一些测试。我知道本地堆栈变量的生命周期与函数进入/退出有关,而堆变量的生命周期与 malloc/free 有关,但我没有意识到堆栈的限制如此之小。所以我已经通过这次测试学到了很多东西。 :-)
  • @Johnson 是的,这是一件值得学习的事情。与堆相比,堆栈非常小,这就是为什么在堆栈溢出之前不能递归很远(即使局部变量不多)
  • @Seth Carnegie -- malloc() 和 free() 同样受限 -- 请参阅使用默认编译的二进制文件中的 dumpbin 输出。他也不能使用它们。他不能不用的是VirtualAlloc()。
  • @Johnson 是一回事,见this thread(忽略关于“道德等价物”这一短语的唯一真正含义的愚蠢辩论)这表明@987654329 之间几乎没有区别@和VirtualAlloc
【解决方案3】:

您通过递归函数调用分配的内存是从堆栈中分配的。所有堆栈内存必须是连续的。当您的进程启动一个线程时,Windows 将为该线程的堆栈保留一定范围的虚拟内存地址空间。要保留的内存量在您的 EXE 文件的“PE 标头”中指定。 PE 代表“可移植的可执行文件”。

使用 Visual Studio 附带的 dumpbin 实用程序,并将其自身 (dumpbin.exe) 作为输入文件:

dumpbin /headers dumpbin.exe

...有一些输出,然后:

      100000 size of stack reserve
        2000 size of stack commit

“100000”是一个十六进制数字,等于 1,048,576,所以这表示大约 1MB。

换句话说,操作系统只会为堆栈保留一个 1MB 的地址范围。当该地址范围用完时,Windows 可能会也可能不会分配更多的连续内存范围以增加堆栈。结果取决于是否有更多的连续地址范围可用。由于线程开始时 Windows 进行的其他分配,它不太可能可用。

要在 Windows 下分配最大数量的虚拟内存,请使用 VirtualAlloc 系列函数。

【讨论】:

    【解决方案4】:

    堆栈溢出。这是一个棘手的问题吗?

    【讨论】:

    • 不,我只是个菜鸟。我很快就知道有很多关于 C 的知识,我需要知道我无法从 K&R 获得(正如我所说,这是我迄今为止唯一的知识来源)。哪里是学习阻止我提出这样一个问题的知识的好地方?堆栈/堆之类的东西(这些概念在 K&R 中不存在,我知道为什么)。
    猜你喜欢
    • 2010-11-14
    • 2015-08-08
    • 2016-10-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-08
    相关资源
    最近更新 更多