Czy nazwa tablicy jest wskaźnikiem?

203

Czy nazwa tablicy jest wskaźnikiem w C? Jeśli nie, jaka jest różnica między nazwą tablicy a zmienną wskaźnika?

Lundin
źródło
4
Nie. Ale tablica jest taka sama i tablica [0]
36
@pst: &array[0]zwraca wskaźnik, a nie tablicę;)
czerwiec
28
@Nava (i pst): tablica i & tablica [0] nie są tak naprawdę takie same. Przykład : sizeof (tablica) i sizeof (i tablica [0]) dają różne wyniki.
Thomas Padron-McCarthy
1
@Thomas się zgadza, ale jeśli chodzi o wskaźniki, po wyrejestrowaniu tablicy i & array [0], generują tę samą wartość z tablicy [0] .ie * array == array [0]. Nikt nie miał na myśli, że te dwa wskaźniki są takie same, ale w tym konkretnym przypadku (wskazując na pierwszy element) możesz także użyć nazwy tablicy.
Nava Carmon
1
Mogą one również pomóc w zrozumieniu: stackoverflow.com/questions/381542 , stackoverflow.com/questions/660752
Dinah

Odpowiedzi:

255

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;

pnie 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 ado 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ć pw 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ń yelementy 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 aw 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 sizeofoperatora w tablicy. Gdyby aw tym kontekście został przekonwertowany na wskaźnik, sizeof adałby rozmiar wskaźnika, a nie rzeczywistej tablicy, co byłoby raczej bezużyteczne, więc w tym przypadku aoznacza samą tablicę.

Thomas Padron-McCarthy
źródło
5
Podobna automatyczna konwersja jest stosowana do wskaźników funkcji - functionpointer()i oba (*functionpointer)()oznaczają to samo, o dziwo.
Carl Norum
3
Nie zapytał, czy tablice i wskaźniki są takie same, ale czy nazwa tablicy jest wskaźnikiem
Ricardo Amores,
32
Nazwa tablicy nie jest wskaźnikiem. Jest to identyfikator zmiennej typu tablica, która ma niejawną konwersję na wskaźnik typu elementu.
Pavel Minaev
29
Poza sizeof()tym innym kontekstem, w którym nie ma zaniku tablicy-> wskaźnika, jest operator &- w powyższym przykładzie &abę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.
Pavel Minaev
3
@ onmyway133, sprawdź tutaj, aby uzyskać krótkie wyjaśnienie i dalsze cytowania.
Carl Norum
36

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 */
pmg
źródło
20

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 adaje rozmiar (w bajtach) całej tablicy. Wyrażenie &apodaje adres tablicy, który jest taki sam jak adres pierwszego elementu . Różnica między &ai &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ąć ielementy ( nie bajty ) od tego adresu i wykluczyć wynik.

Problem polega na tym, że anie 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 sizeofjednoargumentowych &, 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 *ama 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 getsfunkcja 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.


  1. To, co * może * wpłynąć na interpretację wartości adresu - zależy od maszyny.
John Bode
źródło
Długo szukałem tej odpowiedzi. Dziękuję Ci! A jeśli wiesz, czy mógłbyś powiedzieć nieco dalej, czym jest wyrażenie tablicowe. 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)?
Andriy Dmytruk,
I jeszcze jedno. Tablica o długości 5 jest typu int [5]. Więc skąd znamy długość, gdy wywołujemy sizeof (tablica) - od jego typu? A to oznacza, że ​​tablice o różnej długości są jak różne typy stałych?
Andriy Dmytruk,
@AndriyDmytruk: sizeofjest operatorem i oblicza liczbę bajtów w operandzie (albo wyrażenie oznaczające obiekt, albo nazwa typu w nawiasach). Tak więc, dla tablicy, sizeofocenia się liczbę elementów pomnożoną przez liczbę bajtów w jednym elemencie. Jeśli an intma szerokość 4 bajtów, 5-elementowa tablica intzajmuje 20 bajtów.
John Bode,
Czy operator [ ]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 anie jest konwertowany na typ wskaźnika int*(chociaż jeśli ajest argumentem funkcji, powinien zostać przekonwertowany int*).
Stan
2
@Stan: Wyrażenie ama 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. awskazuje na pierwszym 3 elementu tablicy int, a + 1wskazuje na drugim 3 elementu tablicy int, *(a + 1) to drugi 3-elementowa tablica int, *(a + 1) + 2wskazuje 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.
John Bode
5

Tablica zadeklarowana w ten sposób

int a[10];

przydziela pamięć na 10 ints. Nie możesz modyfikować, aale możesz wykonywać arytmetykę wskaźników a.

Taki wskaźnik przydziela pamięć tylko dla wskaźnika p:

int *p;

Nie przydziela żadnych ints. 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
Grumdrig
źródło
2
Tablice nie zawsze są przydzielane na stosie. Jest to szczegół implementacji, który różni się w zależności od kompilatora. W większości przypadków tablice statyczne lub globalne będą przydzielane z innego regionu pamięci niż stos. Tablice typów stałych mogą być przydzielane z jeszcze innego regionu pamięci
Mark Bessey,
1
Myślę, że Grumdrig chciał powiedzieć „przydziela 10 ints z automatycznym czasem przechowywania”.
Lekkość ściga się na orbicie
4

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]);
Michael Buen
źródło
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 ci a„wskaż” tę samą pamięć, możesz uzyskać adres cwskaźnika, ale nie możesz uzyskać adresu awskaźnika.

Palo
źródło
1
„Kompiluje się dobrze (z 2 ostrzeżeniami)”. To nie w porządku. Jeśli powiesz gcc, aby skompilował go jako poprawny standard C przez dodanie -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.
Lundin
Dziękuję Lundin za komentarz. Wiesz, że istnieje wiele standardów. Próbowałem wyjaśnić, co miałem na myśli w tej edycji. Nie chodzi tu o int **typ, lepiej void *do tego użyć .
Palo,
-3

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
Amitesh Ranjan
źródło
-4

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”.

Ricardo Amores
źródło
1
Nazwa tablicy nie jest taka sama jak wskaźnik const. Biorąc pod uwagę: int a [10]; int * p = a; sizeof (p) i sizeof (a) nie są takie same.
Mark Bessey
1
Istnieją inne różnice. Ogólnie rzecz biorąc, najlepiej trzymać się terminologii stosowanej przez standard C, który konkretnie nazywa to „konwersją”. Cytat: „Z wyjątkiem sytuacji, gdy jest to operand operatora sizeof lub unary & operator, lub jest literałem łańcuchowym używanym do inicjalizacji tablicy, wyrażenie o typie„ tablica typu ”jest konwertowane na wyrażenie typu„ „wskaźnik do typu”, który wskazuje na początkowy element obiektu tablicowego i nie jest wartością. Jeśli obiekt tablicowy ma klasę pamięci rejestru, zachowanie jest niezdefiniowane. ”
Pavel Minaev
-5

Nazwa tablicy to adres pierwszego elementu tablicy. Tak więc nazwa tablicy jest stałym wskaźnikiem.

SAQIB SOHAIL BHATTI
źródło