索引付けが「C」でゼロから始まるのはなぜですか?


154

配列のインデックス付けがCではゼロで始まり、1ではないのはなぜですか?


7
それはすべてポインタについてです!
medopal


3
ポインタ(配列)は、メモリ方向であり、ポインタ(配列)の最初の要素が0に等しいオフセットものであるので、インデックスは、そのメモリ方向のオフセットされた
D33pN16h7

3
@drhirschは、オブジェクトのセットを数えるとき、オブジェクトを指して「1」と言うからです。
phoog

1
アメリカ人は、建物の1階から2階までを数えます。ゼロ(地上階)から、英国のカウント、一階まで移動し、その後、二階、など
ジョナサン・レフラー

回答:


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ポインターのサイズではなく、配列オブジェクトのサイズを生成します。
キーストンプソン

あなたは明らかに@KeithThompsonのコメントに反応しなかったが、私はあなたにもっと攻撃的なコースを使いたい:「Cでは、配列の名前は本質的にポインタであり、メモリ位置への参照である」-いいえ、それはそうではない。少なくとも一般的な観点ではありません。完璧な答えは、インデックスの開始として0が重要である方法で質問に答えますが、最初の文は明らかに正しくありません。配列は常に最初の要素へのポインタに減衰するわけではありません。
RobertSは

C標準、(C18)、6.3.2.1 / 4からの引用: " 演算子または単項演算のオペランドsizeof、または&配列の初期化に使用される文字列リテラル、タイプ" array "の式を除くof type "は、配列オブジェクトの最初の要素を指し、左辺値ではないタイプ" pointer to type "の式に変換されます。配列オブジェクトにレジスターストレージクラスがある場合、動作は未定義です。 "
RobertSはMonicaをサポートしていますチェリオ

また、この減衰は、ここで提案されているよりも「暗黙的」または「正式」な方法で発生します。関連するメモリ内のポインタオブジェクトへの減衰はありません。これはこの質問の目的です:ポインター減衰への配列はポインターオブジェクトに変更されますか?-完全に正解になるように回答を編集してください。
RobertSは

103

この質問は1年以上前に投稿されましたが、これは...


上記の理由について

一方でダイクストラの記事(以前今-削除で参照答えは)数学的な観点から理にかなって、それが関連するようではありません、それはプログラミングに来るとき。

言語仕様とコンパイラ設計者による決定は、コンピュータシステム設計者が0からカウントを開始するという決定に基づいています。


考えられる理由

ダニー・コーエンによる「平和のための嘆願」から引用。

基数bの場合、最初のb ^ N の負でない整数は、番号付けが0から始まる場合にのみ、正確にN桁(先行ゼロを含む)で表されます。

これは非常に簡単にテストできます。base-2では、2^3 = 8 8番目の数値は次のとおりです。

  • 8(バイナリ:1000)1からカウントを開始した場合
  • 7(バイナリ:111)0からカウントを開始した場合

1113ビットを使用して表現できます1000が、追加のビット(4ビット)が必要になります。


これはなぜ関連があるのですか

コンピュータのメモリアドレスには2^NNビットでアドレス指定されたセルがあります。ここで、1から数え始めると、2^NセルにはN+1アドレス行が必要になります。正確に1つのアドレスにアクセスするには、追加ビットが必要です。(1000上記の場合)。それを解決する別の方法は、最後のアドレスにアクセスできないままにして、Nアドレス行を使用することです。

どちらも次の最適解ではありません。カウントを0から開始すると、正確にN住所行を使用してすべての住所にアクセスできるようになるからです。


結論

カウントを開始するという決定0は、その上で実行されているソフトウェアを含むすべてのデジタルシステムに浸透しました。これにより、基になるシステムが解釈できるものにコードを簡単に変換できるようになるためです。そうでない場合は、アレイへのアクセスごとに、マシンとプログラマの間で1つの不要な変換操作が発生します。コンパイルが簡単になります。


論文からの引用:

ここに画像の説明を入力してください


2
彼らがビット0を削除した直後の場合はどうなりますか?8番目の数字はまだ111です...
DanMatlin

