Есть ли ограничение максимальной длины массива в C ++?


183

Есть ли максимальная длина для массива в C ++?

Это предел C ++ или это зависит от моей машины? Это настраивается? Зависит ли это от типа массива?

Могу ли я как-то нарушить этот лимит или мне нужно искать лучший способ хранения информации? И какой должен быть самый простой способ?

Что мне нужно сделать, так это хранить long long int в массиве, я работаю в среде Linux. Мой вопрос: что мне делать, если мне нужно хранить массив из N длинных целых чисел с N> 10 цифрами?

Мне это нужно, потому что я пишу некоторый криптографический алгоритм (например, p-Pollard) для школы и попадаю в эту стену целых чисел и длины представления массивов.

Ответы:


163

Есть два ограничения, оба не навязанные C ++, а аппаратными средствами.

Первый предел (никогда не должен быть достигнут) устанавливается ограничениями типа размера, используемого для описания индекса в массиве (и его размера). Это определяется максимальным значением, которое std::size_tможет принять система. Этот тип данных достаточно большой, чтобы содержать размер в байтах любого объекта.

Другой предел - это предел физической памяти. Чем больше ваши объекты в массиве, тем быстрее будет достигнут этот предел, поскольку память заполнена. Например, a vector<int>данного размера n обычно занимает в несколько раз больше памяти, чем массив типа vector<char>(минус небольшое постоянное значение), поскольку intобычно больше, чем char. Следовательно, a vector<char>может содержать больше элементов, чем vector<int>до заполнения памяти. То же самое относится и к необработанным массивам в стиле C, таким как int[]и char[].

Кроме того, этот верхний предел может зависеть от типа, allocatorиспользуемого для построения, vectorпотому что allocatorон свободен для управления памятью любым удобным для него способом. Очень странный, но, тем не менее, мыслимый распределитель может объединять память таким образом, чтобы идентичные экземпляры объекта совместно использовали ресурсы. Таким образом, вы можете вставить много идентичных объектов в контейнер, который в противном случае использовал бы всю доступную память.

Кроме того, C ++ не устанавливает никаких ограничений.


20
Также вы обычно можете легко достичь ограничений размера стека, особенно если вы используете потоки, которые опять-таки зависят от реализации (но могут быть изменены).
Аларик

@Alaric: Верно. Я не хотел вдаваться в подробности системы, потому что они сильно различаются, и я не являюсь экспертом ни в одной из них.
Конрад Рудольф

@ Конрад, интересный момент о типах распределителя, а не то, о чем я знал. Спасибо за информацию.
SmacL

11
std :: size_t обычно (всегда?) является размером указателя, а не размером самого большого целого числа, которое имеет встроенную аппаратную поддержку в целочисленной математической единице. На каждой x86 ОС, которую я использовал, size_t - 32-разрядная для 32-разрядной и 64-разрядная для 64-разрядной.
Мистер Фуз,

2
Насколько я понимаю, максимальный предел массива - это максимальное значение слова процессора . Это связано с оператором индексации. Например, машина может иметь размер слова 16 бит, но регистр адресации 32 бита. Размер памяти ограничен размером, передаваемым в newили malloc. Часть памяти, большая чем массив, может быть доступна через указатель.
Томас Мэтьюз

171

Никто не упомянул ограничение размера стекового фрейма .

Память может быть выделена в двух местах:

  • На куче (динамически выделяемая память).
    Ограничение размера здесь представляет собой комбинацию доступного оборудования и способности ОС имитировать пространство с помощью других устройств для временного хранения неиспользуемых данных ( т.е. перемещения страниц на жесткий диск).
  • В стеке (локально объявленные переменные).
    Ограничение размера здесь определяется компилятором (с возможными аппаратными ограничениями). Если вы читаете документацию компилятора, вы часто можете настроить этот размер.

