Qual é o propósito das matrizes em C, quando ponteiros poderiam ter feito o trabalho?

8

Matrizes e ponteiros não são a mesma coisa em C, embora estejam relacionados e possam ser usados ​​de maneira semelhante. Até agora todos concordamos.

No entanto, não vejo por que matrizes foram incluídas em C, quando ponteiros poderiam ter feito seu trabalho perfeitamente.

Não estou dizendo para remover a notação da matriz (por exemplo, a [5] ou int a [4] = {0,1,2,3};), o que é bastante útil e conveniente. Mas você pode ter essa mesma notação trabalhando em cima de ponteiros (como é o caso), como uma medida cosmética. Portanto, a notação de matriz não é um motivo para ter matrizes, apenas a notação!

A única diferença que vejo é que as matrizes são ponteiros constantes e o tamanho da memória para a qual elas apontam não pode ser alterado. Mas isso também pode ser alcançado com ponteiros, exatamente tornando-os constantes (a memória não seria de tamanho fixo, mas não tenho certeza se isso é um problema).

Então, por que não ter apenas ponteiros e deixar o programador decidir como o ponteiro deve se comportar (ou seja, constante, não constante, tamanho fixo, tamanho variável, etc)?

Daniel Scocco
fonte
6
Por que ter char quando você pode conseguir a mesma coisa com byte?
WuHoUnited
2
Em uma observação mais séria, como você pretende realizar a alocação de memória com um ponteiro?
WuHoUnited
5
Pessoalmente, penso nos ponteiros como (às vezes) fingindo ser matrizes, em vez de vice-versa. Em muitas outras linguagens, os ponteiros não têm comportamento de matriz - se você deseja acessar uma matriz por meio de um ponteiro, use um tipo de ponteiro para matriz ou aritmética de ponteiro (contando em bytes, não em elementos). Eu apostaria uma quantia justa de dinheiro que os inventores de C pensaram em termos de ajustar os ponteiros para obter um comportamento mais conveniente do tipo matriz - duvido muito que eles já tivessem um comportamento do ponteiro do tipo matriz e decidiram "Eu sei, vamos adicionar matrizes também".
Steve314
3
Por que ter 0xAB quando você pode conseguir a mesma coisa com 171?
e-MEE 21/10
2
por que ter expressões compostas como x = a + b * 2;quando você pode obter o mesmo com uma sequência de expressões simples como x = b; x*=2; x+=a;?
fortran

Respostas:

14

Matrizes são memória contígua criada na pilha. Você não pode garantir memória de pilha contígua sem esse açúcar sintático e, mesmo que pudesse, teria que alocar um ponteiro separado para poder fazer a aritmética do ponteiro (a menos que você quisesse *(&foo + x), o que eu não sou) claro, mas isso pode violar a semântica do valor-l, mas é pelo menos bastante estranho e gritaria por algum tipo de açúcar sintático). Em termos de design, também é uma forma de encapsulamento, já que você pode consultar a coleção com um único identificador (que de outra forma exigiria um ponteiro separado). E mesmo que você pudesse alocá-los de forma contígua e alocar um ponteiro separado para referenciá-los, você teria int fooForSomething, fooForSomethingElse... o que força uma quantidade razoável de criatividade à medida que sua coleção cresce, então você pode simplificar com int foo1, foo2 ..., que se parece com uma matriz, mas é mais difícil de manter.

kylben
fonte
Ah, e passar valor para foo1, foo2 ... fica confuso rapidamente, especialmente se você deseja permitir um número variável de membros.
kylben
2
Somente matrizes declaradas como automáticas acabam na pilha. Qualquer outra coisa (ou seja static) geralmente termina em outro lugar, dependendo do ambiente.
Blrfl
Não é um "açúcar sintático", é muito mais profundo - uma parte importante do sistema do tipo C, bem como a infame decadência da matriz.
SK-logic
1
qual é a diferença entre um array na pilha e alloca ()
Sylvanaar
@sylvanaar, veja esta pergunta do SO: stackoverflow.com/questions/1018853/… Na verdade, eu nunca tinha ouvido falar disso antes, mas aparentemente é não-padrão e perigoso da maneira que eu esperava que tal coisa fosse perigosa.
kylben
12

