Existe um limite máximo de comprimento de matriz em C ++?


183

Existe um comprimento máximo para uma matriz em C ++?

É um limite de C ++ ou depende da minha máquina? É tweakable? Depende do tipo de matriz é feita?

Posso quebrar esse limite de alguma forma ou tenho que procurar uma maneira melhor de armazenar informações? E qual deve ser a maneira mais simples?

O que tenho que fazer é armazenar int muito longo em uma matriz, estou trabalhando em um ambiente Linux. Minha pergunta é: o que devo fazer se precisar armazenar uma matriz de N inteiros longos com N> 10 dígitos?

Preciso disso porque estou escrevendo algum algoritmo criptográfico (como, por exemplo, o p-Pollard) para a escola, e acertei esse muro de números inteiros e o comprimento da representação de matrizes.

Respostas:


163

Existem dois limites, ambos não impostos pelo C ++, mas pelo hardware.

O primeiro limite (nunca deve ser atingido) é definido pelas restrições do tipo de tamanho usado para descrever um índice na matriz (e seu tamanho). É dado pelo valor máximo que o sistema std::size_tpode receber. Esse tipo de dado é grande o suficiente para conter o tamanho em bytes de qualquer objeto

O outro limite é um limite de memória física. Quanto maiores seus objetos na matriz, mais rapidamente esse limite é atingido porque a memória está cheia. Por exemplo, um vector<int>de um determinado tamanho n normalmente leva várias vezes mais memória que uma matriz do tipo vector<char>(menos um pequeno valor constante), já que intgeralmente é maior que char. Portanto, a vector<char>pode conter mais itens que um vector<int>antes que a memória esteja cheia. O mesmo vale para matrizes brutas de estilo C como int[]e char[].

Além disso, esse limite superior pode ser influenciado pelo tipo de allocatorusado para construir o arquivo vectorporque allocatoré livre para gerenciar a memória da maneira que desejar. Um alocador muito estranho, mas concebível, pode agrupar memória de maneira que instâncias idênticas de um objeto compartilhem recursos. Dessa forma, você pode inserir muitos objetos idênticos em um contêiner que, de outra forma, consumiria toda a memória disponível.

Além disso, o C ++ não impõe limites.


20
Além disso, você normalmente pode atingir facilmente os limites de tamanho de pilha, especialmente se estiver usando threads que novamente são específicos da implementação (mas podem ser alterados).
Alaric

@Alaric: Verdadeiro. Eu não queria ir muito fundo nas especificidades do sistema porque elas diferem muito e não sou especialista em nenhuma delas.
21978 Konrad Rudolph

@ Konrad, ponto interessante sobre os tipos de alocadores e não algo que eu sabia. Obrigado pela informação.
SmacL 19/10/08

11
std :: size_t geralmente (sempre?) é o tamanho de um ponteiro, não o tamanho do maior número inteiro que possui suporte de hardware nativo na unidade matemática de número inteiro. Em todos os sistemas operacionais x86 que usei, size_t é 32 bits para um sistema operacional de 32 bits e 64 bits para um sistema operacional de 64 bits.
Sr. Fooz

2
Meu entendimento é que o limite máximo de uma matriz é o valor máximo da palavra do processador . Isso ocorre devido ao operador de indexação. Por exemplo, uma máquina pode ter um tamanho de palavra de 16 bits, mas um registro de endereçamento de 32 bits. Um pedaço de memória é limitado em tamanho pelo parâmetro passado para newou malloc. Um pedaço de memória maior que uma matriz pode ser acessado via ponteiro.
21415 Thomas Thomass

171

Ninguém mencionou o limite no tamanho do quadro da pilha .

Existem dois locais em que a memória pode ser alocada:

  • Na pilha (memória alocada dinamicamente).
    O limite de tamanho aqui é uma combinação de hardware disponível e da capacidade do sistema operacional para o espaço simular usando outros dispositivos para armazenar temporariamente os dados não utilizados ( ou seja Mover páginas para o disco rígido).
  • Na pilha (variáveis ​​declaradas localmente).
    O limite de tamanho aqui é definido pelo compilador (com possíveis limites de hardware). Se você ler a documentação do compilador, poderá ajustar esse tamanho.

Portanto, se você alocar uma matriz dinamicamente (o limite é grande e descrito em detalhes por outras postagens).

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

Como alternativa, se a matriz estiver alocada na pilha, você estará limitado pelo tamanho do quadro da pilha. Os vetores NB e outros contêineres têm uma pequena presença na pilha, mas geralmente a maior parte dos dados estará no heap.

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

4
A alocação preferida de matrizes grandes não está em uma pilha ou definida globalmente, mas através da alocação dinâmica (via newou malloc).
21411 Thomas Thomass

