Dlaczego indeksowanie zaczyna się od zera w „C”?

154

Dlaczego indeksowanie w tablicy zaczyna się od zera w C, a nie od 1?

Nitish Pareek
źródło
7
Chodzi o wskaźniki!
medopal
3
możliwy duplikat macierzy Defend opartych na
zerach
3
Wskaźnik (tablica) jest kierunkiem pamięci, a indeks jest przesunięciem tego kierunku pamięci, więc pierwszym elementem wskaźnika (tablicy) jest ten, który offset jest równy 0.
D33pN16h7
3
@drhirsch, ponieważ kiedy liczymy zbiór obiektów, zaczynamy od wskazania obiektu i powiedzenia „jeden”.
phoog
1
Amerykanie liczą piętra (kondygnacje) budynku od jednego na parterze; Brytyjczycy liczą od zera (parter), przechodząc na pierwsze piętro, potem drugie piętro, itd.
Jonathan Leffler

Odpowiedzi:

116

W języku C nazwa tablicy jest zasadniczo wskaźnikiem [ale zobacz komentarze] , odniesieniem do miejsca w pamięci, a więc wyrażenie array[n]odnosi się do nelementów lokalizacji pamięci z dala od elementu początkowego. Oznacza to, że indeks jest używany jako przesunięcie. Pierwszy element tablicy jest dokładnie zawarty w lokalizacji pamięci, do której odwołuje się tablica (0 elementów od siebie), więc powinien być oznaczony jako array[0].

Po więcej informacji:

http://developeronline.blogspot.com/2008/04/why-array-index-should-start-from-0.html

Massimiliano Peluso
źródło
20
Nazwa tablicy to nazwa tablicy; wbrew powszechnemu błędnemu przekonaniu tablice w żadnym sensie nie są wskaźnikami. Wyrażenie tablicowe (takie jak nazwa obiektu tablicowego) jest zwykle, ale nie zawsze , konwertowane na wskaźnik do pierwszego elementu. Przykład: sizeof arrzwraca rozmiar obiektu tablicy, a nie rozmiar wskaźnika.
Keith Thompson,
Chociaż oczywiście nie zareagowałeś na komentarz @ KeithThompsona, chciałbym użyć bardziej obraźliwego kursu: „ W języku C nazwa tablicy jest zasadniczo wskaźnikiem, odniesieniem do miejsca w pamięci ” - Nie, to nie jest . Przynajmniej nie z ogólnego punktu widzenia. Podczas gdy Twoja odpowiedź doskonale odpowiada na pytanie w sposób, w jaki ważne jest 0 jako początek indeksu, pierwsze zdanie jest po prostu błędne. Tablica nie zawsze rozpada się na wskaźnik do jej pierwszego elementu.
RobertS wspiera Monikę Cellio
Cytuj ze standardu C, (C18), 6.3.2.1/4: „ Z wyjątkiem sytuacji, gdy jest to operand sizeofoperatora, &operator jednoargumentowy lub literał tekstowy używany do inicjalizacji tablicy, wyrażenie o typie„ tablica ” typu "jest konwertowane na wyrażenie z typem" wskaźnik do typu ", które wskazuje na początkowy element obiektu tablicy i nie jest wartością l. Jeśli obiekt tablicy ma klasę pamięci rejestru, zachowanie jest niezdefiniowane. "
RobertS obsługuje Monikę Cellio
Również ten rozpad zachodzi w sposób bardziej „ukryty” lub „formalny”, niż sugerowano tutaj; nie ma rozpadu obiektu wskaźnika w pamięci. Oto przedmiot tego pytania: czy tablica wskaźnika zanika zmieniona na obiekt wskaźnika? - Zmień swoją odpowiedź, aby była w pełni poprawna.
RobertS wspiera Monikę Cellio
103

To pytanie zostało wysłane ponad rok temu, ale oto ...


O powyższych powodach

Chociaż artykuł Dijkstry (wcześniej przywoływany w obecnie usuniętej odpowiedzi ) ma sens z matematycznego punktu widzenia, nie jest tak istotny, jeśli chodzi o programowanie.

