【问题标题】:Why does malloc() fail when there is enough memory?为什么当内存足够时 malloc() 会失败?
【发布时间】:2017-05-26 10:04:44
【问题描述】:

我正在使用具有 128GB 内存的服务器进行一些计算。我需要malloc() 一个大小为 56120 * 56120 的二维浮点数组。示例代码如下:

int main(int argc, char const *argv[])
{
    float *ls;
    int num = 56120,i,j;
    ls = (float *)malloc((num * num)*sizeof(float));
    if(ls == NULL){
        cout << "malloc failed !!!" << endl;
        while(1);
    }
    cout << "malloc succeeded ~~~" << endl;
    return 0;
}

代码编译成功,但是当我运行它时,它显示"malloc failed !!!"。正如我计算的那样,保存整个数组只需要大约 11GB 的内存。在开始编写代码之前,我检查了服务器,发现有 110GB 可用内存。为什么会出现错误?

我还发现,如果我将num 减少到,比如 40000,那么 malloc 就会成功。

这是否意味着malloc()可以分配的最大内存有限制?

另外,如果我改变分配方式,直接声明一个这样大小的二维浮点数组,如下:

int main(int argc, char const *argv[])
{
    int num = 56120,i,j;
    float ls[3149454400];
    if(ls == NULL){
        cout << "malloc failed !!!" << endl;
        while(1);
    }
    cout << "malloc succeeded ~~~" << endl;
    for(i = num - 10 ; i < num; i ++){
        for( j = num - 10; j < num ; j++){
            ls[i*num + j] = 1;
        }
    }
    for(i = num - 11 ; i < num; i ++){
        for( j = num - 11; j < num ; j++){
            cout << ls[i*num + j] << endl;
        }
    }
    return 0;
}

然后我编译并运行它。我收到了"Segmentation fault"

我该如何解决这个问题?

【问题讨论】:

  • 3149454400 浮点数对于堆栈分配的数组来说是一个太多...
  • 一个原因可能是分配的内存必须是连续的。如果没有这么大的块可用,那么分配将失败。
  • 至于数组的问题,记住大部分编译器都把自己的局部变量放在栈上,栈是相当有限的(Linux上默认进程栈大小是8MB)。
  • 你的平台是什么? linux 64位?
  • 你真的应该删除C标签,看看你如何使用std::coutstd::endl。那么这当然会带来一个问题,为什么首先使用malloc,为什么使用NULL 而不是nullptr,以及为什么在使用变量之前声明变量。

标签: c++ c memory malloc


【解决方案1】:

问题是,你的计算

(num * num) * sizeof(float)

以 32 位有符号整数计算,num=56120 的结果为

-4582051584

然后将其解释为具有非常大值的 size_t

18446744069127500032

你没有那么多内存;)这就是malloc()失败的原因。

在 malloc 的计算中将num 转换为size_t,那么它应该可以按预期工作。

【讨论】:

  • num 应该是 size_t 而不是 long
  • @Stargateur 为什么会这样? num 本身不是大小,所以size_t 似乎不合适
  • @Ctx malloc() 使用size_t,使用size_t
  • @Stargateur 我的意思是“保留 num 并投射结果”。在这个程序中,num 在语义上不是 size_t,所以在我看来,将其声明为一个是完全误导和错误的。
  • @Ctx num使用数据类型long 并且你可以将(num * num) * sizeof(float)的结果转换成size_t试试在 Windows 上,long 仍然是 32 位 signed 值,即使对于 64 位应用程序也是如此。还是很讨厌。
【解决方案2】:

正如其他人指出的那样,56120*56120 溢出了 OP 平台上的 int 数学。那是未定义的行为 (UB)。

malloc(size_t x) 接受size_t 参数,传递给它的值最好至少使用size_t 数学计算。通过颠倒乘法顺序,就可以做到这一点。 sizeof(float) * num 导致 num 在乘法之前至少扩大到 size_t

