Os pointers a, & a, * a, a , e a e & a são idênticos?

Eu tenho o seguinte programa em C:

#include  int main(){ int a[2][2] = {1, 2, 3, 4}; printf("a:%p, &a:%p, *a:%p \n", a, &a, *a); printf("a[0]:%p, &a[0]:%p \n", a[0], &a[0]); printf("&a[0][0]:%p \n", &a[0][0]); return 0; } 

Dá a seguinte saída:

 a:0028FEAC, &a:0028FEAC, *a:0028FEAC a[0]:0028FEAC, &a[0]:0028FEAC &a[0][0]:0028FEAC 

Eu não sou capaz de entender porque é que todos são idênticos. O mesmo para a[0] , &a[0] e &a[0][0] .

EDITAR:

Graças às respostas, entendi o motivo pelo qual esses valores estão saindo iguais. Esta linha do livro de Kernighan & Ritchie acabou por ser a chave da minha pergunta:

  the name of an array is a synonym for the location of the initial element. 

Então, por isso, conseguimos

a = &a[0] e

a[0] = &a[0][0] (considerando a como uma matriz de matrizes)

Intuitivamente, agora a razão está clara por trás da saída. Mas, considerando como os pointers são implementados em C, não consigo entender como a e &a são iguais. Estou assumindo que existe uma variável a na memory que aponta para o array (e o endereço inicial desse array-memory-block seria o valor dessa variável a ).

Mas, quando fazemos &a , isso não significa tomar o endereço da localização da memory onde a variável a foi armazenada? Por que esses valores são iguais?

Eles não são pointers idênticos . Eles são pointers de tipos distintos que apontam para o mesmo local de memory. O mesmo valor (tipo de), tipos diferentes.

Uma matriz bidimensional em C é nada mais ou menos do que uma matriz de matrizes.

O object a é do tipo int[2][2] ou matriz de 2 elementos da matriz de 2 elementos do int .

Qualquer expressão de tipo de matriz é, na maioria dos contextos, mas não em todos, implicitamente convertida em (“decays”) em um ponteiro para o primeiro elemento do object de matriz. Portanto, a expressão a , a menos que seja o operando de unário & ou sizeof , é do tipo int(*)[2] , e é equivalente a &a[0] (ou &(a[0]) se for mais claro). Torna-se um ponteiro para a linha 0 da matriz bidimensional. É importante lembrar que este é um valor de ponteiro (ou equivalentemente um endereço ), não um object de ponteiro; não há object ponteiro aqui, a menos que você crie explicitamente um.

Então, olhando para as várias expressões que você perguntou sobre:

  • &a é o endereço de todo o object da matriz; é uma expressão de ponteiro do tipo int(*)[2][2] .
  • a é o nome da matriz. Como discutido acima, “decai” para um ponteiro para o primeiro elemento (linha) do object da matriz. É uma expressão de ponteiro do tipo int(*)[2] .
  • *a desreferencia a expressão do ponteiro a . Como a (depois que ela decai) é um ponteiro para uma matriz de 2 int s, *a é uma matriz de 2 int s. Como esse é um tipo de matriz, ele decai (na maioria dos contextos, mas não em todos) para um ponteiro para o primeiro elemento do object de matriz. Então é do tipo int* . *a é equivalente a &a[0][0] .
  • &a[0] é o endereço da primeira (0ª) linha do object da matriz. É do tipo int(*)[2] . a[0] é um object de matriz; não se decompõe em um ponteiro porque é o operando direto do unário & .
  • &a[0][0] é o endereço do elemento 0 da linha 0 do object da matriz. É do tipo int* .

Todas essas expressões apontam para o mesmo local na memory. Esse local é o começo do object array a ; é também o começo do object array a[0] e do object int a[0][0] .

