Был ли язык программирования C считаться языком низкого уровня, когда он вышел?


151

В настоящее время C считается языком низкого уровня , но еще в 70-х он считался языком низкого уровня? Был ли термин даже в употреблении тогда?

Многие популярные языки более высокого уровня не существовали до середины 80-х и далее, поэтому мне интересно, изменилась ли природа низкого уровня за эти годы.


13
В качестве одной точки данных, около 1978 года некоторые наставники по программированию описали мне C как прославленный язык ассемблера.
Бен Кроуэлл

7
@BenCrowell Я не уверен, что означает «прославленный» в контексте вашего высказывания, но я испытывал название C универсальным (= независимым от платформы) языком ассемблера .
Мелебиус

13
Интересно отметить, что есть аргумент, что C не является языком низкого уровня , и теперь он меньше, чем был в 70-х годах, потому что абстрактная машина C удалена от современного аппаратного обеспечения дальше, чем от PDP-11.
Том

5
Размышляя об этом, вы также можете подумать о происхождении - Unix написан на C, c работает на Unix и кросс-компилирует unix для других платформ. Все, что не нужно для компиляции Unix, было ненужными накладными расходами, и главная цель C состояла в том, чтобы сделать его как можно более простым для написания / переноса компилятора. Для этой цели это был правильный уровень EXACT, поэтому я не думаю о C как о высоком или низком уровне, я думаю о нем как о инструменте для переноса Unix, который, как и почти все инструменты Unix, чрезвычайно адаптируется ко многим проблемам.
Билл К

4
Стоит отметить, что лисп был изобретен в 1958 году
Prime

Ответы:


143

Чтобы ответить на исторические аспекты вопроса:

Философия дизайна объясняется на языке программирования Си, написанном Брайаном Керниганом и дизайнером Си Деннисом Ричи, «K & R», о котором вы, возможно, слышали. В предисловии к первому изданию говорится

C не является ни языком "очень высокого уровня", ни "большим" языком ...

и введение говорит

C - это язык «низкого уровня» ... C не предоставляет операций для непосредственного взаимодействия с составными объектами, такими как строки символов, наборы, списки или массивы. Там нет операций, которые манипулируют весь массив или строку ...

Список продолжается некоторое время, прежде чем текст продолжается:

Хотя отсутствие некоторых из этих функций может показаться серьезным недостатком, ... поддержание языка до скромного размера имеет реальные преимущества.

(У меня есть только второе издание с 1988 года, но комментарий ниже указывает, что цитируемый текст такой же, как и в первом издании 1978 года.)

Итак, да, термины «высокий уровень» и «низкий уровень» использовались в то время, но С был разработан так, чтобы находиться где-то между ними в спектре. Можно было написать код на C, который был бы переносимым на аппаратные платформы, и это было основным критерием того, считался ли язык в то время высоким уровнем. Однако в C отсутствовали некоторые функции, которые были характерны для языков высокого уровня, и это было дизайнерское решение в пользу простоты.


42
Это отличный ответ! Поддающиеся проверке исторические доказательства + свидетельство актера, лучше всего информированного о значении низкого и высокого уровня в период, к которому относится ОП. Кстати, я подтверждаю, что цитаты были уже в издании 1978 года.
Кристоф

157

Это зависит от вашего определения языка высокого и низкого уровня. Когда был разработан язык C, все, что было более высокого уровня, чем ассемблер, считалось языком высокого уровня. Это низкий бар, чтобы очистить. Позже эта терминология перешла к тому, что в настоящее время некоторые считают, что даже Java является языком низкого уровня.

Даже в языковой среде высокого уровня 70-х годов стоит отметить, что уровень C довольно низок. Язык C в основном представляет собой B плюс простую систему типов, а B - это не более чем удобный процедурный / структурированный синтаксический уровень для сборки. Поскольку система типов является ретро-версией поверх нетипизированного языка B, в некоторых местах вы все еще можете опускать аннотации типов, и intэто будет предполагаться.

С сознательно не учитывает дорогие или трудные для реализации функции, которые уже были хорошо известны в то время, такие как

  • автоматическое управление памятью
  • вложенные функции или замыкания
  • основной ООП или сопрограммы
  • более выразительные системы типов (например, типы с ограниченным диапазоном, пользовательские типы, такие как типы записей, строгая типизация и т. д.)

