Говоря коротко, С работает везде. Этот язык — не просто большой динозавр, которому каким-то образом удалось дожить до наших дней. Он крайне практичен, поэтому невероятно успешен. Удивительно, но истоки С далеки от успехов. Он стал итогом десятилетий неудач, и, если бы не старания любящего программировать на каникулах школьного учителя, язык мог бы не появиться. Это история о невероятно медленных компьютерах, трёх Дэвидах и давнем наследии языка C. К старту курса о программировании на С++ делимся переводом статьи с краткой биографией Кристофера Стрэтчи.
О распостранённости C от ядра Unix до CPython
Unix написан на C. Вначале написанное на ассемблере, ядро Unix переписали на C ещё в 1973 году. Это сделало Unix намного более переносимой на разные машины и способствовало её популярности. Без Unix у нас не было бы всех прекрасных современных операционных систем — вспомните Linux, Mac OS X, Android, iOS, Chrome OS и вообще всё, на чём работает ваш роутер.
Если вы имели дело с базами данных, то наверняка также работали с C, даже если не знали об этом. Системы управления базами данных — Oracle Database, MySQL и другие написаны на C. Большинство из них с тех пор переписаны на C++, но он — прямой потомок C.
И даже если вы знаете только Python, вероятно, всё это время вы работали с C; только если не выбрали Jython, IronPython или PyPy, то вы писали на реализации языка CPython. Вы пишете код на Python, но интерпретатор, который преобразует ваш код в код, понятный машине, — на самом деле написан на C.
Школьный учитель, друживший с Аланом Тьюрингом
Познакомьтесь с Кристофером Стрэтчи. Он родился в 1916 году во влиятельной британской семье, учился в Кембриджском университете и познакомился там со многими известными учёными. Многие годы он, похоже, пренебрегал учёбой, и его результаты на выпускных экзаменах оказались весьма плачевными. Возможно, именно поэтому он не продолжил академическую карьеру, как многие его сверстники, но во время войны проводил исследования промышленных радаров, затем став школьным учителем и оставаясь им до 1951 года. Именно тогда всё изменилось.
Друг познакомил его с оборудованием Британской национальной физической лаборатории. Стрэтчи стал проводить свои школьные каникулы и вынужденные задержки на работе в лаборатории с Pilot ACE, первым компьютером с автоматическим вычислительным механизмом Алана Тьюринга. Стрэтчи стремился научить компьютер играть в шашки. Решить эту задачу в то время, когда компьютеры в основном применялись в быстром решении уравнений, — это было удивительно.
Первая попытка не удалась: у Pilot ACE не хватало памяти. Возможно, это кажется вам смешным, но вспомните — шли 1950-е годы! Вместе со своим старым приятелем Тьюрингом Стрэтчи удалось разработать машину совершеннее — Ferranti Mark I. Под руководством Тьюринга Стрэтчи наконец-то научил компьютер играть в шашки. Слухи о необычном достижении распространились быстро, и вскоре Стрэтчи позвали продвигать разработки в области информатики для британского правительства.
Три Дэвида захотели создать новый язык
Тем временем в Кембридже хотели купить новый компьютер: в то время это было большим делом, а поскольку компьютер был совершенно новым, было ясно, что ему нужна новая операционная система, но не обязательно новый язык, однако три Дэвида — Хартли, Бэррон и Уиллер — задумались о том, чтобы изобрести его, а заодно развлечься. Добавим, что эти трое не спросили будущих пользователей о плюсах и минусах старых языков. Например, Fortran IV в то время уже использовался и вполне подходил для работы. Однако трое Дэвидов решили, что смогут легко создать нечто лучшее. Кембриджский университет скептически относился к новому языку, но победили трое Дэвидов.
Они назвали язык CPL (Cambridge Programming Language), а после того, как к трём Дэвидам присоединилась пара исследователей из Лондонского университета, он стал называться Combined Programming Language. Оглядываясь назад, Хартли отмечает, что попытка создать новый язык была «чертовски глупым занятием».
Новый язык оказался слишком сложным
Возможно, также было «чертовски глупо» привлекать Кристофера Стрэтчи к руководству проектом. В его вычислительных способностях сомневаться не приходилось, но он казался влюблённым в проект настолько, что не мог расставить приоритеты. Вместо того чтобы эффективно решать крупные проблемы, разработчики зациклилась на мелких — и вскоре CPL стал известен как Christopher’s Programming Language.
Из-за всех этих внедряемых по настоянию Стрэтчи мелких деталей язык стал слишком сложным. Когда разработчики захотели написать компилятор, сделать этого не удалось: получаемый машинный код оказывался неэффективным.
Позже язык стал слишком простым
Когда Стрэтчи уехал на несколько месяцев в MIT, к окманде присоединился Мартин Ричардс и решил упростить язык. Конечно, целью было получить такой язык, который позволял бы написать хороший компилятор и получить эффективный машинный код. Ричардс удалил из CPL всё, что не было важным, превратив CPL в BCPL — Basic CPL, который в каком-то смысле был не только новым языком, но и признанием провала CPL.
Когда чуть позже в Массачусетском технологическом институте Ричардс присоединился к Стрэтчи, посмотреть, чем они вдвоём занимаются, пришёл их коллега из Bell Labs. Этого человека звали Кен Томпсон. Он работал над Multics — ещё одной операционной системой, от которой собирались отказаться. Система выжила, но это уже другая история.
В итоге Томпсон переключился на разработку Unix — по сей день одну из самых востребованных операционных систем. Ему удалось установить Unix на маленький компьютер PDP-7, но BCPL для этой машины оказался громоздким. Томпсон сократил язык до функций, которые посчитал наиболее важными, и назвал новый язык B.
Как родился Си
В 1971 Деннис Ричи адаптировал B и, чтобы сделать его полезным для компьютеров мощнее, добавил языку функциональности. Получившийся язык он назвал NB — New B.
Тем временем Томпсон переписывал Unix на язык высокого уровня. Тогда большинство ОС писали на ассемблере, из-за этого каждый раз после покупки новой машины даже приходилось разрабатывать новую операционную систему; но Unix на языке высокого уровня работала бы на любой машине.
Томпсон попытался переписать Unix на NB, но потерпел неудачу. Затем Ричи и Томпсон добавляли новые функции в NB, до тех пор пока затея не увенчалась успехом. Примечательно, что разработчики добавили в язык структуры — тогда их не было ни в одном языке. Нововведение оказалось достаточно заметным, чтобы переименовать NB, и в 1973 году родился С. Вскоре язык C стал популярным и до сих пор остаётся таковым.
С распространялся подобно медленному лесному пожару
Хотя многочисленные изобретатели С были уверены в себе, они не ожидали, что их язык покорит столь огромные просторы. Одной из причин успеха могла быть чрезвычайная популярность PDP-11. Установить на нём Unix было совсем недорого, а вместе с ней поставлялся С. Большинство компьютеров тогда работали в университетах; заканчивая университет, многие студенты уже были знакомы с C. Это помогло языку распространиться во многих отраслях и, конечно, в академических кругах. Наконец, книга Брайана Кернигана и Денниса Ритчи «Язык программирования C» была простой и лаконичной, особенно для текста о языке программирования. Эта книга упростила новичкам освоение языка.
Несмотря на свой возраст, C может быть проворнее вашего дедушки
Каким-то образом, непроторёнными тропами, язык С достиг успеха. Но этот язык — результат слишком сложного CPL, слишком простого BCPL, ещё более простого B и немного более сложного, но всё же достаточно простого NB. А без задачи Томпсона переписать Unix С никогда не появился бы.
Кристофер Стрэтчи, бывший школьный учитель, слишком усложнил многие вещи и тем самым положил начало череде неудач. Но без них язык С, возможно, не существовал бы.
Особенности языка C проявляются повсюду. Такое важное понятие, как структуры, а также подобные операторам инкремента и декремента ++ и — мелочи сегодня можно найти во многих языках; без С всего этого не было бы, как и потомков языка — C++, C# и т. д.
И всё это — только наследие чрезвычайно быстрого и безопасного языка, качества которого делают его предпочитаемым инструментом в таких сложных областях, как робототехника, компьютерное зрение, интернет вещей и т. д. Это не означает, что подобные Python языки не используются, особенно для первых набросков. Но последний шаг — если хотите, тонкая полировка — часто пишется на C или C++.
Да, если судить по его возрасту, C — динозавр. Но, держу пари, он шустрее и проворнее вашего дедушки! Или вашего внука, если уж на то пошло…
Станьте разработчиком на C++ и пишите надежный код, который легко поддерживать, с помощью нашего курса "Профессия C++ разработчик". Специальная скидка по промокоду HABR.
Узнайте, как прокачаться и в других специальностях или освоить их с нуля:
Другие профессии и курсы
ПРОФЕССИИ
КУРСЫ
kisskin
За индексы, начинающиеся с 0 вместо 1, будет гореть в аду!
Myclass
Я понимаю, что у такого подхода есть минусы. Но ведь есть и плюсы. Поэтому такой радикальности ведь не надо. В разных странах есть например и этажи с номером 0 и ничего - живут ведь люди.
kisskin
минус — это количество времени программистов на то чтобы выискивать случайные баги в угоду не сильно нужной псевдо«оптимизации» скорости работы программы, чтобы ей смещение не нужно было уменьшать на 1. (хотя всю эту работу можно было банально встроить в компилятор)
NeoCode
Ни к каким багам это приводить не должно. Да, Си неидеален, у него есть объективные недостатки — например лексический препроцессор вместо синтаксического, система include вместо модулей, не first-class массивы (имя массива это адрес первого элемента, а не объект), даже такая мелочь как перепутанные приоритеты битовых операций и операций сравнения (правильнее было бы, чтобы битовые операции были более приоритетны чем операции сравнения).
Но вот индексация с нуля — это не недостаток, а вполне нормальная возможность. Пишу на С/С++ уже 20 лет и никаких проблем с этим не испытывал, даже на первом курсе института когда только начинал учить Си:)
Scinolim
Именно за счёт всех этих недостатков он остаётся таким совершенным. Сегодняшние популярные языки через 20 лет никто и не вспомнит, а Си будет вечен, пока процессоры не перестанут быть основаны на фоннеймановской архитектуре.
NeoCode
Нет, просто огромная кодовая база и своеобразный статус Lingua Franca. Перечисленные мной недостатки к популярности никакого отношения не имеют, и если бы их не было, скорее всего Си был бы еще популярнее.
Да и фоннеймановская архитектура здесь не при чем. Есть микроконтроллеры, основанные на гарвардской архитектуре, и под них все равно пишут на Си.
Да, я НЕ считаю недостатками небезопасную работу с указателями, отсутствие сборки мусора, индексы не с нуля, отсутствие проверок выхода за границы массива, не экстремально строгую типизацию как в Rust и т.п. Это все особенности низкоуровневого языка, без которых язык был бы другим. Но это не отменяет того факта, что во время создания Си многие подходы и парадигмы были неотработаны (да и вообще программированием занималось значительно меньше людей чем сейчас), поэтому в дизайне языка разумеется существуют некоторые ошибки.
vesper-bot
Ирония — когда С создавался, его звали "ЯВУ", сейчас "понизили уровень", я в каком-то месте нарывался на определение С как "ассемблер высокого уровня". А то, что сейчас ЯВУ, в те времена просто не могло быть воображено (даже Python, и тот ещё не мог поместиться в тогдашние парадигмы ИМХО).
BD9
Враньё. Выстраивание истории на знакомстве с парой известных ЯП. Малограмотная школота.
Lisp -1958 г., Simula — 1967 г. Можно и другие поискать и найти.
Алгол успел появиться, пройти глубокое обновление, после него — Паскаль уже успел появиться. И всё — до Си.
Си — язык среднего уровня. Затем и нужен.
unC0Rr
Как минимум, если завернуть массив в структуру, можно будет его передавать в/из функции и даже пользоваться простым присваиванием.
AnthonyMikh
Можно. Но зачем мне вообще нужны дополнительные телодвижения ради такой банальной задачи, как "вернуть из функции массив"?
ProLimit
Наверное чтобы вы понимали стоимость такой операции с точки зрения быстродействия. По моему логично не давать способа передачи объектов копированием при тогдашних ресурсах памяти..
tyomitch
Но структура размером 100500 байт передаётся-таки копированием!
gdt
В чужой монастырь со своим уставом не ходят. Пишите на паскале, там хоть с -100 или с true можно начинать массив.
NeoCode
Кстати, на С++ вполне можно написать свой класс массива с индексацией чем угодно и с какого угодно начального индекса.
Gordon01
ПХП делает вид что не слышит того, что вы говорите
bugdesigner
А ещё можно применить карты
OCTAGRAM
А вот чтоб не абстрактно можно написать, а ночью разбуди, любой программист, претендующий на C++, знал, где такое готовое взять?
Где взять ограниченные диапазоном числа и перечисления. Где массив взять, который с ними скомбинируется и позволит использовать такой параметр как индекс, как в обычных языках
mayorovp
Индексация с единицы тоже способна приводить к багам.
Скажем, любые алгоритмы длинной арифметики просто просят индексацию с нуля.
tyomitch
Я жил в доме, где нумерация этажей начиналась с -2, и теперь требую ЯП с индексацией массивов с -2!
tvr
А я сейчас живу на то ли на втором этаже, то ли на минус первом — смотря с какой стороны посмотреть.
Holix
Да начнётся священная война!
0 — это не совсем индекс, а скорее смещение от начала массива. Поэтому у первого элемента индекс 0. Более того, постфиксный оператор
[]
это синтаксический сахар для*
(разыменование указателя). Т.е.<pointer> [ <expression ]
то же самое, что и*( <pointer> + <expression> )
.Хотите детские индексы с 1 — пишите на basic.
kisskin
я это отлично всё знаю. это было актуально в эпоху 2 Мгц процессоров, чтобы компьютеру не нужно было пересчитывать смещение с 1 на 0. Но в итоге это стало больших рассадников багов(не забыть писать Count-1 и т.п.) и потерь человеческого времени, т.к. человеку привычнее считать с 1.
DrPass
И 2МГц, и 12МГц, и 33МГц. Перестало быть актуальным на персональных компьютерах где-то в эпоху Пентиумов. А на бесчисленном множестве контроллеров актуально до сих пор.
Хм. Насколько я знаю, человек привыкает считать массивы с 0 где-то ко второму месяцу обучения программированию :) Кроме того, контроль границ массивов в языки пришёл ещё в 1980-е, а чуть позже и циклы foreach. Так что проблема явно надумана.
hiddenman
Всё же стоит признать, что на C программировать практически никто не умеет. Стоит открыть какую-нибудь cve.mitre.org и поизучать уязвимости, процентов 90, если не больше там будут уязвимости, напрямую связанные с языком, его особенностями, реализацией и возможностью как выстрелить себе в ногу, так и отстрелить её.
К примеру, будь это все написано на каком-нибудь Pascal, этих уязвимостей бы просто не существовало. Все считают, что они умеют писать на C и это дает им какие-то преимущества, на деле же писать на нём грамотно и без ошибок умеют единицы. При это именно программировать, то есть составлять алгоритмы работы, умеют очень многие и очень хорошо. А вот реализовывать их на C почти никто не умеет. Но при этом постоянно лезут это делать.
Почему, кстати, программировать до сих пор учат на паскале, а не на C или C++ или Python, очень хорошо раскрыто в первом томе книги Столярова, рекомендую (https://habr.com/ru/news/t/545142/)
qrKot
Всё же стоит признать, что от замены C на любой другой язык программирования в вашей цитате, ничего не меняется. Фраза однозначно верна для любого языка.
0xd34df00d
Только в других языках возможностей по выстрелу себе в ногу куда меньше.
ZXY000
Язык G на потоках, модульная графическая интерпретация того же С. Даже и представлять не надо, вижу как меня сейчас здесь начнут закидывать тазиками.
"Кодирование больше не является главным событием. Создание программного обеспечения - главное событие. Кодирование - это лишь небольшая часть этого." Chris Wanstrath Co-founder, CEO GitHUB
hiddenman
Серьезно? И как же вы сделаете buffer overflow или нарушение boundary на Pascal/Java/Python/Go/Perl/whatever?
P.S. Ну на каком-нибудь Паскале можно еще, если специально прямо постараться, там указатели есть. Но в целом все эти проблемы уходят как класс. Каждый второй cve - buffer overflow и RCE при парсинге ввода данных в стиле "забыл -1 написать в конце" или "выделил памяти меньше чем мне прислали данных".
mayorovp
Но багов из-за индексации с нуля там будет раз-два и обчёлся.
hiddenman
Смотря что считать таким багом. Багов, где забыли указать -1 и вызвали переполнение - тысячи.
Nick_Shl
Зачем писать Count-1 ??? Если мне надо пройти 10 элементов массива то:
А если бы начиналось с 1, то пришлось бы писать 10 + 1. Т.е. сейчас мне не надо писать count-1, а пришлось бы писать count+1 если бы всё было так как вы предлагаете.
BasicWolf
Не обязательно, если включить десять посредством
i <= 10
то можно начинать от 1 :)Но проблема раздута сверх всякой меры. В школе много лет программировал на бейсике, а когда перешёл на C, нижняя граница массива от 0 стала просто аксиомой.
erzi
Тут лишний символ "=" и операция <= чуть медленнее <
А главное, что были введены { } вместо begin-end, next, then
tyomitch
O_O
С какой это стати?
chersanya
Полно языков с индексами по-умолчанию как с нуля, так и с единицы. Если по какой-то причине для вас это важно, выбирайте язык с учётом этого критерия.
Например, с нуля C(,++,#), Python; с единицы Fortran, Matlab, R, Julia. Я писал и на тех и на тех, в том числе почти одновременно, особых проблем не увидел.
kunix
Начинать индексы с нуля - более математично. Например:
1) array[i%count]
2) индекс - количество элементов перед текущим
Мое ИМХО - индексы с нуля позволяют сократить количество +1 -1 в коде.
BD9
Ряд натуральных (то бишь естественных) чисел намекает что вот нихера.
Си и его предки суть улучшенный ассемблер. Сделали по тупому. Во многом из-за простого и быстрого способа обнуления регистров.
На уровне компилятора и определения циклов можно поправить. Но создателям было пох, ибо всё равно лучше асма, а остальные — привыкли.
tyomitch
Как насчёт международного стандарта ISO 80000-2 «Mathematical signs and symbols to be used in the natural sciences and technology»?
kunix
Ой вы не правы про ряд натуральных чисел.
Без нуля он неполон и убог.
Я продолжу:
3) array[i*N+j]
Scinolim
Более того, без 0 ряд натуральных чисел даже перестаёт быть кольцом и становится совершенно бесполезным.
kunix
Справедливости ради, он и с нулем не кольцо.
Потому, что нет обратных по операции +.
Вот целые числа - это кольца, да.
Однако, без нуля теряется красота.
Рассмотрим полный набор остатков по модулю N.
Можно брать {0,...,N-1} или {1,...,N}.
Это множество - группа с операцией "сложение по модулю".
В первом случае нейтральный элемент вегда 0, независимо от N.
Во втором случае нейтральный элемент - N, у каждой группы свой.
Теряется красота.
mayorovp
Уточнение: без 0 ряд натуральных чисел даже перестаёт быть полукольцом
tzlom
По опыту работы с своим и чужим кодом в языках где индексы начинаются с 0 и 1 — ошибки на 1 совершаются вне зависимости от языка, обобщённого решения этой проблемы нет, помогают итераторы по диапазонам, но всё равно рано или поздно будет индексация и ошибки. Проблема не в индексации которая "неправильная", а в людях которые делают ошибки.
Cryvage
В программировании есть 3 фундаментальные проблемы: инвалидация кэша и ошибка на единицу.
mayorovp
Вообще-то в программировании есть 2 фундаментальные проблемы: наименование объектов, инвалидация кэша и ошибка на единицу.
redsh0927
Индекесация с 1 — проклятье человечества. Впрочем, те кто придумал это говно — уже давно жарятся в аду.
Почему 20хх годы — 21-й век? Потому что какой-то дебил придумал нумерацию с единицы.
И ты не можешь просто разделить или умножить чтобы что-нибудь отмасштабировать, надо сначала вычесть 1, потом добавить. или сначала добавить потом вычесть? да хрен его знает. Ужасно бесит. Работал одно время со сраным матлабом — я не знаю как монитор выжил!
Си и Ассемблер — пара языков, единственные позволяющие создавать качественные программы. Ничего существенно лучше нет и не предвидится. Радует что ныне всё больше людей начинают осознавать куда катится софт и начинают вспоминать про Си.
warlock13
Rust
AnthonyMikh
Эта фраза выдаёт в вас просто фантастическую нехватку кругозора. За всю историю существования человечество успело наклепать несколько тысяч языков программирования. Вы действительно хотя бы кратко ознакомились хотя бы с процентом от этого количества, чтобы иметь основание для подобного утверждения?
geher
А не надо путать индексацию с нумерацией.
20хх годы - 21 век. Номер века - это не первые цифры года. Это именно его порядковый номер. А первый век новой эры был именно что 1-100 годы.
Согласно смыслу количественных числительных, когда у вас появляется первый седой волос - это таки один седой волос, второй - это когда к первому добавляется еще один, т.е. два.
Нулевой - это другое. Это когда у вас еще ничего нет, т.е. ноль. Потому нулевой километр - это начало. Точка. Первый километр пошел, когда что-то уже проехали.
А при индексации можно вообще с любого числа начинать, и даже не подряд (другой вопрос, что не каждый язык программирования такое позволит для массивов). Как нарисуем индексы для элементов, так и будет.
Если же выбора нет, и есть только один вариант, то для индексации массива удобнее таки начинать с нуля, поскольку адрес нулевого элемента указует на начало массива, т.е. еще самое начало, и ни одного элемента еще не прошли. Адрес начала второго элемента указывает на конец первого, т.е первый элемент прошли, второй еще нет (второй палец для счета загибать еще рано).
Опять же, смещение от начала массива у первого элемента - ноль. Тоже удобно плясать при индексации.
Так что x[0] в сях - это не нулевой элемент массива, а таки первый, но в то же время он же - элемент с нулевым индексом.
Опять же, не надо путать индексацию с нумерацией.
Впрочем, любой язык с произвольной индексацией массивов вам в помощь. MUMPS, например.
Хотя в вашем случае и Паскаль справится:
domix32
А это не индекс, а сдвиг (offset) от начала блока памяти на sizeof его элемента
ibrin
Если без нуля, то как из цикла JNZ @loop выходить? С единицей сравнивать чтоли? Бред какой-то...
tyomitch
Сравнивать с нулём перед использованием, а не после — вот и вся разница.