2
あなたは実際にそれに合うように基本的な算術の修正を提案していますか?私たちが今日持っているものは、はるかに優れたソリューションだと思いませんか?
Anirudh Ramanathan 2013

数年後、私の2 cntの価値。私の経験(プログラミングの約35年)では、なんらかの形でのモジュロまたはモジュラー加算演算が驚くほど頻繁に発生します。ゼロベースの場合、シーケンスの次は(i + 1)%nですが、ベース1の場合は(i-1)%n)+1になるため、0ベースが好ましいと思います。これは数学やプログラミングで頻繁に発生します。多分それは私または私が働いている分野だけです。
nyholku

すべての正当な理由はありますが、はるかに単純だと思います。初期のコンパイラとa[b]同じよう*(a+b)に実装されました。今日でも、の2[a]代わりに書くことができますa[2]。インデックスは0から開始しなかった場合、さてa[b]に変わるでしょう*(a+b-1)。これは、CPUに0の代わりに2つの追加を必要とし、速度が半分になります。明らかに望ましくない。
Goswin von Brederlow

1
8つの州が必要だからといって、8の州が必要なわけではありません。私の家での光スイッチは、彼らが数2.表すものではありません、なぜ今まで疑問にせずに、「ライトオフ」の状態、「上の光」を表現するために満足している
Spyryto

27

0は、配列の先頭へのポインタから配列の最初の要素までの距離だからです。

検討してください:

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

0にアクセスするには、次のようにします。

foo[0] 

しかしfooはポインタに分解され、上記のアクセスにはそれにアクセスする類似のポインタ演算方法があります

*(foo + 0)

最近のポインター演算はそれほど頻繁には使用されていません。ただし、アドレスを取得してXの「int」をその開始点から遠ざけるのは便利な方法でした。もちろん、あなたが今いる場所に留まりたいなら、0を足すだけです!


23

0ベースのインデックスでは...

array[index]

...として実装される...

*(array + index)

インデックスが1から始まる場合、コンパイラーは以下を生成する必要があり*(array + index - 1)ます。この "-1"はパフォーマンスを低下させます。


4
あなたは興味深い点を持ち出します。パフォーマンスを低下させる可能性があります。しかし、パフォーマンスヒットは、開始インデックスとして0の使用を正当化するために重要でしょうか?疑わしい。
FirstName LastName

3
@FirstNameLastName 1ベースのインデックスは0ベースのインデックスに比べて利点はありませんが、パフォーマンスは(わずかに)低くなります。これは、ゲインがどれほど「小さい」かに関係なく、0ベースのインデックスを正当化します。1ベースのインデックスがいくつかの利点を提供したとしても、利便性よりパフォーマンスを選択することはC ++の精神にあります。C ++は、パフォーマンスの最後のすべてが重要であるコンテキストで使用されることがあり、これらの「小さな」ものはすぐに追加されます。
ブランコディミトリエビッチ

はい、私は小さなことが合算され、時には大きなものになることを理解しています。たとえば、年間$ 1はそれほど多くのお金ではありません。しかし、20億人が寄付すれば、人類のために多くの利益をもたらすことができます。パフォーマンスの低下を引き起こす可能性のあるコーディングの同様の例を探しています。
FirstName LastName 2013年

2
1を引くのではなく、ベースアドレスとして配列1のアドレスを使用する必要があります。コンパイラで行ったことは、私がかつて取り組んだことです。これにより、ランタイムの減算が不要になります。コンパイラーを作成する場合、これらの追加の命令は非常に重要です。コンパイラーは、数千のプログラムを生成するために使用されます。各プログラムは数千回使用される可能性があり、その余分な1命令はn二乗ループ内の複数の行で発生する可能性があります。何十億もの無駄なサイクルを追加する可能性があります。
progrmr 2013年

コンパイル後はパフォーマンスに悪影響を与えることはありません。最終的にはマシンコードに変換されるため、ビルド時間が少し増えるだけです。コンパイラの設計者に害を与えるだけです。
Hassaan Akbar

12

コンパイラとリンカがよりシンプルになった(書くのが簡単になった)ため。

参照

「...アドレスとオフセットによってメモリを参照することは、事実上すべてのコンピュータアーキテクチャ上のハードウェアで直接表されるため、このCの設計の詳細により、コンパイルが容易になります。」