C имеет некоторые интересные особенности:

  • поддержка рекурсии (как следствие ее автоматических переменных на основе стека по сравнению с языками, где все переменные имеют глобальное время жизни)
  • функциональные указатели
  • Определенные пользователем типы данных (структуры и объединения) были реализованы вскоре после первоначального выпуска C.
  • Строковое представление C (указатели на символы) на самом деле является огромным улучшением по сравнению с B, которое кодирует несколько букв в одно машинное слово.
  • Заголовочные файлы C были эффективными средствами, позволяющими сохранять небольшие размеры модулей компиляции, но также обеспечивали простую модульную систему.
  • Неограниченные указатели и арифметика указателей в стиле сборки по сравнению с более безопасными ссылками. Указатели являются небезопасной функцией, но также очень полезны для низкоуровневого программирования.

Во время разработки C другие инновационные языки, такие как COBOL, Lisp, ALGOL (на разных диалектах), PL / I, SNOBOL, Simula и Pascal, уже были опубликованы и / или широко использовались для конкретных проблемных областей. Но большинство из этих существующих языков предназначались для программирования на мэйнфреймах или были академическими исследовательскими проектами. Например, когда ALGOL-60 был впервые разработан как универсальный язык программирования, необходимых технологий и компьютерных наук для его реализации еще не было. Некоторые из них (некоторые диалекты ALGOL, PL / I, Pascal) также предназначались для низкоуровневого программирования, но, как правило, имели более сложные компиляторы или были слишком безопасными (например, без указателей без ограничений). Паскалю не хватает хорошей поддержки массивов переменной длины.

По сравнению с этими языками C отказывается от «элегантных» и дорогих функций, чтобы быть более практичным для низкоуровневой разработки. C никогда прежде не был исследовательским проектом языкового дизайна. Вместо этого это был ответвление разработки ядра Unix на миникомпьютере PDP-11, который был сравнительно ограничен в ресурсах. Для своей ниши (минималистский низкоуровневый язык для написания Unix с однопроходным компилятором, который легко переносить) C абсолютно преуспел - и спустя более 45 лет он по-прежнему остается языком системного программирования.


17
Небольшое дополнение для людей, которые не знают, что необходимо для существующего семейства ALGOL и языков Pascal: эти языки имеют лексически вложенные функции, в которых вы можете получить доступ к (локальной) переменной, объявленной во внешних функциях. Это означало, что вы должны были поддерживать «отображение» - массив указателей на внешние лексические области - при каждом вызове функции и возврате (что изменило лексический уровень) - или вам приходилось связывать лексические области вверх по стеку и доступ к каждой такой переменной требуется несколько косвенных прыжков вверх по стеку, чтобы найти его. Дорого! C отказался от всего этого. Я все еще скучаю по этому.
Давидбак

12
@davidbak: x86-64 System V ABI (он же соглашение о вызовах в Linux / OS X) определяет %r10как «статический указатель цепи», о чем вы и говорите. Для C это всего лишь еще один регистр с нуля, но я думаю, что Паскаль будет его использовать. (Вложенные функции GNU C используют его для передачи указателя на внешнюю область, когда такая функция не встроена (например, если вы создаете указатель на нее, чтобы компилятор создавал батут машинного кода в стеке): приемлемость обычного использование r10 и r11 )
Питер Кордес

2
@PeterCordes Увлекательный! Pascal все еще широко использовался, когда вышла System V (хотя я не знаю, когда был определен формальный SysV ABI). Ваш связанный ответ очень информативен.
Давидбак

3
C имеет пользовательские типы (struct / union). Я подозреваю, что остальные эти «функции» были опущены, потому что они имеют нулевое или отрицательное использование (если вы не участвуете в конкурсе запутанного кода :-)), так как они отвлекают от цели сохранения языка простым и выразительным. ,
jamesqf

6
@jamesqf: но очень рано C не имел структурного присваивания; Я предполагаю, что вам нужно записать memcpy или скопировать элементы по отдельности, а не писать, a = b;чтобы скопировать всю структуру, как вы можете в ISO C89. Таким образом, в ранних C определяемые пользователем типы были определенно вторым классом и могли передаваться только по ссылке как аргументы функции. Отвращение C к массивам, а также Почему C ++ поддерживает членское присвоение массивов внутри структур, но не в целом?
Питер Кордес

37

В начале 1970-х C был ослепительно свежим воздухом, использующим современные конструкции настолько эффективно, что всю систему UNIX можно было переписать с языка ассемблера на C с незначительным пространством или снижением производительности. В то время многие современники называли его языком высокого уровня.