Таким образом, если вы выделяете массив динамически (ограничение велико и подробно описано в других публикациях.

int* a1 = new int[SIZE];  // SIZE limited only by OS/Hardware

В качестве альтернативы, если массив размещен в стеке, вы ограничены размером кадра стека. NB векторы и другие контейнеры имеют небольшое присутствие в стеке, но обычно большая часть данных будет в куче.

int a2[SIZE]; // SIZE limited by COMPILER to the size of the stack frame

4
Предпочтительное размещение больших массивов не в стеке или не определяется глобально, а скорее через динамическое распределение (через newили malloc).
Томас Мэтьюз

1
@ Томас Мэтьюз: Не в моем мире. Динамически размещаемые объекты требуют управления. Если это необходимо для динамического размещения, я бы использовал объект стека, который представляет динамически распределенную память, например, std :: vector.
Мартин Йорк,

2
Существует один Cornor случай отсутствует: Global Arrays, а не красота и лучше избегать, они не подпадают под ограничения , установленные в stack, и вам не нужно malloc/ freeсделать работу с ними.
Ted

1
@ted, почему глобальные массивы следует «избегать»? Чтобы быть более точным, я думаю, что вы имеете в виду статически распределенные массивы. Их сфера не должна быть глобальной. Я бы сказал, что они лучше динамических массивов, потому что вы можете использовать абсолютную адресацию с ними (по крайней мере, в Linux), чего нельзя сделать с динамически размещаемыми массивами.
Z бозон

2
Очень важный момент. Недавно я натолкнулся на проект с открытым исходным кодом «производственного качества», который предоставил настраиваемый максимальный размер буфера. Все буферы были размещены в стеке, поэтому настройка достаточно большого значения приведет к тому, что программа сразу запустится при запуске.
Аромат

13

Если рассматривать это с практической, а не теоретической точки зрения, то в 32-битной системе Windows максимальный общий объем памяти, доступный для одного процесса, составляет 2 ГБ. Вы можете преодолеть ограничение, перейдя на 64-разрядную операционную систему с гораздо большей физической памятью, но то, делать это или искать альтернативы, во многом зависит от ваших предполагаемых пользователей и их бюджетов. Вы также можете расширить его, используя PAE .

Тип массива очень важен, так как выравнивание структуры по умолчанию на многих компиляторах составляет 8 байтов, что является очень расточительным, если использование памяти является проблемой. Если вы используете Visual C ++ для работы с Windows, воспользуйтесь директивой #pragma pack как способ преодоления этого.

Еще одна вещь, которую нужно сделать, это посмотреть, что могут помочь вам методы сжатия памяти, такие как разреженные матрицы, сжатие на лету и т. Д. Опять же, это сильно зависит от приложения. Если вы отредактируете свой пост, чтобы дать больше информации о том, что на самом деле находится в ваших массивах, вы можете получить более полезные ответы.

Изменить: Учитывая немного больше информации о ваших точных требованиях, ваши потребности в хранилище, по-видимому, находятся между 7,6 ГБ и 76 ГБ без сжатия, что потребовало бы довольно дорогой 64-битной коробки для хранения в виде массива в памяти в C ++. Возникает вопрос, почему вы хотите хранить данные в памяти, где предполагается скорость доступа, и разрешить произвольный доступ. Лучший способ хранить эти данные вне массива в значительной степени основан на том, как вы хотите получить к ним доступ. Если вам нужен случайный доступ к элементам массива, для большинства приложений существуют способы группировки групп данных, к которым обычно обращаются одновременно. Например, в больших ГИС и пространственных базах данных данные часто разбиваются по географическим областям. В терминах программирования C ++ вы можете переопределить оператор массива [], чтобы при необходимости извлекать части ваших данных из внешнего хранилища.


1
Существуют системные вызовы, которые позволяют выделять память вне пространства программы; но это зависит от ОС и не переносимо. Мы использовали их во встроенных системах.
Томас Мэтьюз

4

Я бы согласился с вышесказанным, что если вы начинаете свой массив с

 int myArray[SIZE] 

тогда размер ограничен размером целого числа. Но вы всегда можете malloc кусок памяти и иметь указатель на него, настолько большой, насколько вы хотите, если malloc не возвращает NULL.


Я не уверен, что это неправильно, или я неправильно понял вас, или что-то еще. Например, это предотвращается компилятором MSVC17: int oops[INT_MAX]{0};он генерирует,C2148 - total size of array must not exceed 0x7fffffff bytes
kayleeFrye_onDeck

С 16 ГБ DDR4 и около 66% памяти объемом памяти, используемым в настоящее время перед запуском моего приложения для отладки в Windows 10 с VS2017, у меня есть неопределенное ограничение на размер массива int, с которым я могу инициализироваться 0. Иногда я могу сделать это с ~ 257 тысячами элементов, иногда я получаю переполнение стека. Если я добавлю что-то в свое приложение, кроме основного и массива, это число будет уменьшаться (очевидно). Мне пришлось экспериментировать, чтобы определить это число, поэтому я не понимаю, как на эту метрику можно положиться, не зная ваших теоретических пределов в вакууме.
kayleeFrye_onDeck

4

Чтобы суммировать ответы, расширить их и ответить на ваш вопрос напрямую:

нет, C ++ не накладывает никаких ограничений на размеры массива.

Но так как массив должен храниться где-то в памяти, применяются ограничения, связанные с памятью, налагаемые другими частями компьютерной системы. Обратите внимание, что эти ограничения напрямую не связаны с размерами (= количество элементов) массива, а скорее с его размером (= количество занятой памяти). Размеры ( D ) и в оперативной памяти размер ( S ) из массива не то же самое, как они связаны памяти , принятым одним элементом ( Е ): S = Д * Е . Е

Сейчас зависит от:

  • тип элементов массива (элементы могут быть меньше или больше)
  • выравнивание памяти (для повышения производительности элементы размещаются по адресам, кратным некоторому значению, которое вводит
    «пустое пространство» (заполнение) между элементами
  • размер статических частей объектов (в объектно-ориентированном программировании статические компоненты объектов одного типа сохраняются только один раз, независимо от количества таких объектов одного типа)

Также обратите внимание, что вы обычно получаете различные ограничения, связанные с памятью, выделяя данные массива в стеке (как автоматическая переменная int t[N]:) или в куче (динамическое размещение сmalloc() / newили с использованием механизмов STL), или в статической части памяти процесса (как статическая переменная:) static int t[N]. Даже при выделении в куче вам все равно нужно небольшое количество памяти в стеке для хранения ссылок на выделенные в куче блоки памяти (но обычно это незначительно).

Размер size_tтипа не влияет на программиста (я предполагаю, что программист использует size_tтип для индексации, поскольку он предназначен для него), так как поставщик компилятора должен typedefиметь целочисленный тип, достаточно большой, чтобы адресовать максимальный объем памяти, возможный для данной платформы архитектура.

Источники ограничений объема памяти проистекают из

  • объем памяти, доступный для процесса (который ограничен 2 ^ 32 байтами для 32-битных приложений, даже в ядрах 64-битных ОС),
  • разделение памяти процесса (например, объем памяти процесса, предназначенной для стека или кучи),
  • фрагментация физической памяти (многие разбросанные небольшие фрагменты свободной памяти не применимы для хранения одной монолитной структуры),
  • объем физической памяти,
  • и объем виртуальной памяти.

Они не могут быть «подправлены» на уровне приложения, но вы можете использовать другой компилятор (для изменения пределов размера стека), либо перенести свое приложение на 64-битную версию, либо перенести его на другую ОС, либо изменить физический / конфигурация виртуальной памяти (виртуальной? физической?) машины.

Нередко (и даже желательно) рассматривать все вышеперечисленные факторы как внешние помехи и, следовательно, как возможные источники ошибок времени выполнения, а также тщательно проверять и реагировать на ошибки, связанные с выделением памяти в программном коде.

Итак, наконец: хотя C ++ не накладывает никаких ограничений, вам все равно придется проверять наличие неблагоприятных условий, связанных с памятью, при запуске вашего кода ... :-)


3

Как отмечалось много отличных ответов, существует множество ограничений, которые зависят от вашей версии компилятора C ++, операционной системы и характеристик компьютера. Тем не менее, я предлагаю следующий скрипт на Python, который проверяет ограничение на вашем компьютере.

Он использует бинарный поиск и на каждой итерации проверяет, возможен ли средний размер, путем создания кода, который пытается создать массив такого размера. Скрипт пытается скомпилировать его (извините, эта часть работает только в Linux) и настроить бинарный поиск в зависимости от успеха. Проверьте это:

import os

cpp_source = 'int a[{}]; int main() {{ return 0; }}'

def check_if_array_size_compiles(size):
        #  Write to file 1.cpp
        f = open(name='1.cpp', mode='w')
        f.write(cpp_source.format(m))
        f.close()
        #  Attempt to compile
        os.system('g++ 1.cpp 2> errors')
        #  Read the errors files
        errors = open('errors', 'r').read()
        #  Return if there is no errors
        return len(errors) == 0

#  Make a binary search. Try to create array with size m and
#  adjust the r and l border depending on wheather we succeeded
#  or not
l = 0
r = 10 ** 50
while r - l > 1:
        m = (r + l) // 2
        if check_if_array_size_compiles(m):
                l = m
        else:
                r = m

answer = l + check_if_array_size_compiles(r)
print '{} is the maximum avaliable length'.format(answer)

Вы можете сохранить его на своем компьютере и запустить, и он напечатает максимальный размер, который вы можете создать. Для моей машины это 2305843009213693951.


2

Одна вещь, которую я не думаю, была упомянута в предыдущих ответах.

Я всегда ощущаю «неприятный запах» в смысле рефакторинга, когда люди используют такие вещи в своем дизайне.

Это огромный массив и, возможно, не лучший способ представления ваших данных как с точки зрения эффективности, так и с точки зрения производительности.

веселит,

обкрадывать


Есть ли у вас какие-либо предложения о том, что я должен использовать?
Луис

Если вы можете сказать нам, какие данные вы храните, то, возможно, мы можем. (-:
Роб Уэллс

Извините, Луис, мой первый ответ был очень легкомысленным. Это будет зависеть от характера ваших данных. Отношения между вашими данными будут определять модель, которую вы используете для представления данных. Тогда коллекция должна быть очевидна из этого. Если нет, я бы беспокоился о модели данных.
Роб Уэллс

не так легкомысленен для меня: как насчет кэшированной базы данных с такой игрушкой? tweaktown.com/news/22066/...

2

Если вам приходится иметь дело с такими большими данными, вам нужно разделить их на управляемые куски. Это не все вписывается в память на любом маленьком компьютере. Вероятно, вы можете загрузить часть данных с диска (независимо от того, что уместно), выполнить ваши вычисления и внести в них изменения, сохранить их на диск, а затем повторить до завершения.


См. Также раздел «Сортировка слиянием» для примера алгоритма обработки данных, которые слишком велики, чтобы поместиться в память.
Томас Мэтьюз

2

Как ни досадно неспецифично, как все текущие ответы, они в основном правильные, но со многими оговорками, не всегда упоминаемыми. Суть в том, что у вас есть два верхних предела, и только один из них действительно определен, так что YMMV :

1. Ограничение времени компиляции

В основном то, что позволит ваш компилятор. Для Visual C ++ 2017 в 64-разрядной версии Windows 10 это мой максимальный лимит во время компиляции до ограничения в 2 ГБ,

unsigned __int64 max_ints[255999996]{0};

Если бы я сделал это вместо этого,

unsigned __int64 max_ints[255999997]{0};

Я бы получил:

Error C1126 automatic allocation exceeds 2G

Я не уверен, как 2G коррелирует с 255999996/ 7. Я погуглил оба числа, и единственное, что я смог найти, возможно, было связано с этим * nix Q & A о проблеме точности сdc . В любом случае, кажется, что не имеет значения, какой тип массива int вы пытаетесь заполнить, сколько элементов может быть выделено.

2. Ограничения времени выполнения

Ваш стек и куча имеют свои ограничения. Эти ограничения являются значениями, которые изменяются в зависимости от доступных системных ресурсов, а также от того, насколько «тяжелым» является само ваше приложение. Например, с моими текущими системными ресурсами я могу заставить это работать:

int main()
{
    int max_ints[257400]{ 0 };
    return 0;
}

Но если я немного подправлю ...

int main()
{
    int max_ints[257500]{ 0 };
    return 0;
}

Бам! Переполнение стека!

Exception thrown at 0x00007FF7DC6B1B38 in memchk.exe: 0xC00000FD: Stack overflow (parameters: 0x0000000000000001, 0x000000AA8DE03000). Unhandled exception at 0x00007FF7DC6B1B38 in memchk.exe: 0xC00000FD: Stack overflow (parameters: 0x0000000000000001, 0x000000AA8DE03000).

И просто, чтобы детализировать всю тяжесть вашего приложения, это было хорошо, чтобы пойти:

int main()
{
    int maxish_ints[257000]{ 0 };
    int more_ints[400]{ 0 };
    return 0;
}  

Но это вызвало переполнение стека:

int main()
{
    int maxish_ints[257000]{ 0 };
    int more_ints[500]{ 0 };
    return 0;
}  

1

Я удивлен, что функция-член max_size () в std :: vector здесь не упоминалась.

Msgstr "Возвращает максимальное количество элементов, которое может содержать контейнер из-за ограничений реализации системы или библиотеки, т.е. std :: distance (begin (), end ()) для самого большого контейнера."

Мы знаем, что std::vectorон реализован в виде динамического массива под капотом, поэтому max_size()должен дать очень точное приближение к максимальной длине динамического массива на вашей машине.

Следующая программа создает таблицу приблизительной максимальной длины массива для различных типов данных.

#include <iostream>
#include <vector>
#include <string>
#include <limits>

template <typename T>
std::string mx(T e) {
    std::vector<T> v;
    return std::to_string(v.max_size());
}

std::size_t maxColWidth(std::vector<std::string> v) {
    std::size_t maxWidth = 0;

    for (const auto &s: v)
        if (s.length() > maxWidth)
            maxWidth = s.length();

    // Add 2 for space on each side
    return maxWidth + 2;
}

constexpr long double maxStdSize_t = std::numeric_limits<std::size_t>::max();

// cs stands for compared to std::size_t
template <typename T>
std::string cs(T e) {
    std::vector<T> v;
    long double maxSize = v.max_size();
    long double quotient = maxStdSize_t / maxSize;
    return std::to_string(quotient);
}

int main() {
    bool v0 = 0;
    char v1 = 0;

    int8_t v2 = 0;
    int16_t v3 = 0;
    int32_t v4 = 0;
    int64_t v5 = 0;

    uint8_t v6 = 0;
    uint16_t v7 = 0;
    uint32_t v8 = 0;
    uint64_t v9 = 0;

    std::size_t v10 = 0;
    double v11 = 0;
    long double v12 = 0;

    std::vector<std::string> types = {"data types", "bool", "char", "int8_t", "int16_t",
                                      "int32_t", "int64_t", "uint8_t", "uint16_t",
                                      "uint32_t", "uint64_t", "size_t", "double",
                                      "long double"};

    std::vector<std::string> sizes = {"approx max array length", mx(v0), mx(v1), mx(v2),
                                      mx(v3), mx(v4), mx(v5), mx(v6), mx(v7), mx(v8),
                                      mx(v9), mx(v10), mx(v11), mx(v12)};

    std::vector<std::string> quotients = {"max std::size_t / max array size", cs(v0),
                                          cs(v1), cs(v2), cs(v3), cs(v4), cs(v5), cs(v6),
                                          cs(v7), cs(v8), cs(v9), cs(v10), cs(v11), cs(v12)};

    std::size_t max1 = maxColWidth(types);
    std::size_t max2 = maxColWidth(sizes);
    std::size_t max3 = maxColWidth(quotients);

    for (std::size_t i = 0; i < types.size(); ++i) {
        while (types[i].length() < (max1 - 1)) {
            types[i] = " " + types[i];
        }

        types[i] += " ";

        for  (int j = 0; sizes[i].length() < max2; ++j)
            sizes[i] = (j % 2 == 0) ? " " + sizes[i] : sizes[i] + " ";

        for  (int j = 0; quotients[i].length() < max3; ++j)
            quotients[i] = (j % 2 == 0) ? " " + quotients[i] : quotients[i] + " ";

        std::cout << "|" << types[i] << "|" << sizes[i] << "|" << quotients[i] << "|\n";
    }

    std::cout << std::endl;

    std::cout << "N.B. max std::size_t is: " <<
        std::numeric_limits<std::size_t>::max() << std::endl;

    return 0;
}

На моем macOS (clang версии 5.0.1) я получаю следующее:

|  data types | approx max array length | max std::size_t / max array size |
|        bool |   9223372036854775807   |             2.000000             |
|        char |   9223372036854775807   |             2.000000             |
|      int8_t |   9223372036854775807   |             2.000000             |
|     int16_t |   9223372036854775807   |             2.000000             |
|     int32_t |   4611686018427387903   |             4.000000             |
|     int64_t |   2305843009213693951   |             8.000000             |
|     uint8_t |   9223372036854775807   |             2.000000             |
|    uint16_t |   9223372036854775807   |             2.000000             |
|    uint32_t |   4611686018427387903   |             4.000000             |
|    uint64_t |   2305843009213693951   |             8.000000             |
|      size_t |   2305843009213693951   |             8.000000             |
|      double |   2305843009213693951   |             8.000000             |
| long double |   1152921504606846975   |             16.000000            |

N.B. max std::size_t is: 18446744073709551615

На ideone gcc 8.3 я получаю:

|  data types | approx max array length | max std::size_t / max array size |
|        bool |   9223372036854775744   |             2.000000             |
|        char |   18446744073709551615  |             1.000000             |
|      int8_t |   18446744073709551615  |             1.000000             |
|     int16_t |   9223372036854775807   |             2.000000             |
|     int32_t |   4611686018427387903   |             4.000000             |
|     int64_t |   2305843009213693951   |             8.000000             |
|     uint8_t |   18446744073709551615  |             1.000000             |
|    uint16_t |   9223372036854775807   |             2.000000             |
|    uint32_t |   4611686018427387903   |             4.000000             |
|    uint64_t |   2305843009213693951   |             8.000000             |
|      size_t |   2305843009213693951   |             8.000000             |
|      double |   2305843009213693951   |             8.000000             |
| long double |   1152921504606846975   |             16.000000            |

N.B. max std::size_t is: 18446744073709551615

Следует отметить, что это теоретический предел, и на большинстве компьютеров вам не хватит памяти задолго до того, как вы достигнете этого предела. Например, мы видим, что для типа charon gccмаксимальное количество элементов равно max std::size_t. Пытаясь это , мы получаем ошибку:

prog.cpp: In function int main()’:
prog.cpp:5:61: error: size of array is too large
  char* a1 = new char[std::numeric_limits<std::size_t>::max()];

Наконец, как указывает @MartinYork, для статических массивов максимальный размер ограничен размером вашего стека.


0

Как уже указывалось, размер массива ограничен вашим оборудованием и вашей ОС (man ulimit). Однако ваше программное обеспечение может быть ограничено только вашим творческим потенциалом. Например, можете ли вы сохранить свой «массив» на диске? Вам действительно нужны длинные длинные целые? Вам действительно нужен плотный массив? Вам вообще нужен массив?

Одним из простых решений было бы использовать 64-битный Linux. Даже если у вас недостаточно физического ОЗУ для массива, ОС позволит вам распределить память, как если бы вы это делали, поскольку виртуальная память, доступная вашему процессу, вероятно, намного больше, чем физическая память. Если вам действительно нужен доступ ко всему в массиве, это равносильно хранению его на диске. В зависимости от ваших шаблонов доступа, могут быть более эффективные способы сделать это (например, используя mmap () или просто последовательно хранить данные в файле (в этом случае 32-битного Linux будет достаточно)).


2
Хм, диски, массивы, ... кто-нибудь слышал о виртуальной памяти . Операционные системы, поддерживающие виртуальную память , начнут использовать для памяти внешнее устройство, например жесткий диск, и заменят фрагменты внутренней памятью.
Томас Мэтьюз

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.