Decyzja podjęta przez specyfikację języka i projektantów kompilatorów jest oparta na decyzji podjętej przez projektantów systemów komputerowych, aby rozpocząć liczenie od 0.


Prawdopodobny powód

Cytat z błagania o pokój Danny'ego Cohena.

Dla dowolnej podstawy b pierwsze b ^ N nieujemnych liczb całkowitych jest reprezentowane przez dokładnie N cyfr (w tym zera wiodące) tylko wtedy, gdy numeracja zaczyna się od 0.

Można to dość łatwo przetestować. W podstawie 2 weź 2^3 = 8 ósma liczba to:

  • 8 (binarne: 1000), jeśli zaczniemy liczyć od 1
  • 7 (binarne: 111), jeśli zaczniemy liczyć od 0

111może być reprezentowany za pomocą 3bitów, podczas gdy 1000będzie wymagał dodatkowego bitu (4 bity).


Dlaczego jest to istotne

Adresy pamięci komputerów mają 2^Nkomórki adresowane Nbitami. Jeśli teraz zaczniemy liczyć od 1, 2^Nkomórki będą potrzebowały N+1linii adresu. Dodatkowy bit jest potrzebny, aby uzyskać dostęp do dokładnie 1 adresu. ( 1000w powyższym przypadku.). Innym sposobem rozwiązania tego problemu byłoby pozostawienie ostatniego adresu niedostępnego i użycie Nlinii adresowych.

Oba są nieoptymalnymi rozwiązaniami , w porównaniu do liczenia początkowego od 0, które zapewniłoby dostępność wszystkich adresów, używając dokładnie Nlinii adresowych!


Wniosek

Decyzja o rozpoczęciu liczenia 0od tego czasu przeniknęła wszystkie systemy cyfrowe , w tym działające na nich oprogramowanie, ponieważ ułatwia to przetłumaczenie kodu na to, co może zinterpretować system bazowy. Gdyby tak nie było, byłaby jedna niepotrzebna operacja translacji między maszyną a programistą dla każdego dostępu do tablicy. Ułatwia kompilację.


Cytując z artykułu:

wprowadź opis obrazu tutaj

Anirudh Ramanathan
źródło
2
A co by było, gdyby właśnie usunęli bit 0 ... to 8 numer nadal będzie 111 ...
DanMatlin,
2
Czy faktycznie sugerujesz modyfikację podstawowej arytmetyki, aby pasowała? Nie sądzisz, że to, co mamy dzisiaj, jest znacznie lepszym rozwiązaniem?
Anirudh Ramanathan
Lata później moja wartość 2 centów. Z mojego doświadczenia (~ 35 lat programowania) wynika, że ​​modulo lub modularna operacja dodawania w takiej czy innej formie pojawia się zaskakująco często. Przy zerowej podstawie następny w kolejności jest (i + 1)% n, ale przy podstawie 1 uzyskuje się (i-1)% n) +1, więc myślę, że preferowane jest 0. To pojawia się dość często w matematyce i programowaniu. Może to tylko ja lub dziedzina, w której pracuję.
nyholku
Chociaż wszystkie dobre powody, myślę, że jest znacznie prostsze: a[b]został zaimplementowany tak jak *(a+b)we wczesnych kompilatorach. Nawet dzisiaj możesz nadal pisać2[a] zamiast tegoa[2] . Teraz, jeśli indeksy nie zaczynają się od 0 a[b], zamieniają się w *(a+b-1). Wymagałoby to 2 dodań na procesorach czasu zamiast 0, co oznacza połowę szybkości. Zdecydowanie niepożądane.
Goswin von Brederlow
1
Tylko dlatego, że chcesz 8 stanów, nie oznacza to, że musisz mieć w nich liczbę 8. Przełączniki światła w moim domu z przyjemnością reprezentują stany „światło włączone” i „wyłączone”, bez zastanawiania się, dlaczego nie reprezentują liczby 2.
Spyryto
27

