O que acontece se eu definir uma matriz de tamanho 0 em C / C ++?

127

Apenas curioso, o que realmente acontece se eu definir uma matriz de comprimento zero int array[0];no código? O GCC não reclama de forma alguma.

Programa de amostra

#include <stdio.h>

int main() {
    int arr[0];
    return 0;
}

Esclarecimento

Na verdade, estou tentando descobrir se matrizes de comprimento zero inicializadas dessa maneira, em vez de serem apontadas como o comprimento variável nos comentários de Darhazer, são otimizadas ou não.

Isso ocorre porque eu tenho que liberar algum código para a natureza, então estou tentando descobrir se devo lidar com casos em que o SIZEé definido como 0, o que acontece em algum código com um estaticamente definidoint array[SIZE];

Fiquei realmente surpreso que o GCC não reclame, o que levou à minha pergunta. Das respostas que recebi, acredito que a falta de um aviso se deve em grande parte ao suporte ao código antigo que não foi atualizado com a nova sintaxe [].

Como eu estava pensando principalmente no erro, estou rotulando a resposta de Lundin como correta (a de Nawaz foi a primeira, mas não tão completa) - os outros estavam apontando seu uso real para estruturas acolchoadas, embora relevantes, não são ' exatamente o que eu estava procurando.

Alex Koay
fonte
51
@AlexanderCorwin: Infelizmente em C ++, com comportamento indefinido, extensões não padrão e outras anomalias, tentar algo sozinho muitas vezes não é um caminho para o conhecimento.
Benjamin Lindley
5
@JustinKirk Eu também fiquei preso por isso testando e vendo se funcionava. E devido às críticas que recebi no meu post, aprendi que testá-lo e vê-lo funcionando não significa que seja válido e legal. Portanto, um autoteste às vezes não é válido.
StormByte
2
@ JustinKirk, veja a resposta de Matthieu para um exemplo de onde você a usaria. Também pode ser útil em um modelo em que o tamanho da matriz é um parâmetro do modelo. O exemplo da pergunta está obviamente fora de contexto.
Mark Ransom
2
@JustinKirk: Qual é o objetivo []em Python ou mesmo ""em C? Às vezes, você tem uma função ou macro que requer uma matriz, mas não possui dados para inserir nela.
dan04
15
O que é "C / C ++"? Estes são dois idiomas separados
Lightness Races in Orbit

Respostas:

86

Uma matriz não pode ter tamanho zero.

ISO 9899: 2011 6.7.6.2:

Se a expressão for uma expressão constante, ela deverá ter um valor maior que zero.

O texto acima é verdadeiro tanto para uma matriz simples (parágrafo 1). Para um VLA (array de comprimento variável), o comportamento é indefinido se o valor da expressão for menor ou igual a zero (parágrafo 5). Este é um texto normativo no padrão C. Um compilador não tem permissão para implementá-lo de maneira diferente.

gcc -std=c99 -pedantic emite um aviso para o caso não-VLA.

Lundin
fonte
34
"ele deve realmente dar um erro" - a distinção entre "avisos" e "erros" não é reconhecida no padrão (ele menciona apenas "diagnóstico") e a única situação em que a compilação deve parar [ou seja, a diferença no mundo real entre aviso e erro] é encontrar uma #errordiretiva.
Random832
12
FYI, como regra geral, os padrões (C ou C ++) declaram apenas o que os compiladores devem permitir , mas não o que devem proibir . Em alguns casos, eles afirmam que o compilador deve emitir um "diagnóstico", mas é o mais específico possível. O restante é deixado para o fornecedor do compilador. EDIT: O que Random832 disse também.
Mcmcc 15/03/12
8
@Lundin "Não é permitido que um compilador construa um binário que contenha matrizes de comprimento zero." O padrão não diz absolutamente nada disso. Ele diz apenas que deve gerar pelo menos uma mensagem de diagnóstico quando receber um código-fonte contendo uma matriz com uma expressão constante de tamanho zero para seu tamanho. A única circunstância sob a qual o padrão proíbe que um compilador construa um binário é se ele encontrar uma #errordiretiva de pré - processador.
Random832
5
@Lundin A geração de um binário para todos os casos corretos é satisfatória no 1, e a geração ou não de um para casos incorretos não o afetaria. Imprimir um aviso é suficiente para o número 3. Esse comportamento não tem relevância para o item 2, pois o padrão não define o comportamento desse código-fonte.
Random832
13
@Lundin: O ponto é que sua afirmação está errada; compiladores conformes são permitidos para construir um binário contendo um de comprimento zero matrizes, contanto que um de diagnóstico é emitido.
Keith Thompson
85