A notação de matriz é conveniente, mais fácil de ler e menos propensa a erros. Ele fornece um formalismo sobre ponteiros. Pode ser açúcar sintático, mas todos precisamos de um pouco de doçura de vez em quando, não é?

Como em todas as abstrações, você abre um pouco de flexibilidade para a conveniência que a abstração oferece.

Robert Harvey
fonte
1
Como mencionei na pergunta, não sou contra a notação de matriz. Mas essa mesma notação poderia funcionar em cima de indicadores, então onde está a necessidade de matrizes?
Daniel Scocco
1
@DanielScocco - Talvez pudesse. Mas foi feito assim, no entanto. Fim da história. Ninguém pode realmente responder a isso, pois ninguém sabe o que estava acontecendo na cabeça do autor naquele momento.
Rook
10

Estou surpreso que ninguém tenha comentado nada sobre matrizes multidimensionais ainda.

Se você possui uma matriz feita de "ponteiros aninhados" (digamos int **p), o que você tem em cada "linha" (dimensão externa) é um ponteiro que aponta para o primeiro elemento nessa linha, portanto, acessar um valor requer dois acessos à memória. Além disso, a memória necessária é sizeof(*int)*n + n*m*sizeof(int).

No cenário de matriz bidimensional int p[n][m], o acesso a um elemento requer apenas um acesso à memória, porque o endereço da linha é calculado em vez de procurado; e a memória necessária é justa n*m*sizeof(int).

Outro local onde uma matriz não pode ser substituída por um ponteiro é dentro das estruturas.

struct s {
    int[2];
    float;
}

definitivamente não é o mesmo que

struct s {
   *int;
   float;
}

o tamanho da matriz é importante lá e os ponteiros não possuem essa informação.

Portanto, sim, matrizes unidimensionais e ponteiros únicos são basicamente intercambiáveis, mas suas semelhanças terminam aí.

fortran
fonte
Mostre-nos a diferença no assembler. Matrizes multidimensionais, estrutura de embalagem e preenchimento são apenas maneiras de fornecer um quadro de referência que faz sentido para os humanos que precisam entender e usar a linguagem.
21711 sylvanaar
@sylvanaar você está falando sério? você realmente precisa ver o assembler para garantir que a primeira estrutura mantenha espaço para dois números inteiros e um float, e o segundo apenas para armazenar um endereço de memória e um float?
fortran 21/10
1
Eu não sigo você. O que tem a ver com o conjunto de instruções com tudo isso? O op estava perguntando o que você poderia fazer com matrizes e não com ponteiros, e declarar uma estrutura com uma matriz de tamanho fixo não pode ser emulado com ponteiros. Sobre matrizes multidimensionais, é claro que você pode ter apenas um pedaço de memória e calcular o índice de célula i,jpor i*cols+j, mas acho que não ter que fazer isso sozinho é um motivo bom o suficiente para justificar a existência de tipos de array.
fortran
2
"Declarar estruturas [...] cria alocações de memória estática na imagem compilada" Não, declarar uma variável (do tipo struct ou qualquer outro tipo) faz isso. Declarar uma estrutura apenas diz ao compilador quão grande é esse tipo e quais são as compensações de cada um de seus membros; você nunca pode alocar um ou apenas alocá-los como variáveis ​​locais.
precisa saber é o seguinte
1
+1 para identificar de maneira convincente o valor prático de ter matrizes de tamanho fixo ser um "tipo" no que diz respeito ao compilador - matrizes multidimensionais e, em particular, o fato de que você pode indexá-las trivialmente com o compilador fazendo a aritmética do ponteiro com o tamanho certo para você, desista natural e elegantemente das outras regras simples de linguagem, transformando as matrizes em um "tipo" real - você poderia obter o mesmo açúcar sintático sem isso, mas a lógica teria que ser mais especial, tanto no compilador e na especificação de idioma.
Mtraceur 17/10/19
2

Por que eu gostaria de não conseguir usar matrizes para tipos de valor?

