【问题标题】:What is QList's maximum size?QList 的最大尺寸是多少?
【发布时间】:2015-01-22 20:08:03
【问题描述】:

有人遇到过 QList 的最大大小吗?

我有一个指向我的对象的指针的 QList,并且发现当它到达第 268,435,455 个项目时,它会默默地抛出一个错误,这正是 28 位。我本来希望它至少有 31 位的最大大小(减去一位,因为 size() 返回一个有符号整数),或者在我的 64 位计算机上具有 63 位的最大大小,但情况似乎并非如此。我已经通过在计数循环中执行QList<void*> mylist; mylist.append(0); 在一个最小示例中确认了这一点。

重申问题,QList 的实际最大大小是多少?如果它实际上不是 2^32-1 那为什么?有解决办法吗?

我正在为 MSVC2010 运行 Qt 4.8.5 的 Windows 64 位版本。

【问题讨论】:

标签: c++ qt qt4 qtcore qlist


【解决方案1】:

我使用 qt4.8.6 在具有 4GB RAM 的 Ubuntu 32 位中对此进行了测试。我的最大尺寸是 268,435,450

我使用 qt4.8.4 在具有 4GB RAM 的 Windows7 32 位中对此进行了测试。我的最大尺寸是 134,217,722

发生此错误:'std::bad_alloc'

#include <QCoreApplication>
#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QList<bool> li;
    for(int i=0; ;i++)
    {
        li.append(true);
        if(i>268435449)
            qDebug()<<i;
    }
    return a.exec();
}

输出是:

268435450

在抛出 'std::bad_alloc' 实例后调用终止
什么():std::bad_alloc

【讨论】:

  • 在 64 位 Ubuntu 4 GB RAM Qt 5.3.2 上 268,435,453 之后出现同样的异常
  • 虽然它很有用,但这并不能回答问题的解决方法是什么以及为什么会发生。请使用 cmets 获取更多信息。
【解决方案2】:

QList 将其元素存储在void * 数组中。

因此,一个包含 228 个项目的列表,其中每个项目都是一个 void *,在 32 位机器上将是 230 字节长,而 2 31 字节在 64 位机器上。我怀疑你可以请求这么大块连续内存。

为什么要分配如此庞大的列表呢?你确定你真的需要它吗?


void * 元素组成的数组支持的想法是因为可以将列表上的多个操作移至非模板代码,从而减少生成的代码量。

如果类型足够小(即sizeof(T) &lt;= sizeof(void*)),并且如果类型可以通过memmove 在内存中移动,QList 将项目直接存储在void * 数组中。否则,每个项目将通过new 在堆上分配,并且数组将存储指向这些项目的指针。一组类型特征用于确定如何处理每种类型,请参阅Q_DECLARE_TYPEINFO

虽然理论上这种方法听起来很有吸引力,但在实践中:

  • 对于小于void * 的所有原始类型(char;int 和 float 64 位等),您浪费了数组中分配空间的 50% 到 75%
  • 对于所有大于void * 的可移动类型(32 位双精度、QVariant 等),为列表中的每个项目支付堆分配(加上数组本身)李>
  • QList 代码通常不如 QVector 一优化
  • 如今的编译器在合并模板实例方面做得相当不错,因此这种设计的最初原因已经丢失。

Today it's a much better idea to stick with QVector。不幸的是,Qt API 到处都暴露了 QList 并且无法更改它们(我们需要 C++11 将 QList 定义为 QVector 的模板别名......)

