Por que as matrizes C não podem ter comprimento 0?


13

O padrão C11 diz que as matrizes, tamanho e tamanho variável "devem ter um valor maior que zero". Qual é a justificativa para não permitir um comprimento de 0?

Especialmente para matrizes de comprimento variável, faz todo o sentido ter um tamanho zero de vez em quando. Também é útil para matrizes estáticas quando seu tamanho é de uma opção de configuração de macro ou compilação.

Curiosamente, o GCC (e clang) fornece extensões que permitem matrizes de comprimento zero. Java também permite matrizes de comprimento zero.


7
stackoverflow.com/q/8625572 ... "Uma matriz de comprimento zero seria complicada e confusa para se reconciliar com o requisito de que cada objeto tenha um endereço exclusivo."
Robert Harvey

3
@RobertHarvey: Dado struct { int p[1],q[1]; } foo; int *pp = p+1;, ppseria um ponteiro legítimo, mas *ppnão teria um endereço exclusivo. Por que a mesma lógica não se aplica a uma matriz de comprimento zero? Digamos que, dado int q[0]; em uma estrutura , qse refira a um endereço cuja validade seria semelhante à do p+1exemplo acima.
22714

@DocBrown Do padrão C11 6.7.6.2.5, falando sobre a expressão usada para determinar o tamanho de um VLA "... cada vez que é avaliada, ela deve ter um valor maior que zero". Eu não sei sobre C99 (e parece estranho que eles mudem), mas parece que você não pode ter um comprimento de zero.
21414 Kevin Kevin Cox

@ KevinCox: existe uma versão online gratuita do padrão C11 (ou a parte em questão) disponível?
Doc Brown

A versão final não está disponível gratuitamente (que pena), mas você pode fazer o download de rascunhos. O último rascunho disponível é open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf .
Kevin Cox

Respostas:


11

O problema que eu apostaria é que as matrizes C são apenas indicadores do início de um pedaço de memória alocado. Ter um tamanho 0 significaria que você tem um ponteiro para ... nada? Você não pode ter nada, então teria que haver alguma coisa arbitrária escolhida. Você não pode usar null, porque suas matrizes de comprimento 0 pareceriam ponteiros nulos. E nesse ponto, cada implementação diferente escolherá comportamentos arbitrários diferentes, levando ao caos.



7
@ delnan: Bem, se você quiser ser pedante, a aritmética da matriz e do ponteiro é definida de modo que um ponteiro possa ser convenientemente usado para acessar uma matriz ou simular uma matriz. Em outras palavras, é a aritmética dos ponteiros e a indexação da matriz que são equivalentes em C. Mas o resultado é o mesmo de qualquer maneira ... se o comprimento da matriz for zero, você ainda estará apontando para nada.
Robert Harvey

3
@RobertHarvey Tudo verdade, mas suas palavras finais (e toda a resposta em retrospecto) apenas parecem uma maneira confusa e confusa de explicar que essa matriz ( acho que é isso que essa resposta chama de "um pedaço de memória alocado"?) Teria sizeof0 e como isso causaria problemas. Tudo isso pode ser explicado usando os conceitos e terminologia adequados, sem perda de brevidade ou clareza. Misturar matrizes e ponteiros apenas corre o risco de espalhar o equívoco matrizes = ponteiros (o que é mais importante em outros contextos) sem nenhum benefício.

2
" Você não pode usar nulo, porque suas matrizes de comprimento 0 pareceriam ponteiros nulos " - na verdade, é exatamente isso que o Delphi faz. Os dynarrays e longstrings vazios são ponteiros tecnicamente nulos.
JensG

3
-1, estou cheio de @delnan aqui. Isso não explica nada, especialmente no contexto do que o OP escreveu sobre alguns dos principais compiladores que apóiam o conceito de matrizes de comprimento zero. Tenho certeza de que matrizes de comprimento zero podem ser fornecidas em C de uma maneira independente da implementação, não "levando ao caos".
Doc Brown)

6

Vejamos como uma matriz é tipicamente organizada na memória:

         +----+
arr[0] : |    |
         +----+
arr[1] : |    |
         +----+
arr[2] : |    |
         +----+
          ...
         +----+
arr[n] : |    |
         +----+

Observe que não há um objeto separado nomeado arrque armazena o endereço do primeiro elemento; quando uma matriz aparece em uma expressão, C calcula o endereço do primeiro elemento, conforme necessário.

