Проверим это на C.
#include <stdio.h>
int main()
{
int data[3] = {1, 2, 3};
int i = 0;
printf("Array address: %p\n", data);
do {
printf("Array[%u] = %p\n", i, (void *)(&data[i]));
i++;
} while(i < 3);
}
Получим результат:
Array address: 0x7ffd7c514a6c
Array[0] = 0x7ffd7c514a6c
Array[1] = 0x7ffd7c514a70
Array[2] = 0x7ffd7c514a74
Как первый (нулевой) элемент, так и сам массив находятся по одному и тому же адресу, поскольку 0-й элемент удалён на 0 элементов от начала. Эта связь между указателями и массивами в C настолько тесная, что их даже можно рассматривать вместе.
Однако это ответ на вопрос «зачем», а не «почему». Нумеровать массивы с нуля стали не сразу. Удивительно, но развитие такого простого вопроса не умещается в предложении или абзаце.
Потому что так удобнее
К началу 1960-х годов сформировалось три подхода к организации структуры, которую сегодня мы называем статическим массивом:
- Исчисление с 0. Нижняя граница массива начинается с нуля.
Непривычно для обывателя, если это не житель Германии, свыкшийся с нумерацией этажей в зданиях. Последний элемент массива из, скажем, 8 элементов имеет номер 7.
Многие современные языки программирования имеют хоть какое-то родство с C (или им желательно породниться), поэтому эта схема не вызывает никакого удивления и даже кажется стандартной. - Исчисление с 1. Нижняя граница массива начинается с единицы.
Это удобно, поскольку так мы считаем объекты в реальном мире. Такое встречается, к примеру, в MATLAB или Lua — не самых низкоуровневых языках программирования. - Произвольные границы массива, когда нижняя граница может быть любым числом. Подобное знакомо учившим, например, Delphi.
Часто нумерацию с нуля объясняют тем, что такую традицию заложил C. Подразумевается, что до появления указателей, структур и Unix ни один язык не начинал массивы с нуля, полагаясь на нумерацию с 1.
Не всё так однозначно. Единообразия даже у самых первых языков программирования не существовало:
- Нумерация с нуля: LISP 1.5, APL (допускает выбор при запуске программы).
- Нумерация с единицы: APL, Бейсик, ранний Фортран.
- Произвольные границы: Алгол-60, затем Фортран, CPL, SIMULA, CLU, PL/1, Кобол, Паскаль, Алгол-68, JOVIAL.
Как видно, чаще всего массивы начинались не с единицы.
Конечно, это не самый сильный аргумент. Часто языки снабжались синтаксическим сахаром для нумерации с 1. К примеру, в Алголе массив
V[0:3]
начинается с 0, а массив V[3]
— с 1.Создатель языка APL Кеннет Айверсон в книге «A Programming Language» объясняет, почему далее по тексту он будет пользоваться нумерацией элементов массива с нуля. При этом язык допускает отсчёт как с единицы, так и с нуля. Айверсон приводит уже описанный выше аргумент о сдвигах в позиционной нумерации.
Тем не менее и в эпоху до C звучали предложения выбирать нумерацию с 0. Логично предположить, что это была историческая неизбежность, до которой оставалось несколько лет.
Потому что парусные регаты мешали вычислениям
В 1967 году Мартин Ричардс впервые реализует компилятор своего детища — BCPL (Basic Combined Programming Language). Этот язык программирования был призван исправить проблемы созданного в начале шестидесятых языка CPL путём отказа от технологий, затрудняющих компиляцию.
Первый компилятор BCPL был написан для операционной системы CTSS машины IBM 7094 Массачусетского технологического института. На тот момент компьютеры — это уже не целые комнаты и электронные лампы, но всё ещё огромные шкафы с транзисторами и консоли управления без экранов. Ресурсов серии 7090 хватило, чтобы запустить американца в космос. Но мощность измерялась в тысячах машинных слов, микросекундах тактов и килофлопсах, а цена — в миллионах долларов.
В пятидесятых и шестидесятых IBM выдавала институту щедрые скидки на свои научные компьютеры или даже предоставляла их бесплатно. В 1963 году в МТИ поставили IBM 7094. На компьютер уговор был такой: 8 часов в сутки получают специалисты института, 8 часов — другие колледжи и университеты Новой Англии, а третью смену отдавали самой IBM для личных нужд.
На этом особенности не кончались. Несколько раз в год IBM обсчитывала регаты: президент IBM соревновался в заплыве на больших яхтах в проливе Лонг-Айленд, и каждое из судов получало очки гандикапа по специальной сложной формуле. Когда в вычислительный центр приходило задание, операторам полагалось всё бросить и запустить вычисления этих очков.
Машинный зал с установленным компьютером IBM 7094 в Колумбийском университете США. Фотоархив
Вообще, и без этого был шанс не получить результат вычислений. Изначально 7094 работал в режиме пакетной обработки. Вспомогательная система на IBM 1401 загружала пакет задач с перфокарт на ленту, а затем запускала задачи одну за другой и записывала результаты для последующей печати. Для каждой задачи приводилась оценка времени. Если задача выходила за пределы отпущенного, её принудительно прерывали.
В итоге компиляция в BCPL была оптимизирована по максимуму. То есть и указатели на элементы массива должны максимально близко походить на машинный код, без необходимости вычитать единицу при указании на элемент массива. Хотя во время выполнения программы не играет роли схема организации массивов, нумерация с нуля могла появиться для оптимизации времени компиляции.
Впрочем, конкретно эта версия — лишь гипотеза Майка Хойе. Ей нет никаких подтверждений от собствено Ричардса или хотя бы упоминаний в литературе. Мартин Ричардс в переписке с Хойе лишь приводит общие соображения о том, что он хотел достичь близости к машинному коду, поэтому указатель
p
и p + 0
— это одна и та же переменная. Ни на какие яхты Ричардс не жалуется.К тому же на момент появления первого компилятора BCPL уже была готова операционка Compatible Time-Sharing System. В ней на одной машине с разделением времени компьютер выполняет одну задачу за один раз, но с оптимизацией ввода и вывода, чтобы паузы одного пользователя заполнялись работой других. Это уже не былая пакетная обработка задач.
Точно известно, что в дальнейшем BCPL значительно повлиял на все современные языки программирования.
В 1969 году Кен Томпсон урезал функциональность BCPL до языка B. В дальнейшем, для развития операционной системы Unix, Деннис Ритчи улучшил B добавлением функций PDP-11, в результате чего и получился C. Полвека спустя список языков, на которые оказал влияние C, занимает в «Википедии» целую страницу.
В языке BCPL
v!5
и 5!v
совпадают, поскольку являются указателем на !(v+5)
или !(5+v)
. Аналогично в C v[5]
эквивалентно 5[v]
.Потому что так предложил Дейкстра
В 1982 году Эдсгер Дейкстра опубликовал статью «Почему нумерация должна начинаться с нуля». В ней он низверг как нумерацию с единицы, так и произвольные границы индексов. Очевидно, что Дейкстра — не человек, который легко поддаётся влиянию C, но также он раскритиковал Алгол-60, своё собственное детище, и Паскаль.
Известно, что Дейкстра ближе к концу жизни всё больше писал от руки, а не набирал тексты на машинке. Архив рукописей Эдсгера Вибе Дейкстры
Если необходимо записать интервал натуральных чисел 2, 3, …, 12 без опухоли из точек, возможны четыре варианта:
2 ⩽ i < 13
1 < i ⩽ 12
2 ⩽ i ⩽ 12
1 < i < 13
Дейкстра указывает, что не все из предложенных вариантов эквивалентны. В первом и втором варианте разница между началом и концом последовательности равна её длине. Также в этих вариантах в случае смежных последовательностей конец одного промежутка будет началом другого.
Затем автор замечает, что для нижней границы предпочтителен знак «⩽», поскольку наименьшее натуральное число существует. В противном случае, если последовательность начинается с наименьшего натурального числа, нижняя граница не будет натуральным числом.
Аналогично для верхней границы Дейкстра рекомендует использовать «<», поскольку так удобнее для записи подпоследовательностей нулевого размера. Иначе верхняя граница рискует не оказаться натуральным числом.
Затем статья делает вывод, что из соображений простоты для последовательности из N членов предпочтительнее диапазон
0 ⩽ i < N
, а не 1 ⩽ i < N+1
.Куда менее известный документ — это полушуточная техническая заметка от 1 апреля 1980 года IEN 137 под названием «О священных войнах и призыв к миру» [On Holy Wars and a Plea for Peace].
В заметке Дэнни Коэн приводит интересный аргумент: в системе счисления с основанием
b
при отсчёте с нуля первые b ^ N
неотрицательных чисел представляются ровно N
цифрами. Например, если речь про двоичную запись, то 2 ^ 3 = 8
, и восьмой элемент массива будет иметь номер 1112, а не 10002. Понятно, что с такими преобразованиями легко бы мог справиться компилятор.Потому что так более элегантно
Это объяснение может раздражать субъективностью. Соображения о красоте у каждого свои и совпадать не обязаны. Но авторы языков программирования — тоже люди со своими предпочтениями.
В конце восьмидесятых Гвидо ван Россум при создании Python как учёл свой предыдущий опыт с языком ABC, так и задумал привлечь аудиторию хакеров от мира Unix и C.
С 1983 года Гвидо работал в Центре математики и информатики в Амстердаме над реализацией языка ABC. Проект ставил целью создать язык программирования, пригодный для обычных людей, но не настолько ужасно реализованный, как Бейсик. Нумерация массивов в ABC начиналась с единицы. Такой же схемы придерживались другие знакомые Россуму языки — Алгол, Фортран и Паскаль.
Однако в вышедшем в 1991 году Python нумерация начинается с нуля, а не единицы. Получилось так в результате долгих размышлений.
Одна из причин — слайсы. Чаще всего при создании слайса используются операции «получить первые n элементов» и «начиная с i, получить следующие n элементов». При этом первый случай эквивалентен
i == первый индекс
. Гвидо посчитал, что лучше, если обе операции возможны без лишней головной боли в виде коррекции +1 и −1.Если первый элемент имеет номер 1, то возможно указывать первый элемент и число элементов, которые нужно получить. Россум уже был знаком с подобным по ABC и вполне мог бы положиться на этот опыт.
Тем не менее автора Python очаровал синтаксис слайсов полуоткрытого интервала, если нумерация начинается с нуля:
a[:n]
(или a[0:n]
) и a[i:i+n]
. К примеру, строку a
легко разбить на три части a[:i]
, a[i:j]
и a[j:]
.Заметим, что в воспоминаниях Гвидо не приводит ни призывы гениев информатики, ни практики других языков, ни принципы, заложенные мастодонтами компьютерных вычислений шестидесятых. Зато слово «элегантность» в пяти абзацах его объяснения встречается 3 раза.
Почему же массивы начинаются с нуля?
Так исторически сложилось.
По материалам блогов Альберта Козловски, Майка Хойе, Гвидо ван Россума, Хиллеля Уэйна и ответа Хойе.
Комментарии (176)
2er6e1
01.11.2022 07:09+5Гораздо проще, имхо.
Потому что так больше влазит в байтовое кодирование и лишним перекодирование имён не нужно заниматься.
nin-jin
01.11.2022 07:10+16Ну и в качестве примера неэлегантности нумерации с нуля: если элемента в массиве нет, то поиск его индекса возвращает, внезапно, -1, а не 0. А это:
Требует использования знакового типа большей точности, вместо беззнакового той же точности.
Требуется хитрых манипуляций в условных конструкциях, так как -1 является истиной, а 0 - ложью.
Кроме того, получается неэлегантная асимметрия: первый элемент имеет номер 0, но для обращения к последнему приходится использовать уже -1 или в некоторых языках
$-1
.redsh0927
01.11.2022 08:13+19если элемента в массиве нет, то поиск его индекса возвращает, внезапно, -1
-1 действительно выглядит произвольным непонятно откуда взятым значением. N или UINT_MAX хоть какая-то логика была бы.
Требуется хитрых манипуляций в условных конструкциях, так как -1 является истиной, а 0 - ложью.
Вообще, в си функции поиска не возвращали -1. Это в новодельных языках так. И это к авторам этих языков вопрос, почему при наличии у них булевого типа вообще разрешено проверять int на истину/ложь без
хитрых манипуляцийпредварительного сравнения.
Действительно "хитрых" манипуляций требует нумерация с единицы. Во всех формулах приходится вычитать единицу чтобы получить нормальное смещение, с которым можно работать и потом прибавлять единицу обратно чтобы получить номер элемента. Вот это невероятно бесит в !@#$%ом матлабе.Кроме того, получается неэлегантная асимметрия: первый элемент имеет номер 0, но для обращения к последнему приходится использовать уже -1 или в некоторых языках $-1.
и что же тут неэлегантного? -1 - смещение от конца блока до начала последнего элемента. Это как раз соверешнно логично и естественно.
А потом начались приколы с расположением мета-информации типа счётчиков ссылок или записи размера по отрицательным смещениям.
И что плохого в отрицательных смещениях? Смещение - это просто расстояние от точки до точки. Если в качестве точки отсчёта взято начало блока элементов, то что расположено до блока будет по отрицательным смещениям. Да, "очень элегантно", обращаться к данным, расположенным до блока, по неотрицательным смещениям, только ради того чтобы смещение было неотрицательным. Если же мета-информация и счётчики ссылок расположены внутри блока то и смещение будет неотрицательным при нормальной "индексации с нуля"
nin-jin
01.11.2022 11:23N или UINT_MAX хоть какая-то логика была бы.
Без разницы, через какое имя вы будете записывать -1.
почему при наличии у них булевого типа вообще разрешено проверять int на истину/ложь без
хитрых манипуляцийпредварительного сравненияЧтобы не делать очевидной проверки на пустоту вручную.
Во всех формулах приходится вычитать единицу чтобы получить нормальное смещение
И зачем вам "нормальное смещение"? Обращайтесь по индексам.
и что же тут неэлегантного?
Асимметрия.
И что плохого в отрицательных смещениях?
При работе с памятью нужно очень аккуратно с ними работать, иначе всё сломается. И такие случаи не редки. Программист не всегда в курсе сколько надо "отступить назад", чтобы корректно скопировать или освободить память.
thevlad
01.11.2022 13:42+4У использования нуля и адресации с единицы появляется противоположная сторона, заключающаяся в том, что INT_SIZE_MAX - 1 перестают быть представимыми в рамках заданного "бинарного" типа. В результате абстракции все равно текут.
nin-jin
01.11.2022 16:46-2Давайте поговорим об этом, когда создадите массив хотя бы на 8 петабайт.
thevlad
01.11.2022 16:58Странный аргумент, против -1, для 64 битных чисел, если ssize_t должно хватить всем. А вот то,что из-за граничных случаев, компилятор не может лучше оптимизировать код, такое вполне себе случается.
Fr0sT-Brutal
01.11.2022 16:44+1При работе с памятью нужно очень аккуратно с ними работать, иначе всё сломается
Просто не надо с ними работать. Никогда. Это внутренняя кухня компилятора, куда посторонним вход запрещен.
nin-jin
01.11.2022 17:08-2В нормальных языках это реализуется в библиотеках, а не хардкодится в компиляторе.
Fr0sT-Brutal
01.11.2022 17:19В нормальных языках это хардкодится в компиляторе , а не реализуется в библиотеках.
Обоснования заявления выше будут?
lain8dono
01.11.2022 17:13+4-1 и UINT_MAX идентичны.
Например для 16-битного числа -1 кодируется как 1111_1111_1111_1111 (0xFFFF).
А с точки зрения машинного кода нет разницы для знаковых и беззнаковых чисел на части основных операций. Сложение, вычитание, умножение и большинство битовых операций выполнены таким образом, что знак не важен. Для сравнения, деления и смещения вправо знак важен и эти операции обычно имеют два варианта.
https://ru.wikipedia.org/wiki/Дополнительный_код
Кроме того использование валидного числового значения для возврата ошибки является рудиментом ранних языков программирования. Во всех более современных ошибка с более абстрактной стороны строго типизированна, а с низкоуровневой стороны хранится как отдельное значение.
Элегантное обращение к внутренним метаданным языка программирования не требуется. Это слишком редкая и слишком специфичная штука.
nin-jin
01.11.2022 18:20-2Отсутствие элемента в массиве ошибкой не является.
Это метаданные не языка, а структуры данных. А структур разных миллионы.
lain8dono
01.11.2022 22:32+1Отсутствие элемента в массиве ошибкой не является.
Если вы про выход за границы массива, то является ошибкой. В зависимости от конкретного языка программирования и/или флагов компиляции эта ситуация может по разному обрабатываться (возвращение ошибки или бросок исключения) или даже игнорироваться полностью (как в чистом няшном Си в релизной сборке без специальных флагов).
Кстати, судя по обсуждению выше следует уточнить, что вы вообще подразумеваете под массивом. Следует разделять массив как тип (1), массив как данные на стеке (2), ссылку на массив в куче, слайс (обычно ссылка+длинна) и вектор. Обычно под просто массивом подразумевают (1) и (2). И в любом случае речь идёт о данных в оперативной памяти. Метаинформация о размере массива существует на этапе компиляции и не хранится в рантайме. Если мы конечно говорим о компилируемых языках.
В случае если у вас ссылка на массив в куче, то метаданные о размере этого массива хранятся в аллокаторе. В таком случае не очень понятно, зачем вам туда лезть. Очень сильное колдунство.
Если мы говорим о векторе, то вектор может давать доступ к своим метаданным тем или иным образом. Как минимум ссылку на начало массива (или слайс), а также отдельно длину массива и иногда общий объём занятой памяти. Но детали могут меняться в зависимости от реализации и конкретного языка программирования.
mastan
02.11.2022 13:59-1 и UINT_MAX идентичны.
Полагаться на это не стоит:
long long a = -1; long long b = UINT_MAX;
MainBelia
01.11.2022 09:53+17Извините, но если элемента в массиве нет, то поиск его индекса возвращает по-хорошему не -1 и не 0, а Nothing/null/nil (либо в крайнем случае кидает исключение).
KvanTTT
01.11.2022 18:53Может это и по-хорошему, но использовать ради такой операции дополнительный тип слишком накладно.
potan
01.11.2022 13:06+6Вообще использование -1 как кода особой ситуации неэлегантно. Для наких случаев придуманы элегантные Алгебраические Типы Данных - Option/Maybe.
nin-jin
01.11.2022 13:21-40 для отсутствия индекса вполне элегантно, быстро и компактно. В отличие от ADT, который превращается в discriminated union.
PROgrammer_JARvis
01.11.2022 13:36+3А в чём конкретно проблема discriminated union, если он нормально реализован и, тем более, хорошо подлежит оптимизациям (TL;DR, если у вас есть дырка из невозможного битового представления, то это невозможное значение и будет представлять собой пустой вариант)?
nin-jin
01.11.2022 16:59-4Option<NonZero<Num>>
ничем не отличается от простоNum.
PROgrammer_JARvis
01.11.2022 17:01+2А с чего вдруг
NonZero<Num>
, если речь, как раз, о том, чтобы оставить 0 как индекс начинающего элемента?nin-jin
01.11.2022 17:10-3PROgrammer_JARvis
01.11.2022 17:19+3И? То, что оптимизация, на которую я сослался, есть, не значит, что надо изобретать бессмысленные структуры, как
Option<NonZero<Num>>
(на самом деле, вполне себе осмысленные, но не для данной задачи).Суть моего комментария -- противопоставление Вашему
0 для отсутствия индекса вполне элегантно, быстро и компактно. В отличие от ADT, который превращается в discriminated union.
Контрпример, демонстрирующий, что сум-тип -- очень даже удобно.
Оптимизация были упомянуты дополнительно как что-то, что, в целом, хорошо дружит с последними, но не как инструкция к написанию нелогичного -- для задачи инексирования -- типа.
В дополнение к тому, что индексация с нуля удобна, она ещё и естественна для многих прикладных вещей.
me21
01.11.2022 13:16-1Можно ещё исключение кидать, как в Питоне.
Serge78rus
01.11.2022 14:50+2Если по логике программы искомый элемент должен присутствовать в массиве, а его отсутствие - это ошибка, то можно. Если же отрицательный результат поиска вполне нормальная ситуация, то не стоит - это как затрудняет понимание логики программы, так и вносит ненужные накладные расходы на выброс и обработку исключения.
me21
01.11.2022 18:10+1Ну там накладные расходы небольшие, а если в большинстве случаев его кидать не надо, то получится даже быстрее, чем с дополнительной проверкой результата на -1, потому что она будет выполняться каждую итерацию, а обработчик исключения - гораздо реже.
Это конкретно про Питон. Там в целом исключения используются достаточно часто, например, цикл в конце кидает StopIteration под капотом. Ну и вообще там подход "It's easier to ask for forgiveness than for permission".
Но я согласен, что всё должно быть в разумных рамках и не влиять отрицательно на читабельность кода.
KvanTTT
01.11.2022 18:55Это убийство для производительности.
me21
01.11.2022 19:11-1Ну для Питона производительность падает всего в разы, а не на порядки, и то только в случае выброса исключения; да и числодробилок на чистом Питоне никто не пишет. Первый же сетевой запрос нивелирует разницу в производительности.
0xd34df00d
01.11.2022 17:05поиск его индекса возвращает, внезапно, -1, а не 0.
Где там -1 возвращается? У меня вот совсем не -1.
Кроме того, получается неэлегантная асимметрия: первый элемент имеет номер 0, но для обращения к последнему приходится использовать уже -1 или в некоторых языках $-1.
Как бы вы это симметризовали?
YDR
01.11.2022 08:06+5вообще, альтернативы всего 3: с 0, с 1, или позволить выбирать. У всех вариантов есть достоинства и недостатки. Нужно один раз определиться (хотя бы для конкретного языка), и все. index==-1 не такая страшная штука (signed int не критично больше, чем unsigned)
nin-jin
01.11.2022 11:28Ну да, всего в 2 раза больше.
PowerMetall
01.11.2022 14:05Насколько помню, таки обычно не больше (имеется ввиду размер в памяти). Просто диапазон сдвинут "в минусовую" сторону до середины
Например в C#:
short: хранит целое число от
-32768
до32767
и занимает 2 байтаushort: хранит целое число от
0
до65535
и занимает 2 байтаnin-jin
01.11.2022 17:01+2Индексы в массиве не могут быть отрицательными, так что половина диапазона теряется.
commanderxo
02.11.2022 23:32+1Увы, чудес не бывает, и если в массиве может быть N элементов, а функция поиска должна дополнительно обрабатывать ситуацию когда элемент не найден, то спектр значений по-любому расширяется, и в исходный диапазон уже не входит. Принцип Дирихле никто не отменял. Например, при нумерации элементов с 1, массив из 65536 ячеек вполне укладывается в 16-битную адресацию, но вот номер последнего элемента в 16-битный int уже не влезет. Проблема никуда не исчезает, а лишь смещается в редко используемый диапазон значений, увеличивая риск быть пропущенной при тестировании и остаться лежать граблями замедленного действия.
В Си пошли на сознательный шаг для «особых» значений явно требовать особый (более мощный) тип. Если следовать этой философии с первого дня изучения нового языка программирования, то она намертво закрепляется в памяти и потом выглядитестественнопривычно. Ключевой момент — с самого начала явно оговорить эту ситуацию и объяснить причины.Например, у классиков K&R уже в первой главе чётко объясняется, почему функция getchar возвращает не char, а int.
me21
01.11.2022 21:57+1В C++ возвращается npos, эквивалентный UINT_MAX:
npos is a static member constant value with the greatest possible value for an element of type size_t.
This value, when used as the value for a len (or sublen) parameter in string's member functions, means "until the end of the string".
As a return value, it is usually used to indicate no matches.
Так что можно и обойтись без знакового типа вдвое большего размера.
lain8dono
02.11.2022 12:04Максимальное беззнаковое и -1 это одно и тоже. По крайней мере в рамках C/C++ с их слабоватой типизацией.
me21
02.11.2022 12:35Не совсем: для максимального беззнакового диапазон индексов расширяется вдвое по сравнению со знаковым (например, для 16 бит беззнаковый индекс может быть до 65535 (или, если учесть специальное значение UINT_MAX как "индекс не найден", до 65534), а знаковый индекс только до 32767.
lain8dono
02.11.2022 13:10Сложно говорить о строгости этих значений при слабой типизации. UINT_MAX для signed int будет побитово идентичен -1. INT_MAX для unsigned int будет равен половине от UINT_MAX. Проблема в неявном преобразовании между signed int и unsigned int.
me21
02.11.2022 14:14Это понятно. Но если функция поиска в строке возвращает 16-битное знаковое, вы из неё не сможете вернуть позицию больше 32767. А если беззнаковое - то вернёте до 65534.
lain8dono
02.11.2022 15:05Давайте по другому. В языках со слабой типизацией разница между знаковыми и беззнаковыми на самом деле лишь косметическая. Разница только в выборе инструкций для некоторых операций. Пара деталей: https://habr.com/ru/post/696666/comments/#comment_24872228
me21
02.11.2022 21:37Сначала начал писать длинный пост, а потом проверил. Вы правы, прошу прощения. Что для знаковой переменной, что для беззнаковой, выражение &str[i] генерирует одну и ту же ассемблерную инструкцию add.
С другой стороны, объявлять функцию поиска по строке как возвращающую знаковое значение означает либо отказаться от половины возможных значений (и уменьшить в два раза допустимую длину строки), либо возвращать (на примере 16-битных целых) значения вроде 40000 как -25536, что очень удивит читающих этот код впоследствии.
Это всё в предположении, что битность арифметики указателей совпадает с битностью используемых целых. Если она больше, то там ещё разные инструкции будут (zero-extension vs sign-extension).
Лучше уж пусть эта функция возвращает беззнаковое, а в случае неудачного поиска - UINT_MAX.
lain8dono
02.11.2022 23:11+1Нет, использование простого int в подобных контекстах это просто признак yolo-кода. Ну или этот код старше, чем большинство из нас. Это если мы говорим о Си и его производных.
В современных языках есть возможность использовать всякие Option, Result. Ну или в языке принято использовать исключения. Чисто технически первое будет означать, что требуется задействовать не один регистр для передачи результата, а два. Вариант с исключениями разумеется будет гораздо дороже. Раскрутка стека и вот это всё.
Если она больше, то там ещё разные инструкции будут (zero-extension vs sign-extension).
Это уже зависит от архитектуры. Варианта два: отдельные операции для дополнения (то, что вы говорите), либо специальные операции для каждой размерности. Первое будет в ARMv6 и новее, второе в amd64. Что-то более экзотичное я не проверял. Ну или даже оба варианта, я все случаи с разными компиляторами и инструкциями не проверял.
s_f1
01.11.2022 08:10+5До сих пор возмущен, что «паскалевские» индексы по любым перечисляемым типам не реализуют в других языках. И ещё больше возмущен случаями, когда вместо такого массива предлагается использовать словари.
thevlad
01.11.2022 13:12+3В тех же плюсах сделать массивы с произвольными диапазонами(аналог array), совсем не проблема, просто это особо не кому не нужно.
s_f1
01.11.2022 20:58+1Сделать то можно что угодно, и действительно, это особо никому не нужно, так как на исполняемую программу не влияет – только на читабельность. Но вот код с SO по подсчету количества разных символов в строке на Си:
а на Паскале можно было бы:int counts[26] = { 0 }; for (i = 0; i < len; i++) { counts[(int)(str[i] - 97)]++; }
Как по мне, то польза тут от «паскалевских» индексов очевидна. Только об этом я и хотел сказать.counts: array ['a'..'z'] of integer; ... counts[str[i]] := counts[str[i]] + 1;
MrShoor
02.11.2022 00:54+1Не согласен, что не нужно. Типичный С/С++ программист просто не знает насколько это удобно. Во-первых, код более читабельный. Во-вторых, более безопасный (размер массива всегда равен размеру энама). В-третьих, добавляются проверки на этапе компиляции. Вот я набросал пример: https://rextester.com/VQDRP17163
Если в этом примере в энам добавить meFour четвертым значением, то во-первых, код не скомпилируется, т.к. скажет, что значение cMyEnumText должно содержать больше значений. А во-вторых, цикл автоматически будет итерироваться по всем значениям, включая новое добавленное. В С++ при добавленнии значения в энам нам надо будет: 1. не забыть поправить размеры массивов, иначе упадет в рантайме 2. не забыть поравить руками циклы, иначе не проитерируемся.
Зато в плюсах я видел бесчетное количество раз, когда пишут:
enum class { Some1, Some2, Last }
Где Last - буквально костыль чтобы сделать так, как оно в паскале. При этом все равно приходится руками лепить ущербные тайпкасты.
p.s. Вообще в паскале есть еще одна крутая вещь, это встроенные эффективные множества. Но не будем о грустном.
Fr0sT-Brutal
03.11.2022 14:53+1Энумы Паскаля - одна из самых крутых фич языка, наряду с безгеморными строками и динмассивами (очень было кайфово с ними после голых сей)
aakhamef
01.11.2022 18:12В PHP можно реализовать через произвольный класс с Iterator, ArrayAccess, Countable
vadimr
01.11.2022 08:37+2Нумерация с нуля: LISP 1.5, APL (допускает выбор при запуске программы).
Нумерация с единицы: APL, Бейсик, ранний Фортран.
В лиспе нет массивов. А в бейсике зависит от версии.
Кстати, бейсик для Apple ][ – единственный известный мне язык, где необъявленный массив получал размер по умолчанию. В таком случае его длина равнялась 11 элементам с индексами от 0 до 10.
IvanPetrof
01.11.2022 09:47+2› длина равнялась 11 элементам с индексами от 0 до 10.
Хм. Выглядит как попытка угодить и нашим и вашим (и тем кто с нуля начинает и тем кто с 1). Чтоб ненароком не выйти за пределы массива.
Интересно, а на объявленный массив выделялось ровно сколько объявлено? Или тоже +1 элемент "на всякий случай"?vadimr
01.11.2022 10:11Это же вопрос семантики объявления. Оператор DIM A (N) декларировал массив с N+1 элементами от 0 до N. То же самое с многомерными массивами. Так что вообще-то на практике обычно пропадало много нулевых элементов, что курьёзно для компьютера с 48 килобайтами оперативной памяти.
Лично Билл Гейтс, кстати, писал этот интерпретатор.
Fil
01.11.2022 08:48+9Также упрощается и становится более интуитивной реализация многомерных массивов:
array[row * num_cols + col]
vadimr
01.11.2022 08:51-6Эта формула что конкретно символизирует? Если на уровне самого языка программирования высокого уровня, то индексироваться одним индексом вместо нескольких вам в большинстве языков не позволят. А если на уровне реализации в машинном коде, то там не будет таких арифметических вычислений индекса, так как, во-первых, участвует начальный адрес, не равный нулю, а во-вторых, могут применяться всякие индексные регистры и т.п.
Fil
01.11.2022 08:58+9Это с точки зрения программиста, у которого в распоряжении одномерные массивы, но необходима структура данных, имеющая схожий с многомерными массивами интерфейс.
vadimr
01.11.2022 11:36-5Весьма гипотетическая картина.
Helltraitor
01.11.2022 11:43+12Скорее обыденность
vadimr
01.11.2022 11:44Можно привести практический пример, в котором вы ограничены одномерными массивами?
Helltraitor
01.11.2022 11:49Если мы говорим про C lang, то разницы, на самом деле, нет. Но в других языках, вроде Python мы получаем лишние объекты, которых могли бы избежать (практический пример приводить, если честно, не хочу, просто всегда одномерные использую)
vadimr
01.11.2022 11:58+1Ну это не ответ :) Вы, по существу, написали, что любите использовать одномерные массивы вместо многомерных. Я уважаю Ваши привычки, но согласитесь, что это не повод для проектирования языков программирования. Во всяком случае, в питоне многомерные массивы точно есть.
Helltraitor
01.11.2022 12:04+1Не из коробки. В питоне у вас из коробки может быть только список списков
Если мы говорим про проектирование, то тут стоит использовать одномерные массивы с намеком, что все хранится в одном блоке памяти (что может быть не верно для некоторых языков). Для многомерных можно использовать специальные структуры данных и inline функции
P.S. Но по сути согласен, тут мы мнениями делимся
anka007
01.11.2022 12:30Не совсем. В питоне в качестве элемента массива можно задать любой другой объект, в том числе и еще один массив. Таким образом получается массив массивов (или не массивов, объекты могут быть любыми, не всегда однородными). Это имеет несколько последствий, не всегда понятных тем, кто ожидает простую матрицу n*m.
vadimr
01.11.2022 12:59Справедливое замечание, но тогда надо начинать с того, что и одномерный – не массив и был.
anka007
01.11.2022 13:17+1Питон и не врет - он сразу называет это списком объектов. Использование в качестве массива чисел - всего лишь частный случай, потому что число в питоне тоже объект.
anka007
01.11.2022 12:07-2А потом через полтора года ваш код дадут джуниору добавить какую нибудь ерунду и он полысеет, пока разберется, что значат все эти страшные оптимизированные конструкции. Или просто перепишет все с нуля. И это на python, от которого никто не ждет высокой производительности и экономности, но ожидается читаемость и легкая поддерживаемость.
Можно порадоваться, что не на перле пишите, там логику работы "оптимизированных" программ бывает не может объяснить сам автор на следующий день.
thevlad
01.11.2022 13:15+3В C нет из коробки многомерных динамических массивов, те же матрицы произвольной размерности надо эмулировать через одномерные массивы.
markowww
01.11.2022 13:20Писал в институте Гауссово преобразование матрицы. На линейном массиве работает гораздо быстрее, чем на двухмерном
nin-jin
01.11.2022 13:25-1Вы же не путаете двумерный массив с массивом массивов?
LordCarCar
01.11.2022 13:37В С перебор строки или столбца на линейном массиве можно сделать одной операцией - прибавлением смещения. В двумерном - умножение и смещение.
0xd34df00d
01.11.2022 17:11+3Любой адекватный компилятор сделает LICM и вынесет умножение вне вложенного цикла, сильно снизив штраф за него.
Rsa97
01.11.2022 13:30Например, на 8086 память выделялась системой кратно 16 байтам (сегменту). Из выделенной памяти четыре байта расходовались на организацию списка выделенных фрагментов. Соответственно, создавая массив символов 16х16 в виде char** мы расходуем 48+16*32 = 560 байт, из которых 44 будут служебными. Формируя же линейный массив char* из 256 символов и используя вычисленную адресацию получим расход 272 байт и 4 байта накладных расходов. То есть, хоть формально мы и не ограничены в создании многомерных массивов, но в реальности для экономии памяти выгоднее использовать однимерные с вычислением адреса.
Собственно, такая структура, как куча, в языке появилясь как раз для снижения накладных расходов при выделении небольших блоков памяти. Программа запрашивала у системы большой фрагмент памяти и распределяла её сама с меньшей дискретностью.vadimr
01.11.2022 13:34Ну Вы сами, собственно, и ответили на своё возражение в нём самом. Конечно, никто не говорит о том, чтобы выделять область памяти с дескриптором на каждую строку двумерного массива по отдельности. Выделяется обычно целиком куча или стек.
redsh0927
01.11.2022 09:14+1Формула символизирует то что многомерный массив - всего лишь частный случай вложенных интервалов. С которыми, кстати, приходится работать не только через операторы [] или [][] (реализующие для удобства простейшие случаи). Где есть интервалы, диапазоны и т.п. - там и смещения вылазят на каждом шагу. Вот когда принуждают обращаться к элементам не по смещению а от с потолка взятого начального индекса (1, 42 и т.п.) - то и хочется такой язык сжечь.
MrShoor
02.11.2022 00:58Угу, а еще можно писать так:
int a[5];
2[a] = 4;
printf("%d\n", 2[a]);удобненько
mSnus
01.11.2022 10:12-5Если нумеровать элементы с 1, то в нулевом элементе можно хранить длину массива. Очень удобно иногда, не надо ничего вычислять каждый раз при обращении к нему или держать где-то отдельно таблицы длин.
firehacker
01.11.2022 11:14+4А что, все массивы у нас имеют тип, эквивалентный условному size_t?
redsh0927
01.11.2022 11:22+8Почему-то мне кажется что людей, которые считают логичным хранить длину массива в перемешку с элементами и адресовать элементы не смещением, такие мелочи не волнуют
Hlad
01.11.2022 10:19+9Забавно, что в статье как бы намекается, что "сейчас, в век терафлопсов и гигабайтов в каждом мобильнике все эти заморочки с нумерацией с нуля кажутся неудобных архаизмом". Хотя есть миллиарды устройств на всяких там ARM Cortex M0, и даже более мелких микроконтроллерах. А терафлопсы на мобильниках расходуются так коряво, что тормозят даже самые простые приложения...
AlexeyK77
01.11.2022 10:37+8Во времена, когда память исчислялась килобайтами, основной код писался руками на ассемблере и каждая операция была на счету, то в коде заниматься постоянно отниманием этой единицы, что бы получить адрес смещения - лишний головняк.
Посему и в Си не стали усложнять, эффективность была важнее.
В Паскале кстати по умолчанию индексация массивов была с единицы и редко кто ее переопределял.vadimr
01.11.2022 11:30В Паскале нет индексации по умолчанию, там всегда указывается нижняя граница.
Заниматься отниманием единицы в коде не нужно, потому что смещение в машинном коде относится не к началу массива, а к базовому адресу. Если, допустим, массив 32-разрядных чисел находится по адресу 0x8888, то базовый адрес в таком случае будет 0x8884. Тут единственный головняк для низкоуровневого программиста – с умножением индекса на 4 (но в 1950-е годы, когда всё это безобразие начиналось, память адресовалась словами, и в таком умножении не было нужды).
s_f1
01.11.2022 13:11+2В Object Pascal были динамические массивы, где границы можно было не указывать.
Юмор в том, что индексы в них начинались с нуля )arr: array of integer;
Roman_S
01.11.2022 11:33+5Массивы как дети, их возраст при рождении тоже отсчитывается не сразу с 1 года, а с 0 лет, по крайней мере в большинстве стран.
vadimr
01.11.2022 11:35Вообще это называется “первый год жизни”, а про “0 лет” может сказать только программист :)
redsh0927
01.11.2022 12:22+5Именно потому у обывателей вечные проблемы понять и запомнить что 18хх - это 19-й век и второе тысячелетие начинается с 1 января 2001 а не 2000, а в Корее с рождения ребёнку сразу исполнялся 1 год но сейчас это пофиксили, и прочие прелести что возникают с масштабированием, сдвигом и наложением диапазонов при нумерации не смещением. Вот, смотря на комментарии, удивляет конечно количество желающих тащить этот ужас в программирование, считающих его удобным и рассуждающих что отцы-основатели лишь за такты боролись, просто сделав нормально. Мне кажется, что раньше было лучше.
PsyHaSTe
03.11.2022 10:36"Первый год жизни" да, но на вопрос "сколько ребенку лет" обычно отвечают скажем "3 месяца", что является сокращением от "0 лет 3 месяца"
Iv38
01.11.2022 12:08+1Это количественные и порядковые числительные. Прожитых лет 0, год жизни первый.
le2
01.11.2022 11:48+7Статью я конечно же не читал, но я много писал когда-то на ассемблерах и в частности на Intel 8051. Подобных вопросов у меня не возникало, потому что там множество команд сравнения с нулем. Ценная команда - djnz - "декремент, переход если не ноль". Цикл организовывался в две (!) строчки. Что-то вроде:
mov r1, #8
label: <тело цикла>
djnz r1, label
Indemsys
01.11.2022 12:46+4Эти массивы с нуля и с единицы сейчас создают целую проблему при кодогенерации из MATLAB в C/C++
Жесть начинается когда пытаешься создать интерфейс на C к параметрам в MATLAB.
На мгновение забудешь про индексацию в MATLAB с единицы, и получаешь гейзенбаг на многие дни.
thevlad
01.11.2022 13:29Еще интересный случай это работа с граничными значениями. Если сравнивать границы для байтов, то можно написать i <= NUM при этом NUM тоже будет байтом, если прибавить единичку это все сломается.
unC0Rr
01.11.2022 18:02Если i и NUM оба имеют тип uint8 и NUM положить 255, то i <= 255 всегда истина.
thevlad
01.11.2022 18:21Да, и? Для краевого значения будет всегда истина, но обычно это константа или переменная, код для которой пишется в общем виде. Вопрос, в том какого типа должна быть NUM если это константа, и если это переменная. А то так, даже банальный цикл while (i < NUM) работать не будет, без расширения в более длинные типы, которых нативно может и не быть.
Dark_Purple
01.11.2022 14:08Всё хорошо сделано, оставляйте так!
..ишь, с нуля им видите ли не нравится)))
disputant
01.11.2022 20:52С аргументацией Дейкстры насчет наименьшего натурального числа...
"У них" наименьшее натуральное —ноль, но у нас-то — единица! :)lain8dono
02.11.2022 12:38+1Неотрицательные начинаются с нуля. С единицы начинаются позитивные числа. Забудьте всё, чему вас там пытались научить. Натуральными можно считать и те и другие. Вообще Википедия напоминает, что для случаев, когда это важно, вы можете обозначить это:
Нет никакого "у них" и "у нас", есть только образование низкого качества. У вас.
disputant
02.11.2022 18:20Нет, просто образование и кандидатский диплом советские, когда учили математике, а не хамству :)
Для вас - апеллирую не к умным книгам (сомневаюсь, что вы их сможете понять), а к Википедии :)0xd34df00d
02.11.2022 21:37+1В чём глубокий смысл исключения нуля из натуральных? Натуральные от этого перестают быть моноидом по сложению, как минимум, а это неприятно.
nikolas78
02.11.2022 22:36Все гораздо проще: «naturalis» — с лат. «естественный», то что можно увидеть естественным/природным образом — глазами. Ноль предметов увидеть глазами нельзя, поэтому когда придумывали определение для натуральных чисел (в эпоху антропо- и бого-центризма), ноль туда не включили. А более математические доводы за натуральный ноль подъехали позже, когда традиция уже сформировалась. Даже в XIX веке Пуанкаре и Кронекер выступали против натурального нуля. Вообщем здесь то-ли религиозное, то-ли еще какое-то легаси.
Хотя, по правде сказать, ноль действительно особенное число (среди всех вещественных)…
Bwana
02.11.2022 00:52+1Не все массивы начинаются с нуля. В sql, например, с единицы. В фортране вообще индекс может быть даже отрицательным. Во многих скриптовых языках какой назначишь, такой и будет. Так что какв заголовке статьи опущено слово "в языке си". И встает вопрос, а почему таки оно опущено? А потому, что если его написать, то статья вырождается в один, ЕМНИП, абзац из стандарта на язык, где говорится, что массивы, как таковые, и постфиксный оператор "квадратные скобки" -- это всего лишь синтаксический сахар, а на самом деле компилятор всегда оперирует суммой указателя с типом, соответствующим типу элемента и индекса, приведенного к указателю этого типа. И формула даже приведена для тех, кто слаб в грамматике английской речи.
fk0
02.11.2022 01:53В AWK массивы начинаются с единицы... Очень "удобно". AWK не намного моложе, чем C.
Iv38
02.11.2022 03:01+2Как говорил эксперт по выбору из двух вариантов (по совместительству солист группы Бредор): обе альтернативы не лишены недостатков, поэтому категоричный выбор не приносит удовлетворения.
Недавно я писал код по транспонированию матрицы, в нём удобнее работать с массивом нумерованным от нуля. Так при расчёте индексов при перестановках исключается постоянное добавление и вычитание единицы, код становится значительно менее перегруженным и более понятным.
С другой стороны, когда код работает с порядковыми числительными из реального мира, зачастую гораздо нагляднее выглядит нумерация с единицы. Банальный пример - словарь месяцев, которые и в человеческом календаре нумеруются с единицы, как и в большинстве языков программирования (кроме, кажется, JS). Тут же, не отходя от календаря стоит вспомнить, что дни недели зачастую нумеруются с нуля, либо и так и так.
Если язык программирования предоставляет выбор, это удобно.
Рассуждения по поводу нумерации индексов в C выглядят как попытка рационализировать постфактум то, что и так легко объясняется арифметикой указателей.
YanTsys
02.11.2022 06:04Все просто, раньше любой приличный язык программирования позволял прямо в коде в любом месте делать вставки на ассемблере и этим реально пользовались, а там реально для обращения к элементу массива необходимо было использовать смещение в памяти поэтому первый элемент естественно с нулевым смещением.
Кроме того раньше очень экономно использовали все доступные числовые значения, например тип char это всего 256 значений, вот просто так отказаться от использования значения 0? Да вы так все полимеры ...
iCpu
02.11.2022 09:29+1Как там писал Пелевин, "Миром правит не тайное ложе, а явная лажа".
C++, как и прочие Java, идут корнями в Си, а Си, изначально, - это структурированный макроассемблер. Наивный, тупой и безответственный - за это он и становится первым ЯП для любой новой железки. И все аналогии в нём максимально простые и ассемблерные.
A * a; a[i] == (*(A*)((void*)a + sizeof(A) * i));
Fortran, как и прочие MATLAB, языки глубоко специальные, и оперируют терминами предметной области. А в предметной области математических вычислений принято всё делать с единицы. Проще научить компилятор пересчитывать индексы, чем толпу математиков правильно переписать их формулы, да ещё на богохульную нумерацию с нуля! Вы бы ещё придумали, матрицы переписать в линейные массивы!!!
А задание области определения массива - это, конечно, интересная фишка, - но это синтаксический сахар над, собственно, областями. И если эти самые области больше нигде не встречаются, а индексация в языке реализуется через словарь, - требуйте возврата средств, вас обманули.
lain8dono
02.11.2022 13:16При этом няшный Си это в некоторой степени ассемблер для PDP-11. Особенно если мы говорим о версиях до стандартизации.
MaxLK
02.11.2022 12:46-2а что, нынче программистов не учат что в любой системе счисления первое число - 0, а не 1? для меня такого вопроса никогда не возникало - в первом семестре научили что все считается с нуля.
автору могу подкинуть идею для новой статьи - "почему все положительные координаты начинаются с нуля?".
IVA48
02.11.2022 18:56Нумерацию массивов как структур данных в программировании лучше начинать с 0 или с 1 ? А кому как будет удобнее для реализации конкретного алгоритма. В реальном мире арифметический счёт идёт с 1 и человек начал считать в десятичной системе потому что на руках у него было и остается 10 пальцев. Если структуры данных в массивах отражают объекты материального мира, то здравый смысл подсказывает что их счёт и нумерацию логично вести с 1. Если же массивом моделируется некая структура элементов, которые могут вставляться и удаляться, тогда нужно поддерживать как список занятых элементов, так и список свободных элементов, якоря которых могут быть, например, соответственно в минус первом и нулевом элементах, т.е. нумерация в массиве будет начинаться с -1. В конечном итоге все определяется конкретной задачей и личными предпочтениями программиста.
Aleshonne
02.11.2022 21:12В некоторых алгоритмах фильтрации сигнала чрезвычайно удобно использовать «симметричные» массивы вида a[-N]...a[0]...a[N]. Жаль, что не все языки такое позволяют.
IVA48
02.11.2022 22:28Кто (или что) в этом случае мешает вам использовать 2 одинаково объявленных массива и работать с ними также как если бы они были зеркальными в одном массиве ? Все пожелания в язык программирования "не запихнешь", да и делать этого не следует. Главный принцип в программировании это простота и наглядность, которые в первую очередь должны быть в самом языке и соблюдаться при разработке алгоритма и кода программы.
Aleshonne
02.11.2022 22:38+1Никто не мешает, конечно же. Как никто не мешает выделить кусок памяти и заниматься арифметикой указателей. Но один массив с диапазоном индексов [-N, N] даёт самую элегантную реализацию алгоритма.
max-zhilin
02.11.2022 18:56Главным основанием индексации от нуля все-таки являлас полнота использования размерности индекса. Для индекса, размером byte можно было адресовать массив в 256 ячеек, при адресации от 1 уже только 255.
Ну и для многомерных массивов при адресации от 1 добавляется операция вычитания на каждую доп. размерность в рантайме. Тогда это было важно.
Все остальное лирика.
dmagin
03.11.2022 20:49Путаница возникает из-за смешения двух разных понятий - индексов и их границ. В случае адресации к массиву индексами являются целые числа - 0, 1, 2,... Но их границы - это другой тип данных. 0] - это правая граница нуля, [1 - левая граница единицы и т.д. Обращаться к значениям массива можно либо через границы индексов, либо через сами индексы.
Январь - это первый месяц года, понедельник - первый день недели. Тут мы обращаемся к индексам. Когда же мы ссылаемся якобы на нулевой элемент массива, то на самом деле адресуемся к границе между индексами. Это нулевое смещение от левой границы массива.
В идеале можно было управлять типом адресации через вид скобок. Например, квадратные - обращение к границам, круглые - к индексам. Тогда А[0] == А(1).
Проблема различия элементов (интервалов) и их границ актуальна не только для индексов, но и для других типов данных. Для дат, например. Задать строго конец (или начало) суток обычно нетривиальная задача.
redsh0927
Ой, какие-то притянутые за уши рассуждения о системах счисления и расположении в памяти. Просто при любых действиях с интервалами смещения возникают чуть чаще чем на каждом шагу. Поскольку си делали умные люди для них было естественным что индекс массива - это смещение от начала блока до начала элемента, вот и всё.
nin-jin
А потом начались приколы с расположением мета-информации типа счётчиков ссылок или записи размера по отрицательным смещениям.
PuerteMuerte
Да, но на момент разработки С до потребности в этих приколах ещё четверть века оставалось. Писалось это на (и для) весьма недешёвой мини-ЭВМ, которая с периферией и накопителями занимала несколько 19" шкафов, и имела в те годы аж 64 килобайта ОЗУ в топовой комплектации. Поэтому да, в этой ситуации естественно упрощать в языке всё, что только можно, чтобы добиться максимальной эффективности.
johnfound
Мета-информация может варьировать в размерах, так что ее место именно на отрицательных смещениях. Так избегается перемещение данных.
pvvv
смещения возникают при адресной арифметике с указателями и собственно реализации массивов как указателя на положение первого элемента в памяти.
в языках где указатели как таковые явно не очень присутсвуют: mathematica, matlab, lua, ... массивы нормально индексируются с 1.
redsh0927
Смещения возникают везде где есть какие-нибудь координаты. Например, на линейке "3" находится по смещению 1см от "2", а год 2022 - по смещению 22 от 2000. Но, внезапно, по смещению 21 от начала 2-го тысячелетия. И 19-й век это почему-то 18хх. А матлаб прсото сжечь хочется когда нужно отмасштабировать, сместить, наложить диапазоны и т.п.
pvvv
когда что-нибудь сочитать надо, загибая пальцы, тоже с 0 начинаете?
redsh0927
не надо путать размер набора и смещение элемента
pvvv
я не путаю, я лишь спросил как обычно считаете в повседневной жизни.
с различными типами индексации при подсчёте элементов и при адресации:
первое яблоко, второе яблоко, третье яблоко, ... всего десять.
возьму пожалуй то, что со смещением два от начала массива. или всё-таки третье?
redsh0927
Одно, два, ..., десять яблок. В жизни никто яблокам номера не присваивает при их подсчёте. Просто сколько яблок переложил столько и пальцев загнул.
Если бы участок решил яблоками разметить разложив их через метр то были бы 0-е 1-е 2-е как деления на линейке
pvvv
Одно, два, ..., десять яблок. В жизни никто яблокам номера не присваивает при их подсчёте.
мне кажется что в первом предложении произошло ровно то, что как раз отрицается по втором.
SergeyMax
Ну в этом вы не ошиблись: "десять яблок" - это не то же самое, что "десятое яблоко".
BerkutEagle
Вы инкрементирующий десятичный счётчик яблок с массивом путаете :)
KvanTTT
Так исторически сложилось.
Wesha
Да. Ладонь, на которой ни один палец не загнут — это 0.
Tarakanator
представьте что вы токарь с 4 пальцами на одной руке. Вам нужно подавать сигналы напарнику. Типа передай инструмент N.(говорить не вариант-в цехе шумно)
Если нумерацию инструментов начинать с нуля, то вы можете адресовать только 15 инструментов. А если с нуля, то 16.
Wesha
Это фрезеровщик :)
nin-jin
А на экране первый пиксель начинается со смещения -0.5.
redsh0927
смещение/расстояние от левого края растра до начала "первого" пикселя = 0
nin-jin
Мейби, не помню деталей. Но чтобы его полностью закрасить приходится добавлять пол пикселя к смещению, иначе будут на половину закрашены два соседних пикселя.
me21
Где как, это у вас графический движок такой. У него логические пиксели, видимо, могут иметь произвольную координату, а затем это всё пытаемся нарисовать с помощью физических пикселей с целочисленными координатами, да ещё антиалиасинг задействован.
nin-jin
https://www.realtimerendering.com/blog/the-center-of-the-pixel-is-0-50-5/
me21
Именно это я и имел в виду. OpenGL, DirectX, антиалиасинг.
lightman
Мне так всю жизнь, с самого детства казалось нелогичным и неудобным что именование времени «пол-четвёртого» это 3:30. Всегда подвисал на этом «так, нужно взять четыре, отнять единицу, прибавить полчаса». Только раздражало это. Как и именование времени в старинном 12-часовом формате. «Три часа пополудни». «Так, нужно взять полдень, прибавить три часа, ага, значит сейчас 15:00»
Не, я понимаю что тут конфликт поколений. Любители такого странного именования выросли в советское время, для них «часы» это синоним механических с циферблатом, отсюда все эти странно звучащие отнимания четвертей и половинок.
Я, как человек рождённый уже на закате Союза, тоже конечно застал эти часы, но всё-таки вокруг уже повсюду окружали электронные 24-часовые, все мои наручные были такими.
И когда меня спрашивают время, всегда принципиально говорю «пятнадцать часов тридцать минут» Хотя, к сожалению, это заставляет подвиснуть уже вопрошающего, если он представитель старшего поколения, увы.
Wesha
Так всё правильно: "пол-четвёртого" = "через полчаса будет четыре", "без десяти пять" = "через десять минут будет пять". Меня не волнует время, которое уже прошло; меня волнует время, которое скоро наступит — и мне надо знать, КАК скоро. Например, у меня в пять важная встреча, и мне надо понять — успею я на метро, или надо хватать такси и нестись пулей. А когда мне говорят "четыре двадцать две", я зависаю, пытаясь понять, сколько до пяти осталось-то.
Iv38
А если говорят "без четверти пять", а встреча в 18:00 (так в письме написано), не надо делать пересчёты в голове?
Wesha
Так там всего 6 значений — 13, 14, 15, 16, 17 и 18, маппинг "6->18" заучивается после третьего раза. А в 19-00 нормальные люди уже давно не на работе ;)
lightman
Вы так говорите, будто все работают строго 9-18. Бывают предприятия работающие в несколько смен, дежурства. Да и кроме работы есть жизнь: личные встречи, свидания и т.д.
anka007
Интересно как выглядел мир, если бы победила двенадцатиричная система счисления, а не десятичная.
khajiit
двадцать две минуты пятого
Iv38
Полчетвёртого ещё ладно. В детстве меня подвешивали определения времени вроде "четверть пятого" или "двадцать минут шестого". С половинами я как-то справлялся на лету (не знаю почему, они по сути не отличаются), а вот такие определения приходилось реально раскручивать в голове. Ага, идёт шестой час, значит полных часов пять и ещё двадцать минут. На мой взгляд, часы со стрелками не объясняют зачем переключаться между количественными и порядковыми числительными. Со стрелочных часов вполне легко считывается "пять двадцать". Ещё можно понять зачем говорить "без пяти пять" или "без четверти пять" - стрелка часовая ближе к пяти (и при этом нет перехода к порядковым числительным), но "двадцать минут шестого" зачем?
VXP
А ещё б-гомерзкое "четвёртый час", что значит "3 часа"
Didimus
И 19-й век..
При этом нет нулевого часа в сутках, но есть нулевой километр на дорогах. Считаем это аксиомами и работаем с этим
morijndael
Как это нет? А как же 00:00?
Tarakanator
Это не нулевой час. Это ноль часов.
После рождения Иисуса прошло 0 лет, но шёл первый год нашей эры.
unC0Rr
Нулевого километра нет. Есть отметка с некорректным названием "нулевой километр", но сразу от неё начинается первый километр. Участка дороги протяжённостью километр с нулевым номером не существует.
Tarakanator
Нулевой километр, это точка.
А первый час это отрезок.
Т.е. нулевой километр это не первый километр -1. Это отдельное понятие.
IVA48
Тогда элементы одного массива не будут обладать одинаковыми свойствами: один будет 'точка', другой 'отрезок', ... , последний 'остановка'. То есть будут противоречить фундаментальному определению массива как индексированному набору ОДНОРОДНЫХ данных. Хотя для любителей вести счёт (нумерацию) с 0 все как надо, а тех кому привычнее с 1 могут нулевой элемент просто не использовать.
Tarakanator
вы не поняли.
Нулевой километр, это не нулевой элемент массива километры. Это отдельное понятие.
michael_v89
Ну есть считать, что полностью это выглядит как "прошла половина четвертого часа", то выглядит вполне логично. Четыре часа еще не прошло, только четвертый идет.
lightman
Даже эта фраза-объяснение звучит немного неуклюже. При беглом чтении она воспринимается нелогичной, мозг на ней невольно спотыкается и заставляет перечитать ещё раз.
Насколько бы она лучше звучала в формате "четыре часа ещё не прошло, только третий идёт"
Rsa97
Или «Час ещё не прошёл, только нулевой идёт»?
lightman
Этого достаточно, фраза сама по себе исчерпывающая
michael_v89
"Мама сказала, что придет с работы через четыре часа, а четыре часа еще не прошло, только третий идет, еще больше часа можем мультики смотреть".
Когда время 3:30, третий час после полудня уже прошел, неправильно говорить, что он еще идет. Если днем в 12:42 еще можно сказать, что идет двенадцатый час, а не первый, то ночью в 0:42 как говорить, "время нулевой час ночи"? Просто надо привыкнуть к мысли, что первое число показывает, сколько часов уже прошло, а не какой час идет.
Аналогично с расстоянием, если мы прошли 900 метров, мы идем первый километр, а если 1700, то второй.
soul32bit
Если не секрет, а где вы живёте, что вас "повсюду окружают электронные 24-часовые"? Спрашиваю, потому что в каждом, в каждом доме, где я бывал на стене висят именно стрелочные часы.
lightman
Настенные часы, как мне кажется, это уже больше элемент интерьера, символ домашнего уюта, дань традиции, если угодно.
Да, я себе их тоже повесил, но я на них почти не смотрю (да и через пару лет после покупки надоело в них менять батарейки, теперь просто висят)
Время считываю из операционной системы компьютера, планшета или телефона - в зависимости от того, с каким устройством работаю в данный момент.
Tarakanator
у меня часы висят только в углу экрана. И они там 24 часовые.
aso
А потом Вы пытаетесь выделить подстроку из строки - или подматрицу из матрицы - и начинаются танцы с бубном...
Вообще же, "ноль" - является естественно выделяемым значением в области чисел, хоть натуральных, хоть целых, хоть каких.
pvvv
Танцы с бубном будут в любом случае при перетаскивании данных (оссобенно многомерных, просто упакованных в последовательный кусок памяти за неимением человеческих массивов) между двумя разными способами индексации. А внутри каждого определённого способа с 0 или 1 всё нормально.
Во многих 1-based языках есть ещё синтактический сахар для адресации с конца массива отрицательными индексами, там уже с бубном придётся танцевать если индексация начинается с 0, да и "опечаток" с записью в последний элемент как a[N] вместо a[N-1] тоже хватает. Так что куда эти грабли с +-1 перекладывать в начало массива или в конец - разница не велика.
Может просто привык уже пользуясь и вольфрамовской математикой и C+Lua что способы индексации бывают разные, и не считаю что какой-то из них единственно верный. Если нужны указатели и прямая работа с памятью - удобнее смещениями с 0, а ворочать какие-нибудь многомерные массивы в математике, не задумываясь как они там в памяти расположены, иногда удобнее с 1.
Didimus
В математике принято нумеровать, например, члены ряда, с единицы. Я так помню. Например, в формуле суммы ряда, индекс от 1:
Hidden text
aso
Почему "притянутое"?
Любые типы в каждом конкретном компьютере - занимают вполне конкретное число бит.
И массив из 256 элементов - может быть проиндексирован значением типа uint8.(И, соответственно - аналогично и для других типов.)
redsh0927
Это всё хорошо, но основная причина того что адресовать элементы смещением удобно и правильно лежит не в диапазонах переменных и не в экономии тактов. Это лишь приятные бонусы (хотя и неизбежные, если смотреть глобальную картину).