Czy nazwa tablicy jest wskaźnikiem w C? Jeśli nie, jaka jest różnica między nazwą tablicy a zmienną wskaźnika?
&array[0]
zwraca wskaźnik, a nie tablicę;)
Czy nazwa tablicy jest wskaźnikiem w C? Jeśli nie, jaka jest różnica między nazwą tablicy a zmienną wskaźnika?
&array[0]
zwraca wskaźnik, a nie tablicę;)
Odpowiedzi:
Tablica jest tablicą, a wskaźnik jest wskaźnikiem, ale w większości przypadków nazwy tablic są konwertowane na wskaźniki. Często używanym terminem jest to, że rozpadają się na wskaźniki.
Oto tablica:
int a[7];
a
zawiera miejsce na siedem liczb całkowitych i możesz umieścić wartość w jednej z nich za pomocą przypisania, takiego jak to:
a[3] = 9;
Oto wskaźnik:
int *p;
p
nie zawiera spacji dla liczb całkowitych, ale może wskazywać spację dla liczby całkowitej. Możemy na przykład ustawić go tak, aby wskazywał jedno z miejsc w tablicy a
, na przykład pierwsze:
p = &a[0];
Co może być mylące, to że możesz również napisać to:
p = a;
Ten sposób nie skopiować zawartość tablicy a
do wskaźnika p
(cokolwiek to by znaczyło). Zamiast tego nazwa tablicya
jest konwertowana na wskaźnik do pierwszego elementu. To zadanie robi to samo co poprzednie.
Teraz możesz używać p
w podobny sposób jak tablicę:
p[3] = 17;
Powodem tego jest to, że operator dereferencji tablicowej w C [ ]
jest zdefiniowany w kategoriach wskaźników. x[y]
oznacza: zacznij od wskaźnika x
, przesuń y
elementy do przodu po tym, na co wskazuje wskaźnik, a następnie weź wszystko, co tam jest. Używając składni arytmetycznej wskaźnika, x[y]
można również zapisać jako*(x+y)
.
Aby to działało z normalną tablicą, taką jak nasza a
, nazwa a
w a[3]
musi najpierw zostać przekonwertowana na wskaźnik (do pierwszego elementu w a
). Następnie wykonujemy 3 kroki do przodu i bierzemy wszystko, co tam jest. Innymi słowy: weź element w pozycji 3 w tablicy. (Który jest czwartym elementem w tablicy, ponieważ pierwszy ma numer 0.)
Podsumowując, nazwy tablic w programie C są (w większości przypadków) konwertowane na wskaźniki. Jednym wyjątkiem jest sytuacja, gdy używamy sizeof
operatora w tablicy. Gdyby a
w tym kontekście został przekonwertowany na wskaźnik, sizeof a
dałby rozmiar wskaźnika, a nie rzeczywistej tablicy, co byłoby raczej bezużyteczne, więc w tym przypadku a
oznacza samą tablicę.
functionpointer()
i oba (*functionpointer)()
oznaczają to samo, o dziwo.
sizeof()
tym innym kontekstem, w którym nie ma zaniku tablicy-> wskaźnika, jest operator &
- w powyższym przykładzie &a
będzie to wskaźnik do tablicy 7 int
, a nie wskaźnik do jednego int
; to znaczy, będzie to jego typ int(*)[7]
, który nie może być domyślnie konwertowany na int*
. W ten sposób funkcje mogą faktycznie pobierać wskaźniki do tablic o określonym rozmiarze i wymuszać ograniczenie za pomocą systemu typów.
Gdy tablica jest używana jako wartość, jej nazwa reprezentuje adres pierwszego elementu.
Gdy tablica nie jest używana jako wartość, jej nazwa reprezentuje całą tablicę.
int arr[7];
/* arr used as value */
foo(arr);
int x = *(arr + 1); /* same as arr[1] */
/* arr not used as value */
size_t bytes = sizeof arr;
void *q = &arr; /* void pointers are compatible with pointers to any object */
Jeśli wyrażenie typu tablicowego (takie jak nazwa tablicy) pojawia się w większym wyrażeniu i nie jest operandem operatora &
lub sizeof
, wówczas typ wyrażenia tablicowego jest konwertowany z „N-elementowej tablicy T” na „wskaźnik do T”, a wartością wyrażenia jest adres pierwszego elementu w tablicy.
Krótko mówiąc, nazwa tablicy nie jest wskaźnikiem, ale w większości kontekstów jest traktowana tak, jakby była wskaźnikiem.
Edytować
Odpowiedź na pytanie w komentarzu:
Jeśli używam sizeof, czy liczę rozmiar tylko elementów tablicy? Zatem tablica „head” również zajmuje miejsce z informacją o długości i wskaźniku (a to oznacza, że zajmuje więcej miejsca niż normalny wskaźnik)?
Gdy tworzysz tablicę, jedyną przydzieloną przestrzenią jest przestrzeń dla samych elementów; nie ma miejsca na przechowywanie osobnego wskaźnika lub jakichkolwiek metadanych. Dany
char a[10];
masz w pamięci
+---+
a: | | a[0]
+---+
| | a[1]
+---+
| | a[2]
+---+
...
+---+
| | a[9]
+---+
Wyrażenie a
odnosi się do całej tablicy, ale nie ma obiektu a
oddzielnie od samych elementów macierzy. W ten sposób sizeof a
daje rozmiar (w bajtach) całej tablicy. Wyrażenie &a
podaje adres tablicy, który jest taki sam jak adres pierwszego elementu . Różnica między &a
i &a[0]
jest rodzajem wyniku 1 - char (*)[10]
w pierwszym przypadku i char *
w drugim.
Dziwnie się dzieje, gdy chcesz uzyskać dostęp do poszczególnych elementów - wyrażenie a[i]
jest definiowane jako wynik *(a + i)
- biorąc pod uwagę wartość adresu a
, przesunąć i
elementy ( nie bajty ) od tego adresu i wykluczyć wynik.
Problem polega na tym, że a
nie jest to wskaźnik ani adres - to cały obiekt tablicy. Zatem reguła w C, że ilekroć kompilator widzi wyrażenie typu tablicowego (takie jak a
, który ma typ char [10]
) i to wyrażenie nie jest operandem operatorów sizeof
jednoargumentowych &
, typ tego wyrażenia jest konwertowany („zanika”) na typ wskaźnika ( char *
), a wartością wyrażenia jest adres pierwszego elementu tablicy. Dlatego wyrażenie a
ma ten sam typ i wartość co wyrażenie &a[0]
(a przez rozszerzenie wyrażenie *a
ma ten sam typ i wartość co wyrażenie a[0]
).
C pochodzi z wcześniejszej języku zwanym B, a B a
był odrębny przedmiot wskaźnik z elementów tablicy a[0]
, a[1]
itp Ritchie chce zachować semantykę tablicy B, ale nie chciał zadzierać z przechowywaniem oddzielny obiekt wskaźnika. Więc się tego pozbył. Zamiast tego kompilator konwertuje wyrażenia tablicowe na wyrażenia wskaźnika podczas tłumaczenia, jeśli to konieczne.
Pamiętaj, że powiedziałem, że tablice nie przechowują żadnych metadanych dotyczących ich wielkości. Gdy tylko wyrażenie tablicowe „rozpada się” na wskaźnik, wszystko, co masz, to wskaźnik na pojedynczy element. Ten element może być pierwszym z sekwencji elementów lub może być pojedynczym obiektem. Nie ma sposobu, aby wiedzieć na podstawie samego wskaźnika.
Gdy przekazujesz wyrażenie tablicowe do funkcji, wszystko, co funkcja odbiera, jest wskaźnikiem do pierwszego elementu - nie ma pojęcia, jak duża jest tablica (dlatego gets
funkcja była takim zagrożeniem i ostatecznie została usunięta z biblioteki). Aby funkcja wiedziała, ile elementów ma tablica, musisz albo użyć wartownika (np. Terminator 0 w łańcuchach C), albo przekazać liczbę elementów jako osobny parametr.
sizeof
jest operatorem i oblicza liczbę bajtów w operandzie (albo wyrażenie oznaczające obiekt, albo nazwa typu w nawiasach). Tak więc, dla tablicy, sizeof
ocenia się liczbę elementów pomnożoną przez liczbę bajtów w jednym elemencie. Jeśli an int
ma szerokość 4 bajtów, 5-elementowa tablica int
zajmuje 20 bajtów.
[ ]
też nie jest wyjątkowy? Na przykład int a[2][3];
wtedy x = a[1][2];
, chociaż można go przepisać jako x = *( *(a+1) + 2 );
, tutaj a
nie jest konwertowany na typ wskaźnika int*
(chociaż jeśli a
jest argumentem funkcji, powinien zostać przekonwertowany int*
).
a
ma typ int [2][3]
, który „rozpada się” na typ int (*)[3]
. Wyrażenie *(a + 1)
ma typ int [3]
, który „rozpada się” int *
. Zatem *(*(a + 1) + 2)
będzie miał typ int
. a
wskazuje na pierwszym 3 elementu tablicy int
, a + 1
wskazuje na drugim 3 elementu tablicy int
, *(a + 1)
to drugi 3-elementowa tablica int
, *(a + 1) + 2
wskazuje trzeciego elementu drugiego układu int
, tak *(*(a + 1) + 2)
jest trzeci element drugi rząd int
. Sposób mapowania na kod maszynowy zależy wyłącznie od kompilatora.
Tablica zadeklarowana w ten sposób
int a[10];
przydziela pamięć na 10 int
s. Nie możesz modyfikować, a
ale możesz wykonywać arytmetykę wskaźników a
.
Taki wskaźnik przydziela pamięć tylko dla wskaźnika p
:
int *p;
Nie przydziela żadnych int
s. Możesz to zmienić:
p = a;
i używaj indeksów tablicowych, jak możesz, używając:
p[2] = 5;
a[2] = 5; // same
*(p+2) = 5; // same effect
*(a+2) = 5; // same effect
int
s z automatycznym czasem przechowywania”.
Sama nazwa tablicy daje miejsce w pamięci, więc możesz traktować nazwę tablicy jak wskaźnik:
int a[7];
a[0] = 1976;
a[1] = 1984;
printf("memory location of a: %p", a);
printf("value at memory location %p is %d", a, *a);
I inne fajne rzeczy, które możesz zrobić, aby wskaźnik (np. Dodawanie / odejmowanie przesunięcia), możesz także zrobić z tablicą:
printf("value at memory location %p is %d", a + 1, *(a + 1));
Pod względem językowym, jeśli C nie ujawnił tablicy jako jakiegoś „wskaźnika” (pedantycznie jest to tylko lokalizacja pamięci. Nie może wskazywać na dowolną lokalizację w pamięci, ani nie może być kontrolowana przez programistę). Zawsze musimy to zakodować:
printf("value at memory location %p is %d", &a[1], a[1]);
Myślę, że ten przykład rzuca nieco światła na ten problem:
#include <stdio.h>
int main()
{
int a[3] = {9, 10, 11};
int **b = &a;
printf("a == &a: %d\n", a == b);
return 0;
}
Kompiluje dobrze (z 2 ostrzeżeniami) w gcc 4.9.2 i drukuje następujące:
a == &a: 1
ups :-)
Wniosek jest następujący: nie, tablica nie jest wskaźnikiem, nie jest przechowywana w pamięci (nawet tylko do odczytu) jako wskaźnik, nawet jeśli tak wygląda, ponieważ można uzyskać jego adres za pomocą operatora & . Ale - ups - ten operator nie działa :-)), tak czy inaczej, zostałeś ostrzeżony:
p.c: In function ‘main’:
pp.c:6:12: warning: initialization from incompatible pointer type
int **b = &a;
^
p.c:8:28: warning: comparison of distinct pointer types lacks a cast
printf("a == &a: %d\n", a == b);
C ++ odrzuca wszelkie takie próby z błędami w czasie kompilacji.
Edytować:
Oto, co chciałem zademonstrować:
#include <stdio.h>
int main()
{
int a[3] = {9, 10, 11};
void *c = a;
void *b = &a;
void *d = &c;
printf("a == &a: %d\n", a == b);
printf("c == &c: %d\n", c == d);
return 0;
}
Mimo c
i a
„wskaż” tę samą pamięć, możesz uzyskać adres c
wskaźnika, ale nie możesz uzyskać adresu a
wskaźnika.
-std=c11 -pedantic-errors
, pojawi się błąd kompilatora podczas pisania nieprawidłowego kodu C. Powodem jest to, że próbujesz przypisać int (*)[3]
zmienną int**
, która jest dwoma typami, które absolutnie nie mają ze sobą nic wspólnego. Więc co ten przykład ma udowodnić, nie mam pojęcia.
int **
typ, lepiej void *
do tego użyć .
Nazwa tablicy zachowuje się jak wskaźnik i wskazuje na pierwszy element tablicy. Przykład:
int a[]={1,2,3};
printf("%p\n",a); //result is similar to 0x7fff6fe40bc0
printf("%p\n",&a[0]); //result is similar to 0x7fff6fe40bc0
Obie instrukcje print dadzą dokładnie taką samą wydajność dla maszyny. W moim systemie dało to:
0x7fff6fe40bc0
Tablica to zbiór poufnych i ciągłych elementów w pamięci. W C nazwa tablicy jest indeksem pierwszego elementu, a stosując przesunięcie można uzyskać dostęp do reszty elementów. „Indeks do pierwszego elementu” jest rzeczywiście wskaźnikiem kierunku pamięci.
Różnica między zmiennymi wskaźnikowymi polega na tym, że nie można zmienić lokalizacji, na którą wskazuje nazwa tablicy, więc jest podobny do wskaźnika stałej (jest podobny, a nie taki sam. Zobacz komentarz Marka). Ale także, że nie musisz odrywać nazwy tablicy, aby uzyskać wartość, jeśli używasz arytmetyki wskaźnika:
char array = "hello wordl";
char* ptr = array;
char c = array[2]; //array[2] holds the character 'l'
char *c1 = ptr[2]; //ptr[2] holds a memory direction that holds the character 'l'
Więc odpowiedź brzmi „tak”.
Nazwa tablicy to adres pierwszego elementu tablicy. Tak więc nazwa tablicy jest stałym wskaźnikiem.