'C'에서 색인이 0으로 시작되는 이유는 무엇입니까?


154

왜 배열의 색인 생성이 1이 아닌 C에서 0으로 시작합니까?


7
포인터에 관한 모든 것!
medopal


3
포인터 (배열)는 메모리 방향이고 인덱스는 해당 메모리 방향의 오프셋이므로 포인터 (배열)의 첫 번째 요소는 오프셋이 0 인 요소입니다.
D33pN16h7

3
@drhirsch는 일련의 객체를 계산할 때 객체를 가리키고 "하나"라고 말함으로써 시작합니다.
phoog

1
미국인들은 1 층에있는 건물의 층 (층)을 계산합니다. 영국은 0에서 1 층으로, 1 층으로 올라간 다음 2 층으로 올라갑니다.
Jonathan Leffler

답변:


116

C에서 배열의 이름은 기본적으로 포인터 (그러나 주석 참조)이며 메모리 위치에 대한 참조이므로 표현식 array[n]n시작 요소에서 떨어진 메모리 위치 요소를 나타냅니다. 이는 인덱스가 오프셋으로 사용됨을 의미합니다. 배열의 첫 번째 요소는 해당 배열이 참조하는 메모리 위치 (0 개의 요소)에 정확히 포함되므로로 표시되어야합니다 array[0].

더 많은 정보를 위해서:

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


20
배열의 이름은 배열의 이름입니다. 일반적인 오해와는 달리 배열은 어떤 의미로도 포인터 가 아닙니다 . 배열 표현식 (예 : 배열 객체의 이름)은 일반적으로 항상 그런 것은 아니지만 첫 번째 요소에 대한 포인터로 변환됩니다. 예 : sizeof arr포인터의 크기가 아닌 배열 객체의 크기를 산출합니다.
Keith Thompson

당신은 분명히 @KeithThompson의 의견에 반응하지 않았지만, 당신은 더 공격적인 코스를 사용하고 싶습니다 : " C에서, 배열의 이름은 본질적으로 포인터, 메모리 위치에 대한 참조입니다 -"아니요, 그렇지 않습니다 . 적어도 일반적인 관점에서는 아닙니다. 귀하의 답변은 색인 시작으로 0이 중요한 방식으로 질문에 완벽하게 답변하지만 첫 문장은 정확하지 않습니다. 배열이 항상 첫 번째 요소에 대한 포인터로 붕괴되는 것은 아닙니다.
RobertS는 Monica Cellio

C 표준, (C18), 6.3.2.1/4에서 인용 : " 연산자 의 피연산자 sizeof이거나 단항 &연산자이거나 배열을 초기화하는 데 사용되는 문자열 리터럴 인 경우"배열 유형의 표현식 "of type"은 배열 객체의 초기 요소를 가리키고 lvalue가 아닌 "pointer to type"유형의 표현식으로 변환됩니다. 배열 객체에 레지스터 스토리지 클래스가 있으면 동작이 정의되지 않습니다. "
RobertS는 Monica를 지원합니다

또한이 붕괴는 여기에 제안 된 것보다 "내재적"또는 "공식적으로"발생합니다. 관련된 메모리에 포인터 객체에 대한 붕괴가 없습니다. 이것은이 질문의 대상입니다. 포인터에 대한 배열이 포인터 객체로 변경 되었습니까? -답변을 완전히 수정하십시오.
RobertS는

103

이 질문은 1 년 전에 게시되었지만 여기에갑니다 ...


위의 이유에 대해

하지만 다 익스트라의 기사 (이전에 지금은 삭제에서 참조 대답은 ) 수학적 관점에서 의미가 있습니다, 그것은 관련으로하지 않습니다 이 프로그램에 올 때.

언어 사양 및 컴파일러 디자이너가 내린 결정은 컴퓨터 시스템 디자이너가 0부터 카운트를 시작하기로 한 결정을 기반으로합니다.


가능한 이유

대니 코헨 (Danny Cohen)의 평화대한 간청 에서 인용 .

모든 b의 경우, 첫 번째 b ^ N 음이 아닌 정수는 번호가 0에서 시작하는 경우에만 정확히 N 자리 (앞의 0 포함)로 표시됩니다.

이것은 매우 쉽게 테스트 할 수 있습니다. base-2에서 2^3 = 8 8 번째 숫자는 다음과 같습니다.

  • 1부터 카운트를 시작하면 8 (바이너리 : 1000)
  • 0부터 카운트를 시작하면 7 (바이너리 : 111)

