Arrays são pointers?

Duplicar Possível:
O nome da matriz é um ponteiro em C?

Arrays e pointers são implementados de maneira diferente em C e C ++? Eu me deparei com essa questão porque, em ambos os casos, acessamos elementos do endereço inicial de um elemento. Então, deve haver uma relação estreita entre eles. Por favor, explique a relação exata entre eles. Obrigado.

Vamos tirar as coisas importantes do caminho primeiro: os arrays não são pointers . Tipos de matriz e tipos de ponteiro são coisas completamente diferentes e são tratados de maneira diferente pelo compilador.

Onde a confusão surge é de como o C trata expressões de array. N1570 :

6.3.2.1 Lvalores, arrays e designadores de function


3 Exceto quando é o operando do operador sizeof , o operador _Alignof , ou o operador unário & , ou é um literal de string usado para inicializar uma matriz, uma expressão que possui o tipo ” array of type ” é convertida em uma expressão com o tipo ” pointer to type ” que aponta para o elemento inicial do object array e não é um lvalue. Se o object da matriz tiver uma class de armazenamento de registro, o comportamento é indefinido.

Vamos ver as seguintes declarações:

 int arr[10] = {0,1,2,3,4,5,6,7,8,9}; int *parr = arr; 

arr é uma matriz de 10 elementos do int ; refere-se a um bloco contíguo de memory grande o suficiente para armazenar 10 valores int . A expressão arr na segunda declaração é do tipo array, mas como não é o operando de & ou sizeof e não é uma string literal, o tipo da expressão se torna “pointer to int “, e o valor é o endereço do primeiro elemento, ou &arr[0] .

parr é um ponteiro para int; refere-se a um bloco de memory grande o suficiente para conter o endereço de um único object int . É inicializado para apontar para o primeiro elemento em arr como explicado acima.

Aqui está um mapa de memory hipotética mostrando o relacionamento entre os dois (assumindo ints de 16 bits e endereços de 32 bits):

 Endereço de object 0x00 0x01 0x02 0x03
 ------ ------- ----------------------
    arr 0x10008000 0x00 0x00 0x00 0x01
                  0x10008004 0x00 0x02 0x00 0x03
                  0x10008008 0x00 0x04 0x00 0x05
                  0x1000800c 0x00 0x06 0x00 0x07
                  0x10008010 0x00 0x08 0x00 0x09
   parr 0x10008014 0x10 0x00 0x80 0x00

Os tipos são importantes para coisas como sizeof e & ; sizeof arr == 10 * sizeof (int) , que neste caso é 20, enquanto sizeof parr == sizeof (int *) , que neste caso é 4. Da mesma forma, o tipo da expressão &arr é int (*)[10] , ou um ponteiro para uma matriz de 10 elementos de int , enquanto o tipo de &parr é int ** , ou ponteiro para ponteiro para int .

Note que as expressões arr e &arr produzirão o mesmo valor (o endereço do primeiro elemento em arr ), mas os tipos de expressões são diferentes ( int * e int (*)[10] , respectivamente). Isso faz diferença ao usar a aritmética de ponteiro. Por exemplo, dado:

 int arr[10] = {0,1,2,3,4,5,6,7,8,9}; int *p = arr; int (*ap)[10] = &arr; printf("before: arr = %p, p = %p, ap = %p\n", (void *) arr, (void *) p, (void *) ap); p++; ap++; printf("after: arr = %p, p = %p, ap = %p\n", (void *) arr, (void *) p, (void *) ap); 

a linha “antes” deve imprimir os mesmos valores para todas as três expressões (em nosso mapa hipotético, 0x10008000 ). A linha “depois” deve mostrar três valores diferentes: 0x10008000 , 0x10008002 (base plus sizeof (int) ) e 0x10008014 (base plus sizeof (int [10]) ).

Agora vamos voltar ao segundo parágrafo acima: expressões array são convertidas para tipos de ponteiro na maioria das circunstâncias. Vamos ver a expressão subscrita arr[i] . Como a expressão arr não está aparecendo como um operando de sizeof ou & , e como não é um literal de string sendo usado para inicializar outro array, seu tipo é convertido de “10-array array of int ” para “pointer to int ” e a operação de subscript está sendo aplicada a esse valor de ponteiro . De fato, quando você olha para a definição da linguagem C, você vê o seguinte idioma:

6.5.2.1 Inscrição de matriz

2 Uma expressão postfix seguida por uma expressão entre colchetes [] é uma designação subscrita de um elemento de um object de matriz. A definição do operador de subscrição [] é que E1 [E2] é idêntico a (* ((E1) + (E2))) . Por causa das regras de conversão que se aplicam ao operador binário, se E1 é um object de matriz (equivalentemente, um ponteiro para o elemento inicial de um object de matriz) e E2 é um inteiro, E1 [E2] designa o elemento E2 -th E1 (contando de zero).

