C ++ new int [0] - ele alocará memória?

240

Um aplicativo de teste simples:

cout << new int[0] << endl;

saídas:

0x876c0b8

Então parece que funciona. O que o padrão diz sobre isso? É sempre legal "alocar" um bloco vazio de memória?


fonte
4
+1 Pergunta muito interessante - embora eu não tenha certeza do quanto isso importa em código real.
Zifre 06/07/09
37
@Zifre: Estou pedindo curiosidade, mas isso pode ser importante no mundo real, por exemplo, quando o tamanho dos blocos de memória alocados é calculado de alguma forma e o resultado do cálculo pode ser zero, então não há necessidade direta de adicionar exceções para não alocar blocos de tamanho zero. Porque eles devem ser alocados e excluídos sem erros (se apenas o bloco de tamanho zero não for desreferenciado). Então, geralmente, isso oferece uma abstração mais ampla do que é um bloco de memória.
2
@ emg-2: Na sua situação de exemplo, isso realmente não importa, porque delete [] é perfeitamente legal em um ponteiro NULL :-).
Evan Teran
2
É apenas tangencialmente relacionado - por isso estou comentando aqui -, mas o C ++ de várias maneiras garante que objetos distintos tenham endereços exclusivos ... mesmo que não exijam explicitamente armazenamento. Um experimento relacionado seria verificar o tamanho de uma estrutura vazia. Ou uma matriz dessa estrutura.
Tirou Dormann
2
Para elaborar o comentário de Shmoopty: Especialmente ao programar com modelos (por exemplo, modelos de classe de política como std :: alocador), é comum no C ++ ter objetos de tamanho zero. O código genérico pode precisar alocar dinamicamente esses objetos e usar ponteiros para comparar a identidade do objeto. É por isso que o operador new () retorna ponteiros exclusivos para solicitações de tamanho zero. Embora indiscutivelmente menos importante / comum, o mesmo raciocínio se aplica à alocação de array e ao operador new [] ().
Trevor Robinson

Respostas:

233

A partir de 5.3.4 / 7

Quando o valor da expressão em um novo declarador direto é zero, a função de alocação é chamada para alocar uma matriz sem elementos.

From 3.7.3.1/2

O efeito de desreferenciar um ponteiro retornado como uma solicitação para tamanho zero é indefinido.

Além disso

Mesmo se o tamanho do espaço solicitado [por novo] for zero, a solicitação poderá falhar.

Isso significa que você pode fazê-lo, mas não pode legalmente (de uma maneira bem definida em todas as plataformas) desreferenciar a memória que obtém - você só pode transmiti-la para excluir a matriz - e deve excluí-la.

Aqui está uma nota de rodapé interessante (isto é, não uma parte normativa do padrão, mas incluída para fins de exposição) anexada à frase de 3.7.3.1/2

[32 A intenção é ter o operador new () implementável chamando malloc () ou calloc (), para que as regras sejam substancialmente as mesmas. C ++ difere de C ao exigir uma solicitação zero para retornar um ponteiro não nulo.]

Faisal Vali
fonte
Recebo um vazamento de memória se não excluir. Isso é esperado? Pelo menos eu não esperava.
EralpB
12
@EralpB: pelo contrário, mesmo que sua solicitação seja zero, essa alocação acontece no Heap, em que uma solicitação implica várias operações de contabilidade, como alocar e inicializar heap guardas antes e depois da zona fornecida pelo alocador, inserção em freelists ou outras estruturas horríveis complexas. Liberá-lo significa fazer a contabilidade reversa.
v.oddou
3
@EralpB sim, eu acho que você pode esperar um vazamento de memória toda vez que não equilibrar um new[]com um delete[]- qualquer que seja o tamanho. Em particular, quando você chamar new[i]você precisa de um pouco mais de memória do que o que você está tentando alloc, a fim de armazenar o tamanho da matriz (que é usado mais tarde por delete[]quando desalocá)
pqnet
24

Sim, é legal alocar uma matriz de tamanho zero como esta. Mas você também deve excluí-lo.


fonte
1
Você tem uma citação para isso? Todos nós sabemos que isso int ar[0];é ilegal, por que tudo bem?
Motti
4
É interessante que o C ++ não seja tão forte com a proibição de objetos de tamanho zero. Pense na otimização da classe base vazia: aqui também, um subobjeto da classe base vazia pode ter tamanho zero. Por outro lado, o Padrão C faz um grande esforço para garantir que nunca haja objetos de tamanho zero criados: Ao definir o malloc (0), ele diz que o efeito é executar o malloc com algum argumento diferente de zero, por exemplo. E em struct {...; Tn []; }; quando não há espaço alocado para a matriz (FAM), ele diz que se comporta como se "n" tivesse um elemento. (Em ambos os casos, o uso do objeto de qualquer forma é UB, como xn [0])
Johannes Schaub - litb
1
Eu acho que sizeof (type)é esperado que nunca retorne zero. Veja por exemplo: stackoverflow.com/questions/2632021/can-sizeof-return-0-zero
pqnet 20/17
Mesmo a chamada "otimização da classe base vazia" é relevante apenas devido à insistência em que todos os objetos (em oposição a todos os bytes dentro dos objetos) devem ter endereços únicos. O C ++ poderia ter sido simplificado se o código que realmente se importava com objetos com endereços exclusivos fosse necessário para garantir que eles tivessem tamanho diferente de zero.
Supercat
15

