Jak ten fragment kodu określa rozmiar tablicy bez użycia sizeof ()?

Przechodząc przez kilka pytań wywiadu C, znalazłem pytanie brzmiące " jak znaleźć rozmiar tablicy w C bez użycia operatora sizeof?", z następującym rozwiązaniem. To działa, ale nie mogę zrozumieć dlaczego.

#include <stdio.h>

int main() {
    int a[] = {100, 200, 300, 400, 500};
    int size = 0;

    size = *(&a + 1) - a;
    printf("%d\n", size);

    return 0;
}

Zgodnie z oczekiwaniami zwraca 5.

Edit: ludzie wskazali to odpowiedź, ale składnia trochę się różni, tzn. metoda indeksowania

size = (&arr)[1] - arr;

Więc uważam, że oba pytania są ważne i mają nieco inne podejście za problem. Dziękuję wszystkim za ogromną pomoc i dokładne wyjaśnienie!

Author: janojlic, 2019-05-15

3 answers

Gdy dodasz 1 do wskaźnika, wynikiem jest położenie następnego obiektu w sekwencji obiektów typu wskazywany na (np. tablica). Jeśli p wskazuje na obiekt int, to p + 1 wskaże następny int w sekwencji. Jeśli p wskazuje na 5-elementową tablicę int (w tym przypadku wyrażenie &a), to p + 1 wskaże następną 5-elementową tablicę int w sekwencji.

Odejmowanie dwóch wskaźników (pod warunkiem, że oba wskazują na tę samą tablicę obiekt, lub jeden wskazuje jeden obok ostatniego elementu tablicy) daje liczbę obiektów (elementów tablicy) pomiędzy tymi dwoma wskaźnikami.

Wyrażenie &a daje adres a i ma typ int (*)[5] (wskaźnik do 5-elementowej tablicy int). Wyrażenie &a + 1 daje adres następnej 5-elementowej tablicy int następującej po a, a także ma typ int (*)[5]. Wyrażenie *(&a + 1) dereferuje wynik &a + 1, taki, że daje adres pierwszego int po ostatnim elemencie a i ma typ int [5], który w tym kontekście "rozpada się" na wyrażenie typu int *.

Podobnie, wyrażenie a "rozpada się" na wskaźnik do pierwszego elementu tablicy i ma typ int *.

Zdjęcie może pomóc:

int [5]  int (*)[5]     int      int *

+---+                   +---+
|   | <- &a             |   | <- a
| - |                   +---+
|   |                   |   | <- a + 1
| - |                   +---+
|   |                   |   |
| - |                   +---+
|   |                   |   |
| - |                   +---+
|   |                   |   |
+---+                   +---+
|   | <- &a + 1         |   | <- *(&a + 1)
| - |                   +---+
|   |                   |   |
| - |                   +---+
|   |                   |   |
| - |                   +---+
|   |                   |   |
| - |                   +---+
|   |                   |   |
+---+                   +---+

To dwa widoki tego samego magazynu-po lewej stronie widzimy go jako sekwencję 5-elementowych tablic int, podczas gdy po prawej stronie widzimy go jako sekwencję int. Pokazuję również różne wyrażenia i ich rodzaje.

Bądź świadomy, wyrażenie *(&a + 1) powoduje niezdefiniowane zachowanie :

...
Jeśli wynik wskazuje o jeden obok ostatniego elementu obiektu array, to nie może być stosowany jako operand ocenianego operatora jednoargumentowego *.

C 2011 Projekt Online, 6.5.6/9

 137
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
2020-06-20 09:12:55

Ta linia ma największe znaczenie:

size = *(&a + 1) - a;

Jak widzisz, najpierw pobiera adres a i dodaje do niego jeden. Następnie odejmuje ten wskaźnik i odejmuje od niego pierwotną wartość a.

Arytmetyka wskaźnika w C powoduje zwrócenie liczby elementów w tablicy, lub 5. Dodanie jednej i &a jest wskaźnikiem do następnej tablicy 5 int s po a. Następnie kod ten odczytuje wynikowy wskaźnik i odejmuje a (typ tablicy, który rozkłada się do wskaźnika) z tego, podając liczbę elementów w tablicy.

Szczegóły działania arytmetyki wskaźników:

Powiedzmy, że masz wskaźnik xyz, który wskazuje na typ int i zawiera wartość (int *)160. Gdy odejmujesz dowolną liczbę od xyz, C określa, że rzeczywista ilość odejmowana od xyz jest liczbą razy większą od wielkości typu, na który wskazuje. Na przykład, jeśli odejmujesz 5 od xyz, wartość xyz wynikająca będzie xyz - (sizeof(*xyz) * 5), jeśli arytmetyka wskaźnika nie miała zastosowania.