Conforme o padrão, não é permitido.

No entanto, é prática corrente nos compiladores C tratar essas declarações como uma declaração de membro de matriz flexível ( FAM ) :

C99 6.7.2.1, §16 : Como caso especial, o último elemento de uma estrutura com mais de um membro nomeado pode ter um tipo de matriz incompleto; isso é chamado de membro flexível da matriz.

A sintaxe padrão de uma FAM é:

struct Array {
  size_t size;
  int content[];
};

A ideia é que você o aloque assim:

void foo(size_t x) {
  Array* array = malloc(sizeof(size_t) + x * sizeof(int));

  array->size = x;
  for (size_t i = 0; i != x; ++i) {
    array->content[i] = 0;
  }
}

Você também pode usá-lo estaticamente (extensão gcc):

Array a = { 3, { 1, 2, 3 } };

Isso também é conhecido como estruturas acolchoadas (este termo é anterior à publicação do C99 Standard) ou struct hack (obrigado Joe Wreschnig por apontá-lo).

No entanto, essa sintaxe foi padronizada (e os efeitos garantidos) apenas recentemente em C99. Antes era necessário um tamanho constante.

  • 1 era o caminho portátil a seguir, embora fosse bastante estranho.
  • 0 foi melhor em indicar intenção, mas não é legal no que diz respeito ao Padrão e foi suportado como uma extensão por alguns compiladores (incluindo o gcc).

A prática de preenchimento da cauda, ​​no entanto, depende do fato de que o armazenamento está disponível (cuidado malloc), portanto, não é adequado para empilhar o uso em geral.

Matthieu M.
fonte
@Lundin: Eu não vi nenhum VLA aqui, todos os tamanhos são conhecidos em tempo de compilação. O termo array flexível vem de gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Zero-Length.html e doe se qualifica int content[];aqui até onde eu entendi. Já que eu não sou muito experiente nos termos C da arte ... você poderia confirmar se meu raciocínio parece correto?
precisa
@MatthieuM .: C99 6.7.2.1, §16: Como um caso especial, o último elemento de uma estrutura com mais de um membro nomeado pode ter um tipo de matriz incompleto; isso é chamado de membro flexível da matriz.
Christoph
Esse idioma também é conhecido pelo nome "struct hack" , e eu conheci mais pessoas familiarizadas com esse nome do que "estrutura acolchoada" (nunca ouvi isso antes, exceto talvez como uma referência genérica para preencher uma estrutura para compatibilidade futura com ABI ) ou "membro flexível da matriz", que ouvi pela primeira vez em C99.
1
O uso de um tamanho de matriz 1 para o corte de estrutura evitaria que os compiladores gritassem, mas era apenas "portátil" porque os escritores do compilador eram bons o suficiente para reconhecer esse uso como um padrão de fato. Não fosse a proibição de matrizes de tamanho zero, o consequente uso de matrizes de elemento único pelo programador como um substituto ruim e a atitude histórica dos escritores do compilador de que eles deveriam atender às necessidades do programador mesmo quando não exigido pelo Padrão, os escritores do compilador poderiam foram otimizados foo[x]com facilidade e utilidade para foo[0]sempre que houvesse foouma matriz de elemento único.
21715
1
@RobertSsupportsMonicaCellio: conforme mostrado explicitamente na resposta, mas no final . Também carreguei a explicação antecipadamente, para torná-la mais clara desde o início.
Matthieu M.
58

No padrão C e C ++, a matriz de tamanho zero não é permitida.

Se você estiver usando o GCC, compile-o com a -pedanticopção Dará aviso , dizendo:

zero.c:3:6: warning: ISO C forbids zero-size array 'a' [-pedantic]

No caso de C ++, ele fornece um aviso semelhante.

Nawaz
fonte
9
No Visual C ++ 2010:error C2466: cannot allocate an array of constant size 0
Mark Ransom
4
-O erro simplesmente transforma todos os avisos em erros, que não corrigem o comportamento incorreto do compilador GCC.
precisa
O C ++ Builder 2009 também fornece um erro corretamente:[BCC32 Error] test.c(3): E2021 Array must have at least one element
Lundin 15/03
1
Em vez de -pedantic -Werror, você também pode fazer apenas #-pedantic-errors
Stephan Dollberg 15/03/12
3
Uma matriz de tamanho zero não é a mesma coisa que uma matriz de tamanho zero std::array. (Aparte: Lembro-me, mas não consegue encontrar a fonte que VLAs foram consideradas e explicitamente rejeitada de estar em C ++.)
27

