Qual foi o motivo por trás de não armazenar explicitamente o comprimento de uma matriz com uma matriz em C
?
Do meu ponto de vista, há razões esmagadoras para fazê-lo, mas não muitas em apoio ao padrão (C89). Por exemplo:
- Ter o comprimento disponível em um buffer pode impedir a saturação do buffer.
- Um estilo Java
arr.length
é claro e evita que o programador precise manter muitosint
s na pilha se estiver lidando com várias matrizes - Os parâmetros de função se tornam mais convincentes.
Mas talvez a razão mais motivadora, na minha opinião, seja que, geralmente, nenhum espaço é economizado sem manter o comprimento. Atrevo-me a dizer que a maioria dos usos de matrizes envolve alocação dinâmica. É verdade que pode haver alguns casos em que as pessoas usam uma matriz alocada na pilha, mas essa é apenas uma chamada de função * - a pilha pode lidar com 4 ou 8 bytes extras.
Como o gerenciador de heap precisa rastrear o tamanho do bloco livre usado pela matriz alocada dinamicamente de qualquer maneira, por que não tornar essas informações utilizáveis (e adicionar a regra adicional, verificada no tempo de compilação, que não é possível manipular o comprimento explicitamente, a menos que seja necessário? gosta de dar um tiro no próprio pé).
A única coisa que posso pensar no outro lado é que nenhum seguimento comprimento pode ter feito compiladores mais simples, mas não que muito mais simples.
* Tecnicamente, pode-se escrever algum tipo de função recursiva com uma matriz com armazenamento automático e, nesse caso (muito elaborado), armazenar o comprimento pode realmente resultar em um uso de espaço efetivamente maior.
malloc()
área ed não pode ser solicitado de maneira portátil?" Isso é algo que me faz pensar várias vezes.Respostas:
As matrizes C controlam seu comprimento, pois o comprimento da matriz é uma propriedade estática:
Normalmente, você não pode consultar esse comprimento, mas não precisa, porque é estático de qualquer maneira - basta declarar uma macro
XS_LENGTH
para o comprimento e pronto.A questão mais importante é que as matrizes C se degradam implicitamente em ponteiros, por exemplo, quando passadas para uma função. Isso faz algum sentido e permite alguns bons truques de baixo nível, mas perde as informações sobre o comprimento da matriz. Portanto, uma pergunta melhor seria por que C foi projetado com essa degradação implícita em ponteiros.
Outra questão é que os ponteiros não precisam de armazenamento, exceto o próprio endereço de memória. C nos permite converter números inteiros para ponteiros, ponteiros para outros ponteiros e tratar ponteiros como se fossem matrizes. Enquanto isso, C não é insano o suficiente para fabricar um certo comprimento de matriz, mas parece confiar no lema do Homem-Aranha: com grande poder, o programador cumprirá a grande responsabilidade de acompanhar os comprimentos e transbordamentos.
fonte
sizeof(xs)
wherexs
é uma matriz seria algo diferente em outro escopo é descaradamente falso, porque o design de C não permite que as matrizes deixem seu escopo. Sesizeof(xs)
wherexs
é uma matriz diferente desizeof(xs)
ondexs
está um ponteiro, isso não é surpresa, porque você está comparando maçãs com laranjas .Muito disso tinha a ver com os computadores disponíveis no momento. Não apenas o programa compilado teve que ser executado em um computador com recursos limitados, mas, talvez mais importante, o próprio compilador teve que ser executado nessas máquinas. Na época, Thompson desenvolveu o C, ele estava usando um PDP-7, com 8k de RAM. Recursos complexos de linguagem que não tinham um analógico imediato no código de máquina real simplesmente não foram incluídos no idioma.
Uma leitura cuidadosa do histórico de C gera mais entendimento do que foi dito acima, mas não foi inteiramente o resultado das limitações da máquina que eles tinham:
Matrizes C são inerentemente mais poderosas. Adicionar limites a eles restringe o que o programador pode usá-los. Tais restrições podem ser úteis para programadores, mas necessariamente também são limitativas.
fonte
to avoid the limitation on the length of a string caused by holding the count in an 8- or 9-bit slot, and partly because maintaining the count seemed, in our experience, less convenient than using a terminator
- bem tanto para que :-)No dia em que C foi criado, e 4 bytes de espaço extra para cada string, por mais curto que tenha sido um desperdício!
Há outro problema - lembre-se de que C não é orientado a objetos; portanto, se você usar o prefixo do comprimento de todas as strings, ele deverá ser definido como um tipo intrínseco do compilador, não a
char*
. Se fosse um tipo especial, não seria possível comparar uma sequência com uma constante, ou seja:teria que ter detalhes especiais do compilador para converter essa string estática em uma String ou ter funções diferentes de string para levar em consideração o prefixo do comprimento.
Acho que, no final das contas, eles simplesmente não escolheram o prefixo de comprimento, diferentemente do que diz Pascal.
fonte
for
loop já estiver configurado para respeitar os limites.Em C, qualquer subconjunto contíguo de uma matriz também é uma matriz e pode ser operado como tal. Isso se aplica às operações de leitura e gravação. Esta propriedade não se manteria se o tamanho fosse armazenado explicitamente.
fonte
&[T]
tipos, por exemplo.O maior problema em ter matrizes marcadas com seu comprimento não é tanto o espaço necessário para armazenar esse comprimento, nem a questão de como ele deve ser armazenado (o uso de um byte extra para matrizes curtas geralmente não seria questionável, nem o uso de quatro bytes extras para matrizes longas, mas usar quatro bytes mesmo para matrizes curtas pode ser). Um problema muito maior é esse código, como:
a única maneira pela qual o código seria capaz de aceitar a primeira chamada,
ClearTwoElements
mas rejeitar a segunda, seria oClearTwoElements
método receber informações suficientes para saber que, em cada caso, estava recebendo uma referência a parte da matrizfoo
, além de saber qual parte. Isso normalmente dobraria o custo de passar os parâmetros do ponteiro. Além disso, se cada matriz fosse precedida por um ponteiro para um endereço logo após o final (o formato mais eficiente para validação), o código otimizado paraClearTwoElements
provavelmente se tornaria algo como:Observe que um responsável pela chamada de método pode, em geral, perfeitamente legitimamente transmitir um ponteiro para o início da matriz ou o último elemento para um método; somente se o método tentar acessar elementos que vão para fora da matriz passada, esses ponteiros causarão algum problema. Conseqüentemente, um método chamado precisaria primeiro garantir que a matriz fosse grande o suficiente para que o aritmético do ponteiro para validar seus argumentos não saia dos limites e faça alguns cálculos de ponteiro para validar os argumentos. O tempo gasto em tal validação provavelmente excederia o custo gasto com qualquer trabalho real. Além disso, o método provavelmente poderia ser mais eficiente se fosse escrito e chamado:
O conceito de um tipo que combina algo para identificar um objeto com algo para identificar uma parte dele é bom. Um ponteiro no estilo C é mais rápido, no entanto, se não for necessário executar a validação.
fonte
[]
a sintaxe ainda pode existir para ponteiros, mas seria diferente do que para essas matrizes hipotéticas "reais" e o problema que você descreve provavelmente não existiria.Uma das diferenças fundamentais entre C e a maioria das outras linguagens de terceira geração, e todas as linguagens mais recentes que conheço, é que C não foi projetado para tornar a vida mais fácil ou segura para o programador. Foi projetado com a expectativa de que o programador soubesse o que estava fazendo e desejasse fazer exatamente e somente isso. Ele não faz nada nos bastidores, para que você não tenha surpresas. Até a otimização no nível do compilador é opcional (a menos que você use um compilador da Microsoft).
Se um programador deseja escrever limites de verificação em seu código, C torna mais simples, mas o programador deve optar por pagar o preço correspondente em termos de espaço, complexidade e desempenho. Embora eu não o use com raiva por muitos anos, ainda o uso quando ensino programação para entender o conceito de tomada de decisão baseada em restrições. Basicamente, isso significa que você pode optar por fazer o que quiser, mas todas as decisões tomadas têm um preço que você precisa estar ciente. Isso se torna ainda mais importante quando você começa a dizer aos outros o que deseja que os programas deles façam.
fonte
int f[5];
não criariaf
como uma matriz de cinco itens; em vez disso, foi equivalente aint CANT_ACCESS_BY_NAME[5]; int *f = CANT_ACCESS_BY_NAME;
. A declaração anterior poderia ser processada sem que o compilador realmente "entendesse" os tempos da matriz; ele simplesmente precisava emitir uma diretiva assembler para alocar espaço e, em seguida, poderia esquecer quef
alguma vez tinha algo a ver com uma matriz. Os comportamentos inconsistentes dos tipos de matriz decorrem disso.Resposta curta:
Como C é uma linguagem de programação de baixo nível , ele espera que você cuide desses problemas, mas isso adiciona maior flexibilidade exatamente à maneira como você o implementa.
C tem um conceito em tempo de compilação de uma matriz que é inicializada com um comprimento, mas em tempo de execução a coisa toda é simplesmente armazenada como um ponteiro único para o início dos dados. Se você deseja passar o comprimento da matriz para uma função junto com a matriz, faça você mesmo:
Ou você pode usar uma estrutura com um ponteiro e comprimento, ou qualquer outra solução.
Uma linguagem de nível superior faria isso por você como parte de seu tipo de matriz. Em C, você tem a responsabilidade de fazer isso sozinho, mas também a flexibilidade de escolher como fazê-lo. E se todo o código que você está escrevendo já sabe o comprimento da matriz, você não precisa passar o comprimento como uma variável.
A desvantagem óbvia é que, sem a verificação de limites inerentes às matrizes passadas como ponteiros, você pode criar um código perigoso, mas essa é a natureza das linguagens de baixo nível / sistemas e o trade-off que elas oferecem.
fonte
O problema do armazenamento extra é um problema, mas, na minha opinião, um problema menor. Afinal, na maioria das vezes você precisará rastrear o comprimento de qualquer maneira, embora amon tenha enfatizado que muitas vezes pode ser rastreado estaticamente.
Um problema maior é onde armazenar o comprimento e quanto tempo durar. Não há um lugar que funcione em todas as situações. Você pode dizer apenas armazene o comprimento na memória antes dos dados. E se a matriz não estiver apontando para a memória, mas algo como um buffer UART?
Deixar de lado o comprimento permite que o programador crie suas próprias abstrações para a situação apropriada, e há muitas bibliotecas prontas disponíveis para o caso de uso geral. A verdadeira questão é por que essas abstrações não estão sendo usadas em aplicativos sensíveis à segurança?
fonte
You might say just store the length in the memory just before the data. What if the array isn't pointing to memory, but something like a UART buffer?
Poderia explicar isso um pouco mais? Além disso, algo que pode acontecer com muita frequência ou é apenas um caso raro?T[]
não seria equivalente,T*
mas passaria uma tupla de ponteiro e tamanho para a função. Matrizes de tamanho fixo podem se deteriorar para uma fatia dessa matriz, em vez de se deteriorar para ponteiros como em C. A principal vantagem dessa abordagem não é que ela é segura por si só, mas é uma convenção na qual tudo, incluindo a biblioteca padrão, pode Construir.Do desenvolvimento da linguagem C :
Essa passagem aborda por que as expressões de matriz decaem para ponteiros na maioria das circunstâncias, mas o mesmo raciocínio se aplica ao motivo pelo qual o comprimento da matriz não é armazenado na própria matriz; se você deseja um mapeamento individual entre a definição de tipo e sua representação na memória (como Ritchie fez), não há um bom lugar para armazenar esses metadados.
Além disso, pense em matrizes multidimensionais; onde você armazenaria os metadados de comprimento para cada dimensão, para poder percorrer a matriz com algo como
fonte
A questão assume que existem matrizes em C. Não há. As coisas que são chamadas de matrizes são apenas um açúcar sintático para operações em seqüências contínuas de dados e aritmética de ponteiros.
O código a seguir copia alguns dados do src para o dst em partes com tamanho int sem saber que na verdade é uma sequência de caracteres.
Por que C é tão simplificado que não possui matrizes apropriadas? Não sei a resposta correta para esta nova pergunta. Mas algumas pessoas costumam dizer que C é apenas (um pouco) montador mais legível e portátil.
fonte
struct Foo { int arr[10]; }
.arr
é uma matriz, não um ponteiro.