1
@ Thomas Matthews: Não no meu mundo. Objetos alocados dinamicamente requerem gerenciamento. Se precisar alocar dinamicamente, usaria um objeto de pilha que represente a memória alocada dinamicamente, como um std :: vector.
Martin Iorque

2
Falta um caso importante: Global Arraysembora não seja uma beleza e seja melhor evitada, elas não se enquadram nas restrições da stacke você não precisa malloc/ freetrabalha com elas.
ted

1
@ted, por que as matrizes globais devem ser "melhor evitadas"? Para ser mais preciso, acho que você quer dizer matrizes alocadas estaticamente. Seu escopo não precisa ser global. Eu diria que eles são melhores do que matrizes dinâmicas porque você pode usar o endereçamento absoluto com elas (pelo menos no Linux), o que não pode ser feito com matrizes alocadas dinamicamente.
Z boson

2
Ponto muito importante. Recentemente, deparei com um projeto de software livre de "qualidade de produção" que fornecia um tamanho máximo de buffer configurável. Todos os buffers foram alocados na pilha, portanto, a configuração de um valor suficientemente grande faria com que o programa falhasse imediatamente no lançamento.
Aroth

13

Analisando isso do ponto de vista prático, e não teórico, em um sistema Windows de 32 bits, a quantidade total máxima de memória disponível para um único processo é de 2 GB. Você pode quebrar o limite acessando um sistema operacional de 64 bits com muito mais memória física, mas fazer isso ou procurar alternativas depende muito dos usuários pretendidos e de seus orçamentos. Você também pode estendê-lo um pouco usando o PAE .

O tipo da matriz é muito importante, pois o alinhamento da estrutura padrão em muitos compiladores é de 8 bytes, o que é muito inútil se o uso da memória for um problema. Se você estiver usando o Visual C ++ para direcionar o Windows, confira a diretiva #pragma pack como uma maneira de superar isso.

Outra coisa a fazer é analisar o que as técnicas de compactação de memória podem ajudá-lo, como matrizes esparsas, compactação instantânea, etc ... Novamente, isso depende muito do aplicativo. Se você editar sua postagem para fornecer mais informações sobre o que realmente está em suas matrizes, poderá obter respostas mais úteis.

Editar: com um pouco mais de informações sobre seus requisitos exatos, seu armazenamento parece estar entre 7,6 GB e 76 GB descompactado, o que exigiria uma caixa de 64 bits bastante cara para armazenar como uma matriz na memória em C ++. Isso levanta a questão de por que você deseja armazenar os dados na memória, onde se presume a velocidade do acesso e permitir o acesso aleatório. A melhor maneira de armazenar esses dados fora de uma matriz é baseada em como você deseja acessá-los. Se você precisar acessar os membros da matriz aleatoriamente, para a maioria dos aplicativos, existem maneiras de agrupar grupos de dados que tendem a ser acessados ​​ao mesmo tempo. Por exemplo, em grandes bancos de dados geográficos e espaciais, os dados geralmente são agrupados por área geográfica. Em termos de programação C ++, você pode substituir o operador da matriz [] para buscar partes de seus dados do armazenamento externo, conforme necessário.


1
Existem chamadas de sistema que permitem alocação de memória fora do espaço do programa; mas isso depende do sistema operacional e não é portátil. Nós os usamos em sistemas embarcados.
21415 Thomas Thomass

4

Concordo com o exposto acima, que se você estiver inicializando sua matriz com

 int myArray[SIZE] 

SIZE é limitado pelo tamanho de um número inteiro. Mas você sempre pode localizar um pedaço de memória e apontar para ele, do tamanho que desejar, desde que o malloc não retorne NULL.


Não tenho certeza se isso está incorreto ou se eu o entendi mal, ou algo mais. Por exemplo, isso é evitado pelo compilador MSVC17: int oops[INT_MAX]{0};Ele gera,C2148 - total size of array must not exceed 0x7fffffff bytes
kayleeFrye_onDeck

Com 16GB DDR4 e sobre a 66%memória atualmente usada antes de iniciar meu aplicativo como depuração no Windows 10 com o VS2017, tenho um limite indefinido sobre o tamanho de uma matriz int com a qual posso inicializar 0. Às vezes eu posso fazer isso com ~ 257k elementos, às vezes eu recebo um estouro de pilha. Se eu adicionar algo ao meu aplicativo além do principal e da matriz, esse número será reduzido (obviamente). Eu tive que experimentar para determinar esse número, para não ver como essa métrica pode ser usada além de conhecer seus limites teóricos no vácuo.
precisa saber é o seguinte

4

Para resumir as respostas, estenda-as e responda diretamente à sua pergunta:

