W C, są tablice wskaźniki lub używane jako wskaźniki?

Zrozumiałem, że tablice są po prostu stałymi wskaźnikami do sekwencji wartości, a kiedy deklarowałeś tablicę w C, deklarowałeś wskaźnik i przydzielałeś przestrzeń dla sekwencji, do której ona wskazuje.

Ale to mnie myli: następujący kod:

char y[20];
char *z = y;

printf("y size is %lu\n", sizeof(y));
printf("y is %p\n", y);
printf("z size is %lu\n", sizeof(z));
printf("z is %p\n", z);

Po skompilowaniu z Apple GCC daje następujący wynik:

y size is 20
y is 0x7fff5fbff930
z size is 8
z is 0x7fff5fbff930

(moja maszyna ma 64 bity, wskaźniki mają długość 8 bajtów).

Jeśli' y ' jest stałym wskaźnikiem, dlaczego ma rozmiar 20, tak jak sekwencja wartości, na które wskazuje? Czy nazwa zmiennej " y " jest zastępowana przez adres pamięci podczas kompilacji, gdy jest odpowiednia? Czy tablice są więc jakimś cukrem składniowym w C, który po skompilowaniu jest tłumaczony na rzeczy pointer?

Author: salvador p, 2011-01-05

6 answers

Oto dokładny język ze standardu C ( n1256):

6.3.2.1 Lvalues, arrays, and function designators
...
3 z wyjątkiem sytuacji, gdy jest operandem operatora sizeof lub operatora jednoargumentowego &, lub jest ciągiem literalnym używanym do inicjalizacji tablicy, wyrażenie, które ma typ "array of type" jest konwertowane do wyrażenia z typem " pointer to type", które wskazuje na początkowy element obiektu array i nie jest lvalue. Jeśli obiekt array posiada klasę register storage, zachowanie jest niezdefiniowane.

Ważną rzeczą do zapamiętania jest to, że istnieje różnica między obiektem (w języku C, co oznacza coś, co zajmuje pamięć) a wyrażeniem używanym do odniesienia się do tego obiektu.

Gdy zadeklarujesz tablicę taką jak

int a[10];

Obiekt wyznaczony przez wyrażenie a jest tablicą (tzn. sąsiadującym blokiem pamięci jest wystarczająco duży, aby pomieścić 10 int wartości), a typ wyrażenia a to " 10-elementowa tablica int" lub int [10]. Jeśli wyrażenie a pojawia się w kontekście innym niż operand operatorów sizeof lub &, wtedy jego typ jest domyślnie konwertowany na int *, a jego wartością jest adres pierwszego elementu.

W przypadku operatora sizeof, jeśli operand jest wyrażeniem typu T [N], to wynikiem jest liczba bajtów w obiekt array, a nie wskaźnik do tego obiektu: N * sizeof T.

W przypadku operatora & wartością jest adres tablicy, który jest taki sam jak adres pierwszego elementu tablicy, ale typ wyrażenia jest inny: biorąc pod uwagę deklarację T a[N];, typ wyrażenia &a to T (*)[N], lub wskaźnik do tablicy N-elementowej T. wartość jest taka sama jak a lub &a[0] (adres tablicy jest taki sam jak adres pierwszy element w tablicy), ale różnica typów ma znaczenie. Na przykład, biorąc pod uwagę kod

int a[10];
int *p = a;
int (*ap)[10] = &a;

printf("p = %p, ap = %p\n", (void *) p, (void *) ap);
p++;
ap++;
printf("p = %p, ap = %p\n", (void *) p, (void *) ap);

Zobaczysz wynik w kolejności

p = 0xbff11e58, ap = 0xbff11e58
p = 0xbff11e5c, ap = 0xbff11e80

IOW, advancing p dodaje sizeof int (4) do pierwotnej wartości, podczas gdy advancing ap dodaje 10 * sizeof int (40).

Bardziej standardowy język:

6.5.2.1 array subscripting

ograniczenia

1 jedno z wyrażeń ma typ " wskaźnik do obiektu typ ", inne wyrażenie ma typ integer, a wynik ma typ " type ".