O que o padrão diz sobre isso? É sempre legal "alocar" um bloco vazio de memória?

Todo objeto tem uma identidade única, ou seja, um endereço exclusivo, o que implica um comprimento diferente de zero (a quantidade real de memória será aumentada silenciosamente, se você solicitar zero bytes).

Se você alocasse mais de um desses objetos, descobriria que eles têm endereços diferentes.

ChrisW
fonte
"Todo objeto tem uma identidade única, ou seja, um endereço exclusivo, o que implica um comprimento diferente de zero" - verdadeiro, mas errado. O objeto tem endereço, mas o ponteiro para o objeto pode apontar para memória aleatória. "A quantidade real de memória será aumentada silenciosamente, se você solicitar zero bytes" - não tenho certeza. operator []também armazena o tamanho da matriz em algum lugar (consulte isocpp.org/wiki/faq/freestore-mgmt#num-elems-in-new-array ). Portanto, se a implementação alocar a contagem de bytes com os dados, ela poderá alocar a contagem de bytes e 0 bytes para os dados, retornando o ponteiro 1-passado-último.
Steed
Um comprimento diferente de zero da memória alocada, não um comprimento diferente de zero da memória utilizável. Meu argumento era que dois ponteiros para dois objetos distintos não deveriam apontar para o mesmo endereço.
ChrisW
1
O padrão ( open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf ) realmente diz (3.7.4.1/2) que chamadas diferentes operator new[]devem retornar indicadores diferentes. Entre, new expressionhá algumas regras adicionais (5.3.4). Não consegui encontrar nenhuma pista de que, newcom tamanho 0, seja realmente necessário para alocar qualquer coisa. Desculpe, diminuí a votação porque acho que sua resposta não está respondendo às perguntas, mas está fornecendo algumas declarações controversas.
Steed
@ A memória da memória para armazenar o comprimento do bloco pode ser implicitamente calculada usando o espaço de endereço. Para matrizes de tamanho zero, por exemplo, poderia ser possível para a new[]implementação para retornar endereços em uma faixa onde não há memória dinâmica mapeada, portanto, não realmente usando qualquer memória (ao usar espaço de endereço)
pqnet
@pqnet: uma implementação de boa qualidade deve permitir um número ilimitado de novos ciclos de exclusão / exclusão, não deveria? Em uma plataforma com ponteiros de 64 bits, pode ser razoável dizer que um computador executando um while(!exitRequested) { char *p = new char[0]; delete [] p; }loop sem reciclar ponteiros entraria em pó antes que pudesse ficar sem espaço de endereço, mas em uma plataforma com ponteiros de 32 bits, seria suposição muito menos razoável.
supercat 27/06
14

Sim, é totalmente legal alocar um 0bloco de tamanho new. Você simplesmente não pode fazer nada útil com ele, pois não há dados válidos para você acessar. int[0] = 5;é ilegal.

No entanto, acredito que o padrão permite coisas como malloc(0)retornar NULL.

Você ainda precisará de delete []qualquer ponteiro que voltar da alocação.

Evan Teran
fonte
5
Em relação ao malloc, você está certo - é a implementação definida. Isso geralmente é visto como uma característica incorreta.
Suponho que uma pergunta interessante seja: a versão nothrow do novo retornará NULL se receber um tamanho 0?
Evan Teran
1
A semântica de new e malloc não está vinculada de forma alguma, pelo menos pelo padrão.
não dizendo que estão ... estava perguntando especificamente sobre a versão nothrow do novo.
Evan Teran
@Evan - a versão do nothrow de novos retornará nulo somente se a solicitação falhar - não apenas se o tamanho da solicitação for 0 @ Nee - não está normativamente vinculado - mas está vinculado por intenção (ou seja, o operador novo pode ser implementado em termos de malloc, mas não o outro contrário) - veja a nota i incluído na minha resposta
Faisal Vali
1

Curiosamente, o C ++ exige que o operador retorne um ponteiro legítimo mesmo quando zero bytes são solicitados. (Exigir esse comportamento estranho simplifica as coisas em outras partes do idioma.)

Eu encontrei o Effective C ++ Third Edition dito assim no "Item 51: Aderir à convenção ao escrever novo e excluir".

shuaihanhungry
fonte
0

Eu garanto a você que o novo int [0] lhe custa espaço extra desde que eu o testei.

Por exemplo, o uso de memória de

int **arr = new int*[1000000000];

é significativamente menor que

int **arr = new int*[1000000000];
for(int i =0; i < 1000000000; i++) {
    arr[i]=new int[0];
}

O uso de memória do segundo trecho de código menos o do primeiro trecho de código é a memória usada para os numerosos novos int [0].

Shi Jieming
fonte