Então, vamos pensar sobre isso: uma matriz com 0 elementos não teria armazenamento reservado para ela, o que significa que não há nada para calcular o endereço da matriz (em outras palavras , não há mapeamento de objetos para o identificador). É como dizer: "Quero criar uma intvariável que não ocupa memória". É uma operação sem sentido.

Editar

Matrizes Java são animais completamente diferentes das matrizes C e C ++; eles não são um tipo primitivo, mas um tipo de referência derivado Object.

Editar 2

Um ponto levantado nos comentários abaixo - a restrição "maior que 0" se aplica apenas a matrizes em que o tamanho é especificado por meio de uma expressão constante ; é permitido que um VLA tenha um comprimento 0 Declarar um VLA com uma expressão não constante com valor 0 não é uma violação de restrição, mas invoca um comportamento indefinido.

É claro que os VLAs são animais diferentes das matrizes regulares e sua implementação pode permitir um tamanho 0 . Eles não podem ser declarados staticou no escopo do arquivo, porque o tamanho desses objetos deve ser conhecido antes do início do programa.

Também não vale nada que, a partir do C11, as implementações não sejam necessárias para dar suporte aos VLAs.


3
Desculpe, mas IMHO você está perdendo o ponto, assim como Telastyn. Matrizes de comprimento zero podem fazer muito sentido, e implementações existentes como as que o OP nos falou mostram que isso pode ser feito.
Doc Brown

@ DocBrown: Primeiro, eu estava falando sobre por que o padrão de linguagem provavelmente os desaprova. Em segundo lugar, eu gostaria de um exemplo de onde uma matriz de comprimento 0 faz sentido, porque honestamente não consigo pensar em uma. A implementação mais provável é tratar T a[0]como T *a, mas por que não usar T *a?
31914 John Bode

Desculpe, mas não compro o "raciocínio teórico" por que o padrão proíbe isso. Leia minha resposta sobre como o endereço pode ser realmente calculado facilmente. E eu sugiro que você siga o link no primeiro comentário de Robert Harveys sob a pergunta e leia a segunda resposta, há um exemplo útil.
Doc Brown

@DocBrown: Ah. O structhack. Eu nunca o usei pessoalmente; nunca trabalhou em um problema que precisava de um structtipo de tamanho variável .
John Bode

2
E para não esquecer o AFAIK desde C99, C permite matrizes de comprimento variável. E quando o tamanho da matriz é um parâmetro, não ter que tratar o valor 0 como um caso especial pode simplificar muitos programas.
Doc Brown

2

Você normalmente deseja que sua matriz de tamanho zero (de fato variável) saiba seu tamanho em tempo de execução. Em seguida, coloque-o em um structe use membros de matriz flexíveis , como por exemplo:

struct my_st {
   unsigned len;
   double flexarray[]; // of size len
};

Obviamente, o membro da matriz flexível deve ser o último structe você precisa ter algo antes. Frequentemente, isso seria algo relacionado ao comprimento real em tempo de execução desse membro flexível da matriz.

Claro que você alocaria:

 unsigned len = some_length_computation();
 struct my_st*p = malloc(sizeof(struct my_st)+len*sizeof(double));
 if (!p) { perror("malloc my_st"); exit(EXIT_FAILURE); };
 p->len = len;
 for (unsigned ix=0; ix<len; ix++)
    p->flexarray[ix] = log(3.0+(double)ix);

AFAIK, isso já era possível no C99 e é muito útil.

BTW, os membros flexíveis da matriz não existem em C ++ (porque seria difícil definir quando e como eles devem ser construídos e destruídos). No entanto, veja o futuro std :: dynarray


Você sabe, eles poderiam ser limitados a tipos triviais, e não haveria dificuldade.
Deduplicator

2

Se a expressão type name[count]estiver escrita em alguma função, você solicita que o compilador C aloque nos sizeof(type)*countbytes do quadro da pilha e calcule o endereço do primeiro elemento na matriz.

Se a expressão type name[count]for gravada fora de todas as funções e definições de estruturas, você instrui o compilador C a alocar nos sizeof(type)*countbytes do segmento de dados e computa o endereço do primeiro elemento na matriz.