1113비트 를 사용하여 표현할 수 있지만 1000추가 비트 (4 비트)가 필요합니다.


이것이 왜 관련이 있습니까?

컴퓨터 메모리 주소에는 비트 단위의 2^N셀 이 있습니다 N. 이제 1부터 계산을 시작하면 2^N셀에 N+1주소 줄 이 필요 합니다. 정확히 하나의 주소에 액세스하려면 추가 비트가 필요합니다. ( 1000위의 경우). 이를 해결하는 또 다른 방법은 마지막 주소에 액세스 할 수 없게하고 N주소 행을 사용하는 것 입니다.

둘 다 최적의N 주소 라인을 사용하여 모든 주소에 액세스 할 수있는 시작 카운트 0에 비해 차선책입니다 .


결론

에서 시작하기로 결정한 0이후에는 실행중인 소프트웨어를 포함한 모든 디지털 시스템에 코드가 침투 하여 기본 시스템이 해석 할 수있는 코드로 코드를 변환하는 것이 더 간단 해졌습니다. 그렇지 않은 경우, 머신과 프로그래머 사이에서 모든 어레이 액세스에 대해 불필요한 변환 조작이 하나 있습니다. 컴파일이 쉬워집니다.


논문에서 인용 :

여기에 이미지 설명을 입력하십시오


2
그들이 비트 0을 제거했다면 어떻게됩니까? 그렇다면 8 번째 숫자는 여전히 111 일 것입니다.
DanMatlin

2
실제로 기본 산술의 수정을 제안하고 있습니까? 오늘날 우리가 가지고있는 것이 훨씬 더 나은 솔루션이라고 생각하지 않습니까?
Anirudh Ramanathan

몇 년 후 나의 2 cnt 가치. 내 경험 (~ 35 년의 프로그래밍)에서 모듈 식 또는 모듈 식 추가 연산이 한 형태 또는 다른 형태로 나타나는 경우가 종종 있습니다. 염기가 0 인 경우 다음 순서는 (i + 1) % n이지만 염기 1의 경우 (i-1) % n) +1이되므로 0을 기준으로하는 것이 좋습니다. 이것은 수학과 프로그래밍에서 자주 발생합니다. 어쩌면 그것은 나 또는 내가 일하는 분야 일 것입니다.
nyholku

모든 좋은 이유 a[b]는 훨씬 간단하다고 생각하지만 *(a+b)초기 컴파일러에서 와 같이 구현되었습니다 . 오늘도 2[a]대신에 글을 쓸 수 있습니다 a[2]. 이제 인덱스가 0에서 시작하지 않으면 a[b]로 바뀝니다 *(a+b-1). 이것은 0 대신에 CPU에 2 개의 추가를 요구했을 것인데, 이는 속도의 절반을 의미합니다. 분명히 바람직하지 않습니다.
Goswin von Brederlow 2016 년

1
8 개의 주를 원한다고해서 8 개의 주가 있어야한다는 의미는 아닙니다. 내 집의 전등 스위치는 왜 숫자 2를 나타내지 않는지 궁금하지 않고 "점등", "점등"상태를
나타내게되어 기쁘다

27

왜냐하면 0은 배열의 포인터에서 배열의 첫 번째 요소까지의 거리입니다.

치다:

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

0에 액세스하려면 다음을 수행하십시오.

foo[0] 

그러나 foo는 포인터로 분해되고 위의 액세스는 포인터에 액세스하는 유사한 포인터 산술 방법을 갖습니다.

*(foo + 0)

요즘 포인터 산술은 자주 사용되지 않습니다. 그러나 되돌아 가면 주소를 가져 와서 X "ints"를 시작점에서 멀어지게하는 편리한 방법이었습니다. 물론 현재 위치를 유지하려면 0을 추가하십시오!


23

0 기반 인덱스가 허용하기 때문에 ...

array[index]

...로 구현하려면 ...

*(array + index)

인덱스가 1 기반 인 경우 컴파일러는 다음을 생성해야합니다. *(array + index - 1)이 "-1"은 성능을 저하시킵니다.


4
당신은 흥미로운 지적을합니다. 성능이 저하 될 수 있습니다. 그러나 시작 인덱스로 0을 사용하는 것을 정당화하려면 성능 히트가 중요합니까? 나는 그것을 의심한다.
이름 성