É totalmente ilegal, e sempre foi, mas muitos compiladores negligenciam o sinal do erro. Não sei por que você quer fazer isso. O único uso que eu sei é acionar um erro de tempo de compilação de um booleano:

char someCondition[ condition ];

Se conditioné um falso, então eu recebo um erro de tempo de compilação. Como os compiladores permitem isso, no entanto, passei a usar:

char someCondition[ 2 * condition - 1 ];

Isso fornece um tamanho de 1 ou -1, e eu nunca encontrei um compilador que aceitasse um tamanho de -1.

James Kanze
fonte
Este é um truque interessante para usá-lo.
precisa
10
É um truque comum na metaprogramação, eu acho. Eu não ficaria surpreso se as implementações do STATIC_ASSERTusassem.
precisa
Por que não apenas:#if condition \n #error whatever \n #endif
Jerfov2 13/04
1
@ Jerfov2 porque a condição não pode ser conhecido pelo pré-processamento de tempo, só o tempo de compilação
rmeador
9

Acrescentarei que há uma página inteira da documentação on-line do gcc sobre esse argumento.

Algumas citações:

Matrizes de comprimento zero são permitidas no GNU C.

Na ISO C90, você teria que fornecer ao conteúdo um comprimento de 1

e

As versões do GCC anteriores ao 3.0 permitiam que matrizes de tamanho zero fossem inicializadas estaticamente, como se fossem matrizes flexíveis. Além dos casos úteis, também permitiu inicializações em situações que corromperiam dados posteriores

então você poderia

int arr[0] = { 1 };

e boom :-)

xanatos
fonte
Posso fazer como int a[0], então a[0] = 1 a[1] = 2?
precisa
2
@SurajJain Se você deseja sobrescrever sua pilha :-) C não verifica o índice versus o tamanho da matriz que está escrevendo, então você pode, a[100000] = 5mas se tiver sorte, simplesmente travará seu aplicativo, se tiver sorte: -)
xanatos
Int a [0]; meio uma matriz variável (matriz de tamanho zero), How Can I Agora atribuí-lo
Suraj Jain
@SurajJain Que parte de "C não verifica o índice versus o tamanho da matriz que você está escrevendo" não está clara? Não há verificação de índice em C, você pode escrever após o final da matriz e travar o computador ou substituir bits preciosos da sua memória. Portanto, se você tiver uma matriz de 0 elementos, poderá escrever após o final dos 0 elementos.
Xanatos
Veja isto quora.com/…
Suraj Jain
9

Outro uso de matrizes de comprimento zero é para criar objetos de comprimento variável (pré-C99). Matrizes de comprimento zero são diferentes das matrizes flexíveis que têm [] sem 0.

Citado no documento gcc :

Matrizes de comprimento zero são permitidas no GNU C. Elas são muito úteis como o último elemento de uma estrutura que é realmente um cabeçalho para um objeto de comprimento variável:

 struct line {
   int length;
   char contents[0];
 };
 
 struct line *thisline = (struct line *)
   malloc (sizeof (struct line) + this_length);
 thisline->length = this_length;

Na ISO C99, você usaria um membro flexível da matriz, que é um pouco diferente na sintaxe e na semântica:

  • Membros flexíveis da matriz são gravados como conteúdo [] sem o 0.
  • Membros de matriz flexíveis têm tipo incompleto e, portanto, o operador sizeof pode não ser aplicado.

Um exemplo do mundo real são matrizes de comprimento zero struct kdbus_itemno kdbus.h (um módulo do kernel do Linux).