Em termos práticos, isso significa que você pode aplicar o operador subscrito a um object ponteiro como se fosse um array. É por isso que código como

 int foo(int *p, size_t size) { int sum = 0; int i; for (i = 0; i < size; i++) { sum += p[i]; } return sum; } int main(void) { int arr[10] = {0,1,2,3,4,5,6,7,8,9}; int result = foo(arr, sizeof arr / sizeof arr[0]); ... } 

funciona da maneira que faz. main está lidando com uma matriz de int , enquanto foo está lidando com um ponteiro para int , mas ambos são capazes de usar o operador subscript como se ambos estivessem lidando com um tipo de array.

Isso também significa que a subseqüência de matrizes é comutativa : assumindo que a é uma expressão de matriz e que i é uma expressão de inteiro, a[i] e i[a] são expressões válidas e ambas produzirão o mesmo valor.

Não sei sobre o C ++. Para C, o c-faq responde muito melhor do que eu jamais poderia.

Pequeno trecho de c-faq:

6.3 Então, o que significa a equivalência de pointers e arrays em C?

[…]

Especificamente, a pedra angular da equivalência é essa definição fundamental:

Uma referência a um object do tipo array-de-T que aparece em uma expressão decai (com três exceções) em um ponteiro para seu primeiro elemento; o tipo do ponteiro resultante é ponteiro para T.

[…]

Em C ++ de acordo com o padrão C ++ 4.2:

Um lvalue ou rvalue do tipo “array de N T” ou “array de bound desconhecido de T” pode ser convertido para um rvalue do tipo “pointer to T.” O resultado é um ponteiro para o primeiro elemento do array.

Não, eles não são implementados de maneira diferente. Ambos encontram elementos com o mesmo cálculo: a[i] está no endereço a + i*sizeof(a[0]) , também p[i] está no endereço p + i*sizeof(p[0]) .

Mas, eles são tratados de maneira diferente pelo sistema de tipos . C ++ tem informações de digitação em matrizes que podem ser vistas através do sizeof operator (como C), inferência de modelo, sobrecarga de function, RTTI e assim por diante. Basicamente, em qualquer lugar na linguagem que as informações de tipo são usadas, é possível que pointers e matrizes se comportem de maneira diferente.

Há muitos exemplos em C ++ em que dois conceitos de linguagem diferentes têm a mesma implementação. Apenas alguns: arrays vs pointers, pointers vs referências, funções virtuais vs pointers de function, iteradores vs pointers, for loops loops vs while, exceções vs longjmp

Em todos os casos, há uma syntax diferente e uma maneira diferente de pensar sobre os dois conceitos, mas eles resultam no mesmo código de máquina no final.

Em C ++ (e em C também acho), um array não é um ponteiro e isso pode ser provado da seguinte maneira.

 #include  int main() { char arr[1000]; std::cout < < sizeof arr; } 

se arr fosse um ponteiro este programa imprimiria sizeof (char *) que é tipicamente 4. Mas imprime 1000.

outra prova:

 template  void f(T& obj) { T x = obj; //this will fail to compile if T is an array type } int main() { int a[30] = {}; int* p = 0; f(p); //OK f(a); //results in compile error. Remember f takes by ref therefore needs lvalue and no conversion applies } 

Formalmente, uma matriz é convertida em um ponteiro para seu primeiro elemento em conversões lvalue-para-rvalue, ou seja, quando um lvalue do tipo array é dado em um contexto quando um rvalue é esperado, o array é convertido em um ponteiro para seu primeiro elemento.

Além disso, uma function declarada para obter uma matriz por valor é equivalente à function de usar um ponteiro, ou seja,

 void f(int a[]); void f(int a[10]); void f(int* a); 

são três declarações equivalentes. HTH

No tipo de matriz C ++ tem um “atributo de tamanho”, então para

 T a[10]; T b[20]; 

aeb tem diferentes tipos.

Isso permite usar código como este

 template void foo(T (&a)[N]) { ... } 

O maior ponto de confusão entre arrays e pointers vem da decisão do K & R de fazer parâmetros de function que são declarados como sendo do tipo array, comportam-se como se fossem declarados como pointers. As declarações

  void foo (int a []); 

e

  void foo (int * a); 

são equivalentes, como é (até onde eu sei)

  void foo (int a [5]); 

embora eu não seja positivo, um compilador seria obrigado a aceitar uma referência a um [6] dentro da última function. Em outros contextos, uma declaração de matriz aloca espaço para o número indicado de elementos. Note que dado:

 typedef int foo [1];

qualquer declaração de um tipo foo alocará espaço para um elemento, mas qualquer tentativa de passar foo como um parâmetro de function, em vez disso, passará o endereço. Algo de um truque útil que aprendi ao estudar uma implementação de va_list.