Авторы C, в первую очередь Деннис Ритчи, были более осмотрительными, и в статье в Техническом журнале Bell System говорится, что «C не является языком высокого уровня». С кривой улыбкой и намерением быть провокационным, Деннис Ричи сказал бы, что это язык низкого уровня. Главной целью проекта C было сохранить язык близко к машине, но при этом обеспечить мобильность, то есть независимость от машины.

Для получения дополнительной информации обратитесь к оригинальной статье BSTJ:

Спасибо, Деннис. Покойся с миром.


3
По сути, это были типизированные обертки с более приятным синтаксисом для сборки PDP.
einpoklum


2
@einpoklum Я склонен с этим согласиться. Я выучил C примерно в 1980 году в университете, на PDP-11 / 34a, как это происходит, и он был описан одним из специалистов как «Портативный язык ассемблера». Возможно, потому что в дополнение к PDP-11 у нас в лаборатории было несколько машин Superbrain CP / M, на которых были доступны компиляторы Си. ru.wikipedia.org/wiki/Intertec_Superbrain
dgnuff

2
Также отличный ответ, основанный на проверяемых исторических данных (вау! Там была предварительная версия K & R!).
Кристоф

7
@einpoklum Разве это не надумано? У меня была возможность написать системный и прикладной код в середине 80-х годов на ассемблере (Z80 и M68K), «переносимом ассемблере» под названием M (синтаксис PDP11 с абстрактным 16-битным регистром и набором команд) и C. По сравнению с ассемблером C определенно является языком высокого уровня: производительность программирования на C была на порядок выше, чем на ассемблере! Конечно, нет строк (SNOBOL), нет поддержки собственных файлов данных (COBOL), нет самогенерирующего кода (LISP), нет расширенной математики (APL), нет объектов (SIMULA), поэтому мы можем согласиться с тем, что он был не очень высоким
Кристоф

21

Как я писал в другом месте на этом сайте, когда кто-то упоминал шаблон управления памятью malloc / free как «низкоуровневое программирование»

Забавно, как определение «низкого уровня» меняется со временем. Когда я впервые учился программировать, любой язык, который предоставлял стандартизированную модель кучи, которая делает возможным простой шаблон размещения / освобождения, действительно считался высокоуровневым. В низкоуровневом программировании вам придется следить за памятью самостоятельно (не за выделениями, а за самими областями памяти!) Или писать свой собственный распределитель кучи, если вы чувствуете, что это действительно интересно.

Для контекста, это было в начале 90-х, задолго до того, как вышел C.


Разве стандартная библиотека не была чем-то особенным с момента стандартизации в конце 80-х (ну, на основе существующих Unix API)? Кроме того, программирование ядра (которое было исходной проблемной областью C) естественно требует низкоуровневых вещей, таких как ручное управление памятью. В то время C должен был быть языком программирования самого высокого уровня, на котором было написано серьезное ядро ​​(я думаю, что в настоящее время ядро ​​NT также использует достаточное количество C ++).
Амон

6
@hyde, я использовал Algol 60, два разных диалекта FORTRAN и несколько разных диалектов BASIC еще в 1970-х, и ни один из этих языков не имел указателей или распределителя кучи.
Соломон Слоу

7
Строго говоря, вы все равно можете программировать без malloc()непосредственного вызова brk(2)или mmap(2)управления полученной памятью самостоятельно. Это огромная PITA без какой-либо мыслимой выгоды (если только вы не реализуете подобную malloc вещь), но вы можете это сделать.
Кевин

1
@amon - за исключением замечательных машин на основе стека Берроуза, которые были запрограммированы в ALGOL снизу вверх ... и намного раньше, чем Unix. Да, и, кстати, Multics, которая вдохновила Unix: написано на PL / I. Подобно Алголу, более высокий уровень, чем C.
Давидбак

6
На самом деле, причина того, что многие из нас сегодня не используют Multics, скорее всего, связана с тем фактом, что он работал только на адски дорогих мэйнфреймах - то есть на типичных неоправданных расходах мэйнфреймов за день плюс дополнительные расходы на половину кабинета специализированное оборудование для реализации виртуальной памяти с безопасностью. Когда вышли все 32-битные миникомпьютеры, такие как VAX-11, все, кроме банков и правительства, отошли от IBM и Seven Dwarfs и перенесли свою масштабную обработку на «мини» компьютеры.
Давидбак

15

Многие ответы уже ссылались на ранние статьи, в которых говорилось что-то вроде «C не является языком высокого уровня».

Однако я не могу устоять перед накоплением: многие, если не большинство или все HLL в то время - Algol, Algol-60, PL / 1, Pascal - обеспечивали проверку границ массивов и обнаружение переполнения чисел.