Ponieważ 0 to odległość od wskaźnika do początku tablicy do pierwszego elementu tablicy.

Rozważać:

int foo[5] = {1,2,3,4,5};

Aby uzyskać dostęp do 0, robimy:

foo[0] 

Ale foo rozkłada się na wskaźnik, a powyższy dostęp ma analogiczny arytmetyczny sposób dostępu do wskaźnika

*(foo + 0)

Obecnie arytmetyka wskaźnikowa nie jest używana tak często. Dawno temu był to wygodny sposób na pobranie adresu i przeniesienie X „int” poza punkt początkowy. Oczywiście, jeśli chcesz po prostu zostać tam, gdzie jesteś, po prostu dodaj 0!

Doug T.
źródło
23

Ponieważ indeks oparty na 0 pozwala ...

array[index]

... do wdrożenia jako ...

*(array + index)

Gdyby indeks był oparty na 1, kompilator musiałby wygenerować:, *(array + index - 1)a to „-1” zaszkodziłoby wydajności.

Branko Dimitrijevic
źródło
4
Poruszasz interesujący punkt. Może to zaszkodzić wydajności. Ale czy trafienie w wydajność będzie znaczące, aby uzasadnić użycie 0 jako indeksu początkowego? Wątpię.
FirstName LastName
3
Indeksy oparte na @FirstNameLastName 1 nie oferują żadnej przewagi nad indeksami opartymi na 0, ale działają (nieco) gorzej. To usprawiedliwia indeksy oparte na 0, bez względu na to, jak „mały” jest zysk. Nawet jeśli indeksy oparte na 1 oferują jakąś przewagę, w duchu C ++ jest wybieranie wydajności zamiast wygody. C ++ jest czasami używany w kontekstach, w których liczy się każdy szczegół wydajności, a te „małe” rzeczy mogą szybko się sumować.
Branko Dimitrijevic
Tak, rozumiem, że małe rzeczy mogą się sumować i czasami stać się dużymi. Na przykład 1 dolar rocznie to niewiele pieniędzy. Ale jeśli przekażą je 2 miliardy ludzi, możemy zrobić wiele dobrego dla ludzkości. Szukam podobnego przykładu w kodowaniu, które mogłoby spowodować słabą wydajność.
FirstName LastName
2
Zamiast odejmować 1, powinieneś użyć adresu tablicy-1 jako adresu bazowego. To właśnie zrobiliśmy w kompilatorze, nad którym kiedyś pracowałem. To eliminuje odejmowanie czasu wykonywania. Kiedy piszesz kompilator, te dodatkowe instrukcje mają duże znaczenie. Kompilator zostanie użyty do wygenerowania tysięcy programów, z których każdy może być użyty tysiące razy, a ta dodatkowa 1 instrukcja może wystąpić w kilku wierszach wewnątrz pętli n-kwadratowej. Może sumować się do miliardów zmarnowanych cykli.
progrmr
Nie, po skompilowaniu nie zaszkodzi to wydajności, doda tylko mały czas kompilacji, ponieważ w końcu zostanie przetłumaczony na kod maszynowy, co zaszkodzi tylko projektantom kompilatora.
Hassaan Akbar
12

Ponieważ uprościł kompilator i konsolidator (łatwiejszy do napisania).

Odniesienie :

„... Odwoływanie się do pamięci za pomocą adresu i przesunięcia jest reprezentowane bezpośrednio w sprzęcie na praktycznie wszystkich architekturach komputerowych, więc ten szczegół projektu w C ułatwia kompilację”

i

„… to upraszcza implementację…”