semantyka

2 wyrażenie postfix, po którym następuje wyrażenie w nawiasach kwadratowych [] jest podpisanym oznaczeniem elementu obiektu array. Definicja operatora indeksu dolnego [] jest taka, że E1[E2] jest identyczny z (*((E1)+(E2))). Ze względu na reguły konwersji, które mają zastosowanie do operatora binarnego +, jeśli E1 jest obiektem tablicy (równoważnie wskaźnikiem do element początkowy obiektu tablicy) i E2 jest liczbą całkowitą, E1[E2] wyznacza E2 - ten element E1 (licząc od zera).

Tak więc, kiedy indeksujesz wyrażenie tablicy, to pod maską dzieje się to, że przesunięcie od adresu pierwszego elementu tablicy jest obliczane, a wynik jest dereferowany. Wyrażenie

a[i] = 10;

Jest równoważne

*((a)+(i)) = 10;

Co jest równoważne

*((i)+(a)) = 10;

Co jest równoważne do

 i[a] = 10;

Tak, zapisywanie tablicy w C jest przemienne; na miłość boską, nigdy nie rób tego w kodzie produkcyjnym.

Ponieważ array subscripting jest zdefiniowany w kategoriach operacji wskaźnika, można zastosować operator indeksu do wyrażeń typu wskaźnika oraz typu tablicy:

int *p = malloc(sizeof *p * 10);
int i;
for (i = 0; i < 10; i++)
  p[i] = some_initial_value(); 
Oto przydatna tabela, aby zapamiętać niektóre z tych pojęć:]}
Declaration: T a[N];

Expression    Type    Converts to     Value
----------    ----    ------------    -----
         a    T [N]   T *             Address of the first element in a;
                                        identical to writing &a[0]
        &a    T (*)[N]                Address of the array; value is the same
                                        as above, but the type is different
  sizeof a    size_t                  Number of bytes contained in the array
                                        object (N * sizeof T)
        *a    T                       Value at a[0]
      a[i]    T                       Value at a[i]
     &a[i]    T *                     Address of a[i] 

Declaration: T a[N][M];

Expression     Type        Converts to     Value
----------     ----        ------------    -----
          a    T [N][M]    T (*)[M]        Address of the first subarray (&a[0])
         &a    T (*)[N][M]                 Address of the array (same value as
                                             above, but different type)
   sizeof a    size_t                      Number of bytes contained in the
                                             array object (N * M * sizeof T)
         *a    T [M]      T *              Value of a[0], which is the address
                                             of the first element of the first subarray
                                             (same as &a[0][0])
       a[i]    T [M]      T *              Value of a[i], which is the address
                                             of the first element of the i'th subarray
      &a[i]    T (*)[M]                    Address of the i-th subarray; same value as
                                             above, but different type
sizeof a[i]    size_t                      Number of bytes contained in the i'th subarray
                                             object (M * sizeof T)
      *a[i]    T                           Value of the first element of the i'th 
                                             subarray (a[i][0])
    a[i][j]    T                           Value at a[i][j]
   &a[i][j]    T *                         Address of a[i][j]

Declaration: T a[N][M][O];

Expression        Type             Converts to
----------        ----             -----------
         a        T [N][M][O]      T (*)[M][O]
        &a        T (*)[N][M][O]
        *a        T [M][O]         T (*)[O]
      a[i]        T [M][O]         T (*)[O]
     &a[i]        T (*)[M][O]
     *a[i]        T [O]            T *
   a[i][j]        T [O]            T *
  &a[i][j]        T (*)[O]
  *a[i][j]        T 
a[i][j][k]        T

Stąd wzór dla tablic wyższych wymiarów powinien być jasny.

Tak więc w podsumowaniu: tablice nie są wskaźnikami. W większości kontekstów wyrażenia array są konwertowane na typy wskaźników.

 51
Author: John Bode,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-05 19:36:14

