W poniższym fragmencie kodu wartości wskaźników i adresy wskaźników różnią się zgodnie z oczekiwaniami.
Ale wartości tablicowe i adresy nie!
Jak to może być?
Wynik
my_array = 0022FF00
&my_array = 0022FF00
pointer_to_array = 0022FF00
&pointer_to_array = 0022FEFC
#include <stdio.h>
int main()
{
char my_array[100] = "some cool string";
printf("my_array = %p\n", my_array);
printf("&my_array = %p\n", &my_array);
char *pointer_to_array = my_array;
printf("pointer_to_array = %p\n", pointer_to_array);
printf("&pointer_to_array = %p\n", &pointer_to_array);
printf("Press ENTER to continue...\n");
getchar();
return 0;
}
sizeof(&array)
zwraca?Odpowiedzi:
Nazwa tablicy zazwyczaj ocenia się adres pierwszego elementu tablicy, tak
array
i&array
mają taką samą wartość (ale różnych typów, więcarray+1
i&array+1
będzie nie być równe, jeśli tablica jest większa niż długość 1 elementu).Istnieją dwa wyjątki od tej reguły: kiedy nazwa tablicy jest operandem
sizeof
lub jednoargumentowym&
(adres-of), nazwa odnosi się do samego obiektu tablicy. W ten sposóbsizeof array
podaje rozmiar w bajtach całej tablicy, a nie rozmiar wskaźnika.Tablica zdefiniowana jako
T array[size]
będzie miała typT *
. Kiedy / jeśli ją zwiększasz, przechodzisz do następnego elementu w tablicy.&array
zwraca się do tego samego adresu, ale przy tej samej definicji tworzy wskaźnik typuT(*)[size]
- tj. jest to wskaźnik do tablicy, a nie do pojedynczego elementu. Jeśli zwiększysz ten wskaźnik, doda on rozmiar całej tablicy, a nie rozmiar pojedynczego elementu. Na przykład z takim kodem:char array[16]; printf("%p\t%p", (void*)&array, (void*)(&array+1));
Możemy oczekiwać, że drugi wskaźnik będzie o 16 większy niż pierwszy (ponieważ jest to tablica 16 znaków). Ponieważ% p zwykle konwertuje wskaźniki na szesnastkowe, może to wyglądać mniej więcej tak:
0x12341000 0x12341010
źródło
&array
jest wskaźnikiem do pierwszego elementu tablicy, gdzie asarray
odnosi się do całej tablicy. Zasadniczą różnicę można również zaobserwować, porównującsizeof(array)
dosizeof(&array)
. Zauważ jednak, że jeśli przekazujeszarray
jako argument do funkcji,&array
w rzeczywistości tylko jest przekazywany. Nie można przekazać tablicy według wartości, chyba że jest ona hermetyzowana za pomocą rozszerzeniastruct
.&array[0]
jest przekazywany, a nie&array
który byłby wskaźnikiem do tablicy. Może to być dobry wybór, ale myślę, że ważne jest, aby to wyjaśnić; kompilatory będą ostrzegać, jeśli funkcja ma prototyp, który pasuje do typu przekazanego wskaźnika.int *p = array; int **pp = &p;
.Dzieje się tak, ponieważ nazwa tablicy (
my_array
) różni się od wskaźnika do tablicy. Jest to alias adresu tablicy, a jej adres jest zdefiniowany jako adres samej tablicy.Wskaźnik jest jednak zwykłą zmienną C na stosie. W ten sposób możesz wziąć jego adres i uzyskać inną wartość niż adres, który zawiera.
Pisałem o tym tutaj - zapraszam.
źródło
register
) niezależnie od czasu jej przechowywania: statyczna, dynamiczna lub automatyczna.my_array
sam jest na stosie, ponieważmy_array
jest całą tablicą.my_array
, gdy nie jest przedmiotem operatorów&
lubsizeof
, jest oceniana jako wskaźnik do swojego pierwszego elementu (tj.&my_array[0]
) - alemy_array
sama nie jest tym wskaźnikiem (my_array
nadal jest tablicą). Ten wskaźnik to tylko efemeryczna wartość r (np. Podanaint a;
, jest podobnaa + 1
) - przynajmniej koncepcyjnie jest „obliczana w razie potrzeby”. Prawdziwą „wartością”my_array
jest zawartość całej tablicy - po prostu ustalenie tej wartości w C jest jak próba złapania mgły w słoiku.W C, kiedy używasz nazwy tablicy w wyrażeniu (włączając w to przekazanie jej do funkcji), chyba że jest to operand operatora address-of (
&
) lubsizeof
operator, rozpada się na wskaźnik do jej pierwszego elementu.Oznacza to, że w większości kontekstów
array
jest odpowiednikiem&array[0]
zarówno typu, jak i wartości.W twoim przykładzie
my_array
ma typ,char[100]
który rozpada się na a,char*
gdy przekazujesz go do printf.&my_array
ma typchar (*)[100]
(wskaźnik do tablicy 100char
). Ponieważ jest to operand do&
, jest to jeden z przypadków, któremy_array
nie rozpadają się natychmiast na wskaźnik do pierwszego elementu.Wskaźnik do tablicy ma taką samą wartość adresową jak wskaźnik do pierwszego elementu tablicy, ponieważ obiekt tablicy jest po prostu ciągłą sekwencją jej elementów, ale wskaźnik do tablicy ma inny typ niż wskaźnik do elementu ta tablica. Jest to ważne, gdy wykonujesz arytmetykę wskaźników na dwóch typach wskaźników.
pointer_to_array
ma typchar *
- zainicjowany, aby wskazywać na pierwszy element tablicy, ponieważ to właśniemy_array
rozpada się w wyrażeniu inicjatora - i&pointer_to_array
ma typchar **
(wskaźnik do wskaźnika do achar
).Spośród nich:
my_array
(po rozpadzie nachar*
),&my_array
apointer_to_array
wszystkie wskazują bezpośrednio na tablicę lub pierwszy element tablicy, a więc mają tę samą wartość adresu.źródło
Powód, dla którego
my_array
i&my_array
wynik w tym samym adresie można łatwo zrozumieć, patrząc na układ pamięci tablicy.Powiedzmy, że masz tablicę 10 znaków (zamiast 100 w kodzie).
char my_array[10];
Pamięć dla
my_array
wygląda mniej więcej tak:W C / C ++ tablica rozpada się na wskaźnik do pierwszego elementu w wyrażeniu, takim jak
printf("my_array = %p\n", my_array);
Jeśli zbadasz, gdzie znajduje się pierwszy element tablicy, zobaczysz, że jego adres jest taki sam jak adres tablicy:
my_array[0] | v +---+---+---+---+---+---+---+---+---+---+ | | | | | | | | | | | +---+---+---+---+---+---+---+---+---+---+ ^ | Address of my_array[0].
źródło
W języku programowania B, który był bezpośrednim poprzednikiem C, wskaźniki i liczby całkowite były swobodnie zamienne. System zachowywałby się tak, jakby cała pamięć była gigantyczną tablicą. Każda nazwa zmiennej miała powiązany z nią adres globalny lub względny dla stosu, dla każdej nazwy zmiennej kompilator musiał tylko śledzić, czy była to zmienna globalna, czy lokalna, oraz jej adres względem pierwszej globalnej lub lokalnej zmienna.
Biorąc pod uwagę globalne deklaracja jak
i;
[nie było potrzeby, aby określić typ, ponieważ wszystko było liczbą całkowitą / wskaźnik] będą przetwarzane przez kompilator jako:address_of_i = next_global++; memory[address_of_i] = 0;
a oświadczeniu jaki++
będą przetwarzane jako:memory[address_of_i] = memory[address_of_i]+1;
.Deklaracja taka jak
arr[10];
byłaby przetwarzana jakoaddress_of_arr = next_global; memory[next_global] = next_global; next_global += 10;
. Zauważ, że gdy tylko ta deklaracja została przetworzona, kompilator mógł natychmiast zapomnieć oarr
byciu tablicą . Oświadczenie, takie jakarr[i]=6;
, będzie przetwarzane jakomemory[memory[address_of_a] + memory[address_of_i]] = 6;
. Kompilator nie przejmowałby się tym, czyarr
reprezentował tablicę ii
liczbę całkowitą, czy odwrotnie. Rzeczywiście, nie obchodziło by, gdyby obie były tablicami czy obiema liczbami całkowitymi; całkowicie szczęśliwie wygenerowałoby kod zgodnie z opisem, bez względu na to, czy wynikowe zachowanie byłoby prawdopodobnie przydatne.Jednym z celów języka programowania C było zapewnienie dużej zgodności z B. W B, nazwa tablicy [zwanej „wektorem” w terminologii B] identyfikowała zmienną zawierającą wskaźnik, który początkowo był przypisany do do pierwszego elementu alokacji o podanym rozmiarze, więc gdyby ta nazwa pojawiła się na liście argumentów funkcji, funkcja otrzymałaby wskaźnik do wektora. Mimo że C dodał "prawdziwe" typy tablic, których nazwa była sztywno powiązana z adresem alokacji, a nie zmienną wskaźnikową, która początkowo wskazywałaby na alokację, posiadanie tablic rozkładających się na wskaźniki tworzyło kod, który zadeklarował tablicę typu C zachowuje się identycznie do kodu B, który zadeklarował wektor, a następnie nigdy nie modyfikował zmiennej przechowującej jego adres.
źródło
Właściwie
&myarray
imyarray
oba są adresem bazowym.Jeśli chcesz zobaczyć różnicę, zamiast używać
printf("my_array = %p\n", my_array); printf("my_array = %p\n", &my_array);
posługiwać się
printf("my_array = %s\n", my_array); printf("my_array = %p\n", my_array);
źródło