progrmr
źródło
1
+1 Nie wiem, dlaczego głosuje negatywnie Chociaż nie daje to bezpośredniej odpowiedzi na pytanie, indeksowanie oparte na zerach nie jest naturalne dla ludzi lub matematyków - jedynym powodem jest to, że implementacja jest logicznie spójna (prosta).
phkahler
4
@phkahler: błąd występuje w autorach i językach wywołujących indeksy tablicowe jako indeksy; jeśli myślisz o tym jako o przesunięciu, to wartość zerowa staje się naturalna również dla laika. Weź pod uwagę zegar, pierwsza minuta jest zapisana jako 00:00, a nie 00:01, prawda?
Lie Ryan,
3
+1 - to prawdopodobnie najbardziej poprawna odpowiedź. C jest starszym językiem niż artykuł Djikistras i był jednym z najwcześniejszych języków „zaczynających się od 0”. C zaczynał swoje życie "jako asembler wysokiego poziomu" i jest prawdopodobne, że K&R chciał trzymać się tak blisko sposobu, w jaki robiono to w asemblerze, gdzie normalnie miałbyś adres bazowy plus offset zaczynający się od zera.
James Anderson,
Pomyślałem, że pytanie brzmi, dlaczego użyto opartego na 0, a nie co jest lepsze.
progrmr
2
Nie będę negatywnie głosować, ale zgodnie z komentarzem programu progrmr powyżej bazy można się zająć dostosowując adres tablic, więc niezależnie od podstawowego czasu wykonania jest taki sam i jest to trywialne do zaimplementowania w kompilatorze lub interpretatorze, więc tak naprawdę nie ułatwia to prostszej implementacji . Świadek Pascala, gdzie można użyć dowolnego zakresu do indeksowania IIRC, minęło już 25 lat;)
nyholku
5

Indeks tablicy zawsze zaczyna się od zera. Załóżmy, że adres bazowy to 2000. Teraz arr[i] = *(arr+i). To if i= 0znaczy *(2000+0) jest równe adresowi bazowemu lub adresowi pierwszego elementu w tablicy. indeks ten jest traktowany jako przesunięcie, więc indeks głuchy zaczyna się od zera.

Amit Prakash
źródło
5

Z tego samego powodu, kiedy jest środa i ktoś pyta Cię, ile dni do środy, mówisz 0 zamiast 1, a kiedy jest środa i ktoś cię pyta, ile dni pozostało do czwartku, mówisz 1 zamiast 2.

R .. GitHub PRZESTAŃ POMÓC LODOWI
źródło
6
Twoja odpowiedź wydaje się być tylko kwestią opinii.
heltonbiker,
6
Cóż, to właśnie sprawia, że ​​dodawanie indeksów / przesunięć działa. Na przykład, jeśli „dzisiaj” to 0, a „jutro” to 1, „jutro” to 1 + 1 = 2. Ale jeśli „dzisiaj” to 1, a „jutro” to 2, „jutro” nie jest równe 2 + 2. W tablicach to zjawisko ma miejsce, gdy chcesz rozważyć podzakres tablicy jako tablicę samą w sobie.
R .. GitHub STOP HELPING ICE
7
Nazwanie zbioru 3 rzeczy „3 rzeczy” i numerowanie ich 1, 2, 3 nie jest brakiem. Numerowanie ich przesunięciem od pierwszego nie jest naturalne nawet w matematyce. Jedynym momentem, w którym indeksujesz od zera w matematyce, jest sytuacja, w której chcesz uwzględnić w wielomianu coś w rodzaju potęgi zerowej (stałego członu).
phkahler
9
Re: „Numerowanie tablic zaczynających się od 1 zamiast od 0 jest przeznaczone dla osób z poważnym niedoborem myślenia matematycznego”. Moje wydanie „Wstępu do algorytmów” języka CLR używa indeksowania tablic na podstawie 1; Nie sądzę, by autorzy mieli ułomności w myśleniu matematycznym.
RexE
Nie, powiedziałbym, że siódmy znajduje się pod indeksem 6, czyli 6 pozycji od pierwszego.
R .. GitHub STOP HELPING ICE
2