namena verdade, é um objeto constante que armazena o endereço do primeiro elemento na matriz e todo objeto que armazena um endereço de alguma memória é chamado ponteiro; portanto, esse é o motivo pelo qual você trata namecomo ponteiro, e não como matriz. Observe que as matrizes em C podem ser acessadas apenas através de ponteiros.

Se counté uma expressão constante avaliada como zero, você diz ao compilador C para alocar zero bytes no quadro da pilha ou no segmento de dados e retorna o endereço do primeiro elemento na matriz, mas o problema é que o primeiro elemento da matriz de comprimento zero não existe e você não pode calcular o endereço de algo que não existe.

É racional esse elemento não. count+1não existe na countmatriz -length, portanto, esse é o motivo pelo qual o compilador C proíbe definir a matriz de comprimento zero como variável dentro e fora de uma função, porque qual é o conteúdo namedela? Que endereço namearmazena exatamente?

Se pé um ponteiro, a expressão p[n]é equivalente a*(p + n)

Onde o asterisco * na expressão correta é uma operação de desreferência do ponteiro, o que significa acessar a memória apontada p + nou acessar a memória cujo endereço está armazenado p + n, onde p + nestá a expressão do ponteiro, ele pega o endereço de pe adiciona a esse endereço o número nmultiplique o tamanho do tipo do ponteiro p.

É possível adicionar um endereço e um número?

Sim, é possível, porque o endereço é um número inteiro sem sinal geralmente representado na notação hexadecimal.


Muitos compiladores usados ​​para permitir declarações de matriz de tamanho zero antes que o Padrão a proibisse, e muitos continuam permitindo tais declarações como uma extensão. Tais declarações não causarão problemas se alguém reconhecer que um objeto de tamanho Ntem N+1endereços associados, o primeiro Ndos quais identifica bytes únicos e o último Ndos quais cada ponto após um desses bytes. Definição tal iria funcionar muito bem mesmo no caso degenerado, onde Né 0.
supercat

1

Se você deseja um ponteiro para um endereço de memória, declare um. Uma matriz, na verdade, aponta para um pedaço de memória que você reservou. As matrizes se deterioram para os ponteiros quando passadas para as funções, mas se a memória apontada estiver na pilha, não há problema. Não há motivo para declarar uma matriz de tamanho zero.


2
Geralmente, você não faria isso diretamente, mas como resultado de uma macro ou ao declarar uma matriz de comprimento variável com dados dinâmicos.
Kevin Cox

Uma matriz nunca aponta, nunca. Ele pode conter ponteiros e, na maioria dos contextos, você realmente usa um ponteiro para o primeiro elemento, mas essa é uma história diferente.
Deduplicator

1
O nome da matriz é um ponteiro constante para a memória contida na matriz.
Ncmathsadist

1
Não, o nome da matriz decai para um ponteiro para o primeiro elemento, na maioria dos contextos. A diferença é frequentemente crucial.
Deduplicator

1

Desde os dias do C89 original, quando um Padrão C especificava que algo tinha Comportamento Indefinido, o que isso significava era "Fazer o que tornaria uma implementação em uma plataforma de destino específica mais adequada para a finalidade pretendida". Os autores da Norma não quiseram adivinhar quais comportamentos poderiam ser mais adequados para qualquer finalidade específica. As implementações C89 existentes com extensões VLA podem ter comportamentos diferentes, mas lógicos, quando recebem tamanho zero (por exemplo, alguns podem ter tratado a matriz como uma expressão de endereço que produz NULL, enquanto outros a tratam como uma expressão de endereço que pode ser igual ao endereço de outra variável arbitrária, mas poderia com segurança adicionar zero a ela sem capturar). Se algum código pudesse ter se baseado em comportamentos tão diferentes, os autores da Norma não

Em vez de tentar adivinhar o que as implementações podem fazer, ou sugerir que qualquer comportamento deve ser considerado superior a qualquer outro, os autores da Norma simplesmente permitiram que os implementadores usassem julgamento ao lidar com esse caso da melhor maneira que quisessem. Implementações que usam malloc () nos bastidores podem tratar o endereço da matriz como NULL (se malloc de tamanho zero gerar nulo), aquelas que usam cálculos de endereço de pilha podem gerar um ponteiro que corresponde ao endereço de alguma outra variável, e algumas outras implementações podem fazer outras coisas. Eu não acho que eles esperavam que os escritores de compiladores se esforçassem para fazer com que a caixa de canto de tamanho zero se comporte de maneira deliberadamente inútil.

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.