- C — это не то, «как работает компьютер».
- Не думаю, что большинство людей говорят буквально, так что это неважно.
- Понимание контекста означает, что учить С по этой причине всё еще может иметь смысл, в зависимости от ваших целей.
Я планирую написать ещё две статьи с более подробным объяснением выводов, но этого уже достаточно. Добавлю сюда ссылки, когда статьи выйдут.
Я часто слышал от людей такое:
Изучая C, вы можете понять, как работают компьютеры.
Не думаю, что идея изначально неправильна, но она имеет некоторые оговорки. Если держать их в уме, то она вполне может быть жизнеспособной стратегией для изучения новых и важных вещей. Однако я редко вижу, чтобы люди подробно обсуждали данные оговорки, поэтому пишу эту статью, чтобы предоставить, по-моему, очень нужный контекст… Если вы думаете об изучении C для понимания работы компьютера, то статья для вас. Надеюсь, она поможет во всём разобраться.
Прежде чем мы действительно начнём, хотел бы сказать ещё кое-что: если хотите изучить C, то изучайте! Учиться — это здорово. Изучение C стало очень важным для моего понимания вычислительной техники и моей карьеры. Изучение этого языка и его места в истории языка программирования сделает вас лучшим программистом. Вам не нужно никакое оправдание. Изучайте вещи просто ради обучения. Эта статья призвана стать ориентиром, чтобы разобраться в истине, она не обсуждает, нужно или нет изучать С.
Прежде всего, кому вообще рекомендуется эта идея. Если вы пытаетесь «узнать, как работают компьютеры», то само собой разумеется, что вы в настоящее время этого не понимаете. Какие программисты не понимают, как работают компьютеры? Я в основном видел, что это чувство исходит от людей, которые в основном программируют на динамически типизированных «скриптовых» языках, таких как Ruby, Python или JavaScript. Они якобы «не знают, как работают компьютеры», потому что эти языки работают внутри виртуальной машины, где имеет значение только семантика виртуальной машины. В конце концов, вся идея виртуальной машины заключается в обеспечении переносимости. Цель в том, чтобы не зависеть от оборудования, на котором работает VM.
Есть только одна проблема: C тоже работает внутри виртуальной машины.
Абстрактная машина C
Из спецификации C99, раздел 5.1.2.3, «Выполнение программы»:
Семантические описания в этом Международном Стандарте описывают поведение абстрактной машины, в которой вопросы оптимизации не имеют значения.
На мой взгляд, это важнее всего понять при изучении C. Язык не «описывает, как работает компьютер», а описывает, как работает «абстрактная машина C». Всё остальное важное вытекает из этой концепции.
Еще одно замечание: здесь я выбрал C99, который не является последним стандартом C. Почему? Ну, в MSVC есть… интересная поддержка языка С, и в наши дни я пользователь Windows. Да, вы можете запускать
clang
иgcc
под Windows. Между C89, C99 и C11 не такая большая разница в отношении того, о чём мы говорим. В какой-то момент приходится выбирать. Версия, которую я здесь упомянул, включает в себя некоторые правки к первоначальной спецификации.
Возможно, в разговорах о C вы слышали ещё одну фразу: «C — переносимый ассемблер». Если задуматься об этой фразе, то вы поймёте, что если это правда, то C не может соответствовать работе компьютера: существует много разных компьютеров с разной архитектурой. Если C похож на ассемблер, который работает на разных компьютерах с разными архитектурами, то он не может одновременно функционировать в точности так, как каждый из этих компьютеров. Он должен прятать детали, иначе не будет переносимым!
Тем не менее, я думаю, что данный факт не имеет значения, потому что вряд ли люди буквально имеют в виду «C — это то, как работает компьютер». Прежде чем вернуться к этому, поговорим об абстрактной машине C, и почему многие, кажется, не понимают этот аспект языка C.
Отступление: почему люди заблуждаются?
Могу рассказать только о своём опыте, хотя наверняка он не уникален.
Я изучил GW-BASIC, потом С, потом С++, потом Java. Я слышал о Java до того, как начал писать на ней примерно в 1999 году, через четыре года после её появления. Маркетинг в то время активно противопоставлял Java и C++, он сосредоточился на JVM как платформе, и на том, что модель машины отличает её от C++, и, следовательно, C. Sun Microsystems больше не существует, но зеркало пресс-релиза напоминает нам:
Приложения на Java не зависят от платформы; нужно лишь портировать виртуальную машину Java на каждую платформу. Она действует как интерпретатор между компьютером пользователя и Java-приложением. Приложение, написанное в среде Java, может работать в любом месте, избавляя от необходимости переноса приложений на несколько платформ.
Главным девизом было «Пиши один раз, запускай везде». Эти два предложения стали тем, как я (и многие другие) пришёл к пониманию Java, и как она отличается от C++. У Java есть интерпретатор, виртуальная машина Java. В C++ нет виртуальной машины.
С таким мощным маркетингом «виртуальная машина» в умах многих людей стала синонимом «большой среды выполнения и/или интерпретатора». Языки без этой функции были слишком привязаны к конкретному компьютеру и требовали портирования, поскольку не являются по-настоящему независимыми от платформы. Главной причиной существования Java было изменение этого недостатка C++.
«Среда выполнения», «виртуальная машина» и «абстрактная машина» — разные слова для одного и того же фундаментального понятия. Но с тех пор они получили разные коннотации из-за незначительной дисперсии в реализациях этих идей.
Я лично считаю, что этот маркетинг 1995 года — причина, почему программисты до сих пор неправильно понимают природу C.
Так это утверждение ложно? Зачем Sun Microsystems тратить миллионы и миллионы долларов на пропаганду лжи? Если C тоже основан на абстрактной машине, которая предлагает переносимость между платформами, зачем нужна Java? Думаю, что это ключ к пониманию того, что люди действительно имеют ввиду, когда говорят «С — это то, как работает компьютер».
Что люди на самом деле имеют в виду?
Хотя C работает в контексте виртуальной машины, он по-прежнему значительно отличается от Java-подобных языков. Sun не врала. Чтобы понять, нужно знать историю С.
В 1969 году в Bell Labs была написана компьютерная операционная система на языке ассемблера. В 1970 году её окрестили UNIX. С течением времени Bell Labs покупала всё больше и больше новых компьютеров, включая PDP-11.
Когда пришло время портировать Unix на PDP-11, они решили использовать язык более высокого уровня, что было довольно радикальной идеей в то время. Представьте, что сегодня я вам скажу: «Я собираюсь написать ОС на Java» — вероятно, вы будете смеяться, хотя идея реализуема. Ситуация (в моём понимании, я тогда не жил) была примерно аналогичной. Рассматривался язык под названием B, но он не поддерживал некоторые функции, которые были у PDP-11, и поэтому они создали преемника, назвав его “C”, поскольку это была следующая буква в алфавите.
Языка “A” не было; B стал преемником BCPL (Basic Combined Programming Language).
В 1972 году на PDP-11 написали первый компилятор C и одновременно переписали UNIX на C. Изначально о переносимости не думали, но C получил известность, так что компиляторы C портировали на другие системы.
В 1978 году вышло первое издание книги «Язык программирования С». Ласково именуемая «K&R», по именам её авторов, книга совсем не была похожа на спецификацию, но при этом достаточно подробно описывала язык, в результате чего другие тоже попытались написать компиляторы С. Позже эту «версию» будут называть «K&R C».
По мере распространения UNIX и C их обоих портировали на многие компьютеры. В 70-х и 80-х годах их аппаратная база непрерывно росла. Точно так же, как C создали, потому что B не поддерживал все функции PDP-11, многие компиляторы использовали расширения языка. Поскольку существовал только K&R, а не спецификация, то это считалось приемлемым, пока расширения были достаточно близки. К 1983 году отсутствие какой-либо стандартизации стало вызывать проблемы, поэтому в ANSI создали группу для подготовки спецификации. В 1989 году вышел стандарт C89, который иногда называется “ANSI C”.
Спецификация C пыталась унифицировать эти разнообразные реализации на различном оборудовании. Таким образом, абстрактная машина C — это своего рода минимально возможная спецификация, которая позволила бы одному и тому же коду работать одинаково на всех платформах. Реализации C компилировались, а не интерпретировались, поэтому не было интерпретатора, поэтому не было “VM” в том смысле 1995 года. Однако программы на языке C пишутся на этом абстрактном несуществующем компьютере, а затем код преобразуется в ассемблер, специфичный для конкретного компьютера, на котором выполняется программа. Вы не могли полагаться на некоторые конкретные детали для написания переносимого кода на С. Это делает написание переносимого C очень сложным, так как вы, возможно, сделали специфичное для платформы предположение при написании начальной версии своего кода.
Это лучше всего иллюстрируется примером. Одним из основных типов данных в языке C является char
, от слова «символ». Однако абстрактная машина C не определяет, сколько бит должно быть в char
. Ну, определяет, но не числом; она определяет размер CHAR_BIT
, который является константой. Раздел 5.2.4.2.1 спецификации:
Приведённые ниже значения должны быть заменены константными выражениями, подходящими или используемыми в директивах предобработки
#if
.… Значения в конкретных реализациях должны быть равны или больше по величине (абсолютное значение) тех, которые приведены здесь, с тем же знаком.
CHAR_BIT: 8
Другими словами, вы знаете, что char
составляет не менее 8 бит, но реализации могут быть больше. Чтобы правильно кодировать «абстрактную машину C», в качестве размера при обработке char
необходимо использовать CHAR_BIT
вместо 8
. Но это не какая-то функция интерпретатора, как мы думаем о виртуальных машинах; это свойство того, как компилятор переводит исходный код в машинный код.
Да, есть системы, где
CHAR_BIT
не8
.
Таким образом, эта «абстрактная машина», хотя технически и является той же идеей, что и виртуальная машина Java, скорее представляет собой конструкцию компиляции для управления компиляторами при создании ассемблерного кода, а не какой-то проверкой в рантайме или свойством. Эквивалентный тип в Java — это byte
, который всегда составляет 8 бит, а на реализацию JVM возлагается задача, что делать на платформах, где байт больше. (Не уверен, работает ли JVM на любой из этих платформ, но именно так это должно функционировать). Абстрактная машина C создана как минимальная обёртка для различного «железа», а не в качестве какой-то платформы из цельной ткани, написанной в софте для вашего кода.
Таким образом, хотя Sun была технически не права, на практике они имеют в виду немного не то, что буквально говорят, и что они имеют в виду — верно. То же самое с фразой «Изучай C, чтобы понять, как работают компьютеры».
Изучай С, чтобы ЛУЧШЕ понять, как работают компьютеры
Что на самом деле люди имеют в виду? В контексте «должен ли рубист изучать C, чтобы понять, как работают компьютеры» — это совет снизиться «до уровня железа». То есть не только понять, как своя программа работает внутри виртуальной машины, но и как сочетание программы и VM работают в контексте самой машины.
Изучение C обеспечит вам больше таких деталей, потому что абстрактная машина гораздо ближе к аппаратному обеспечению, а также абстракциям операционных систем. Язык C сильно отличается от языков высокого уровня, поэтому его изучение может многому научить.
Но важно помнить, что C по сути является абстракцией аппаратного обеспечения, а абстракции несовершенны. Осторожнее с тем, что делает C или как он работает с самой машиной. Если слишком углубиться, то вы обязательно столкнетесь с этими различиями, что может вызвать проблемы. Большинство учебных ресурсов для C, особенно сегодня, когда оборудование становится все более гомогенным, будет продвигать идею о том, что именно так работает компьютер. Поэтому ученику может быть трудно понять, что происходит под капотом и что такое абстракция, предоставленная C.
В этой дискуссии мы даже не затронули другие вопросы. Например, что из-за огромной популярности C аппаратное обеспечение стало более однородным, потому что оно имеет тенденцию двигаться к семантике абстрактной машины C. Если ваша архитектура слишком сильно отличается от семантики языка C, программы на языке C могут работать намного медленнее, чем другие, а скорость аппаратного обеспечения часто измеряется тестами на языке C. Эта статья и так достаточно длинная…
По этой причине я думаю, что более точная версия этого утверждения будет «Изучая C, вы больше узнаете о том, как работают компьютеры». Я действительно думаю, что примерное знакомство с C полезно многим программистам, даже если они сами не пишут C. Знакомство с C также даст вам представление об истории развития нашей отрасли.
Есть и другие способы изучить эту тему; C по своей сути не предназначен для изучения компьютера, но это хороший вариант.
В программировании так много всего, чему стоит поучиться. Желаю вам успехов на этом пути.
Источник