Почему индексация начинается с нуля в 'C'?

154

Почему индексация в массиве начинается с нуля в C, а не с 1?

Nitish Pareek
источник
7
Это все о указателях!
Медопал
3
возможный дубликат массива Defend на основе нуля
dmckee --- котенок экс-модератора
3
Указатель (массив) - это направление памяти, а индекс - это смещение этого направления памяти, поэтому первый элемент указателя (массива) - это смещение, равное 0.
D33pN16h7
3
@drhirsch, потому что когда мы подсчитываем набор объектов, мы начинаем с того, что указываем на объект и говорим «один».
Фог
1
Американцы считают этажи (этажи) здания от одного на первом этаже; британцы считают с нуля (первый этаж), поднимаются на первый этаж, затем на второй этаж и т. д.
Джонатан Леффлер

Ответы:

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оператора или унарный &оператор или строковый литерал, используемый для инициализации массива, выражение с типом« массив » типа "преобразуется в выражение с типом" указатель на тип ", которое указывает на начальный элемент объекта массива и не является lvalue. Если объект массива имеет класс хранения регистров, поведение не определено. "
RobertS поддерживает Monica Cellio
Также этот распад происходит более «неявно» или «формально», чем предлагается здесь; в памяти не происходит никакого затухания объекта указателя. Это объект этого вопроса: изменился ли массив для распада указателя на объект указателя? - Пожалуйста, отредактируйте свой ответ, чтобы он был полностью правильным.
RobertS поддерживает Монику Челлио
103

Этот вопрос был опубликован более года назад, но здесь идет ...


О вышеуказанных причинах

Хотя статья Дейкстры (ранее упоминавшаяся в теперь удаленном ответе ) имеет смысл с математической точки зрения, она не так актуальна, когда речь идет о программировании.

Решение, принятое проектировщиком спецификаций языка и базирующимся на нем, основано на решении дизайнеров компьютерных систем начать отсчет с 0.


Возможная причина

Цитата из « Просьбы о мире » Дэнни Коэна.

Для любого основания b первые b ^ N неотрицательных целых чисел представлены ровно N цифрами (включая ведущие нули), только если нумерация начинается с 0.

Это можно проверить довольно легко. В базе-2 возьмем 2^3 = 8 8-е число:

  • 8 (двоичный код: 1000), если мы начнем считать с 1
  • 7 (двоичный код: 111), если мы начнем считать с 0

111может быть представлен с использованием 3битов, в то время как 1000потребуется дополнительный бит (4 бита).


Почему это актуально

Адреса памяти компьютера имеют 2^Nячейки, адресуемые Nбитами. Теперь, если мы начнем считать с 1, 2^Nячейки будут нуждаться в N+1адресных строках. Дополнительный бит необходим для доступа ровно к 1 адресу. ( 1000в приведенном выше случае.). Другой способ решить эту проблему - оставить последний адрес недоступным и использовать Nадресные строки.

Оба являются неоптимальными решениями , по сравнению с начальным счетом в 0, который будет держать все адреса доступными, используя ровно Nадресные строки!


Вывод

Решение начать отсчет с 0тех пор проникло во все цифровые системы , включая программное обеспечение, работающее на них, потому что это упрощает перевод кода в то, что может интерпретировать базовая система. Если бы это было не так, была бы одна ненужная операция перевода между машиной и программистом для каждого доступа к массиву. Это облегчает компиляцию.


Цитата из статьи:

введите описание изображения здесь

Anirudh Ramanathan
источник
2
Что если бы они только что удалили бит 0 .. тогда 8-е число все равно будет 111 ...
DanMatlin
2
Вы действительно предлагаете модификацию базовой арифметики, чтобы она вписалась? Не думаете ли вы, что у нас сегодня есть гораздо лучшее решение?
Анируд Раманатан
Спустя годы мои 2 цента стоят. По моему опыту (~ 35 лет программирования) операция модульного или модульного сложения в той или иной форме встречается на удивление часто. С нулевым основанием следующий в последовательности (i + 1)% n, но с базовым 1 он приходит (i-1)% n) +1, поэтому я думаю, что 0 на основе является предпочтительным. Это возникает в математике и программировании довольно часто. Может быть, это только я или область, в которой я работаю.
Нихолку
Хотя по всем веским причинам я думаю, что все гораздо проще: a[b]реализовано как *(a+b)в ранних компиляторах. Даже сегодня вы можете написать 2[a]вместо a[2]. Теперь, если индексы не начинаются с 0, то a[b]превратились бы в *(a+b-1). Это потребовало бы 2 добавления на процессорах времени вместо 0, что означает половину скорости. Явно не желательно.
Госвин фон Бредерлоу
1
То, что вы хотите 8 штатов, не означает, что в них должно быть число 8. Выключатели света в моем доме с удовольствием представляют состояния «свет
включен
27

Потому что 0 - это расстояние от указателя на начало массива до первого элемента массива.

Рассматривать:

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

Для доступа к 0 мы делаем:

foo[0] 