Najbardziej eleganckim wyjaśnieniem numeracji od zera, jakie przeczytałem, jest spostrzeżenie, że wartości nie są przechowywane w zaznaczonych miejscach na osi liczbowej, ale raczej w odstępach między nimi. Pierwsza pozycja jest przechowywana między zerem a jednym, następna między jednym a dwoma itd. N-ty element jest przechowywany między N-1 a N. Zakres elementów można opisać numerami po obu stronach. Poszczególne pozycje są umownie opisane za pomocą numerów poniżej. Jeśli podano zakres (X, Y), identyfikacja poszczególnych numerów za pomocą numeru poniżej oznacza, że ​​można zidentyfikować pierwszą pozycję bez użycia arytmetyki (to jest pozycja X), ale należy odjąć jedną od Y, aby zidentyfikować ostatnią pozycję (Y -1). Identyfikacja pozycji za pomocą powyższego numeru ułatwiłaby identyfikację ostatniej pozycji w zakresie (byłaby to pozycja Y),

Chociaż identyfikacja pozycji na podstawie liczby znajdującej się nad nimi nie byłaby okropna, zdefiniowanie pierwszej pozycji z zakresu (X, Y) jako pozycji znajdującej się powyżej X generalnie działa lepiej niż zdefiniowanie jej jako tej poniżej (X + 1).

superkat
źródło
1

Przyczyna techniczna może wynikać z faktu, że wskaźnikiem do lokalizacji pamięci tablicy jest zawartość pierwszego elementu tablicy. Jeśli zadeklarujesz wskaźnik z indeksem równym jeden, programy normalnie dodają wartość jeden do wskaźnika, aby uzyskać dostęp do treści, która nie jest tym, czego chcesz.

Obrabować
źródło
1

Spróbuj uzyskać dostęp do ekranu z pikselami, używając współrzędnych X, Y na macierzy o wartości 1. Formuła jest całkowicie złożona. Dlaczego jest złożony? Ponieważ kończy się konwersją współrzędnych X, Y na jedną liczbę, przesunięcie. Dlaczego musisz zamienić X, Y na przesunięcie? Ponieważ w ten sposób pamięć jest zorganizowana w komputerach jako ciągły strumień komórek pamięci (tablic). Jak komputery radzą sobie z komórkami macierzy? Korzystanie z przesunięć (przemieszczenia od pierwszej komórki, model indeksowania od zera).

Więc w pewnym momencie kodu, którego potrzebujesz (lub kompilator potrzebuje), aby przekonwertować formułę o podstawie 1 na formułę opartą na 0, ponieważ w ten sposób komputery radzą sobie z pamięcią.

Gianluca Ghettini
źródło
1

Załóżmy, że chcemy utworzyć tablicę o rozmiarze 5
int array [5] = [2,3,5,9,8]

niech pierwszy element tablicy będzie wskazywał na położenie 100

i rozważmy indeksowanie zaczynające się od 1, a nie od 0.

teraz musimy znaleźć położenie pierwszego elementu za pomocą indeksu
(pamiętaj, że położenie pierwszego elementu to 100),

ponieważ rozmiar liczby całkowitej jest 4-bitowy,
więc -> biorąc pod uwagę indeks 1, pozycja byłaby
wielkości of index (1) * size of integer (4) = 4,
więc rzeczywista pozycja, którą nam pokaże, to

100 + 4 = 104

co nie jest prawdą, ponieważ początkowa lokalizacja była na 100.
powinna wskazywać na 100, a nie na 104
to jest błędne

teraz przypuśćmy, że wzięliśmy indeksowanie od 0
to
pozycja pierwszego elementu powinna mieć
rozmiar indeksu (0) * rozmiar liczby całkowitej (4) = 0,

więc ->
położenie 1-go elementu to 100 + 0 = 100

i taka była rzeczywista lokalizacja elementu,
dlatego indeksowanie zaczyna się od 0;

Mam nadzieję, że to wyjaśni Twój punkt widzenia.

sahil singh
źródło
1

Pochodzę z języka Java. Odpowiedź na to pytanie przedstawiłem na poniższym schemacie, który napisałem na kartce papieru, która jest oczywista

Główne kroki:

  1. Tworzenie odniesienia
  2. Tworzenie instancji Array
  3. Alokacja danych do tablicy

  • Zwróć także uwagę, kiedy tablica jest właśnie tworzona ... Domyślnie zero jest przydzielane do wszystkich bloków, dopóki nie przypiszemy mu wartości
  • Tablica zaczyna się od zera, ponieważ pierwszy adres będzie wskazywał na odniesienie (i: e - X102 + 0 na obrazku)

