Комментарии 199
С таки язык высокого уровня, поскольку он создает слой абстракции над железом, позволяя вместо инструкций конкретного процессора и его регистров использовать более общий набор команд и более приближенную к человечесаому форму представления программы.
Другой вопрос, что из всех языков высокого уровня он имеет самый низкий уровень, но вск же его уровень существенно выше, чем у любого действительно низкоуровневого языка (ассембрера).
"высокий" или "низкий" — понятия относительные. Относительно большей части прикладных (и системных ) приложений C очень даже низкий: низкоуровневые интерфейсы ОС определены в терминах С, сама ОС, как правило, написана на С и так далее.
Если же говорить про абстракции над железом, то можно договориться, что мнемоники языков ассемблера — тоже вполне себе абстракция над машинным кодом, и поэтому единственный низкоуровенвый язык — машинные коды.
А оттуда уже и до микрокода рукой подать…
Это вопрос "достаточной низкоуровневости".
Для ОС в основном, как мы на практике знаем, вполне достаточно тех средств, что предоставляет C (с некоторыми общепринятыми нестандартными расширениями).
Но может не хватить тех средств, что предоставляет, скажем, Java. Хотя использовать высокоуровневые языки в этой роли, конечно, были :-) Мы все знаем, где они, эти попытки, оказались.
А машинные коды это тоже абстракция над железом, так что либо мы отказываем в существовании низкоуровневых языков вообще, либо убираем этот критерий.
Turbo Pascal получается тоже низкоуровневый?
По возможностям очень даже рядом с С, хотя низкоуровневые аспекты вроде указателей в Турбо Паскале немножко все же глубже спрятаны.
Mem[segment:offset]
), массивы для доступа к портам (Port, PortW) и даже директива inline для вставок машинного кода (не ассемблера, а именно машинного, в виде чисел).да, все есть, я не спорю. (Турбо) Паскаль нисколько не уступал C.
Но язык рассказывается явно не этот этих возможностей. В Паскале традиционно упор делался на статическую аллокацию ресурсов, указатели можно вообще не использовать. Что, быть может, даже хорошо.
В отличие от Си, где без указателей даже шевельнуться нельзя.
Красиво же:
const
map: array ['A'..'F'] of byte = (10, 11, 12, 13, 14, 15);
или
if s[i] in ['a'..'f', 'A'..'F'] then ...
Удивительно, но статических массивов очень долго хватало даже программистам на Фортране… :-)
Я вот стандарт Паскаля совсем не знаю, и смутно помню только Турбо Паскаль. Там же указатели довольно сильно ограничены, не так ли? Никаких там скрытых приведений типов и т.д.?
Про хаскель сложный вопрос, я склоняюсь считать его применимым для системного программирования, хотя и с рядом серьёзных ограничений.
А кто низкоуровневый?
Вон в js/java/php нельзя вставить ассемблер, они точно нет.
Вон в js/java/php нельзя вставить ассемблер, они точно нет.
В Java можно, вероятно в php и js на node.js тоже есть способы вставить нативный код. Практически любой язык (кроме браузерных) позволяет вставки C кода и вызовы С библиотек, а от него через него и асемблера.
Если можно на нем написать bootloader — низкоуровневый, если нет — то нет.
На Java можно написать bootloader, более того они уже написаны. В реальности, практически на любом языке можно написать bootloader, для языков, требующих виртуальной машины, придется повозится с компилятором, но все равно — можно.
Насчёт джавы вы неправы. По ссылке по сути способы с помощью программы на Java сгенерировать ассемблерный код, а потом результат слинковать динамически с текущим рантаймом. Если у языка имеется интероп а нативным кодом, то такую конструкцию построить всегда можно, но это не является ассемблерный вставкой само по себе. Да и интероп у джавы довольно поганенький.
В статье я обсуждал именно процессоры общего назначения. Я много провел времени, вручную планируя инструкции.
И что-то не думаю, что в таких процессорах можно прогнозировать "время исполнения транслированных инструкций" даже при прямом кодировании на асмах. Очень уж много переменных тут надо учесть: нет ли обращения к памяти, находятся ли данные в кеше, не сработает ли внеочередное выполнение инструкций, что делает парный поток исполнения на ядре и какая в момент исполнения инструкции тактовая частота…
Скорее всего, соотношение между временем работы Intel ME и основных ядер — один к миллиону. Но в любом случае, точно гарантировать ничего нельзя, даже на bare metal, без операционки.
Я мривык к старой границе между языками высокого и низкого уровня.
Нам ее давали в виде строгого определения (точную формулировку которого я забыл).
Но языки еизкого уровня оперируют только инструкциями процессора, заменяя в лучшем случае реальные коды операций мнемоническими обозначениями, а адреса и блоки памяти именами.
А языки высокого уровня имеют в своем арсенале команды, которые не могут быть записаны одной инструкцией процессора, и являются абстракцией над устройством компьютера.
Язык высокого уровня может содержать элементы языка низкого уровня, но не обязан.
Да, сам читал книги 70-х, где есть упоминались только языки ассемблера и высокоуровневые языки.
Во-первых, тогда вообще нет смысла говорить "язык низкого уровня", их как бы тогда нет.
Во-вторых, с тех пор случились 80-ые, за которые разработка системных приложений и ОС перешла с ассемблера на портативный код на С, и последний стали рассматривать как основной низкоуровневый язык.
Во-первых, тогда вообще нет смысла говорить "язык низкого уровня", их как бы тогда нет.
Как нет? А язык ассемблера ____ (подставить нужное)?
В ваше определение подходят только языки ассемблера? Ну, может быть.
Но, согласитесь, никто уже сто лет как не пишет на ассемблере, только в крайне узких областях. И как-то ранжировать языки все же придется по степени близости к железу, нет? Условно, тех, на которых имеет смысл драйвера и ОС писать, и тех, на которых пишут веб-сервисы?
Не только ассемблера, есть ещё язык машинных кодов. И вообще, чисто технически можно создать например Brainfuck-машину, для которой языком низкого уровня будет соответствующий язык.
Да, побаловаться с маленькими бывает очень весело :-) И в мире микроконтроллеров до сих пор частенько пишут на языках ассемблера, спорить не буду. "Частенько" в смысле "чаще, чем в среднем по палате".
Но все же очень много С и еще, говорят, Форт местами. И именно некоторые их низкоуровневые аспекты позволяют это делать.
Это да, мир микроконтроллеров очень многооборазен.
В ваше определение подходят только языки ассемблера?
На данный момент — да, если забыть про машинные коды.
Но с развитием железа все может поменяться.
И как-то ранжировать языки все же придется по степени близости к железу, нет?
Ранжировать языки надо, но это не повод перекраивать сложившуюся терминологию.
Что мешает классифицировать ЯВУ по близости к железу без именования какого-либо из них низкоуровневым?
Можно назвать С, например, языком системного программирования.
Признаться, в контексте статьи это не важно :-) Системный так системный.
Вообще, Чизнэлл не об этом писал, а я, в свою очередь, полемизировал именно с ним. Думаю, он мог бы вполне осмысленно назвать статью иначе: "Архитектура фон Неймана и следующая из нее модель программирования устарели". Но это не так сильно звучит, как "популярный в определенных областях язык — плохой"
Это если говорить о современном x86 ассемблере и наследниках. Есть архитектуры где что написано — то и исполняется.
Внутренняя кухня микропроцессора — это, конечно, интересно, но для программиста это вполне конкретный набор регистров и инструкций. И в ассемблере никаких абстракций, выходящих за рамки этих инструкций.
А страничная адресация — вполне себе аппаратная штука, управляемая командами процессора и регистрами. Всегда есть код, из которого при желании можно понять, что и где выполпяется (в BIOS или ОС).
Почему же нет, есть макросы. Тот же masm позволяет навертеть "сахара" поверх голых инструкций.
Тогда уж можно вспомнить и турбоассемблер с некоторой толикой поддержки ООП.
Но тут все сводится к упаковке некоторого текста в макроопределения, чтобы не вводить текст повторно, некоторым расширениям к именованию областей кода и данных, а также управлению компиляцией, позволяющему по условию исключать или добавлять ассемблерный код в итоговый компилируемый в машинные инструкции текст.
Непосредственно в языке дополнительных возможностей, выходящих за мнемокоды операций процессора не было.
Или вот еще есть такая игрушка: https://en.wikipedia.org/wiki/High_Level_Assembly
единственный низкоуровенвый язык — машинные коды
А оттуда уже и до микрокода рукой подать…
Особенно на x86, где машинный код тоже высокоуровневый, так как на уровне проца это просто API для копилятора...
Классификации языков на низко- и высокоуровневые уже полвека, и эта классификация попросту изжила себя. Какой смысл в этом классификации, если практически все современные языки по этой классификации высокоуровневые?
Более правильно говорить о возможностях. В том же C вполне себе имееются низкоуровневые возможности: преобразование указателя к числу, возможность использования интринсиков для работы с определёнными регистрами и т.д.
Я бы классифицировал языки по возможностям:
- Работа с регистрами процессора, с железом.
- Минимиальные абстракции от аппаратного обеспечения (вызовы функций, переменные).
- Наличие алгоритмических абстракций (классы).
- Скриптовые языки.
Причём язык относится не к конкретному классу, а к диапазону.
И, если уж на то пошло, машинный код современных процессоров — тоже высокоуровневый, потому что уже давно не соответствует реальной архитектуре. Команды транслируются во внутренние инструкции, регистры маппятся.
Так или иначе, нам по-прежнему нужен язык низкого уровня, причём построенный именно для популярных фоннеймановских компьютеров. И пускай C устарел, но, видимо, любому его преемнику всё равно придётся отталкиваться от тех же самых принципов.
С последним выводом в статье не согласен. Сделали Rust, который не наследует архаику C и дает все возможности низкоуровневого программирования, но имеет современные возможности
Да, действительно. И помимо Rust есть множество приличных языков.
Оригинальная статья сильно критиковала не только сам С как язык, а самую модель программирования, общую для С и Rust, на этой критике я и постарался сконцентрироваться.
Теоретически говоря Rust (и С++) предоставляют возможности для оптимизации, которых так просят компиляторщики.
Практически же выходит, что идиоматический код на С++ с шаблонами или Расте с макросами часто оказывается медленней С в выскокоуровневом стиле, и даже с использованием того же самого компилирующего бэкэнда. :-)
https://benchmarksgame-team.pages.debian.net/benchmarksgame/fastest/c.html
Да, зависит от практики, и это парадокс.
Говорю ж: нет серьезных причин, по которым С++ должен быть медленней С, особенно в случае GCC/Clang. Вроде как вычисления во время компиляции и шаблоны позволяют, например, не использовать так обильно void* и, скажем, более эффектевно инлайнить код.
Но практический код на С++ регулярно оказывается медленней. Неплохо об этом не так давно на Хабре писали: https://habr.com/ru/post/347688/
ну, последняя языкодельная мода нечто подобное и диктует, когда от полиморфизма через наследование в принципе отказываются.
Хотя остаются всякого рода интерфейсоподобные приемы, но все же.
ага, я в курсе :-) Вообще, чего только не делают на макросах… Есть еще моя неплохая klib, где популярные структуры данных оформлены так, чтобы указатели не требовались.
Но, признаться, макросы — умирающее искусство.
Ага, но Тьюринг полнотой обладают даже вычисления на ДНК :-) Все же страшно неудобно, и потом разбираться бывает трудно, чего это случайная запятая вдруг в третьем месте ошибки вызывает. Впрочем, плюсы тут не много лучше.
В конечном итоге, ни макросы Си, ни шаблоны плюсов для вычислений не создавались, и это видно :-)
Вон, в D все то же самое, но чистенько. Хотя всем все равно :-)
не, ну ваш пример еще очень даже чистый. :-) Это не ассемблер ли на макросах? :-)
- У вас 50 разных типов?
- Если эти функции написать ручками, их будет не 50?
Интересно. Куда же девается система типов, возможности для разграничения потенциально опасного и безопасного кода, дженерики и т.п.?
Аналоги borrow checker есть в Dyon (скриптовый), MLKit (функциональный), Carp (вариант Lisp'а), LinearML (функциональный язык с потенциалом системного использования, в разработке, последний коммит 5 лет назад), Cyclone (safe C), Discus (функциональный), Cone (системный язык, в разработке, последний коммит 4 месяца назад), Rust.
Типы суммы, pattern matching, дженерики/темплейты в потенциально системных языках: Cone, Rust.
В общем — да, не уникально.
Ох, каждый раз когда вижу упоминания компиляторов Standard ML — грустно становится. Такой красивый язык, такие шикарные компиляторы… И из всего семейства более-менее популярным стали только ленивый Haskell и некрасивый Ocaml.
Вкусовщина, конечно, спорить тут не буду :-)
уф… это ж такой спор можно развести… :-) А тут и так флейма...
В общем, я за предсказуемость eager evaluation, тем более, что ленивые вычисления на Standard ML или Окамле очень даже делаются.
Я все это знаю, Окасаки проделал потрясающую работу и вообще большой молодец, но… У него простейшие структуры данных делаются весьма нетривиально, если с чисто практической точки зрения.
Ну а тот факт, что для околоМЛ языков легче теоремы доказывать я никому никогда не продам :-)
Вы можете меня к стенке поставить и расстрелять, но я за предсказуемость и простоту :-)
Задержки при получении инструкциями данных из памяти частично компенсируются внеочередным выполнением (англ. instruction-level parallelism)
Строго говоря, в ссылку надо ставить статью про out-of-order, раз уж по-русски написано именно про это. Хотя бы потому, что параллелизм уровня инструкций может быть реализован и на in-order процессорах с явной параллельностью (здравствуй, VLIW). Вдобавок, про параллелизм уровня инструкций неявно уже сказано в пункте про суперскалярность, которая как раз ILP и реализует.
Да, можно было бы тут раскрыть получше.
Я когда думал над этими абзацами все время сталкивался с тем, что кратко изложить все эти тесносвязанные понятия сложно. Надо начинать с какой-нибудь простой модели, а потом вносить в нее оптимизации и раскрывать их суть и то, как они взаимодействуют… Материала на приличного размера книгу хватит :-)
Мораль тут в любом случае простая: это неизбежные оптимизации, применяемые во всех современных производительных процессорах общего назначения.
Материала на приличного размера книгу хватит :-)
Это даа...:)
Мораль тут в любом случае простая: это неизбежные оптимизации, применяемые во всех современных производительных процессорах общего назначения.
А вот тут опять неточность. Большинство VLIW-процессоров не включают out-of-order, но некоторые из них являются современными, производительными процессорами общего назначения. Так что с формальной точки зрения ИМХО стоило бы написать «оптимизации, применяемые в большинстве… процессоров ...».
Хотя про ILP и branch predictor согласен: без них сейчас вообще никак.
а я там везде пишу "популярные процессоры общего назначения".
VLIW же не особо пока взлетели, не так ли? :-)
а я там везде пишу «популярные процессоры общего назначения».
В статье по большей части да. Но в Вашем сообщении, на которое я и отвечал, ни слова о популярности не было. Это во-первых.
Во-вторых, архитектуры VLIW есть и общего назначения: Эльбрус и Itanium как минимум. Так что утверждение в Вашем сообщении я посчитал некорректным, о чём и написал.
Впрочем, есть вероятность, что мы с Вами понимаем слова «процессор общего назначения» по-разному. Я под этими словами понимаю синоним CPU в том смысле, что есть также и специализированные processing units: GPU, TPU и тому подобные.
Я совершенно с вами согласен: такие архитектуры были, есть и используются. Но не слишком широко, если сравнивать с традиционными RISC/CISC гибридами (тут же мы не будем спорить? :-) ).
Просто если упоминать VLIW, то придется еще обсуждать весь зоопарк опробованных в роли ЦПУ архитектур, и это уже слишком. :-)
Никто не станет спорить с тем, что язык ассемблера находится на самом низком уровне.
Если быть точным, то по эту сторону кристалла самый низкий — это язык машинных кодов. Ну, если брать ГОСТовскую терминологию; язык ассемблера — ака язык мнемокодов — на ступень выше.
VlK
я не имею в виду «ГОСТ на терминологию», я имею в виду «типичную терминологию, используемую в отраслевых ГОСТах». Сейчас уже не вспомню точно где, но год или два назад, когда в предыдущий раз на хабре были холиворы касательно того, что такое низкий уровень, что такое высокий, что такое транслятор и что такое компилятор и т.п. — нашел это.
VlK
а, нет, вру. Все же это были разные стандарты, втч ГОСТ на терминологию.
ГОСТ 19781-90. Обеспечение систем обработки информации программное. Термины и определения
Обратите внимание, что там вводится понятие автокода, который занимает промежуточное положение между машинным языком и ассемблером.
Никто не станет спорить с тем, что язык ассемблера находится на самом низком уровне.
Наверно всё-таки машинный код на самом низком уровне. Некоторые виды макро-ассемблеров после компиляции могут такого нагенерировать, что мало не покажется…
А язык Форд по вашему мнению выше или ниже уровня С?
"Форд" или все же "Форт"? :-)
Но вообще вопрос хороший.
Если в контексте статьи, то вполне себе удовлетворяет требованиям: легко портируется, есть стандарт, детали платформ различает (в определенных рамках). Хотя я не спец по Форту, признаться.
Вы как думаете?
Басовые идеи Форта и отличия от С, по моему мнению, следующие:
А) Форт абстрагирует стековую машину, а не регистровую PDP-11 как С
Б) Базовая идея Форта — это организация программ и данных в виде словаря, который и исполняется. Поэтому Форт может сам себя динамически расширять (это похоже на Лисп). У С таких динамических способностей не наблюдается
В) Большинство реализаций Форта — интерпретаторы, а не компиляторы, хотя компилируемый Форт наверняка возможен (я не сталкивался). А вот об интерпретаторах С я не слышал
Г) Наконец, Форт — это безтиповой язык, а вот С хотя и близок к безтипности, но всё-таки официально таковым не считается
Так что решайте сами…
Кстати, вопрос о наличии типов в языке — основной с точки зрения различия «высоких» и «низких» языков
Большинство реализаций Форта — интерпретаторы, а не компиляторы
Форт одновременно является и тем и другим.
\ тут форт интерпретируемый
S" Hello from interpreting mode!" TYPE CR
: hello \ а тут уже компилируемый, : нас перевело в этот режим
S" Hello from compiling mode" \ но S" является immediate-словом и поэтому
\ не скомпилировалось, а было вызвано, оно
\ распарсило строковый литерал, где-то его
\ сохранило и скомпилировало код, который
\ заносит на стек адрес строки и ее длину
TYPE CR \ это обычные слова, они просто скомпилировались
; \ ; тоже immediate-слово, заканчивает определение и переводит нас обратно
\ в режим интерпретации
\ мы снова в интерпретаторе
hello \ зовем только что скомпилированное слово
BYE \ пока ;)
К тому же есть системы, компилирующиеся в нативный код: SP-Forth, SwiftForth
В случае с косвенным шитым кодом, компиляция происходит в цепочку адресов ячеек, содержащих адрес подпрограмм в машинном коде, в начале цепочки размешается адрес подпрограммы инициирующей запуск этого шитого кода (по аналогии с прямым шитым кодом).
Т.е. любом случае происходит трансляция с более высокоуровневого кода на форте в более низкоуровневый (т.е. компиляция) шитый код, либо байт-код, либо машинный код, а в случае с прямым без компиляции машинного кода даже никак не обойтись.
Большая разница с компиляторами большинства других языков в том, что этот код компилируется в оперативной памяти и часто там и остается, если в программе явно не было указано команды сохранения в файл. Чтобы было понятнее, можно считать, что это аналог запуска Си программы командой
tcc -run program.c
.P.S. Да и, кстати, настоящих интерпретируемых языков осталось немного, почти всегда это компиляторы в байткод, которые (если явно не было указано другого) сразу запускают интерпретатор этого байткода, да и там тоже часто JIT-компилятор.
Ну, вот встречные соображения.
Буду честнен, простой Форт на шитом коде я делал в качестве упражнения, и прикладной код на нем не писал. Поправьте, если мой ограниченный опыт ложен.
а) в PDP-11 и x86 вполне себе есть как стек, так и регистры. Использование этого стека, в сущности, вопрос реализации как в С, так и в Форте.
б) да
в) делают и интерпретаторы С, например, для проверки работы программ на предмет всяких там неопределенных поведений, хотя, конечно, практически ими никто не пользуется
г) я так понимаю, что оперирование машинным словом, как в простых Фортах, это именно бестиповое (или, верней, дотиповое, т.к. появилось оно раньше популяризации идеии типов) программирование. В каком-то смысле это, безусловно ниже уровня Си с его явными простыми типами, пускай даже и сильно прибитыми к доступным на конкретнйо платформе типам.
Последней же вопрос. Типы в форме Си, где они привязаны к платформе, разве не достаточно низкий уровень? Железные вопросы они отражают, это ж не Haskell.
Стековая машина предполагает не просто наличие стека, а расположение операндов в стеке. Как в fpu.
а) Без сомнений — любая мало-мальски универсальная регистровая машина (PDP-11, x86) может работать в качестве или эмулировать стековую (так делаются в большинстве реализаций Форта). Моя мысль была в том что Форту требуется стековая виртуальная машина, поскольку всё взаимодействие в словаре происходит через стек. Если убрать стек и оставить словарь, то Форта — как мы его знаем — не будет. А вот С семантически от наличия или отсутствия стека не зависит, хотя все его реализации о которых я знаю безусловно стек используют.
б) да, да :)
в) Не представляю каким образом система типов в С будет работать в режиме интерпретирования без жесткой run-time поддержки (тут мы уже движемся в сторону Питона), хотя наверно всё возможно при желании… Система типов в С слабая, особенно в ранних стандартах, но всё же компилятор на неё опирается.
г) Тут опять-таки я имел в виду, что работа ядра Форта (стек + словарь) никак не зависит от того что содержится в том или ином машинном слове (или в терминологии Форта — ячейке). Семантика того что содержат отдельные ячейки — дело самой программы. А вот работа компилятора С от типов очень даже зависит, например (и особенно) в части приведения к типам в выражениях.
Последний вопрос: Конечно, система типов в С базируется на «низких» машинных типах, отсюда и утверждения о том что С — это glorified assembler. И в этом сила С как системного языка. Но всё же там есть и более «высокие» т.е. производные типы — структуры и массивы, хотя их реализация очень проста и максимально приближена к машинному уровню.
а как вы определите что есть "язык низкого уровня"? Тут выше я уже указывал, что определения бывают разные, в том числе в моей статье и статье исходной.
Очень просто, если язык позволяет использовать конкретную команду процессора это язык низкого уровня.
При таком определении языком низкого уровня является любой язык, поддерживающий ассемблерные вставки, например, Object Pascal (Delphi) или С++.
Не могу принять такое определение.
Только хотел написать то же самое :-)
На Си, например, вполне себе используют векторные встроенные функции (intrinsic), которые один к одному соответствуют векторным инструкциям процессорам.
Ассемблерные вставки потому и называются вставками, что не входят в сам язык.
C это, безусловно, язык высокого уровня. Ассемблер — низкого. Даже Форт можно считать языком низкого уровня только если он работает на форт-процессоре (таких хватает), хотя, безотносительно, у него куда как больше оснований чем у С чтобы на это претендовать.
почему это безусловно-то? определение тогда дадите?
Мои вот требования плюс-минус соответствуют требованиям разработчиков системного софта: ОС, драйверов и т.д.
Но ведь вы сами в своей статье не приводите определений! Вы не пишите, что считаете высокооуровневым языком, а что низкоуровневым. Мы можем только догадываться, что под низкоуровневым вы понимаете компилируемые неуправляемве языки без механизмов обеспечивающих безопасность использования памяти. Тогда сюда вроде как и упомянутый Delphi подходит и Fortran и бог знает кто ещё.
Безусловно потому, что это самоочевидно. Границу провести очень просто. Если в языке есть абстракция от машинных кодов, за исключением мнемонических обозначений для упрощения восприятия, то это язык высокого уровня. Конец истории.
В этом плане даже продвинутые ассемблеры могут быть не вполне языками низкого уровня? поскольку включают некоторые конструкции над машинным кодом.
В C нет ничего от языка низкого уровня, за исключением атавизмов (впрочем, полезных) вроде автоинкремента.
Мои вот требования
Ваши требования не отменяют необходимости понимания базовых вещей.
Я бы порекомендовал автору, если не пописать в/на машинных кодах / Ассемблере / Форте / С, то, хотя бы, почитать побольше для понимания проблематики.
"очевидно потому что очевидно"
"вы, незнакомый мне человек, не писали на ассемблере, форте, с и в машинных кодах"
Аж даже не знаю, с чего начать конструктивную дискуссию. :-)
Вообще не очень понятно это желание некоторых отнести Си к языкам низкого уровня, а ещё больше удивляет с каким упорством эти люди пытаются отстоять эту точку зрения. Зачем это все? Чтобы быть "крутым" программистом пишущим на низкоуровневом языке? Так на это всем, кроме сишников желающих относится к "низкоуровневом" вообще наплевать — они либо пишут на высокоуровневых и не парятся, либо, если хотят основательно выпендриться на эту тему, пишут на чем-то более низкоуровневом. И вообще, надо понимать, что никакой особой крутости в том на чем писать нет, крутость она в том что конкретно ты на этом напишешь.
позволяет программисту создавать новые конструкции и абстрактные сущности
Это какие же? Новую функцию или структуру?
не забывайте про такое мощное средство как препроцессор!
Стандартный сишный препроцессор очень убог и примитивен. Тупая подстановка и пара операций. Сравните с m4 или макросами в лиспе, которые являются полноценными тьюринг-полными языками.
Читаю комментарии, споры об определениях… В рамках приведённых автором определений автор, как мне кажется, прав. Оппоненты оспаривают именно определения. Тут, похоже, конфликт религиозных убеждений — кто-то предпочитает академические определения, кто-то — прагматические.
Вспоминается товарищ прапорщика с крокодилами:
- А товарищ профессор говорит, что си — высокоуровневый язык!
- ну да, ну да… Но высота у него такая низкая-низкая...
Или даже так:
- Язык си живёт на третьем этаже. Берусь утверждать, что его можно отнести к жителям нижних этажей. Дадим определение нижнего этажа: в современном мире, где строят от семнадцати этажей и выше...
- коллега, Вы чудовищно неправы! Магазин можно открыть не выше первого этажа, всё что выше — уже слишком высоко!
- Ну не всем же открывать магазины. Моей бабушке важна доступность без лифта. Низко!
И так и спорят, каждый при своём.
Резюмируя, можно подытожить, что язык С самый низкоуровневый из высокоуровневых языков))
Язык Си безусловно низкоуровневый, как и всякий другой, который может создавать бинарь, способную работать на голом железе (ОС сюда входит, вместе со всеми своими компонентами в виде драйверов, модулей… ).
По поводу конфликта интересов прикладных и системных разработчиков — здесь сложно однозначно говорить, что Си тому виной. К примеру, разработчики БД стараются сами управлять взаимодействием СУБД и дискового хранилища, чтобы получить максимум скорости, но, на мой взгляд, здесь больше борьба с ОС, чем с компилятором.
По поводу памяти, кто мешает запросить у ОС кусок памяти достаточно большого размера и самому рулить его нарезкой под свои нужды? А вот следить, чтобы он не ушел в swap, больше чем нужно — это уже опять борьба с ОС, и компилятор здесь ни при чем.
Аппаратные платформы совершенствуются. Все идет в сторону параллельного и асинхронного исполнения кода. Но винить Си в отставании, на мой взгляд странно. Потоки и процессы — это не аппаратная, а программная абстракция, и так, или иначе, но именно из этого языка их реализация ушла в другие, а не наоборот.
По поводу, например, расширенных инструкций процессоров от Intel терзают смутные сомнения, что компании AMD будет позволено их реализовать в полном объеме, а значит авторам компиляторов каждый раз придется решать: работаем везде, или по максимуму, но только здесь.
По поводу стандарта, это каждый решает для себя сам. Я только недавно выучил этот язык, и просто стараюсь не использовать конструкции, работу которых не понимаю. Про сам стандарт наслышан. Как по мне, 900 страниц — это капец как много. Стоит написать его с нуля, не привязываясь к существующим реализациям и не оставляя неопределенностей. Несоответствие стандарту автоматически не делает компилятор непригодным к использованию, но может мотивировать к исправлению проблем в них. Особенно, если о них постоянно будет кто — то напоминать.
способную работать на голом железе
Т.е. без необходимости наличия операционной системы и/или виртуальной машины. Так-то и Java компилируемая, только компилируется в байткод, а не в самостоятельный бинарник.
Здесь ключевой момент — это способность работать без ОС. Интерпретируемые языки, даже с промежуточным представлением кода, сразу мимо. Для компилируемых — все зависит от того, можно слинковать с бинарником все зависимости или нет (сюда входит и рантайм самого языка, который, теоретически, может зависеть от наличия тех или иных библиотек и сами библиотеки, которые используются программой).
Интерпретируемые языки, даже с промежуточным представлением кода, сразу мимо.
Промежуточное представление кода, и то, как исполняется программа на каком-то языке — это не часть языка, а деталь реализации. Для любого интерпретируемого языка можно написать компилятор, для любого компилируемого — интерпретатор. Просто для каких-то языков проще сделать первое, для других — второе.
Кстати, например система команд x86 тоже является промежуточным представлением, «ниже» идут микрокоды.
Для любого интерпретируемого языка можно написать компилятор
Просто интересно: на Ваш взгляд, можно ли написать компилятор, например, JavaScript, не запихнув в итоговый бинарник целиком движок JavaScript? Не стремлюсь никак опровергнуть и/или ограничить Ваше утверждение, мне именно что интересно понять ход мысли.
можно ли написать компилятор, например, JavaScript,
А разве в nodeJS не использует именно компилятор V8, переводящий скрипты непосредственно в машинный код? Вроде есть проекты, которые позволяют сделать и бинарник в виде exe файла.
Вроде есть проекты, которые позволяют сделать и бинарник в виде exe файла.
А какие конкретно? Скорее всего там тупая упаковка js-движка и исходного кода на js в один файл.
можно ли написать компилятор, например, JavaScript, не запихнув в итоговый бинарник целиком движок JavaScript?
Запихнуть движок (в данном случае компилятор, раз мы хотим его компилировать) просто придется при использовании eval или Function, они его просто требуют, по другому никак.
И? Компиляция уже куда-то пропала, или что?
Си, как язык, абсолютно не требует подгонки подо что бы то ни было. Тем более, что и самого определения "системное программирование" в строгой и исчерпывающей форме вряд ли существует. Граница довольно сильно размыта. Например: ядро ОС безусловно системный компонент, командная оболочка csh/bash… еще, скорее всего, системная программа, утилиты, вроде find/wc уже ближе к прикладным, куда отнести KDE или XFce с гномом — это вообще тема холиварная. :)
Моя мысль была проще: если компилер может собрать самодостаточный файл, который можно запустить на голой железке, то язык, им реализуемый, можно назвать системным. И кстати, я не ограничивался одним Си. :))
Никто не станет спорить с тем, что язык ассемблера находится на самом низком уровне.У нас так не бывает, чтобы никто не спорил. Verilog и VHDL двумя уровнями ниже ассемблера.
:-) я знал, что статья в любом случае спорная. Но не ожидал, что спорить будут даже не про тезисы статьи, и не про тезисы оригинальной статьи, а какие-то второстепенные вопросы.
Но вообще да, есть и такой Verilog/VHDL, который, можно сказать, "компилируется в кремний". Условно: схемотехнический уровень < микрокод < язык ассемблера < C <… < декларативные DSL (или что там еще на вершину можно поставить)
И всё же C — низкоуровневый язык
Да как вы его не называйте, каждый найдем в нем то, что ему нужно.
Одни на нем пишут операционные системы, другие, используя препроцессор, создают высокоуровневую среду разработки и т.д.
Си это Си и этим все сказано.
Поправка по поводу GPU: большинство современных видеокарт (NVIDIA, AMD, ARM Mali) используют модель SIMT (Single Instruction, Multiple Threads), а не SIMD. Что не отменяет того факта, что алгоритмы для них нужны другие, чем для CPU общего назначения, и, соответственно, набор эффективно решаемых задач на CPU и GPU не совпадает.
Если я все правильно понимаю, то SIMT — развитие подхода SIMD, не так ли? Т.е. GPU все же пытаются базовый подход (одна программа — разные данных) по возможности расширить.
В каком-то смысле да, но для программиста выглядит ближе к многопоточной парадигме, в частности доступно ветвление кода внутри потока (реализуется выполнением обеих ветвей и применением маски).
А в NVIDIA Volta планировщик уже даже может при необходимости разделять потоки и вести для каждого отдельный стек и счётчик инструкций. Оптимизацией под Volta я пока не занимался, так что не могу сказать, насколько сильно это в реальности меняет подход к программированию.
В статье не упомянуто главное — в Си есть UB, и компилятор совершенно спокойно к этому относится.
В следующей версии gcc будет встраивать в программу с UB код для запуска ядерных ракет каждый раз, когда алиасят указатели.
ага, UB — адская штука, и это справедливая критика, и я сам готов гневные письма по поводу изобилия UB в комитет писать.
Но в оригинальной статье про это не было ни слова, там все больше про высокие материи, поэтому я решил на скучные темы не рассуждать.
Я считаю, что если бы в Си не было UB, то это был бы просто старый язык. Не старый и ужасный язык, а просто старый.
Но, благодаря UB, у нас ужас не только в старом ужасном Си, но и в новом суперсияющем C++!!27##.
Да, вы правы, бардак.
Знаменитый Джон Регэр даже предлагал выделить более адекватный и строгий диалект — Friendly C: https://blog.regehr.org/archives/1180. Но что-то не полетело. Сишники и компиляторщики — народ упертый...
#pragma launch nuclear missiles
Ещё один пример неопределенного поведения: курьёз с ANSI-директивой «#pragma». Согласно спецификации языка компиляторам предоставлена полная свобода при обработке этой конструкции. До версии 1.17 компилятор GCC при нахождении в исходном коде этой директивы пытался запустить Emacs с игрой «Ханойские башни».)))
Си — язык для системного программирования, вот в чем его особенность.
Предлагаю порассуждать об ОЗУ (устройство запоминающее оперативно, то есть быстро) и ПЗУ (устройство, которое постоянно что-то запоминает). Смысл будет примерно тот же.
Кстати, а байт-код это высокоуровневый или низкоуровневый язык?
Люди пишущие на абстрактных языках для абстрактных виртуальных машин выражаются абстрактным языком об абстрактных сущностях… =)))
А если по теме то, Си низкоуровневый или другими словами приближенный к машинному язык в том, что для каждой данной платформы данным компилятором сформируется условно одинаковый машинный код на условную операцию f(x) не зависимо от типа операнда, зависимость от разрядности единственное отличие в условностях.
Так что я бы сказал, что C достаточно низкоуровневый язык, по крайней мере для меня лично.
Тем временем сотрудники Intel заявляют "Rust — будущее системного программирования" :)
Найти правого невозможно без предварительных дефиниций.
В статье пару раз говорится о "грубых ошибках в дизайне языка". Можно дать несколько примеров этих ошибок? В чём они заключаются?
Сходу ошибки в языке еще в оригинальном K&R варианте:
- хитрые правила неявных преобразований типов.
- иерархия преобразований типов.
- приоритеты операторов.
- присваивание как оператор.
- многочисленные мутные моменты в работе массивов.
Стандарт ANSI — отдельная песня.
Приоритет побитовых операторов меньше приоритета операторов сравнения.
a & b == 7
означает a & (b == 7)
и 2. как базовые типы друг в друга превращаются. Переход от целочисленных типов к числам с плавающей запятой чреват проблемами. Переход от знаковых целых чисел к беззнаковым с потерей значений. Неопределенность поведения при переполении знаковых чисел.
тут уже написали простейший пример.
потеря типа массива при передаче в функцию. Невозможность передачи массива фиксированной длины без оборачивания в структуру.
Я бы еще добавил дебильный синтаксис объявления массивов, когда, например, int a[10] используется, хотя явно ж длина массива — часть типа. K&R тоже сказали, что это ошибка, но им хотелось, чтобы объявление типа было похоже на использование значения с этим типом, а потом было поздно менять.
Можно еще добавить про непонятный страх специальных ключевых слов. Тот же static в разных контекстах (глобальные и локальные объявления переменных) означает совершенно разные вещи.
Вот еще сильная ошибка: глобальные объявления по умолчанию имеют глобальную видимость.
Да там много всяких мелочей, к которым все привыкли, но у которых и объяснений-то нет, а K&R говорили в какой-то книжке, что "да, ошибки, но у нас тогда уже было 4 пользователя, поэтому мы решили ничего не менять ради обратной совместимости".
При всем при этом я люблю Си, да :-))
присваивание как оператор.
Правда?!
int a = b = c = d;
"Присаивание как выражение", конечно же, пардон. :-)))
Я всякого интересного начитался с присваиванием. Ещё радуют всякие хитрые сочетание инкерементов, разадресации и.д.
Это сейчас принято как можно проще и чище. А сами K&R настаивали на компактном стиле кода:-) да и была такая своего культура шифрования кода....
Sequence points остались в Си, но их вроде отменили в плюсах недавно.
присваивание как оператор.
Это наследие Алгола. Есть еще, например, в PL/M.
А какая собственно разница С высокоуровневый или нет?
Какие это задачи поможет решить ответ на этот вопрос?
Ну, тут дело в заочной дискуссии с криком души автора оригинальной статьи. Практический вопрос звучит так: можно ли опираться на Си как на самый низкий уровень при разработке приложений и ОС? Фактически язык сейчас именно так и используется, и очень много усилий вложено в оптимизацию даже железа под приложения на Си и Си-подобных языках.
И всё же C — низкоуровневый язык