Tablice nie są wskaźnikami, chociaż w większości wyrażeń nazwa tablicy jest obliczana jako wskaźnik do pierwszego elementu tablicy. Jest więc bardzo, bardzo łatwo używać nazwy tablicy jako wskaźnika. Często zobaczysz termin 'decay' używany do opisania tego, jak w "tablica rozpadła się do wskaźnika".

Jedynym wyjątkiem jest operand do operatora sizeof, Gdzie wynikiem jest rozmiar tablicy (w bajtach, a nie elementach).

Kilka dodatkowych kwestii związanych z tym:

An parametr array do funkcji jest fikcją-kompilator przekazuje zwykły wskaźnik (Nie dotyczy to parametrów reference-to-array w C++), więc nie możesz określić rzeczywistego rozmiaru tablicy przekazywanej do funkcji - musisz przekazać tę informację w inny sposób (być może używając jawnego dodatkowego parametru lub używając elementu sentinel-podobnie jak ciągi C)

Również, powszechnym idiomem, aby uzyskać liczbę elementów w tablicy, jest użycie makra takiego jak:

#define ARRAY_SIZE(arr) ((sizeof(arr))/sizeof(arr[0]))

To ma problem z zaakceptowaniem nazwy tablicy, gdzie będzie działać, lub wskaźnika, gdzie da nonsensowny wynik bez ostrzeżenia ze strony kompilatora. Istnieją bezpieczniejsze wersje makra (szczególnie dla C++), które generują ostrzeżenie lub błąd, gdy są używane ze wskaźnikiem zamiast tablicy. Zobacz następujące pozycje:


Uwaga: C99 VLAs (tablice o zmiennej długości) mogą nie spełniać wszystkich tych reguł (w szczególności mogą być przekazywane jako parametry o rozmiarze tablicy znanym przez wywołaną funkcję). Mam małe doświadczenie z Vlasem i z tego co wiem, nie są one powszechnie używane. Chciałbym jednak zaznaczyć, że powyższa dyskusja może mieć inne zastosowanie do VLAs.

 23
Author: Michael Burr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:09:24

sizeof jest obliczana w czasie kompilacji, a kompilator wie, czy operand jest tablicą czy wskaźnikiem. Dla tablic podaje liczbę bajtów zajętych przez tablicę. Twoja tablica jest char[] (i sizeof(char) jest 1), więc sizeof daje Ci liczbę elementów. Aby uzyskać liczbę elementów w przypadku ogólnym, wspólnym idiomem jest (tutaj dla int):

int y[20];
printf("number of elements in y is %lu\n", sizeof(y) / sizeof(int));

Dla wskaźników sizeof podaje liczbę bajtów zajętych przez typ wskaźnika raw.

 6
Author: Péter Török,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-05 17:43:48

Oprócz tego, co powiedzieli inni, może ten artykuł pomoże: http://en.wikipedia.org/wiki/C_%28programming_language%29#Array-pointer_interchangeability

 1
Author: Mark Loeser,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-05 17:31:36

W

char hello[] = "hello there"
int i;

I

char* hello = "hello there";
int i;

W pierwszym przypadku (wyrównanie dysków) 12 bajtów zostanie zapisanych dla hello z przydzieloną przestrzenią inicjalizowaną na hello there, podczas gdy w drugim hello there jest zapisywane gdzie indziej (ewentualnie statyczna przestrzeń) i hello jest inicjalizowana tak, aby wskazywała na podany łańcuch.

hello[2] podobnie jak *(hello + 2) zwróci "e" w obu przypadkach.

 1
Author: doron,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-05 17:44:20

Jeśli' y ' jest stałym wskaźnikiem, dlaczego ma rozmiar 20, tak jak sekwencja wartości, na którą wskazuje?

Ponieważ z jest adresem zmiennej i zawsze zwróci 8 dla twojej maszyny. Aby uzyskać zawartość zmiennej, musisz użyć wskaźnika dereference ( & ).

EDIT: dobre rozróżnienie między tymi dwoma: http://www.cs.cf.ac.uk/Dave/C/node10.html

 -2
Author: Andrew Sledge,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-05 17:40:28