3
@FirstNameLastName 1 기반 인덱스는 0 기반 인덱스보다 이점이 없지만 성능이 약간 떨어집니다. 게인이 "작은"정도에 관계없이 0 기반 인덱스를 정당화합니다. 1 기반 인덱스가 어떤 이점을 제공하더라도 편의상 성능을 선택하는 것이 C ++의 정신에 있습니다. C ++은 때때로 모든 마지막 성능이 중요한 상황에서 사용되며 이러한 "작은"것들이 빠르게 합쳐질 수 있습니다.
Branko Dimitrijevic

예, 나는 작은 것들이 더해질 수 있고 때로는 큰 것이 될 수 있음을 이해합니다. 예를 들어, 연간 $ 1은 많은 돈이 아닙니다. 그러나 20 억 명의 사람들이 기부하면 인류를 위해 많은 일을 할 수 있습니다. 성능이 저하 될 수있는 코딩의 비슷한 예를 찾고 있습니다.
이름 성

2
1을 빼지 않고 배열 -1의 주소를 기본 주소로 사용해야합니다. 우리가 한 번 컴파일러에서 한 일. 런타임 뺄셈을 제거합니다. 컴파일러를 작성할 때 이러한 추가 명령어는 매우 중요합니다. 컴파일러는 수천 개의 프로그램을 생성하는 데 사용되며 각 프로그램은 수천 번 사용될 수 있으며 n 제곱 루프 내부의 여러 줄에서 추가 1 명령이 발생할 수 있습니다. 최대 수십억 개의 낭비되는 사이클을 추가 할 수 있습니다.
progrmr

컴파일 된 후에는 성능이 저하되지 않으며, 결국 빌드 코드가 기계 코드로 변환되므로 빌드 시간이 짧아 지므로 컴파일러 설계자에게만 피해를줍니다.
Hassaan Akbar

12

컴파일러와 링커를 더 간단하게 만들 수 있기 때문입니다 (작성하기 쉬움).

참고 :

"... 주소와 오프셋으로 메모리를 참조하는 것은 거의 모든 컴퓨터 아키텍처의 하드웨어에서 직접 표현되므로 C의이 디자인 세부 사항으로 인해 컴파일이 더 쉬워집니다."

"... 이것은 더 간단한 구현을 만듭니다 ..."


1
+1 왜 하락 투표인지 모르겠습니다. 이 질문에 직접 대답하지는 않지만 0 기반 색인 생성은 사람이나 수학자에게는 자연스럽지 않습니다. 구현이 논리적으로 일관성이 있기 때문에 (단순한) 유일한 이유입니다.
phkahler

4
@phkahler : 오류는 배열 인덱스를 인덱스로 호출하는 저자와 언어에 있습니다. 오프셋으로 생각하면 0 기반은 평신도에게도 자연스러워집니다. 시계를 고려하면 첫 번째 분은 00:01이 ​​아니라 00:00으로 쓰여집니다.
Lie Ryan

3
+1-아마도 가장 정답 일 것입니다. C는 지키 스트 라스 (Jijikistras) 논문보다 이전의 언어였으며 가장 이른 "0에서 시작"언어 중 하나였다. C는 "높은 수준의 어셈블러로"삶을 시작했으며, K & R은 기본 주소와 오프셋이 0으로 시작하는 어셈블러에서 수행 한 방식에 가깝게 고수하고자했습니다.
James Anderson

문제는 0 기반이 왜 사용 되었는 지, 더 낫지는 않다고 생각했습니다.
progrmr

2
나는 downvote하지는 않지만 기본 실행 시간에 관계없이 배열 주소를 조정하여 기본을 언급 할 수있는 progrmr을 처리 할 수 ​​있으므로 컴파일러 또는 인터프리터에서 구현하는 것이 쉽지 않으므로 실제로는 간단한 구현이 아닙니다. . IIRC를 색인하기 위해 모든 범위를 사용할 수있는 Witness Pascal, 25 년이 지났습니다.)
nyholku

5