そして

「...これにより、実装が簡単になります...」


1
+1反対票を投じた理由がわかりません。質問に直接答えることはできませんが、0ベースのインデックス付けは人や数学者にとって自然なことではありません。これが行われる唯一の理由は、実装が論理的に一貫している(シンプル)ためです。
phkahler、2011

4
@phkahler:エラーは作者と配列のインデックスをインデックスとして呼び出す言語にあります。これをオフセットと考えると、0ベースは素人にも自然になります。時計を考えると、最初の1分は00:01ではなく00:00と書かれていますね。
ライライアン

3
+1-これがおそらく最も正しい答えです。CはDjikistras論文よりも古く、初期の「0から始まる」言語の1つです。Cは「高レベルのアセンブラー」としての生活を開始し、K&Rは、通常、ベースアドレスとゼロから始まるオフセットを持つ通常のアセンブラーで行われた方法に忠実に従うことを望んでいた可能性があります。
Jamesアンダーソン、

私は、なぜ0ベースが使用されたのか、どちらがより良いのかという問題だと思いました。
progrmr 2011

2
私は反対投票しませんが、ベース上でprogrmrがコメントしたように、ベースの実行時間に関係なく配列アドレスを調整することでベースに対処でき、これはコンパイラーまたはインタープリターで実装するのは簡単です。 。IIRCのインデックス作成に任意の範囲を使用できるWitness Pascal、25年になります;)
nyholku 2018

5

配列インデックスは常にゼロから始まりますarr[i] = *(arr+i)。ベースアドレスが2000であると仮定します。さてif i= 0、これは*(2000+0)がベースアドレスまたは配列の最初の要素のアドレスに等しいことを意味します。このインデックスはオフセットとして扱われるため、デフォルトのインデックスはゼロから始まります。


5

同じ理由で、水曜日で誰かが水曜日までの日数を尋ねた場合、1ではなく0と答え、水曜日で誰かが木曜日までの日数を尋ねた場合、2ではなく1と答えます。


6
あなたの答えは単なる意見の問題のようです。
heltonbiker、2011

6
まあ、それがインデックス/オフセットの追加を機能させるものです。たとえば、「今日」が0で「明日」が1の場合、「明日の明日」は1 + 1 = 2です。しかし、「今日」が1で「明日」が2の場合、「明日の明日」は2 + 2ではありません。配列では、この現象は、配列の部分範囲をそれ自体が配列と見なしたいときに発生します。
R .. GitHub ICEのヘルプの停止

7
3つのもののコレクションを「3つのもの」と呼び、1、2、3に番号を付けることは、欠陥ではありません。最初のオフセットからオフセットしてそれらに番号を付けることは、数学においても自然ではありません。数学でゼロからインデックスを作成するのは、多項式にゼロ乗(定数項)などを含める場合のみです。
phkahler、2011

9
Re:「0ではなく1から始まる配列の番号付けは、数学的思考に深刻な欠陥がある人のためのものです。」CLRの「アルゴリズム入門」の私の版では、1ベースの配列インデックスを使用しています。著者が数学的な思考に欠陥があるとは思わない。
RexE、2011

いいえ、7番目はインデックス6、つまり最初のものから6ポジション離れていると思います。
R .. GitHub ICE HELPING ICEの停止

2

私がゼロベースの番号付けについて読んだ最もエレガントな説明は、値がナンバーラインのマークされた場所ではなく、それらの間のスペースに格納されているという観察です。最初のアイテムは0と1の間に保存され、次は1と2の間に保存されます。N番目のアイテムはN-1とNの間に保存されます。アイテムの範囲は、両側の数字を使用して記述できます。個々のアイテムは、慣例により、その下の番号を使用して説明されています。範囲(X、Y)が指定されている場合、以下の番号を使用して個々の番号を識別することは、算術を使用せずに最初の項目を識別できることを意味します(項目X)、Yから1を引いて最後の項目(Y -1)。上記の番号を使用してアイテムを識別すると、範囲内の最後のアイテム(アイテムY)を識別しやすくなります。

それらの上にある数に基づいてアイテムを識別することは恐ろしいことではありませんが、範囲(X、Y)の最初のアイテムをXの上にあるものとして定義すると、通常、下のアイテム(X + 1)。