A maneira correta de imprimir um valor de ponteiro é usar o formato "%p" e converter o valor do ponteiro em void* :

 printf("&a = %p\n", (void*)&a); printf("a = %p\n", (void*)a); printf("*a = %p\n", (void*)*a); /* and so forth */ 

Essa conversão para void* produz um endereço “bruto” que especifica apenas um local na memory, não o tipo de object que está nesse local. Portanto, se você tiver vários pointers de tipos diferentes que apontam para objects que começam no mesmo local de memory, convertê-los todos para void* produz o mesmo valor.

(Eu fiz o encobrimento do funcionamento interno do operador de indexação [] . A expressão x[y] é por definição equivalente a *(x+y) , onde x é um ponteiro (possivelmente o resultado da conversão implícita de um array y é um inteiro, ou vice-versa, mas isso é feio, arr[0] e 0[arr] são equivalentes, mas isso é útil apenas se você estiver escrevendo um código deliberadamente ofuscado, se considerarmos essa equivalência, parágrafo ou mais para descrever o que a[0][0] significa, e esta resposta provavelmente já é muito longa.)

Por uma questão de completude, os três contextos nos quais uma expressão do tipo array não é implicitamente convertida em um ponteiro para o primeiro elemento da matriz são:

  • Quando é o operando de unário & , então &arr retorna o endereço de todo o object da matriz;
  • Quando é o operando de sizeof , então sizeof arr gera o tamanho em bytes do object array, não o tamanho de um ponteiro; e
  • Quando é um string literal em um inicializador usado para inicializar um array (sub) object, então char s[6] = "hello"; copia o valor da matriz em s em vez de inicializar sem sentido um object de matriz com um valor de ponteiro. Esta última exceção não se aplica ao código que você está perguntando.

(O rascunho do N1570 do padrão ISO C de 2011 afirma incorretamente que _Alignof é uma quarta exceção; isso é incorreto, pois _Alignof só pode ser aplicado a um nome de tipo entre parênteses, não a uma expressão. O erro é corrigido no padrão C11 final. )

Leitura recomendada: Seção 6 da FAQ comp.lang.c.

Porque todas as expressões estão apontando para o começo da matriz:

 a = {{a00},{a01},{a10},{a11}} 

a aponta para o array, só porque é um array, então a == &a[0]

e &a[0][0] está posicionado na primeira célula da matriz 2D.

Ele está imprimindo os mesmos valores porque todos eles estão apontando para o mesmo local.

Tendo dito isto,

&a[i][i] é do tipo int * que é um ponteiro para um inteiro.

a e &a[0] possuem o tipo int(*)[2] que indica um ponteiro para uma matriz de 2 ints.

&a tem o tipo de int(*)[2][2] que indica um ponteiro para uma 2-D array ou um ponteiro para uma matriz de dois elementos em que cada elemento é uma matriz de 2 ints.

Assim, todos eles são de tipo diferente e se comportam de maneira diferente se você começar a fazer aritmética de pointers neles.

(&a[0][1] + 1) aponta para o próximo elemento inteiro na matriz 2-D, ou seja, para a[0][1]

&a[0] + 1 aponta para o próximo array de inteiros, isto é, para a[1][0]

&a + 1 aponta para a próxima matriz 2-D que é inexistente neste caso, mas seria a[2][0] se presente.

  +------------------------------+ | a[0][0] <-- a[0] <-- a | // <--&a, a,*a, &a[0],&a[0][0] |_a[0][1]_ | | a[1][0] <-- a[1] | | a[1][1] | +------------------------------+ 

Você sabe que a é o endereço do primeiro elemento da matriz e, de acordo com o padrão C, a[X] é igual a *(a + X) .

Assim:

&a[0] == a porque &a[0] é o mesmo que &(*(a + 0)) = &(*a) = a .

&a[0][0] == a porque &a[0][0] é o mesmo que &(*(*(a + 0) + 0))) = &(*a) = a