배열 인덱스는 항상 0으로 시작합니다. 기본 주소가 2000이라고 가정합니다 arr[i] = *(arr+i). 이제이 if i= 0의미 *(2000+0는 배열에서 첫 번째 요소의 기본 주소 또는 주소와 같습니다. 이 인덱스는 오프셋으로 취급되므로 deaault 인덱스는 0부터 시작합니다.


5

같은 이유로 수요일이고 누군가가 수요일까지 며칠을 물으면 1이 아니라 0이라고 말하고 수요일이 며칠 동안 목요일까지 며칠을 물으면 2가 아니라 1을 말합니다.


6
당신의 대답은 단지 의견의 문제인 것 같습니다.
heltonbiker

6
글쎄, 인덱스 / 오프셋 추가가 작동하는 이유입니다. 예를 들어 "오늘"이 0이고 "내일"이 1 인 경우 "내일의 내일"은 1 + 1 = 2입니다. 그러나 "오늘"이 1이고 "내일"이 2 인 경우 "내일의 내일"은 2 + 2가 아닙니다. 배열에서이 현상은 배열의 하위 범위를 자체 배열로 간주하려고 할 때마다 발생합니다.
R .. GitHub 중지 지원 얼음

7
3 가지를 "3 가지"모음으로 부르고 1,2,3으로 번호를 매기는 것은 결함이 아닙니다. 처음부터 오프셋으로 번호를 매기는 것은 수학에서도 자연스럽지 않습니다. 수학에서 0에서 색인을 생성하는 유일한 시간은 다항식에 0의 제곱 (일정 항)과 같은 것을 포함하려는 경우입니다.
phkahler

9
Re : "0이 아닌 1로 시작하는 숫자 배열은 심각한 수학적 사고력이 부족한 사람들을위한 것입니다." CLR의 "Introduction to Algorithms"에디션은 1 기반 배열 인덱싱을 사용합니다. 저자들이 수학적 사고에 결함이 있다고 생각하지 않습니다.
RexE

아니요, 일곱 번째 숫자는 인덱스 6 또는 첫 번째 숫자에서 6 위치에 있습니다.
R .. GitHub 중지 지원 얼음

2

0에서 시작하는 번호 매기기에 대해 읽은 가장 우아한 설명은 값이 숫자 줄의 표시된 위치가 아니라 그 사이의 공백에 저장된다는 관찰입니다. 첫 번째 항목은 0과 1 사이에 저장되고 다음 항목은 1과 2 사이에 저장됩니다. N 번째 항목은 N-1과 N 사이에 저장됩니다. 항목의 범위는 양쪽의 숫자를 사용하여 설명 할 수 있습니다. 개별 항목은 일반적으로 아래의 숫자를 사용하여 설명됩니다. 범위 (X, Y)가 주어지면 아래 숫자를 사용하여 개별 숫자를 식별하면 산술을 사용하지 않고 첫 번째 항목을 식별 할 수 있지만 (항목 X) 마지막 항목을 식별하려면 Y에서 하나를 빼야합니다 (Y -1). 위의 번호를 사용하여 항목을 식별하면 범위의 마지막 항목을 쉽게 식별 할 수 있습니다 (항목 Y 임).

위의 숫자를 기준으로 항목을 식별하는 것은 끔찍하지는 않지만 범위 (X, Y)에서 첫 번째 항목을 X 위의 항목으로 정의하면 일반적으로 아래 항목으로 정의하는 것보다 더 잘 작동합니다 (X + 1).


1

기술적 이유는 배열의 메모리 위치에 대한 포인터가 배열의 첫 번째 요소의 내용이라는 사실에서 비롯 될 수 있습니다. 1의 색인으로 포인터를 선언하면 프로그램은 일반적으로 원하는 값이 아닌 내용에 액세스하기 위해 포인터에 1의 값을 추가합니다.


1

1 기반 행렬에서 X, Y 좌표를 사용하여 픽셀 화면에 액세스하십시오. 공식은 완전히 복잡합니다. 왜 복잡한가? X, Y 좌표를 오프셋 인 하나의 숫자로 변환하게됩니다. 왜 X, Y를 오프셋으로 변환해야합니까? 그것이 메모리가 연속적인 메모리 셀 스트림으로 컴퓨터 내부에서 구성되는 방식이기 때문입니다. 컴퓨터가 배열 셀을 처리하는 방법 오프셋 사용 (제로 기반 인덱싱 모델 인 첫 번째 셀로부터의 변위).

따라서 코드의 어느 시점에서 1 기반 수식을 0 기반 수식으로 변환하는 데 필요한 (또는 컴파일러 필요) 컴퓨터가 메모리를 처리하는 방식이기 때문입니다.


1

우리는 크기 5의 배열을 만들려면 가정
int 배열 [5] = [2,3,5,9,8]

배열의 첫 번째 요소는 위치 (100)에 놓고 보자

우리가 1없는에서 인덱싱 시작을 생각해 보자 0.

이제 정수의 크기가 4 비트 이므로 인덱스의 도움으로 첫 번째 요소의 위치를 ​​찾아야합니다
(1 번째 요소의 위치는 100임을 기억하십시오) 따라서 인덱스 1을 고려하면 위치는 크기가됩니다 of index (1) * integer (4) = 4의 크기 이므로 실제 위치는 다음과 같습니다.






100 + 4 = 104입니다.

이것은 초기 위치가 100에 있었기 때문에 사실이 아닙니다.
104
가 아닌 100을 가리켜 야합니다 . 이것은

이제 0에서 인덱싱을 취한
다음
첫 번째 요소의 위치
는 index (0) * integer의 크기 여야합니다 (4) = 0

따라서->
첫 번째 요소의 위치는 100 + 0 = 100

이며 이는 요소의 실제 위치이므로
색인이 0에서 시작하는 이유입니다.

나는 그것이 당신의 요점을 분명히하기를 바랍니다.


1

저는 Java 배경을 가지고 있습니다. 나는 아래의 다이어그램에서이 질문에 대한 답을 제시했다.

주요 단계 :

  1. 참조 작성
  2. 배열의 인스턴스화
  3. 배열에 데이터 할당

  • 배열이 방금 인스턴스화되었을 때에도주의하십시오 .. 값을 할당 할 때까지 기본적으로 모든 블록에 0이 할당됩니다.
  • 첫 번째 주소가 참조를 가리 키므로 배열은 0으로 시작합니다 (그림의 i : e-X102 + 0).

여기에 이미지 설명을 입력하십시오

참고 : 이미지에 표시된 블록은 메모리 표현입니다


0

우선 "배열 이름 자체에 배열의 첫 번째 요소 주소가 포함되어 있기 때문에"배열이 내부적으로 포인터로 간주된다는 것을 알아야합니다.

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

배열이 주소 100에서 시작한다고 가정하면 요소 첫 번째 요소는 주소 100에 있고 두 번째는 104에 있습니다. 배열 색인이 1에서 시작하면

arr[1]:-

이것은 포인터 식에서 다음과 같이 쓸 수 있습니다.

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

int의 크기가 4 바이트라고 생각하십시오.

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

아시다시피 배열 이름에는 첫 번째 요소의 주소가 포함되므로 arr = 100입니다.

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

그것은,

arr[1] = 4;

이 표현으로 인해 공식 첫 번째 요소 인 주소 100의 요소에 액세스 할 수 없습니다.

이제 배열 인덱스가 0에서 시작한다고 가정하십시오.

arr[0]:-

이것은 다음과 같이 해결됩니다

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

이제 배열 이름에 첫 번째 요소의 주소가 포함되어 있다는 것을 알고 있습니다.

arr[0] = *(100);

올바른 결과를 제공합니다

arr[0] = 5;

따라서 배열 인덱스는 항상 c에서 0부터 시작합니다.

참조 : 모든 세부 사항은 "Brian Kerninghan 및 Dennis ritchie의 C 프로그래밍 언어"책에 작성되었습니다.


0

배열에서 색인은 시작 요소와의 거리를 알려줍니다. 따라서 첫 번째 요소는 시작 요소와 0 거리에 있습니다. 그래서 배열이 0에서 시작하는 이유입니다.


0

배열 address에서 오른쪽을 가리켜 야하기 때문 element입니다. 아래 배열을 가정 해 봅시다.

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

우리는 이제 주소 존재의 시작 살펴 보겠습니다 12과의 크기 elementBE를 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

그것이 경우 되지 zero-based 는 기술적으로 우리의 첫 번째 요소의 주소 array16그것의 위치는 다음과 같이 잘못된 것입니다 12.


-2

배열 이름은 기본 주소를 가리키는 상수 포인터입니다. arr [i]를 사용할 때 컴파일러는이를 * (arr + i)로 조작합니다. int 범위가 -128 ~ 127이므로 컴파일러는 -128 ~ -1은 음수와 0 ~ 128은 양수이므로 배열 인덱스는 항상 0으로 시작합니다.


1
'int range is -128 to 127'은 무슨 뜻 입니까? int타입 지원 적어도 16 비트 범위에 필요한 대부분의 시스템 요즘 32 비트를 지원한다. 귀하의 논리에 결함이 있다고 생각하며 귀하의 답변은 다른 사람들이 이미 제공 한 다른 답변에서 실제로 향상되지 않습니다. 이것을 삭제하는 것이 좋습니다.
Jonathan Leffler
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.