Но foo разлагается на указатель, и вышеупомянутый доступ имеет аналогичный указатель арифметического способа доступа к нему

*(foo + 0)

В наши дни арифметика указателей используется не так часто. Еще тогда, когда это был удобный способ взять адрес и убрать X «целых» из этой начальной точки. Конечно, если вы хотите просто остаться на месте, просто добавьте 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 доллар в год - это не много денег. Но если пожертвуют 2 миллиарда человек, мы сможем сделать много хорошего для человечества. Я ищу подобный пример в кодировании, который может привести к снижению производительности.
FirstName LastName
2
Вместо того, чтобы вычитать 1, вы должны использовать адрес массива-1 в качестве базового адреса. Это то, что мы сделали в компиляторе, над которым я когда-то работал. Это исключает вычитание времени выполнения. Когда вы пишете компилятор, эти дополнительные инструкции очень важны. Компилятор будет использоваться для генерации тысяч программ, каждая из которых может использоваться тысячи раз, и эта дополнительная 1 инструкция может появляться в нескольких строках внутри цикла n в квадрате. Это может добавить до миллиардов потраченных впустую циклов.
прогрмр
Нет, это не повлияет на производительность после компиляции, это только добавит небольшое время сборки, потому что в итоге оно будет переведено в машинный код. Это только повредит дизайнерам компилятора.
Хассаан Акбар
12

Потому что это сделало компилятор и компоновщик проще (легче писать).

Ссылка :

«... Ссылка на память по адресу и смещению представлена ​​непосредственно в аппаратном обеспечении практически на всех компьютерных архитектурах, поэтому эта деталь конструкции в C упрощает компиляцию»

и

«... это упрощает реализацию ...»

progrmr
источник
1
+1 Не уверен, почему проголосовали против. Хотя это не дает прямого ответа на вопрос, индексирование на основе 0 не является естественным для людей или математиков - единственная причина, по которой это делается, заключается в том, что реализация логически последовательна (проста).
phkahler
4
@phkahler: ошибка в авторах и языках, называющих индексы массивов индексами; если вы думаете об этом как о смещении, то 0-й становится естественным и для непрофессионала. Посмотрите на часы, первая минута записана как 00:00, а не 00:01, не так ли?
Ли Райан
3
+1 - это, наверное, самый правильный ответ. C до появления газеты Джикистрас и был одним из самых ранних языков «начала в 0». C начал жизнь «как ассемблер высокого уровня», и вполне вероятно, что K & R хотел придерживаться того же подхода, который был сделан в ассемблере, где у вас обычно был бы базовый адрес плюс смещение, начинающееся с нуля.
Джеймс Андерсон
Я думал, что вопрос был, почему 0 основанный был использован, а не, что лучше.
Прогрмр
2
Я не буду понижать голос, но, как прокомментировал выше progrmr, о базе можно позаботиться, отрегулировав адрес массивов, поэтому независимо от базового времени выполнения то же самое, и это тривиально для реализации в компиляторе или интерпретаторе, так что это на самом деле не упрощает реализацию , Свидетель Паскаля, где можно использовать любой диапазон для индексации IIRC, прошло уже 25 лет;)
nyholku
5

Индекс массива всегда начинается с нуля. Предположим, базовый адрес равен 2000. Теперь arr[i] = *(arr+i). Теперь if i= 0это означает *(2000+0) равен базовому адресу или адресу первого элемента в массиве. этот индекс обрабатывается как смещение, поэтому индекс по умолчанию начинается с нуля.

Амит Пракаш
источник
5

По той же причине, по которой когда среда и кто-то спрашивает вас, сколько дней до среды, вы говорите 0, а не 1, а когда это среда, и кто-то спрашивает вас, сколько дней до четверга, вы говорите 1, а не 2.

R .. GitHub ОСТАНОВИТЬ, ПОМОГАЯ ЛЕД
источник
6
Ваш ответ кажется просто вопросом мнения.
хелтонбайкер
6
Ну, это то, что делает добавление индексов / смещений работать. Например, если «сегодня» равно 0, а «завтра» равно 1, «завтрашнее завтра» равно 1 + 1 = 2. Но если «сегодня» равно 1, а «завтра» равно 2, то «завтрашнее завтра» не равно 2 + 2. В массивах это явление происходит всякий раз, когда вы хотите рассматривать поддиапазон массива как отдельный массив.
R .. GitHub СТОП ПОМОГАЕТ ЛЬДУ
7
Называть коллекцию из 3-х вещей «3-мя» и нумерацию их 1,2,3 не является недостатком. Нумерация их со смещением от первого не является естественной даже в математике. Единственный раз, когда вы индексируете от нуля в математике, это когда вы хотите включить что-то вроде нулевой степени (постоянное слагаемое) в полином.
phkahler
9
Re: «Нумерация массивов, начинающихся с 1, а не с 0, предназначена для людей с серьезным дефицитом математического мышления». В моей редакции CLR «Введение в алгоритмы» используется индексирование массивов на основе 1; Я не думаю, что у авторов есть недостаток в математическом мышлении.
RexE
Нет, я бы сказал, что седьмой находится в индексе 6, или 6 позиций от первого.
R .. GitHub ОСТАНОВИТЬ ЛЬДА
2