1

技術的な理由は、配列のメモリ位置へのポインタが配列の最初の要素の内容であることに由来する可能性があります。インデックスを1にしてポインタを宣言すると、プログラムは通常、その値に1をポインタに追加して、当然のことではないコンテンツにアクセスします。


1

1ベースのマトリックスのX、Y座標を使用してピクセルスクリーンにアクセスしてみます。式は完全に複雑です。なぜ複雑なのですか?X、Y座標を1つの数値、つまりオフセットに変換することになるからです。X、Yをオフセットに変換する必要があるのはなぜですか?それは、メモリセル(アレイ)の連続したストリームとして、メモリがコンピュータ内部でどのように編成されているかです。コンピューターはどのようにアレイセルを処理しますか?オフセットの使用(最初のセルからの変位、ゼロベースのインデックスモデル)。

したがって、コードのある時点で、1ベースの数式を0ベースの数式に変換する必要があります(またはコンパイラが必要です)。これが、コンピュータがメモリを処理する方法です。


1

サイズ5の配列を作成するとします
。array[5] = [2,3,5,9,8]

配列の最初の要素が位置100

を指し、インデックス付けが1ではなく、 0. 整数のサイズは4ビットな ので

、インデックスを使用して最初の要素の場所を見つける必要があります
(最初の要素の場所は100であることを忘れないでください)。したがって、インデックス1を考えると、位置は サイズになります。 index(1)* integer(4)のサイズ= 4 なので、実際に表示される位置は 100 + 4 = 104です







最初の場所は100であったので、真実ではない
、それは104になっていない100を指している必要があり
、これは間違っている

今、私たちは0からインデックスをとっていると仮定し
、その後
第一の要素の位置があるべき
整数のインデックス(0)のサイズ*サイズ(4)= 0

したがって、->
最初の要素の位置は100 + 0 = 100で

あり、
これが要素の実際の位置でした。これが、インデックス付けが0から始まる理由です。

それがあなたの要点を明確にすることを願っています。


1

私はJavaの出身です。私はこの質問への回答を下の図に示しました

主な手順:

  1. 参照の作成
  2. アレイのインスタンス化
  3. 配列へのデータの割り当て

  • また、配列がインスタンス化されたときも注意してください。値を割り当てるまで、デフォルトですべてのブロックにゼロが割り当てられます。
  • 最初のアドレスが参照を指しているため、配列はゼロで始まります(つまり、画像のX102 + 0)。

ここに画像の説明を入力してください

:画像に表示されているブロックはメモリ表現です


0

まず、「配列の名前自体に配列の最初の要素のアドレスが含まれている」ため、配列は内部的にポインタと見なされることを知る必要があります。

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

配列がアドレス100から始まることを考慮して、要素の最初の要素がアドレス100になり、2番目が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から始まります。

参照:詳細はすべて「ブライアン・カーニングハンとデニス・リッチーによるCプログラミング言語」の本に書かれています


0

配列では、インデックスは開始要素からの距離を示します。したがって、最初の要素は開始要素から0の距離にあります。そのため、配列は0から始まります。


0

これは、配列内でaddress右を指す必要があるためelementです。以下の配列を想定しましょう:

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

次に、アドレスの開始とbeの12サイズについて考えてみましょう。element4 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、では技術的に私たちの最初の要素のアドレスarrayになり16、それの場所があるように間違っています12


-2

配列名は、ベースアドレスを指す定数ポインターです。arr[i]を使用すると、コンパイラーはそれを*(arr + i)として操作します。intの範囲は-128〜127であるため、コンパイラーは-128〜-1が負の数と0〜128は正の数であるため、配列のインデックスは常にゼロから始まります。


1
'int range is -128 to 127'とはどういう意味ですか?intタイプは、少なくとも16ビットの範囲をサポートするために必要な、そしてほとんどのシステムで、これらの日は、32ビットをサポートしています。私はあなたの論理に欠陥があると思います、そしてあなたの答えは本当に他の人々によってすでに提供されている他の答えを改善しません。これを削除することをお勧めします。
Jonathan Leffler、2015
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.