Um array 2D em C é tratado como um array 1D cujos elementos são arrays 1D (as linhas).
Por exemplo, uma matriz 4×3 de T (onde “T” é algum tipo de dado) pode ser declarada por: T a[4][3] , e descrita pelo seguinte esquema:

  +-----+-----+-----+ a == a[0] ---> | a00 | a01 | a02 | +-----+-----+-----+ +-----+-----+-----+ a[1] ---> | a10 | a11 | a12 | +-----+-----+-----+ +-----+-----+-----+ a[2] ---> | a20 | a21 | a22 | +-----+-----+-----+ +-----+-----+-----+ a[3] ---> | a30 | a31 | a32 | +-----+-----+-----+ 

Além disso, os elementos da matriz são armazenados na memory linha após linha.
Preendendo o T e acrescentando o [3] ao a temos uma matriz de 3 elementos do tipo T Mas, o nome a[4] é ele próprio um array indicando que há 4 elementos sendo cada um array de 3 elementos. Por isso, temos uma matriz de 4 matrizes de 3 elementos cada.
Agora está claro que aponta para o primeiro elemento ( a[0] ) de a[4] . Por outro lado, &a[0] dará o endereço do primeiro elemento ( a[0] ) de a[4] e &a[0][0] fornecerá o endereço da a00 | a01 | a02) linha ( a00 | a01 | a02) do array a[4][3] . &a dará o endereço do array 2D a[3][4] . *a decai para pointers para a[0][0] .
Note que a não é um ponteiro para a[0][0] ; em vez disso, é um ponteiro para a[0] .
Conseqüentemente

  • G1: a e &a[0] são equivalentes.
  • G2: *a , a[0] e &a[0][0] são equivalentes.
  • G3: &a (fornece o endereço do array 2D a[3][4] ).
    Mas o grupo G1 , G2 e G3 não são idênticos apesar de estarem dando o mesmo resultado (e eu expliquei acima porque está dando o mesmo resultado).

Isso também significa que em arrays C não há sobrecarga. Em outras linguagens, a estrutura das matrizes é

 &a --> overhead more overhead &a[0] --> element 0 element 1 element 2 ... 

e &a != &a[0]

Intuitivamente, agora a razão está clara por trás da saída. Mas, considerando como os pointers são implementados em C, não consigo entender como a e & a são iguais. Estou assumindo que existe uma variável a na memory que aponta para o array (e o endereço inicial desse array-memory-block seria o valor dessa variável a).

Bem não. Não existe um endereço armazenado em qualquer lugar na memory. Há apenas memory alocada para os dados brutos, e é isso. O que acontece é que, quando você usa um nu, ele imediatamente decai em um ponteiro para o primeiro elemento, dando a impressão de que o ‘valor’ de a era o endereço, mas o único valor de a é o armazenamento de matriz bruta.

De fato, a e &a são diferentes, mas apenas em tipo, não em valor. Vamos facilitar um pouco o uso de matrizes 1D para esclarecer este ponto:

 bool foo(int (*a)[2]) { //a function expecting a pointer to an array of two elements return (*a)[0] == (*a)[1]; //a pointer to an array needs to be dereferenced to access its elements } bool bar(int (*a)[3]); //a function expecting a pointer to an array of three elements bool baz(int *a) { //a function expecting a pointer to an integer, which is typically used to access arrays. return a[0] == a[1]; //this uses pointer arithmetic to access the elements } int z[2]; assert((size_t)z == (size_t)&z); //the value of both is the address of the first element. foo(&z); //This works, we pass a pointer to an array of two elements. //bar(&z); //Error, bar expects a pointer to an array of three elements. //baz(&z); //Error, baz expects a pointer to an int //foo(z); //Error, foo expects a pointer to an array //bar(z); //Error, bar expects a pointer to an array baz(z); //Ok, the name of an array easily decays into a pointer to its first element. 

Como você vê, a e se comportam de maneira muito diferente, embora compartilhem o mesmo valor.