Самым элегантным объяснением, которое я прочитал для нумерации с нуля, является наблюдение, что значения хранятся не в отмеченных местах на числовой строке, а в промежутках между ними. Первый элемент сохраняется от нуля до единицы, следующий - от одного до двух и т. Д. N-й элемент сохраняется от N-1 до N. Диапазон элементов может быть описан с использованием чисел с любой стороны. Отдельные предметы по договоренности описываются с помощью номеров под ними. Если каждому дан диапазон (X, Y), идентификация отдельных чисел с использованием числа, указанного ниже, означает, что можно идентифицировать первый элемент без использования какой-либо арифметики (это элемент X), но нужно вычесть один из Y, чтобы идентифицировать последний элемент (Y -1). Идентификация предметов с использованием числа, указанного выше, облегчит идентификацию последнего предмета в диапазоне (это будет предмет Y),

Хотя было бы не страшно идентифицировать предметы на основе числа над ними, определение первого предмета в диапазоне (X, Y) как того, что выше X, обычно получается более приятным, чем определение его как нижнего (X +). 1).

Supercat
источник
1

Техническая причина может быть вызвана тем фактом, что указатель на область памяти массива является содержимым первого элемента массива. Если вы объявляете указатель с индексом единицы, программы обычно добавляют это значение единицы к указателю, чтобы получить доступ к содержимому, которое, конечно, не то, что вам нужно.

обкрадывать
источник
1

Попробуйте получить доступ к пиксельному экрану, используя координаты X, Y на основе матрицы 1. Формула очень сложная. Почему это сложно? Поскольку вы в конечном итоге преобразуете координаты X, Y в одно число - смещение. Почему вам нужно конвертировать X, Y в смещение? Потому что так организована память внутри компьютеров, как непрерывный поток ячеек памяти (массивов). Как компьютеры работают с элементами массива? Использование смещений (смещения из первой ячейки, модель индексации с нуля).

Поэтому в какой-то момент кода, который вам нужен (или необходим компилятор), нужно преобразовать формулу с 1 базой в формулу с 0, потому что именно так компьютеры работают с памятью.

Джанлука Геттини
источник
1

Предположим, что мы хотим создать массив размером 5
int array [5] = [2,3,5,9,8],

пусть 1-й элемент массива направлен в местоположение 100,

и пусть мы считаем, что индексирование начинается с 1, а не с 0.

Теперь мы должны найти местоположение 1-го элемента с помощью индекса
(помните, что расположение 1-го элемента равно 100),

поскольку размер целого числа 4-битный,
поэтому -> с учетом индекса 1 позиция будет
размером of index (1) * размер целого числа (4) = 4,
поэтому фактическая позиция, которую он нам покажет,

100 + 4 = 104

что неверно, потому что начальное местоположение было в 100.
это должно указывать на 100, а не на 104,
это неправильно,

теперь предположим, что мы взяли индексирование с 0,
тогда
позиция 1-го элемента должна быть
размером индекса (0) * размер целого числа (4) = 0,

поэтому ->
местоположение 1-го элемента равно 100 + 0 = 100,

и это было фактическим местоположением элемента,
поэтому индексация начинается с 0;

Я надеюсь, что это прояснит вашу точку зрения.

Сахил Сингх
источник
1

Я из происхождения Java. Я представил ответ на этот вопрос на диаграмме ниже, которую я написал на листе бумаги, который говорит само за себя

Основные шаги:

  1. Создание ссылки
  2. Создание массива
  3. Распределение данных в массив

  • Также обратите внимание, когда массив только что создан ... По умолчанию все блоки распределяются по нулям, пока мы не присвоим ему значение
  • Массив начинается с нуля, потому что первый адрес будет указывать на ссылку (i: e - X102 + 0 на изображении)

введите описание изображения здесь

Примечание : блоки, показанные на изображении, являются представлением памяти

Devrath
источник
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;

поэтому индекс массива всегда начинается с 0 в 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 , технически наш первый адрес элемента в arrayбудет 16неправильным, поскольку он находится 12.

Thalaivar
источник
-2

Имя массива - это постоянный указатель, указывающий на базовый адрес. Когда вы используете arr [i], компилятор обрабатывает его как * (arr + i). Поскольку диапазон int равен от -128 до 127, компилятор считает, что от -128 до -1 отрицательные числа и от 0 до 128 являются положительными числами. Поэтому индекс массива всегда начинается с нуля.

Niks
источник
1
Что вы подразумеваете под 'int range? -128 до 127' ? intТипа требуется для поддержки по меньшей мере , 16-битный диапазон, и на большинстве систем эти дни поддерживает 32 бита. Я думаю, что ваша логика ошибочна, и ваш ответ действительно не улучшается по сравнению с другими ответами, уже предоставленными другими людьми. Я предлагаю удалить это.
Джонатан Леффлер