Duque
fonte
2
IMHO, não havia uma boa razão para o Padrão ter proibido matrizes de comprimento zero; poderia ter objetos de tamanho zero como membros de uma estrutura e considerá-los void*para fins aritméticos (portanto, é proibido adicionar ou subtrair ponteiros a objetos de tamanho zero). Embora os membros de matriz flexível sejam na maioria melhores do que matrizes de tamanho zero, eles também podem atuar como uma espécie de "união" para coisas de alias, sem adicionar um nível extra de indireto "sintático" ao que se segue (por exemplo, dado que struct foo {unsigned char as_bytes[0]; int x,y; float z;}é possível acessar membros x.. z...
supercat
... diretamente, sem precisar dizer myStruct.asFoo.x, por exemplo , etc. Além disso, o IIRC, C grita com qualquer esforço para incluir um membro flexível da matriz dentro de uma estrutura, tornando impossível ter uma estrutura que inclua vários outros membros da matriz flexível de comprimento conhecido conteúdo.
Supercat
@supercat uma boa razão é manter a integridade da regra sobre o acesso a limites de array externos. Como o último membro de uma estrutura, o membro da matriz flexível C99 alcança exatamente o mesmo efeito que a matriz de tamanho zero do GCC, mas sem a necessidade de adicionar casos especiais a outras regras. IMHO, é uma melhoria que sizeof x->contentsé um erro no ISO C, em vez de retornar 0 no gcc. Matrizes de tamanho zero que não são membros de estrutura apresentam muitos outros problemas.
MM
@MM: Quais problemas eles causariam se subtrair dois ponteiros iguais a um objeto de tamanho zero fosse definido como produzindo zero (assim como subtrair ponteiros iguais a qualquer tamanho de objeto) e subtrair ponteiros desiguais para objetos de tamanho zero fosse definido como produzindo Valor não especificado? Se o Padrão especificou que uma implementação pode permitir que uma estrutura contendo uma FAM seja incorporada em outra estrutura, desde que o próximo elemento na última estrutura seja uma matriz com o mesmo tipo de elemento que a FAM ou uma estrutura iniciada com essa matriz e desde que ...
supercat
... reconhece o FAM como aliasing da matriz (se as regras de alinhamento fizessem com que as matrizes chegassem a diferentes deslocamentos, seria necessário um diagnóstico), isso teria sido muito útil. Como é, não há uma boa maneira de ter um método que aceite ponteiros para estruturas do formato geral struct {int n; THING dat[];}e possa trabalhar com itens de duração estática ou automática.
Supercat 01/09
6

Declarações de matriz de tamanho zero dentro de estruturas seriam úteis se fossem permitidas e se a semântica fosse tal que (1) forçasse o alinhamento, mas não alocasse espaço, e (2) indexasse a matriz seria considerado um comportamento definido no caso em que o ponteiro resultante estaria dentro do mesmo bloco de memória que a estrutura. Esse comportamento nunca foi permitido por nenhum padrão C, mas alguns compiladores antigos o permitiram antes que se tornasse padrão para os compiladores permitirem declarações de matriz incompletas com colchetes vazios.

O truque de estrutura, como geralmente implementado usando uma matriz de tamanho 1, é desonesto e não acho que exista nenhum requisito de que os compiladores evitem quebrá-lo. Por exemplo, seria de esperar que se um compilador vê int a[1], seria no seu direito de respeito a[i]como a[0]. Se alguém tentar solucionar os problemas de alinhamento do struct hack através de algo como

typedef struct {
  tamanho uint32_t;
  dados uint8_t [4]; // Use four, para evitar que o preenchimento diminua o tamanho da estrutura
}

um compilador pode ficar esperto e assumir que o tamanho da matriz é realmente quatro:

; Como escrito
  foo = myStruct-> dados [i];
; Como interpretado (assumindo hardware little-endian)
  foo = ((* (uint32_t *) myStruct-> dados) >> (i << 3)) & 0xFF;

Essa otimização pode ser razoável, especialmente se myStruct->datapuder ser carregada em um registro na mesma operação que myStruct->size. Não sei nada no padrão que proíba essa otimização, embora, é claro, quebre qualquer código que possa esperar acessar coisas além do quarto elemento.

supercat
fonte
1
O membro de matriz flexível foi adicionado a C99 como uma versão legítimo do corte struct
MM
O Padrão diz que o acesso a diferentes membros da matriz não entra em conflito, o que tenderia a tornar essa otimização impossível.
Ben Voigt
@BenVoigt: O padrão da linguagem C não especifica o efeito de escrever um byte e ler a palavra que contém simultaneamente, mas 99,9% dos processadores especificam que a gravação será bem-sucedida e a palavra conterá a versão nova ou antiga do byte, juntamente com o conteúdo inalterado dos outros bytes. Se um compilador segmentar esses processadores, qual seria o conflito?
Supercat
@ supercat: O padrão da linguagem C garante que gravações simultâneas em dois elementos diferentes da matriz não entrem em conflito. Portanto, seu argumento de que (leia enquanto escreve) funciona bem, não é suficiente.
Ben Voigt
@BenVoigt: Se um pedaço de código fosse, por exemplo, gravar nos elementos 0, 1 e 2 da matriz em alguma sequência, não seria permitido ler todos os quatro elementos em um longo, modificar três e escrever novamente todos os quatro, mas eu pense que seria permitido ler todos os quatro em um longo, modificar três, escrever de volta os 16 bits inferiores como um curto e os bits 16-23 como um byte. Você discordaria disso? E o código que apenas precisava ler elementos da matriz poderia simplesmente lê-los por um longo período e usá-lo.
Supercat