int a[4] = {0,1,2,3};

Demian Brecht
fonte
Você ainda seria capaz de usar isso, mesmo se tivéssemos apenas ponteiros, não é? O compilador precisaria apenas saber o que fazer.
quer
2
@DanielScocco e como saberia o que fazer? matrizes
catraca aberração
Concordado - como você chamaria o recurso que reserva espaço para uma sequência de itens do mesmo tipo que uma variável local ou global, se não uma matriz? Um tipo de ponteiro é um tipo de ponteiro - não especifica se o que aponta é uma sequência ou qual sequência de tamanho.
Steve314
4
@DanielScocco O que vem com uma matriz que não vem com um ponteiro são informações sobre o tamanho. Portanto, um par (ponteiro, tamanho) ou um par (ponteiro, ponteiro) pode de fato conter tanta informação quanto o nome de uma matriz. Se isso significa que matrizes são pares de ponteiros (ou vice-versa) parece mais uma pergunta metafísica, então não estou realmente interessado em responder a isso. (A resposta provavelmente é não como pares de ponteiros parecem mais com fatias, e, portanto, pode fazer mais do que apenas referir-se a um array.)
Luc Danton
1
@ Daniel Scocco, matrizes tem um tamanho . Você pode alocar um array na pilha, pode colocar um array em uma estrutura contígua, pode usá-lo dentro de uma união. Se o tipo de matriz não existisse, não seria possível.
SK-logic
1

Como você lidaria com plataformas, como a 8031 ​​sem memória externa, que não suportam mallocou alloca? Talvez você esteja esquecendo que C não é apenas para grandes ferros, mas também para controladores de elevador e torradeiras.

David Schwartz
fonte
1

Em C, a notação de matriz dentro de expressões é sempre simplesmente aritmética de ponteiro. Todos os usos de um identificador de matriz em uma expressão são convertidos imediatamente de "matriz de T" para "ponteiro para T" e o valor é convertido em um ponteiro para o primeiro elemento da matriz. A notação da matriz (por exemplo a[1][2]) é sempre expandida para a aritmética do ponteiro (por exemplo *(*(t+1)+2)).

No entanto, a notação de matriz em declarações e definições é algo totalmente diferente. Uma matriz Declarador descreve uma "matriz de T tipo" onde os valores deste tipo são sequências de elementos de tipo T . Uma definição de um objeto de matriz tem tudo a ver com usar uma notação de matriz conveniente e fácil de entender para alocar a quantidade apropriada de armazenamento para a matriz de objetos desejada, de modo que o identificador da matriz se refira a esse armazenamento sem parecer um ponteiro. Com efeito, a notação de matriz em uma declaração ou definição é uma macro que gera uma expressão usando sizeof()e aritmética e, no caso de uma definição, o equivalente aalloca() para matrizes automáticas ou seu equivalente no vinculador para matrizes globais e fazer tudo isso em tempo de compilação (exceto, por exemplo, matrizes de tamanho variável C99).

O uso de notação de matriz em expressões e o uso de tipos de matriz não são tão intimamente conectados, embora seja tradição e idioma usar a notação de matriz em expressões para referenciar o armazenamento em objetos declarados e / ou definidos como matrizes. Você pode facilmente usar a notação de matriz com um tipo de ponteiro para tornar a aritmética do ponteiro mais limpa e mais significativa. De fato, em C, uma expressão da forma e1[e2]é precisamente equivalente à expressão *((e1)+(e2)). A conversão binária usual é aplicada aos dois operandos e o resultado é sempre um valor l . Como o operador de indireção ( *) deve ter um ponteiro como operando, um de e1e e2deve ser um ponteiro e o outro deve ser um número inteiro, mas não importa qualuma vez que a conversão unária inicial para qualquer "matriz de T" é convertê-la em "ponteiro para T". A notação de matriz em expressões é uma macro do compilador (no nível da linguagem) para gerar expressões aritméticas de ponteiro.

Então, realmente C já funciona da maneira que você sugere, mas você está confundindo o uso da notação em dois contextos muito separados.

Greg A. Woods
fonte