int num = 56120,i,j;
// ls = (float *)malloc((num * num)*sizeof(float));
ls = (float *) malloc(sizeof(float) * num * num);

尽管这可以防止 UB,但这并不能防止溢出,因为数学上 sizeof(float)*56120*56120 仍可能超过 SIZE_MAX

代码可以预先检测到潜在的溢出。

if (num < 0 || SIZE_MAX/sizeof(float)/num < num) Handle_Error();

无需转换malloc()的结果。
使用引用变量的大小比调整类型更容易编码和维护。
num == 0 时,malloc(0) == NULL 不一定是内存不足。
一起来:

int num = 56120;
if (num < 0 || ((num > 0) && SIZE_MAX/(sizeof *ls)/num < num)) {
  Handle_Error();
}
ls = malloc(sizeof *ls * num * num);
if (ls == NULL && num != 0) {
  Handle_OOM();
}

【讨论】:

    【解决方案3】:
    int num = 56120,i,j;
    ls = (float *)malloc((num * num)*sizeof(float));
    

    num * num56120*56120 这是 3149454400 溢出 signed int 导致未定义的行为。

    40000 起作用的原因是 40000*40000 可以表示为 int。

    num的类型改为long long(甚至unsigned int

    【讨论】:

    • num 应该是 size_t 而不是 long long 甚至是 unsigned int
    • @Stargateur 实际上,任何大到足以容纳数字的类型都可以。一个长的,甚至无符号的 int 可以保存一个大小的数字。
    • @UKMonkey malloc() 使用size_t,使用size_t。不要混合类型,否则你会遇到这样的问题......
    • @Stargateur 这取决于用例,我们真的不知道num 在这种情况下意味着什么。想象一下,它的意思是“我正在计算的维数”,那么这不是将其设为size_t 的好理由。是的,它用于最终将导致size_t 的表达式,因为分配了这些维度的内存,但通常你不会像那样向后传播类型并使num 成为size_t
    • @MikeVine 如果num 代表数组的大小,是的,它应该是size_t。或者OP需要正确转换它。在这里您说“使用 long long”,但如果下次值不适合 long long 怎么办?您提议更改num的类型为什么不使用正确的类型?
    【解决方案4】:

    这与其他人写的相反,但对我来说,将变量 num 从 int 更改为 size_t 允许分配。可能是 num*num 溢出了 malloc 的 int。使用 56120 * 56120 而不是 num*num 执行 malloc 应该会引发溢出错误。

    【讨论】:

    • 请注意,使用 32 位 size_t 并使用 size_t num = 56120;(num * num)*sizeof(float) 仍然是一个问题。该乘积溢出了 32 位数学运算。
    【解决方案5】:

    float ls[3149454400];是一个自动存储类型的数组,通常分配在进程栈上。默认情况下,进程堆栈受限于一个小于 12GB 的值,您尝试将其推送到那里。所以你观察到的分段错误是由堆栈溢出引起的,而不是由malloc引起的。

    【讨论】:

    • @Stargateur 当然。它会将其从自动存储移动到静态存储。虽然仍然不是一个好主意...启动代码必须将整个数组设置为零,这可能有点耗时。
    • 这里稍微跑题了,但大多数主流操作系统可能实际上会在静态存储中管理这个非常大的数组而不会非常耗时 - 它们要么共享一个“零”页面”或提供“已经为零的页面”或仅在访问时分配零页面。不过,这可能仍然不是一个好主意——大的分配应该总是在堆上。
    • 这也不是 OP 正在做的 - 3x10^9 是乘法的结果,但溢出导致了非常不同的请求
    猜你喜欢
    • 2019-11-18
    • 2014-04-24
    • 2016-02-10
    • 2012-02-09
    • 2016-07-28
    • 2023-03-30
    • 2023-03-10
    • 2015-02-08
    相关资源
    最近更新 更多