Não, o C ++ não impõe limites para as dimensões de uma matriz.

Porém, como a matriz precisa ser armazenada em algum lugar da memória, também se aplicam os limites relacionados à memória impostos por outras partes do sistema do computador. Observe que esses limites não estão diretamente relacionados às dimensões (= número de elementos) da matriz, mas ao tamanho (= quantidade de memória consumida). Dimensões ( D ) e de tamanho de memória ( S ) de uma matriz não é a mesma, como estes são sujeitos a memória ocupada por um único elemento ( E ): S = D * E .

AgoraE depende de:

  • o tipo dos elementos da matriz (os elementos podem ser menores ou maiores)
  • alinhamento de memória (para aumentar o desempenho, os elementos são colocados em endereços que são multiplicados por algum valor, o que introduz
    'espaço desperdiçado' (preenchimento) entre os elementos
  • tamanho das partes estáticas dos objetos (na programação orientada a objetos, os componentes estáticos dos objetos do mesmo tipo são armazenados apenas uma vez, independentemente do número desses objetos do mesmo tipo)

Observe também que você geralmente obtém diferentes limitações relacionadas à memória, alocando os dados da matriz na pilha (como uma variável automática:) int t[N]ou no heap ( alocação dinâmica com malloc()/ newou usando mecanismos STL) ou na parte estática da memória do processo (como uma variável estática:) static int t[N]. Mesmo ao alocar no heap, você ainda precisa de uma pequena quantidade de memória na pilha para armazenar referências aos blocos de memória alocados ao heap (mas isso geralmente é insignificante).

O tamanho do size_ttipo não tem influência sobre o programador (presumo que o programador use o size_ttipo para indexação, como foi projetado para ele), pois o provedor do compilador precisa typedefdele para um tipo inteiro grande o suficiente para endereçar a quantidade máxima de memória possível para a plataforma fornecida arquitetura.

As fontes das limitações de tamanho de memória derivam de

  • quantidade de memória disponível para o processo (limitada a 2 ^ 32 bytes para aplicativos de 32 bits, mesmo nos kernels do SO de 64 bits),
  • a divisão da memória do processo (por exemplo, quantidade da memória do processo projetada para pilha ou pilha),
  • a fragmentação da memória física (muitos pequenos fragmentos dispersos de memória livre não são aplicáveis ​​ao armazenamento de uma estrutura monolítica),
  • quantidade de memória física,
  • e a quantidade de memória virtual.

Eles não podem ser 'ajustados' no nível do aplicativo, mas você pode usar um compilador diferente (para alterar os limites de tamanho da pilha), ou portar seu aplicativo para 64 bits, ou para outro sistema operacional ou alterar a configuração física / configuração de memória virtual da máquina (virtual? física?).

Não é incomum (e até aconselhável) tratar todos os fatores acima como distúrbios externos e, portanto, como possíveis fontes de erros de tempo de execução, e verificar cuidadosamente e reagir a erros relacionados à alocação de memória no código do seu programa.

Então, finalmente: enquanto o C ++ não impõe limites, você ainda precisa verificar condições adversas relacionadas à memória ao executar seu código ... :-)


3

Como muitas excelentes respostas observadas, existem muitos limites que dependem da sua versão do compilador C ++, sistema operacional e características do computador. No entanto, sugiro o seguinte script no Python que verifica o limite em sua máquina.

Ele usa a pesquisa binária e, em cada iteração, verifica se o tamanho do meio é possível, criando um código que tenta criar uma matriz do tamanho. O script tenta compilá-lo (desculpe, esta parte funciona apenas no Linux) e ajustar a pesquisa binária, dependendo do sucesso. Confira:

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)

Você pode salvá-lo em sua máquina e iniciá-lo, e ele imprimirá o tamanho máximo que você pode criar. Para minha máquina, é 2305843009213693951.


2

Acho que uma coisa não foi mencionada nas respostas anteriores.

Estou sempre sentindo um "mau cheiro" no sentido da refatoração quando as pessoas estão usando essas coisas em seu design.

Essa é uma variedade enorme e possivelmente não é a melhor maneira de representar seus dados, tanto do ponto de vista da eficiência quanto do desempenho.

Felicidades,

Roubar


Você tem alguma sugestão sobre o que devo usar?
luiss