В последний раз я проверял, что переполнение буфера и целочисленные значения были основной причиной многих уязвимостей. ... Да, все еще так ...

Ситуация с динамическим управлением памятью была более сложной, но все же Malloc / free в стиле C был большим шагом назад с точки зрения безопасности.

Так что, если ваше определение HLL включает «автоматически предотвращает множество ошибок низкого уровня», то состояние кибербезопасности, к сожалению, будет совсем другим, возможно, лучше, если бы C и UNIX не случались.


7
Так как мне довелось быть вовлеченным в реализацию Intel MPX и компилятора проверки указателей / границ, который работал, если это так, я могу отослать вас к статьям об их производительности: по существу, 5-15%. Во многом это связано с анализом компилятора, который был едва ли возможен в 1970-х и 1980-х годах - по сравнению с наивными проверками, которые могут быть на 50% медленнее. Тем не менее, я думаю, что было бы справедливо сказать, что C и UNIX отодвинули работу над такими анализами на 20 лет - когда C стал самым популярным языком программирования, спрос на безопасность был намного меньше.
Крейзи Глеу

9
@jamesqf Более того, многие машины до C имели специальную аппаратную поддержку для проверки границ и целочисленных переполнений. Поскольку C не использовал этот HW, он в конечном итоге устарел и был удален.
Крейзи Глеу

5
@jamesqf Например: MISS RISC ISA изначально основывался на тестах Stanford, которые изначально были написаны на Pascal, только позже перемещены в C. Так как Pascal проверял на целочисленное переполнение со знаком, следовательно, MIPS также выполнял такие инструкции, как ADD. Примерно в 2010 году я работал в MIPS, мой начальник хотел удалить неиспользуемые инструкции в MIPSr6, и исследования показали, что проверки переполнения почти никогда не использовались. Но оказалось, что Javascript делал такие проверки - но не мог использовать дешевые инструкции из-за отсутствия поддержки ОС.
Крейзи Глеу

3
@KrazyGlew - Очень интересно, что вы подняли этот вопрос, поскольку в C / C ++ борьба между пользователями и авторами компиляторов за «неопределенное поведение» из-за целочисленного переполнения со знаком разгорается, поскольку современные авторы компиляторов приняли старую мантру «Делать неправильно» Программа хуже не грешит "и поднял ее до 11. Множество постов на stackoverflow и в других местах отражают это ...
Давидбак

2
Если бы у Rust были встроенные SIMD-компоненты, такие как C, это мог бы быть почти идеальный современный портативный язык ассемблера. C становится все хуже и хуже как переносимый язык ассемблера из-за агрессивной оптимизации на основе UB и неспособности переносить на экран новые примитивные операции, поддерживаемые современными процессорами (такие как popcnt, нули в начале / конце счета, обратный бит, обратный байт, насыщение математика). Чтобы компиляторы C могли эффективно использовать их для процессоров с поддержкой HW, часто требуются непереносимые встроенные функции. Наличие компилятора, эмулирующего popcnt для целей без него, лучше, чем распознавание идиом popcnt.
Питер Кордес

8

Рассмотрим более старые и намного более высокие языки, предшествовавшие C (1972):

Фортран - 1957 (не намного выше уровня С)

Лисп - 1958

Кобол - 1959

Фортран IV - 1961 г. (не намного выше уровня С)

PL / 1 - 1964

APL - 1966

Плюс язык среднего уровня, такой как RPG (1959), в основном язык программирования, заменяющий основанные на платах системы записи единиц измерения.

С этой точки зрения C казался языком очень низкого уровня, только немного выше макросборщиков, которые использовались на мэйнфреймах в то время. В случае мэйнфреймов IBM для доступа к базе данных использовались макросы на ассемблере, такие как BDAM (базовый метод доступа к диску), поскольку интерфейсы базы данных не были портированы на Cobol (в то время), что привело к наследованию сочетания сборок и Программы Cobol до сих пор используются на мэйнфреймах IBM.


2
Если вы хотите перечислить более старые, более высокие языки, не забудьте LISP .
Дедупликатор

@Deduplicator - я добавил его в список. Я сосредоточился на том, что использовалось на мэйнфреймах IBM, и я не помню, чтобы LISP был таким популярным, но APL был также нишевым языком для мэйнфреймов IBM (с помощью консолей с разделением времени) и IBM 1130. Подобно LISP, APL является одним из более уникальные языки высокого уровня, например, посмотрите, как мало кода требуется для создания игры жизни Конвея с текущей версией APL: youtube.com/watch?v=a9xAKttWgP4 .
rcgldr