wprowadź opis obrazu tutaj

Uwaga : Bloki pokazane na obrazku to reprezentacja pamięci

Devrath
źródło
0

przede wszystkim musisz wiedzieć, że tablice są wewnętrznie traktowane jako wskaźniki, ponieważ „sama nazwa tablicy zawiera adres pierwszego elementu tablicy”

ex. int arr[2] = {5,4};

weź pod uwagę, że tablica zaczyna się od adresu 100, więc element pierwszy będzie miał adres 100, a drugi będzie teraz 104, weź pod uwagę, że jeśli indeks tablicy zaczyna się od 1, więc

arr[1]:-

można to zapisać w wyrażeniu wskaźników w ten sposób:

 arr[1] = *(arr + 1 * (size of single element of array));

Rozważ teraz rozmiar int to 4 bajty,

arr[1] = *(arr + 1 * (4) );
arr[1] = *(arr + 4);

jak wiemy nazwa tablicy zawiera adres jej pierwszego elementu, więc arr = 100 teraz,

arr[1] = *(100 + 4);
arr[1] = *(104);

co daje,

arr[1] = 4;

z powodu tego wyrażenia nie możemy uzyskać dostępu do elementu pod adresem 100, który jest oficjalnym pierwszym elementem,

teraz rozważmy, że indeks tablicy zaczyna się od 0, więc

arr[0]:-

zostanie to rozwiązane jako

arr[0] = *(arr + 0 + (size of type of array));
arr[0] = *(arr + 0 * 4);
arr[0] = *(arr + 0);
arr[0] = *(arr);

teraz wiemy, że nazwa tablicy zawiera adres jej pierwszego elementu, więc

arr[0] = *(100);

co daje poprawny wynik

arr[0] = 5;

dlatego indeks tablicy zawsze zaczyna się od 0 w c.

źródło: wszystkie szczegóły są opisane w książce „Język programowania C autorstwa briana kerninghana i dennisa ritchie”

akshay chaudhari
źródło
0

W tablicy indeks wskazuje odległość od elementu początkowego. Zatem pierwszy element znajduje się w odległości 0 od elementu początkowego. Dlatego właśnie tablica zaczyna się od 0.

Rishi Raj Tandon
źródło
0

Dzieje się tak, ponieważ addressmusi wskazywać na prawo elementw tablicy. Załóżmy poniższą tablicę:

let arr = [10, 20, 40, 60]; 

Rozważmy teraz początek istnienia adresu 12i rozmiar elementbycia 4 bytes.

address of arr[0] = 12 + (0 * 4) => 12
address of arr[1] = 12 + (1 * 4) => 16
address of arr[2] = 12 + (2 * 4) => 20
address of arr[3] = 12 + (3 * 4) => 24

Gdyby tak nie było zero-based, z technicznego punktu widzenia nasz pierwszy adres elementu w elemencie arraybyłby 16nieprawidłowy, ponieważ jego lokalizacja jest błędna 12.

Thalaivar
źródło
-2

Nazwa tablicy jest stałym wskaźnikiem wskazującym na adres bazowy. Kiedy używasz arr [i], kompilator przetwarza go jako * (arr + i). Ponieważ zakres int wynosi od -128 do 127, kompilator uważa, że ​​od -128 do -1 to liczby ujemne i od 0 do 128 są liczbami dodatnimi, więc indeks tablicy zawsze zaczyna się od zera.

Niks
źródło
1
Co masz na myśli , mówiąc „zakres int to -128 do 127” ? intTypu wymagane jest w zakresie przynajmniej 16-bitowego, a w większości systemów dzisiejszych czasach obsługuje 32 bitów. Myślę, że twoja logika jest błędna, a twoja odpowiedź naprawdę nie poprawia innych odpowiedzi już udzielonych przez innych ludzi. Proponuję to usunąć.
Jonathan Leffler