Se você pode nos dizer quais são os dados que você está armazenando, talvez possamos. (-:
Rob Wells

Desculpe Luis, minha primeira resposta foi muito irreverente. Será orientado pela natureza dos seus dados. Os flashes de relação dos seus dados direcionarão o modelo usado para representar os dados. Então a coleção deve ser aparente a partir disso. Caso contrário, eu me preocuparia com o modelo de dados.
Rob Wells

não é tão irreverente para mim: que tal um banco de dados em cache com um brinquedo como este? tweaktown.com/news/22066/…

2

Se você precisar lidar com dados tão grandes, precisará dividi-los em partes gerenciáveis. Nem tudo se encaixa na memória de qualquer computador pequeno. Provavelmente, você pode carregar uma parte dos dados do disco (o que for razoavelmente adequado), executar seus cálculos e alterações, armazená-los no disco e repetir até concluir.


Consulte também Merge Sort em um algoritmo de exemplo para manipular dados muito grandes para caber na memória.
21415 Thomas Thomass

2

Por mais irritantemente inespecíficas que sejam todas as respostas atuais, elas estão certas, mas com muitas ressalvas, nem sempre mencionadas. A essência é que você tem dois limites superiores e apenas um deles é algo realmente definido, então YMMV :

1. Limites de tempo de compilação

Basicamente, o que o seu compilador permitirá. Para o Visual C ++ 2017 em uma caixa x64 do Windows 10, esse é o meu limite máximo em tempo de compilação antes de incorrer no limite de 2 GB,

unsigned __int64 max_ints[255999996]{0};

Se eu fiz isso,

unsigned __int64 max_ints[255999997]{0};

Eu receberia:

Error C1126 automatic allocation exceeds 2G

Não sei como o 2G se correlaciona com 255999996/ 7. Pesquisei os dois números no Google, e a única coisa que pude achar que estava possivelmente relacionada foi essa * nix Perguntas e Respostas sobre uma problema de precisãodc . De qualquer maneira, não parece importar qual tipo de matriz int você está tentando preencher, apenas quantos elementos podem ser alocados.

2. Limites de tempo de execução

Sua pilha e pilha têm suas próprias limitações. Esses limites são valores que mudam com base nos recursos disponíveis do sistema, além de quão "pesado" é seu próprio aplicativo. Por exemplo, com meus recursos atuais do sistema, posso executar isso:

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

Mas se eu ajustá-lo um pouco ...

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

Bam! Estouro de pilha!

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).

E apenas para detalhar todo o peso do ponto do seu aplicativo, foi bom:

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

Mas isso causou um estouro de pilha:

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

1

Estou surpreso que a função de membro max_size () do std :: vector não tenha sido mencionada aqui.

"Retorna o número máximo de elementos que o contêiner pode conter devido a limitações de implementação do sistema ou da biblioteca, ou seja, std :: distance (begin (), end ()) para o maior contêiner."

Sabemos que isso std::vectoré implementado como uma matriz dinâmica embaixo do capô, portanto, max_size()devemos fornecer uma aproximação muito próxima do comprimento máximo de uma matriz dinâmica em sua máquina.

O programa a seguir cria uma tabela com o comprimento máximo aproximado da matriz para vários tipos de dados.

#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;
}

No meu macOS (clang versão 5.0.1), obtenho o seguinte:

|  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

No ideone gcc 8.3 , recebo:

|  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

Deve-se notar que esse é um limite teórico e que, na maioria dos computadores, você ficará sem memória muito antes de atingir esse limite. Por exemplo, vemos que para o tipo charde gcc, o número máximo de elementos é igual ao máximo de std::size_t. Tentando isso , obtemos o erro:

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()];

Por fim, como aponta @MartinYork, para matrizes estáticas, o tamanho máximo é limitado pelo tamanho da sua pilha.


0

Como já foi apontado, o tamanho da matriz é limitado pelo seu hardware e seu sistema operacional (man ulimit). Seu software, porém, pode ser limitado apenas por sua criatividade. Por exemplo, você pode armazenar sua "matriz" em disco? Você realmente precisa de longas ints? Você realmente precisa de uma matriz densa? Você precisa mesmo de uma matriz?

Uma solução simples seria usar o Linux de 64 bits. Mesmo que você não possua memória RAM suficiente para o seu array, o sistema operacional permitirá que você aloque memória como se tivesse, uma vez que a memória virtual disponível para o seu processo é provavelmente muito maior que a memória física. Se você realmente precisar acessar tudo na matriz, isso significa armazená-lo no disco. Dependendo dos seus padrões de acesso, pode haver maneiras mais eficientes de fazer isso (por exemplo: usando mmap () ou simplesmente armazenando os dados sequencialmente em um arquivo (nesse caso, o Linux de 32 bits seria suficiente)).


2
Hmm, discos, matrizes, ... alguém ouve sobre memória virtual . Os sistemas operacionais que suportam memória virtual começarão a usar um dispositivo externo para memória, como um disco rígido, e trocarão os pedaços pela memória interna.
9789 Thomas Thomass

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.