2
Я бы не считал RPG или COBOL особенно высокоуровневым, по крайней мере, до COBOL-85. Когда вы поцарапаете поверхность COBOL, вы увидите, что это по сути коллекция очень продвинутых макросов ассемблера. Начнем с того, что ему не хватает как функций, процедур и рекурсии, так и каких-либо областей видимости. Все хранилище должно быть объявлено в верхней части программы, что приводит либо к чрезмерно длительным перегрузкам, либо к болезненному повторному использованию переменных.
idrougge

У меня плохие воспоминания об использовании Fortran IV, я не помню, чтобы он был заметно «более высокого уровня», чем C.
DaveG

@idrougge - COBOL и RPG, а также наборы инструкций для мэйнфреймов включали полную поддержку упакованных или неупакованных BCD, что необходимо для финансового программного обеспечения в таких странах, как США. Я считаю родные операторы, такие как «переместить соответствующие», на высокий уровень. RPG был необычным в том смысле, что вы указали связь между необработанными полями ввода и отформатированными полями вывода и / или аккумуляторами, но не порядок операций, аналогично программированию на сменной плате.
rcgldr

6

Ответ на ваш вопрос зависит от того, о каком языке C он спрашивает.

Язык, описанный в Справочном руководстве С 1974 года Денниса Ритчи, был языком низкого уровня, который предлагал некоторые удобства программирования языков высокого уровня. Диалекты, производные от этого языка, также, как правило, были языками программирования низкого уровня.

Однако, когда был опубликован стандарт С 1989/1990 гг., Он не описывал язык низкого уровня, ставший популярным для программирования на реальных машинах, а вместо этого описывал язык более высокого уровня, который мог бы быть - но не обязателен - - реализовано в терминах более низкого уровня.

Как отмечают авторы стандарта C, одна из вещей, которая сделала язык полезным, заключалась в том, что многие реализации можно рассматривать как ассемблеры высокого уровня. Поскольку C также использовался в качестве альтернативы другим языкам высокого уровня, и поскольку многим приложениям не требовалась способность делать то, чего не могли делать языки высокого уровня, авторы стандарта позволили реализациям вести себя произвольным образом. если программы пытались использовать низкоуровневые конструкции. Следовательно, язык, описанный в Стандарте C, никогда не был языком программирования низкого уровня.

Чтобы понять это различие, рассмотрим, как язык Ричи и C89 будут просматривать фрагмент кода:

struct foo { int x,y; float z; } *p;
...
p[3].y+=1;

на платформе, где «char» равен 8 битам, «int» равен 16 битам с прямым порядком байтов, «float» равен 32 битам, и структуры не имеют специальных требований к заполнению или выравниванию, поэтому размер «struct foo» составляет 8 байтов.

На языке Ричи поведение последнего оператора будет принимать адрес, сохраненный в «p», добавлять к нему 3 * 8 + 2 [то есть 26] байта и извлекать 16-битное значение из байтов по этому адресу и следующему добавьте единицу к этому значению, а затем запишите это 16-битное значение в те же два байта. Поведение будет определяться как действие на 26-й и 27-й байты, следующие за байтом по адресу p, независимо от того, какой тип объекта там хранится.

На языке, определенном Стандартом C, в случае, если * p идентифицирует элемент «struct foo []», за которым следуют как минимум еще три полных элемента этого типа, последний оператор добавит один к члену y из третий элемент после * р. Поведение не будет определяться Стандартом ни при каких других обстоятельствах.

Язык Ричи был языком программирования низкого уровня, потому что, хотя он позволял программисту использовать абстракции, такие как массивы и структуры, когда это было удобно, он определял поведение в терминах базовой структуры объектов в памяти. В отличие от этого, язык, описанный в C89 и более поздних стандартах, определяет вещи в терминах абстракции более высокого уровня и определяет только поведение кода, которое соответствует этому. Качественные реализации, подходящие для низкоуровневого программирования, будут вести себя полезно в большем количестве случаев, чем предписано Стандартом, но не существует «официального» документа, определяющего, что реализация должна делать, чтобы подходить для таких целей.

Таким образом, язык Си, изобретенный Деннисом Ритчи, является языком низкого уровня и был признан таковым. Тем не менее, язык, изобретенный Комитетом по стандартам C, никогда не был языком низкого уровня в отсутствие предоставленных реализацией гарантий, выходящих за рамки мандатов Стандарта.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.