Jako a jest tablicą 5 int typów, wartość wynikowa wyniesie 5. Jednak nie będzie to działać ze wskaźnikiem, tylko z tablicą. Jeśli spróbujesz tego ze wskaźnikiem, wynikiem będzie zawsze 1.

Oto mały przykład, który pokazuje adresy i jak to jest niezdefiniowane. Po lewej stronie wyświetlane są adresy:

a + 0 | [a[0]] | &a points to this
a + 1 | [a[1]]
a + 2 | [a[2]]
a + 3 | [a[3]]
a + 4 | [a[4]] | end of array
a + 5 | [a[5]] | &a+1 points to this; accessing past array when dereferenced

Oznacza to, że kod odejmuje a od &a[5] (lub a+5), dając 5.

zauważ, że jest to niezdefiniowane zachowanie i nie powinno być używane w żadnych okolicznościach. Nie oczekuj, że zachowanie tego będzie spójne na wszystkich platformach i nie używaj go w programach produkcyjnych.

 35
Author: S.S. Anne,
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
2019-07-16 13:55:28

Hmm, podejrzewam, że to coś, co nie działało we wczesnych dniach C. Jest to jednak sprytne.

Podejmowanie kroków po kolei:

  • &a pobiera wskaźnik do obiektu typu int[5]
  • +1 pobiera następny taki obiekt zakładając, że istnieje tablica tych
  • * skutecznie zamienia ten adres na wskaźnik typu na int
  • -a odejmuje Dwa wskaźniki int, zwracając liczbę instancji int pomiędzy oni.

Nie jestem pewien, czy jest to całkowicie legalne (w tym mam na myśli język-prawnik prawny - nie będzie działać w praktyce), biorąc pod uwagę niektóre operacje typu toczące się. Na przykład" dozwolone " jest odejmowanie tylko dwóch wskaźników, gdy wskazują one elementy w tej samej tablicy. *(&a+1) został zsyntetyzowany przez dostęp do innej tablicy, aczkolwiek tablicy nadrzędnej, więc nie jest w rzeczywistości wskaźnikiem do tej samej tablicy co a. Ponadto, podczas gdy możesz zsyntetyzować wskaźnik obok ostatniego elementu array, a każdy obiekt można traktować jako tablicę 1 elementu, operacja dereferencing (*) nie jest "dozwolona" na tym zsyntetyzowanym wskaźniku, mimo że w tym przypadku nie ma on żadnego zachowania!

Podejrzewam, że w pierwszych dniach składni C (K&R, ktoś?), tablica rozkładana do wskaźnika znacznie szybciej, więc *(&a+1) może zwracać tylko adres następnego wskaźnika typu int**. Bardziej rygorystyczne definicje nowoczesnego C++ zdecydowanie pozwalają na istnienie wskaźnika do typu array i znaj rozmiar tablicy i prawdopodobnie standardy C poszły w ślady. Cały kod funkcji C przyjmuje tylko wskaźniki jako argumenty, więc techniczna widoczna różnica jest minimalna. Ale ja tylko zgaduję.

Ten rodzaj szczegółowego pytania o legalność zwykle dotyczy interpretera C lub narzędzia typu lint, a nie skompilowanego kodu. Interpreter może zaimplementować tablicę 2D jako tablicę wskaźników do tablic, ponieważ do zaimplementowania jest jedna funkcja runtime mniej, w takim przypadku dereferowanie +1 byłoby fatalne, a nawet gdyby zadziałało, dałoby złą odpowiedź.

Kolejną możliwą słabością może być to, że kompilator C może wyrównać zewnętrzną tablicę. Wyobraź sobie, że jest to Tablica 5 znaków (char arr[5]), gdy program wykonuje &a+1, wywołuje zachowanie "array of array". Kompilator może zdecydować, że tablica tablicy 5 znaków (char arr[][5]) jest faktycznie generowana jako tablica tablicy 8 znaków (char arr[][8]), tak że zewnętrzna tablica ładnie się wyrównuje. The code we are dyskusja będzie teraz zgłaszać rozmiar tablicy jako 8, a nie 5. Nie mówię, że konkretny kompilator na pewno by to zrobił, ale może.

 27
Author: Gem Taylor,
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
2019-05-15 18:23:59