【讨论】:

    【解决方案3】:

    有人遇到过 QList 的最大大小吗?我有一个指向我的对象的指针的 QList,并且发现当它到达第 268,435,455 个项目时,它会默默地抛出一个错误,这正是 28 位。我本来希望它至少有 31 位的最大大小(减去一位,因为 size() 返回一个有符号整数),或者在我的 64 位计算机上具有 63 位的最大大小,但情况似乎并非如此。

    int 中存储的理论最大正数为 2^31 - 1。指针大小为 4 字节(对于 32 位机器),因此它们的最大可能数量为 2^29 - 1。将数据附加到容器会增加碎片堆内存,因此您可能只能分配一半可能的内存。尝试使用 reserve() 或 resize() 代替。

    此外,Win32 对内存分配有一些限制。因此,没有特殊选项编译的应用程序不能分配超过此限制(1G 或 2G)。

    你确定这个巨大的容器吗?优化应用会更好吗?

    【讨论】:

    • 我投了赞成票。 OP 还应该注意,代码在带有 Qt 容器的 64 位系统上不会得到 63 位,甚至不会得到 2^(63-3) -1 = 2^60-1。标准容器应该用于那些。 Qt 容器对于此类事情非常有限。 This 是相关的。还应注意,内核和其他进程也需要内存。
    • 大约 1GB 或 2GB,这是真的。
    【解决方案4】:

    虽然其他答案在解释问题方面做出了有用的尝试,但它们都没有真正回答问题或没有抓住重点。感谢大家帮我追查问题。

    正如 Ali Mofrad 所提到的,当 QList 未能在我的 QList::append(MyObject*) 调用中分配额外空间时,引发的错误是 std::bad_alloc 错误。这是 Qt 源代码中发生这种情况的地方:

    qlist.cpp: line 62:
    static int grow(int size)         //size = 268435456
    {
        //this is the problem line
        volatile int x = qAllocMore(size * sizeof(void *), QListData::DataHeaderSize) / sizeof(void *);
        return x;                     //x = -2147483648
    }
    
    qlist.cpp: line 231:
    void **QListData::append(int n)   //n = 1
    {
        Q_ASSERT(d->ref == 1);
        int e = d->end;
        if (e + n > d->alloc) {
            int b = d->begin;
            if (b - n >= 2 * d->alloc / 3) {
                //...
           } else {
                realloc(grow(d->alloc + n));    //<-- grow() is called here
            }
        }
        d->end = e + n;
        return d->array + e;
    }
    

    grow() 中,请求的新大小 (268,435,456) 乘以 sizeof(void*) (8) 以计算新内存块的大小以适应不断增长的 QList。问题是,如果是无符号 int32,则 268435456*8 等于 +2,147,483,648,对于有符号 int32,则等于 -2,147,483,648,这是在我的操作系统上从 grow() 返回的内容。因此,当在QListData::realloc(int) 中调用 std::realloc() 时,我们正试图增长到负大小。

    正如ddriver 建议的那样,这里的解决方法是使用QList::reserve() 预先分配空间,防止我的QList 不得不增长。

    简而言之,QList 的最大大小为 2^28-1 个项目除非您预先分配,在这种情况下,最大大小确实是预期的 2^31-1。

    更新(2020 年 1 月):Qt 5.5 中的This appears to have changed,这样 2^28-1 现在是 QList 和 QVector 允许的最大大小,无论您是否提前预订。可惜了。

    【讨论】:

    • 如果您错过了我的最后一条评论,请考虑切换到 QVectorstd::vector 并将这些点保留在容器中,而不是动态分配它们并存储指向它们的指针,您将节省cpu时间和内存大时间。另外,如果你真的需要继续加分,你可以考虑用更保守的增长策略来实现你自己的容器
    • 注意问题不是乘法(sizeof 返回一个 size_t,这将是 64 位上的 64 位整数)但 qAllocMore 处理普通整数.而且,该代码在 Qt 5 中发生了重大变化。
    • @peppe 很好的观察。我没有 Qt5 源代码。他们是否更改了 QList 和相关函数以使用 quint64 或 size_t 像它应该的那样?
    • 更新:我现在使用的是 Qt 5.11.1,它似乎已将大小限制硬编码为 2^28-1 个项目。 QList::reserve() 现在在保留较大尺寸时会引发 badalloc 错误。相关代码注释为Qt 5.7新增。我不知道这会在较新的版本中得到修复。
    猜你喜欢
    • 2011-07-25
    • 1970-01-01
    • 2011-10-12
    • 2011-02-05
    • 2012-09-30
    • 2013-04-13
    • 2021-01-14
    • 2011-03-23
    • 2019-04-30
    相关资源
    最近更新 更多