Pull to refresh

Comments 199

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


Другой вопрос, что из всех языков высокого уровня он имеет самый низкий уровень, но вск же его уровень существенно выше, чем у любого действительно низкоуровневого языка (ассембрера).

"высокий" или "низкий" — понятия относительные. Относительно большей части прикладных (и системных ) приложений C очень даже низкий: низкоуровневые интерфейсы ОС определены в терминах С, сама ОС, как правило, написана на С и так далее.


Если же говорить про абстракции над железом, то можно договориться, что мнемоники языков ассемблера — тоже вполне себе абстракция над машинным кодом, и поэтому единственный низкоуровенвый язык — машинные коды.


А оттуда уже и до микрокода рукой подать…

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

Это вопрос "достаточной низкоуровневости".


Для ОС в основном, как мы на практике знаем, вполне достаточно тех средств, что предоставляет C (с некоторыми общепринятыми нестандартными расширениями).


Но может не хватить тех средств, что предоставляет, скажем, Java. Хотя использовать высокоуровневые языки в этой роли, конечно, были :-) Мы все знаем, где они, эти попытки, оказались.

Уже само наличие возможности делать ассемблерные вставки, даже с сахаром, говорит о том, что С — низкоуровневый.
А машинные коды это тоже абстракция над железом, так что либо мы отказываем в существовании низкоуровневых языков вообще, либо убираем этот критерий.

Turbo Pascal получается тоже низкоуровневый?

Да, в версиях ниже 5.5 TP находится на том же уровне, что и С.
В этом вашем Си, в отличие от Паскаля, нет самостоятельных перечислимых типов, диапазонов, множеств, вложенных функций, вдобавок паскалевые массивы и строки знают о своей длине.

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

Так можно сказать, что любой язык компилирующийся в нативный код: суть макроассемблер на стероидах.
И где же они там спрятаны? В ТП были вполне полноценные указатели, появилась арифметика указателей, были массивы для доступа в произвольное место памяти (Mem, MemW и MemL, синтаксис: Mem[segment:offset]), массивы для доступа к портам (Port, PortW) и даже директива inline для вставок машинного кода (не ассемблера, а именно машинного, в виде чисел).

да, все есть, я не спорю. (Турбо) Паскаль нисколько не уступал C.


Но язык рассказывается явно не этот этих возможностей. В Паскале традиционно упор делался на статическую аллокацию ресурсов, указатели можно вообще не использовать. Что, быть может, даже хорошо.


В отличие от Си, где без указателей даже шевельнуться нельзя.

Ну, не знаю. Турбо Паскаль был моим вторым языком (первым был ассемблер). Разок попробовал C и желания вернуться на ТП больше не возникло. Впрочем, мне вообще не нравился Филип Кан… )))
Мне в сях часто не хватает множеств, нормальных строк, нормального switch/case, вложенных функций и иногда возможности установить любой диапазон индексов у массива.

Красиво же:
const
  map: array ['A'..'F'] of byte = (10, 11, 12, 13, 14, 15);

или
if s[i] in ['a'..'f', 'A'..'F'] then ...
В паскале это не расширение какого-то одного конкретого компилятора, а стандарт.
Статических массивов было достаточно разве что школьникам, когда в подпрограмму надо было передать массив заранее неизвестной длины передавали указатель, пока не появилось динамических массивов.

Удивительно, но статических массивов очень долго хватало даже программистам на Фортране… :-)


Я вот стандарт Паскаля совсем не знаю, и смутно помню только Турбо Паскаль. Там же указатели довольно сильно ограничены, не так ли? Никаких там скрытых приведений типов и т.д.?

Есть все, что нужно для работы с указателями: взятие адреса, разыменование, арифметика. Причем тут «скрытые приведения типов»?

Да не переживайте вы так, я с глубоким уважением отношусь к Паскалю. :-)


Напротив, я именно что поинтересовался, т.к. во многом для меня Паскаль — язык более строгий, благодаря чему некоторые неудачные решения Си в нем так и не появились.

UFO just landed and posted this here

Про хаскель сложный вопрос, я склоняюсь считать его применимым для системного программирования, хотя и с рядом серьёзных ограничений.
А кто низкоуровневый?
Вон в js/java/php нельзя вставить ассемблер, они точно нет.

Вон в js/java/php нельзя вставить ассемблер, они точно нет.

В Java можно, вероятно в php и js на node.js тоже есть способы вставить нативный код. Практически любой язык (кроме браузерных) позволяет вставки C кода и вызовы С библиотек, а от него через него и асемблера.
Мне кажется низкоуровневый язык или нет определить проще простого. Если можно на нем написать bootloader — низкоуровневый, если нет — то нет.
UFO just landed and posted this here
Если можно на нем написать bootloader — низкоуровневый, если нет — то нет.

На Java можно написать bootloader, более того они уже написаны. В реальности, практически на любом языке можно написать bootloader, для языков, требующих виртуальной машины, придется повозится с компилятором, но все равно — можно.

Насчёт джавы вы неправы. По ссылке по сути способы с помощью программы на Java сгенерировать ассемблерный код, а потом результат слинковать динамически с текущим рантаймом. Если у языка имеется интероп а нативным кодом, то такую конструкцию построить всегда можно, но это не является ассемблерный вставкой само по себе. Да и интероп у джавы довольно поганенький.

тут недавно статья была, т.е. шарп низкоуровневый?
И BASIC низкоуровневый, у него вообще можно вставки шестнадцатеричного кода делать)))

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


И что-то не думаю, что в таких процессорах можно прогнозировать "время исполнения транслированных инструкций" даже при прямом кодировании на асмах. Очень уж много переменных тут надо учесть: нет ли обращения к памяти, находятся ли данные в кеше, не сработает ли внеочередное выполнение инструкций, что делает парный поток исполнения на ядре и какая в момент исполнения инструкции тактовая частота…

Строго говоря, написание программы на ассемблере или напрямую в машинных кодах тоже не может гарантировать время исполнения инструкций на современных х86. Поскольку есть такая штука, как Intel ME, имеющий доступ ко всей памяти и имеющий возможность залезть туда в любой момент. А поскольку шина памяти общая, то время доступа Intel ME отнимет кусочек от работы основного, написанного на ассемблере, блока. Причем произвольный доступ в SDRAM — это далеко не один такт. Далее, вследствие этого доступа, пойдут изменения в таблицах LRU для кэша и т.п.
Скорее всего, соотношение между временем работы Intel ME и основных ядер — один к миллиону. Но в любом случае, точно гарантировать ничего нельзя, даже на bare metal, без операционки.

Я мривык к старой границе между языками высокого и низкого уровня.
Нам ее давали в виде строгого определения (точную формулировку которого я забыл).
Но языки еизкого уровня оперируют только инструкциями процессора, заменяя в лучшем случае реальные коды операций мнемоническими обозначениями, а адреса и блоки памяти именами.
А языки высокого уровня имеют в своем арсенале команды, которые не могут быть записаны одной инструкцией процессора, и являются абстракцией над устройством компьютера.
Язык высокого уровня может содержать элементы языка низкого уровня, но не обязан.

Да, сам читал книги 70-х, где есть упоминались только языки ассемблера и высокоуровневые языки.


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


Во-вторых, с тех пор случились 80-ые, за которые разработка системных приложений и ОС перешла с ассемблера на портативный код на С, и последний стали рассматривать как основной низкоуровневый язык.

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

Как нет? А язык ассемблера ____ (подставить нужное)?

В ваше определение подходят только языки ассемблера? Ну, может быть.


Но, согласитесь, никто уже сто лет как не пишет на ассемблере, только в крайне узких областях. И как-то ранжировать языки все же придется по степени близости к железу, нет? Условно, тех, на которых имеет смысл драйвера и ОС писать, и тех, на которых пишут веб-сервисы?

Не только ассемблера, есть ещё язык машинных кодов. И вообще, чисто технически можно создать например Brainfuck-машину, для которой языком низкого уровня будет соответствующий язык.

UFO just landed and posted this here

Да, побаловаться с маленькими бывает очень весело :-) И в мире микроконтроллеров до сих пор частенько пишут на языках ассемблера, спорить не буду. "Частенько" в смысле "чаще, чем в среднем по палате".


Но все же очень много С и еще, говорят, Форт местами. И именно некоторые их низкоуровневые аспекты позволяют это делать.

UFO just landed and posted this here

Это да, мир микроконтроллеров очень многооборазен.

Современные микроконтроллеры мощнее первых пентиумов, четкой границы нет, как это понималось в 90е годы, например, когда Microchip/Atmel были преимущественно, с 64 байтами оперативки.

Так точно, я про это и говорил. Я бы не взялся какие-то конкретные параметры называть, т.к. устройств — огромное количество, с самыми разными интерфейсами и возможностями.

UFO just landed and posted this here
Узкие это не связанные с обычным ПО на ПК? Ибо у меня добрая половина знакомых что пишут под микроконтроллеры используют асм.

Да, частенько приходится и на асмах, насколько мне известно. Но еще чаще встречаются диалекты Си, нет?

В ваше определение подходят только языки ассемблера?

На данный момент — да, если забыть про машинные коды.
Но с развитием железа все может поменяться.


И как-то ранжировать языки все же придется по степени близости к железу, нет?

Ранжировать языки надо, но это не повод перекраивать сложившуюся терминологию.
Что мешает классифицировать ЯВУ по близости к железу без именования какого-либо из них низкоуровневым?
Можно назвать С, например, языком системного программирования.

Признаться, в контексте статьи это не важно :-) Системный так системный.


Вообще, Чизнэлл не об этом писал, а я, в свою очередь, полемизировал именно с ним. Думаю, он мог бы вполне осмысленно назвать статью иначе: "Архитектура фон Неймана и следующая из нее модель программирования устарели". Но это не так сильно звучит, как "популярный в определенных областях язык — плохой"

Тогда и ассемблер — не язык низкого уровня. Страничная адресация, risc-ядро, переименовывание регистров на лету и уже не знаешь, ни где лежит, ни что исполняется, ни сколько времени команде(ам) нужно.

Это если говорить о современном x86 ассемблере и наследниках. Есть архитектуры где что написано — то и исполняется.

Внутренняя кухня микропроцессора — это, конечно, интересно, но для программиста это вполне конкретный набор регистров и инструкций. И в ассемблере никаких абстракций, выходящих за рамки этих инструкций.
А страничная адресация — вполне себе аппаратная штука, управляемая командами процессора и регистрами. Всегда есть код, из которого при желании можно понять, что и где выполпяется (в BIOS или ОС).

Почему же нет, есть макросы. Тот же masm позволяет навертеть "сахара" поверх голых инструкций.

Тогда уж можно вспомнить и турбоассемблер с некоторой толикой поддержки ООП.
Но тут все сводится к упаковке некоторого текста в макроопределения, чтобы не вводить текст повторно, некоторым расширениям к именованию областей кода и данных, а также управлению компиляцией, позволяющему по условию исключать или добавлять ассемблерный код в итоговый компилируемый в машинные инструкции текст.
Непосредственно в языке дополнительных возможностей, выходящих за мнемокоды операций процессора не было.

Да, но отсутствие высокоуровневых конструкций — достаточное условие, чтобы признать язык низкоуровневым. Но не необходимое.
единственный низкоуровенвый язык — машинные коды
А оттуда уже и до микрокода рукой подать…

Особенно на x86, где машинный код тоже высокоуровневый, так как на уровне проца это просто API для копилятора...

C имеет самый низкий уровень из тех языков высокого уровня, что используются сейчас, но существуют и более низкоуровневые языки высокого уровня — например C--.

Классификации языков на низко- и высокоуровневые уже полвека, и эта классификация попросту изжила себя. Какой смысл в этом классификации, если практически все современные языки по этой классификации высокоуровневые?


Более правильно говорить о возможностях. В том же C вполне себе имееются низкоуровневые возможности: преобразование указателя к числу, возможность использования интринсиков для работы с определёнными регистрами и т.д.


Я бы классифицировал языки по возможностям:


  1. Работа с регистрами процессора, с железом.
  2. Минимиальные абстракции от аппаратного обеспечения (вызовы функций, переменные).
  3. Наличие алгоритмических абстракций (классы).
  4. Скриптовые языки.
    Причём язык относится не к конкретному классу, а к диапазону.

И, если уж на то пошло, машинный код современных процессоров — тоже высокоуровневый, потому что уже давно не соответствует реальной архитектуре. Команды транслируются во внутренние инструкции, регистры маппятся.

Так или иначе, нам по-прежнему нужен язык низкого уровня, причём построенный именно для популярных фоннеймановских компьютеров. И пускай C устарел, но, видимо, любому его преемнику всё равно придётся отталкиваться от тех же самых принципов.

С последним выводом в статье не согласен. Сделали Rust, который не наследует архаику C и дает все возможности низкоуровневого программирования, но имеет современные возможности

Да, действительно. И помимо Rust есть множество приличных языков.


Оригинальная статья сильно критиковала не только сам С как язык, а самую модель программирования, общую для С и Rust, на этой критике я и постарался сконцентрироваться.

UFO just landed and posted this here

Теоретически говоря Rust (и С++) предоставляют возможности для оптимизации, которых так просят компиляторщики.


Практически же выходит, что идиоматический код на С++ с шаблонами или Расте с макросами часто оказывается медленней С в выскокоуровневом стиле, и даже с использованием того же самого компилирующего бэкэнда. :-)


https://benchmarksgame-team.pages.debian.net/benchmarksgame/fastest/c.html

UFO just landed and posted this here

Да, зависит от практики, и это парадокс.


Говорю ж: нет серьезных причин, по которым С++ должен быть медленней С, особенно в случае GCC/Clang. Вроде как вычисления во время компиляции и шаблоны позволяют, например, не использовать так обильно void* и, скажем, более эффектевно инлайнить код.


Но практический код на С++ регулярно оказывается медленней. Неплохо об этом не так давно на Хабре писали: https://habr.com/ru/post/347688/

UFO just landed and posted this here
Сами по себе классы не тянут за собой RTTI и т.д., это все появляется, когда мы хотим полиморфизм. Например, классы без RTTI часто используются при программировании тех же микроконтроллеров.

ну, последняя языкодельная мода нечто подобное и диктует, когда от полиморфизма через наследование в принципе отказываются.


Хотя остаются всякого рода интерфейсоподобные приемы, но все же.

UFO just landed and posted this here
UFO just landed and posted this here

ага, я в курсе :-) Вообще, чего только не делают на макросах… Есть еще моя неплохая klib, где популярные структуры данных оформлены так, чтобы указатели не требовались.


Но, признаться, макросы — умирающее искусство.

UFO just landed and posted this here

Ага, но Тьюринг полнотой обладают даже вычисления на ДНК :-) Все же страшно неудобно, и потом разбираться бывает трудно, чего это случайная запятая вдруг в третьем месте ошибки вызывает. Впрочем, плюсы тут не много лучше.


В конечном итоге, ни макросы Си, ни шаблоны плюсов для вычислений не создавались, и это видно :-)


Вон, в D все то же самое, но чистенько. Хотя всем все равно :-)

UFO just landed and posted this here

не, ну ваш пример еще очень даже чистый. :-) Это не ассемблер ли на макросах? :-)

UFO just landed and posted this here

а что за книжка?

UFO just landed and posted this here

не поделитесь набросками? :-) Интересно, такой литературы почти уже и не пишут.

UFO just landed and posted this here
Просто отмечу, что очень забавно, что небольшую картинку вы под спойлер спрятали, а простыню текста на несколько экранов — нет
  • У вас 50 разных типов?
  • Если эти функции написать ручками, их будет не 50?
UFO just landed and posted this here
Шаблоны вида return x == y любой адекватный компилятор встроит и драгоценное место они занимать не будут.

А конкретно в вашем примере (const T& x, const T& y) комбинаторного взрыва не будет. Думаю, очевидно, почему.
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
Еще бы эти тесты честно писали, а то в одном тесте в С используют VLA, в С++ коде std::vector
Если бы еще эти тесты говорили о чем-то кроме немного разной оптимизации GCC и Clang под расширения наборов инструкций x86, было бы совсем хорошо.
там, где rust «дает все возможности низкоуровневого программирования», он теряет все свои преимущества.

Интересно. Куда же девается система типов, возможности для разграничения потенциально опасного и безопасного кода, дженерики и т.п.?

там, где начинается низкоуровневое программирование, начинается unsafe. А там, где начинается unsafe, теряется киллер фича раста — гарантия безопасности. Остальные фичи раста не уникальны

Аналоги borrow checker есть в Dyon (скриптовый), MLKit (функциональный), Carp (вариант Lisp'а), LinearML (функциональный язык с потенциалом системного использования, в разработке, последний коммит 5 лет назад), Cyclone (safe C), Discus (функциональный), Cone (системный язык, в разработке, последний коммит 4 месяца назад), Rust.


Типы суммы, pattern matching, дженерики/темплейты в потенциально системных языках: Cone, Rust.


В общем — да, не уникально.

Ох, каждый раз когда вижу упоминания компиляторов Standard ML — грустно становится. Такой красивый язык, такие шикарные компиляторы… И из всего семейства более-менее популярным стали только ленивый Haskell и некрасивый Ocaml.


Вкусовщина, конечно, спорить тут не буду :-)

UFO just landed and posted this here

уф… это ж такой спор можно развести… :-) А тут и так флейма...


В общем, я за предсказуемость eager evaluation, тем более, что ленивые вычисления на Standard ML или Окамле очень даже делаются.

UFO just landed and posted this here

Я все это знаю, Окасаки проделал потрясающую работу и вообще большой молодец, но… У него простейшие структуры данных делаются весьма нетривиально, если с чисто практической точки зрения.


Ну а тот факт, что для околоМЛ языков легче теоремы доказывать я никому никогда не продам :-)


Вы можете меня к стенке поставить и расстрелять, но я за предсказуемость и простоту :-)

Контора пишет — крестьянин пашет ©.
Задержки при получении инструкциями данных из памяти частично компенсируются внеочередным выполнением (англ. 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, то придется еще обсуждать весь зоопарк опробованных в роли ЦПУ архитектур, и это уже слишком. :-)

тут же мы не будем спорить? :-)

Ага, тут мы не будем спорить:)

Просто если упоминать VLIW, то придется еще обсуждать весь зоопарк опробованных в роли ЦПУ архитектур, и это уже слишком. :-)

ИМХО нужно просто оставить точными формулировки. А это можно сделать, и не упоминая VLIW.
Никто не станет спорить с тем, что язык ассемблера находится на самом низком уровне.

Если быть точным, то по эту сторону кристалла самый низкий — это язык машинных кодов. Ну, если брать ГОСТовскую терминологию; язык ассемблера — ака язык мнемокодов — на ступень выше.

VlK
я не имею в виду «ГОСТ на терминологию», я имею в виду «типичную терминологию, используемую в отраслевых ГОСТах». Сейчас уже не вспомню точно где, но год или два назад, когда в предыдущий раз на хабре были холиворы касательно того, что такое низкий уровень, что такое высокий, что такое транслятор и что такое компилятор и т.п. — нашел это.

VlK
а, нет, вру. Все же это были разные стандарты, втч ГОСТ на терминологию.
ГОСТ 19781-90. Обеспечение систем обработки информации программное. Термины и определения


Обратите внимание, что там вводится понятие автокода, который занимает промежуточное положение между машинным языком и ассемблером.

А что это за ГОСТ такой? Я не знал, что где-то вообще можно найти строгие определения.

Интересный, кстати, документ!


Но там "языки низкого уровня" вообще не упоминаются! Имеются только машинный код, автокод, языки ассемблера и языки высокого уровня.

Никто не станет спорить с тем, что язык ассемблера находится на самом низком уровне.


Наверно всё-таки машинный код на самом низком уровне. Некоторые виды макро-ассемблеров после компиляции могут такого нагенерировать, что мало не покажется…

А язык Форд по вашему мнению выше или ниже уровня С?

"Форд" или все же "Форт"? :-)

Проклятая автокоррекция… :-\

Но вообще вопрос хороший.


Если в контексте статьи, то вполне себе удовлетворяет требованиям: легко портируется, есть стандарт, детали платформ различает (в определенных рамках). Хотя я не спец по Форту, признаться.


Вы как думаете?

Сложный вопрос, поэтому и спрашиваю… ;-)

Басовые идеи Форта и отличия от С, по моему мнению, следующие:

А) Форт абстрагирует стековую машину, а не регистровую PDP-11 как С

Б) Базовая идея Форта — это организация программ и данных в виде словаря, который и исполняется. Поэтому Форт может сам себя динамически расширять (это похоже на Лисп). У С таких динамических способностей не наблюдается

В) Большинство реализаций Форта — интерпретаторы, а не компиляторы, хотя компилируемый Форт наверняка возможен (я не сталкивался). А вот об интерпретаторах С я не слышал

Г) Наконец, Форт — это безтиповой язык, а вот С хотя и близок к безтипности, но всё-таки официально таковым не считается

Так что решайте сами…

Кстати, вопрос о наличии типов в языке — основной с точки зрения различия «высоких» и «низких» языков
UFO just landed and posted this here
Это компилятор, просто он может сразу запустить скомпилированный код и внешне подобен интерпретатору.

Например есть церновский CINT или пришедший к нему на замену Cling (но это уже вроде как C++).
Большинство реализаций Форта — интерпретаторы, а не компиляторы

Форт одновременно является и тем и другим.
Например
\ тут форт интерпретируемый
S" Hello from interpreting mode!" TYPE CR
: hello \ а тут уже компилируемый, : нас перевело в этот режим
    S" Hello from compiling mode"  \ но S" является immediate-словом и поэтому
                                   \ не скомпилировалось, а было вызвано, оно
                                   \ распарсило строковый литерал, где-то его
                                   \ сохранило и скомпилировало код, который
                                   \ заносит на стек адрес строки и ее длину
    TYPE CR  \ это обычные слова, они просто скомпилировались
;  \ ; тоже immediate-слово, заканчивает определение и переводит нас обратно
   \ в режим интерпретации

\ мы снова в интерпретаторе
hello  \ зовем только что скомпилированное слово

BYE  \ пока ;)

UFO just landed and posted this here
А при использовании косвенного или подпрограммного не получается чтоли? Или например байткода? Хотите сказать там ничего не компилируется?

К тому же есть системы, компилирующиеся в нативный код: SP-Forth, SwiftForth
UFO just landed and posted this here
В случае с прямым шитым кодом каждое «высокоуровневое» (то, которое определяется через двоеточие) слово компилируется в виде цепочки адресов подпрограмм в машинном коде, а в начале этой цепочки компилируется машинный код, который устанавливает указатель форт-инструкций на эту цепочку и сохраняет его предыдущее значение в стеке возвратов.

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

Большая разница с компиляторами большинства других языков в том, что этот код компилируется в оперативной памяти и часто там и остается, если в программе явно не было указано команды сохранения в файл. Чтобы было понятнее, можно считать, что это аналог запуска Си программы командой tcc -run program.c.

P.S. Да и, кстати, настоящих интерпретируемых языков осталось немного, почти всегда это компиляторы в байткод, которые (если явно не было указано другого) сразу запускают интерпретатор этого байткода, да и там тоже часто JIT-компилятор.

Ну, вот встречные соображения.


Буду честнен, простой Форт на шитом коде я делал в качестве упражнения, и прикладной код на нем не писал. Поправьте, если мой ограниченный опыт ложен.


а) в PDP-11 и x86 вполне себе есть как стек, так и регистры. Использование этого стека, в сущности, вопрос реализации как в С, так и в Форте.


б) да


в) делают и интерпретаторы С, например, для проверки работы программ на предмет всяких там неопределенных поведений, хотя, конечно, практически ими никто не пользуется


г) я так понимаю, что оперирование машинным словом, как в простых Фортах, это именно бестиповое (или, верней, дотиповое, т.к. появилось оно раньше популяризации идеии типов) программирование. В каком-то смысле это, безусловно ниже уровня Си с его явными простыми типами, пускай даже и сильно прибитыми к доступным на конкретнйо платформе типам.


Последней же вопрос. Типы в форме Си, где они привязаны к платформе, разве не достаточно низкий уровень? Железные вопросы они отражают, это ж не Haskell.

Стековая машина предполагает не просто наличие стека, а расположение операндов в стеке. Как в fpu.

вы про абстрактную стековую машину? Это ж просто модель.


Реальные популярные наборы инструкций — страшные химеры в этом смысле, особенно x86. Что-то так, через стек, что-то эдак, только с регистрами...

Хочу лишь немного уточнить свои мысли по Форту и С:

а) Без сомнений — любая мало-мальски универсальная регистровая машина (PDP-11, x86) может работать в качестве или эмулировать стековую (так делаются в большинстве реализаций Форта). Моя мысль была в том что Форту требуется стековая виртуальная машина, поскольку всё взаимодействие в словаре происходит через стек. Если убрать стек и оставить словарь, то Форта — как мы его знаем — не будет. А вот С семантически от наличия или отсутствия стека не зависит, хотя все его реализации о которых я знаю безусловно стек используют.

б) да, да :)

в) Не представляю каким образом система типов в С будет работать в режиме интерпретирования без жесткой run-time поддержки (тут мы уже движемся в сторону Питона), хотя наверно всё возможно при желании… Система типов в С слабая, особенно в ранних стандартах, но всё же компилятор на неё опирается.

г) Тут опять-таки я имел в виду, что работа ядра Форта (стек + словарь) никак не зависит от того что содержится в том или ином машинном слове (или в терминологии Форта — ячейке). Семантика того что содержат отдельные ячейки — дело самой программы. А вот работа компилятора С от типов очень даже зависит, например (и особенно) в части приведения к типам в выражениях.

Последний вопрос: Конечно, система типов в С базируется на «низких» машинных типах, отсюда и утверждения о том что С — это glorified assembler. И в этом сила С как системного языка. Но всё же там есть и более «высокие» т.е. производные типы — структуры и массивы, хотя их реализация очень проста и максимально приближена к машинному уровню.

Все так :-)


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

Си — высокоуровневый язык программирования. См. Ассемблер.

а как вы определите что есть "язык низкого уровня"? Тут выше я уже указывал, что определения бывают разные, в том числе в моей статье и статье исходной.

Очень просто, если язык позволяет использовать конкретную команду процессора это язык низкого уровня.

При таком определении языком низкого уровня является любой язык, поддерживающий ассемблерные вставки, например, Object Pascal (Delphi) или С++.
Не могу принять такое определение.

Только хотел написать то же самое :-)


На Си, например, вполне себе используют векторные встроенные функции (intrinsic), которые один к одному соответствуют векторным инструкциям процессорам.

Ассемблерные вставки потому и называются вставками, что не входят в сам язык.

В Delphi ещё GOTO есть. Это не вставка, а часть языка. Считается за «процессорную команду»?
Нет, потому что вы не можете использовать адрес или регистр в goto; у процессора нет концепции метки, процессорная команда безусловного перехода использует адрес или регистр.

C это, безусловно, язык высокого уровня. Ассемблер — низкого. Даже Форт можно считать языком низкого уровня только если он работает на форт-процессоре (таких хватает), хотя, безотносительно, у него куда как больше оснований чем у С чтобы на это претендовать.

почему это безусловно-то? определение тогда дадите?


Мои вот требования плюс-минус соответствуют требованиям разработчиков системного софта: ОС, драйверов и т.д.

Но ведь вы сами в своей статье не приводите определений! Вы не пишите, что считаете высокооуровневым языком, а что низкоуровневым. Мы можем только догадываться, что под низкоуровневым вы понимаете компилируемые неуправляемве языки без механизмов обеспечивающих безопасность использования памяти. Тогда сюда вроде как и упомянутый Delphi подходит и Fortran и бог знает кто ещё.

Безусловно потому, что это самоочевидно. Границу провести очень просто. Если в языке есть абстракция от машинных кодов, за исключением мнемонических обозначений для упрощения восприятия, то это язык высокого уровня. Конец истории.
В этом плане даже продвинутые ассемблеры могут быть не вполне языками низкого уровня? поскольку включают некоторые конструкции над машинным кодом.
В C нет ничего от языка низкого уровня, за исключением атавизмов (впрочем, полезных) вроде автоинкремента.


Мои вот требования

Ваши требования не отменяют необходимости понимания базовых вещей.
Я бы порекомендовал автору, если не пописать в/на машинных кодах / Ассемблере / Форте / С, то, хотя бы, почитать побольше для понимания проблематики.

"очевидно потому что очевидно"


"вы, незнакомый мне человек, не писали на ассемблере, форте, с и в машинных кодах"


Аж даже не знаю, с чего начать конструктивную дискуссию. :-)

Определение дано, но его вы, видимо, не поняли.
В этой ситуации, действительно, дискутировать будет самоубийственно.
Но к рекомендации, всё же, прислушайтесь. Чтобы больше не попадать впросак.

Вообще не очень понятно это желание некоторых отнести Си к языкам низкого уровня, а ещё больше удивляет с каким упорством эти люди пытаются отстоять эту точку зрения. Зачем это все? Чтобы быть "крутым" программистом пишущим на низкоуровневом языке? Так на это всем, кроме сишников желающих относится к "низкоуровневом" вообще наплевать — они либо пишут на высокоуровневых и не парятся, либо, если хотят основательно выпендриться на эту тему, пишут на чем-то более низкоуровневом. И вообще, надо понимать, что никакой особой крутости в том на чем писать нет, крутость она в том что конкретно ты на этом напишешь.

"Да не пригорает у меня!!!111" ©

Зачем все эти споры, низкого или высокого уровня? Раз с одного конца он смотрится эдак, а с другого наоборот, совершенно логично из этого вытекает, что Си — язык СРЕДНЕГО уровня.
И все таки язык Си — ВЫШЕ СРЕДНЕГО уровня, так как позволяет программисту создавать новые конструкции и абстрактные сущности, кторыми он в последствии оперирует для решения задачи. И не забывайте про такое мощное средство как препроцессор! Или это уже не Си? ;-)
позволяет программисту создавать новые конструкции и абстрактные сущности

Это какие же? Новую функцию или структуру?

не забывайте про такое мощное средство как препроцессор!

Стандартный сишный препроцессор очень убог и примитивен. Тупая подстановка и пара операций. Сравните с m4 или макросами в лиспе, которые являются полноценными тьюринг-полными языками.
Заголовок спойлера
Единственные реально расширяемые языки — это форт и его потомки (например более высокоуровневый Factor или недавно представленный fift для TON). Форт одновременно является и препроцессором, и интерпретатором, и компилятором, и линкером, и операционной системой и вообще чем угодно. Например в форте нет никакого синтаксиса для описания классов, пишем небольшое расширение и тут же можем его использовать. Абсолютно любое слово можно переопределить как угодно прямо во время работы транслятора. Можно ввести в язык любую синтаксическую конструкцию, какая только взбредет в голову. В добавок механизм словарей (в других языках наиболее близким понятием является неймспейс) дает возможность иметь несколько лексиконов и переключатся между ними как угодно. К тому же есть возможность самому заниматься парсингом исходного кода, и можно отказатся от основного синтаксиса в виде слов разделяемых пробелами и перейти к любому другому.

Читаю комментарии, споры об определениях… В рамках приведённых автором определений автор, как мне кажется, прав. Оппоненты оспаривают именно определения. Тут, похоже, конфликт религиозных убеждений — кто-то предпочитает академические определения, кто-то — прагматические.


Вспоминается товарищ прапорщика с крокодилами:


  • А товарищ профессор говорит, что си — высокоуровневый язык!
  • ну да, ну да… Но высота у него такая низкая-низкая...

Или даже так:


  • Язык си живёт на третьем этаже. Берусь утверждать, что его можно отнести к жителям нижних этажей. Дадим определение нижнего этажа: в современном мире, где строят от семнадцати этажей и выше...
  • коллега, Вы чудовищно неправы! Магазин можно открыть не выше первого этажа, всё что выше — уже слишком высоко!
  • Ну не всем же открывать магазины. Моей бабушке важна доступность без лифта. Низко!

И так и спорят, каждый при своём.

если б запилили голосовалку, я б сказал что низкоуровневый!

Резюмируя, можно подытожить, что язык С самый низкоуровневый из высокоуровневых языков))

Язык Си безусловно низкоуровневый, как и всякий другой, который может создавать бинарь, способную работать на голом железе (ОС сюда входит, вместе со всеми своими компонентами в виде драйверов, модулей… ).
По поводу конфликта интересов прикладных и системных разработчиков — здесь сложно однозначно говорить, что Си тому виной. К примеру, разработчики БД стараются сами управлять взаимодействием СУБД и дискового хранилища, чтобы получить максимум скорости, но, на мой взгляд, здесь больше борьба с ОС, чем с компилятором.
По поводу памяти, кто мешает запросить у ОС кусок памяти достаточно большого размера и самому рулить его нарезкой под свои нужды? А вот следить, чтобы он не ушел в swap, больше чем нужно — это уже опять борьба с ОС, и компилятор здесь ни при чем.
Аппаратные платформы совершенствуются. Все идет в сторону параллельного и асинхронного исполнения кода. Но винить Си в отставании, на мой взгляд странно. Потоки и процессы — это не аппаратная, а программная абстракция, и так, или иначе, но именно из этого языка их реализация ушла в другие, а не наоборот.
По поводу, например, расширенных инструкций процессоров от Intel терзают смутные сомнения, что компании AMD будет позволено их реализовать в полном объеме, а значит авторам компиляторов каждый раз придется решать: работаем везде, или по максимуму, но только здесь.
По поводу стандарта, это каждый решает для себя сам. Я только недавно выучил этот язык, и просто стараюсь не использовать конструкции, работу которых не понимаю. Про сам стандарт наслышан. Как по мне, 900 страниц — это капец как много. Стоит написать его с нуля, не привязываясь к существующим реализациям и не оставляя неопределенностей. Несоответствие стандарту автоматически не делает компилятор непригодным к использованию, но может мотивировать к исправлению проблем в них. Особенно, если о них постоянно будет кто — то напоминать.

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

Т.е. без необходимости наличия операционной системы и/или виртуальной машины. Так-то и Java компилируемая, только компилируется в байткод, а не в самостоятельный бинарник.

а не в самостоятельный бинарник

можно и в бинарник, более того существуют полностью написанные на OC: JavaOS, jEmbryoS и JNode (в последнем, есть, правда микроядро с асемблером).

Здесь ключевой момент — это способность работать без ОС. Интерпретируемые языки, даже с промежуточным представлением кода, сразу мимо. Для компилируемых — все зависит от того, можно слинковать с бинарником все зависимости или нет (сюда входит и рантайм самого языка, который, теоретически, может зависеть от наличия тех или иных библиотек и сами библиотеки, которые используются программой).

Это что, 100500-я попытка подогнать понятие низкоуровнего языка под Си? )))

Интерпретируемые языки, даже с промежуточным представлением кода, сразу мимо.

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

Кстати, например система команд x86 тоже является промежуточным представлением, «ниже» идут микрокоды.
Для любого интерпретируемого языка можно написать компилятор

Просто интересно: на Ваш взгляд, можно ли написать компилятор, например, JavaScript, не запихнув в итоговый бинарник целиком движок JavaScript? Не стремлюсь никак опровергнуть и/или ограничить Ваше утверждение, мне именно что интересно понять ход мысли.

можно ли написать компилятор, например, JavaScript,

А разве в nodeJS не использует именно компилятор V8, переводящий скрипты непосредственно в машинный код? Вроде есть проекты, которые позволяют сделать и бинарник в виде exe файла.
Там же вроде JIT, код компилируется прямо во время работы, и из-за оптимизаций может быть разным при различных запусках в разных условиях с разными данными.

Вроде есть проекты, которые позволяют сделать и бинарник в виде exe файла.

А какие конкретно? Скорее всего там тупая упаковка js-движка и исходного кода на js в один файл.
можно ли написать компилятор, например, JavaScript, не запихнув в итоговый бинарник целиком движок JavaScript?

Запихнуть движок (в данном случае компилятор, раз мы хотим его компилировать) просто придется при использовании eval или Function, они его просто требуют, по другому никак.

И? Компиляция уже куда-то пропала, или что?

Си, как язык, абсолютно не требует подгонки подо что бы то ни было. Тем более, что и самого определения "системное программирование" в строгой и исчерпывающей форме вряд ли существует. Граница довольно сильно размыта. Например: ядро ОС безусловно системный компонент, командная оболочка csh/bash… еще, скорее всего, системная программа, утилиты, вроде find/wc уже ближе к прикладным, куда отнести KDE или XFce с гномом — это вообще тема холиварная. :)
Моя мысль была проще: если компилер может собрать самодостаточный файл, который можно запустить на голой железке, то язык, им реализуемый, можно назвать системным. И кстати, я не ограничивался одним Си. :))

Тут в последнее время шарписты всячески извращаются, один добавил в язык асмовставки, другой генерирует ассемблерный код для AVR. Относить ли после этого C# к низкоуровневым?

Ага, даже SIMD в C# уже завозят.
Да и вообще, является ли MSIL языком низкого уровня или нет? Ведь это же, по факту, ассемблер.

UFO just landed and posted this here
Никто не станет спорить с тем, что язык ассемблера находится на самом низком уровне.
У нас так не бывает, чтобы никто не спорил. 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 я пока не занимался, так что не могу сказать, насколько сильно это в реальности меняет подход к программированию.

Я, признаюсь, лучше знаю как работают векторные юниты в ЦПУ, принципы GPU знаю довольно поверхностно, без практической работы, так что могу только кивнуть :-)

В статье не упомянуто главное — в Си есть UB, и компилятор совершенно спокойно к этому относится.


В следующей версии gcc будет встраивать в программу с UB код для запуска ядерных ракет каждый раз, когда алиасят указатели.

ага, UB — адская штука, и это справедливая критика, и я сам готов гневные письма по поводу изобилия UB в комитет писать.


Но в оригинальной статье про это не было ни слова, там все больше про высокие материи, поэтому я решил на скучные темы не рассуждать.

Я считаю, что если бы в Си не было UB, то это был бы просто старый язык. Не старый и ужасный язык, а просто старый.


Но, благодаря UB, у нас ужас не только в старом ужасном Си, но и в новом суперсияющем C++!!27##.

Да, вы правы, бардак.


Знаменитый Джон Регэр даже предлагал выделить более адекватный и строгий диалект — Friendly C: https://blog.regehr.org/archives/1180. Но что-то не полетело. Сишники и компиляторщики — народ упертый...

Никому ненужная попытка переписать терминологию.

Си — язык для системного программирования, вот в чем его особенность.

Предлагаю порассуждать об ОЗУ (устройство запоминающее оперативно, то есть быстро) и ПЗУ (устройство, которое постоянно что-то запоминает). Смысл будет примерно тот же.

Кстати, а байт-код это высокоуровневый или низкоуровневый язык?

Люди пишущие на абстрактных языках для абстрактных виртуальных машин выражаются абстрактным языком об абстрактных сущностях… =)))
А если по теме то, Си низкоуровневый или другими словами приближенный к машинному язык в том, что для каждой данной платформы данным компилятором сформируется условно одинаковый машинный код на условную операцию f(x) не зависимо от типа операнда, зависимость от разрядности единственное отличие в условностях.

Спор о том, является ли C низкоуровневым языком кажется мне пустым. В моей скромной личной практике не так редко встречались задачи, для которых хотелось, чтобы в C имелись более «высокоуровневые» средства абстракции. А вот ситуации, когда не хватало низкоуровневых средств — можно пересчитать по пальцам, и это все были сильно платформозависимые моменты: запись в порт IO на PC, генерация прерывания, доступ к регистру флагов и т.д.
Так что я бы сказал, что C достаточно низкоуровневый язык, по крайней мере для меня лично.
Есть некоторая разница между низким уровнем и не достаточно высоким.
И все таки Си язык высокого уровня с возможностью низкоуровневого доступа, так как в ассемблере уровень абстракций данных это регистры процессора и ячейки памяти именованные через метки или EQU, именованные ячейки обычно глобальные (чтоб ограничить время жизни в локальных именованных ячейках придется писать пролог и эпилог в каждой процедуре).
UFO just landed and posted this here

Ну, Лисп-машины в свое время пользовались некоторой популярностью. В итоге универсальность традиционных архитектур взяла свое, по крайней мере мне неизвестны удачные попытки абстрактные машины высокоуровневых языков оформить в железе.

Тем временем сотрудники Intel заявляют "Rust — будущее системного программирования" :)

Очень может быть. :-) Прет как ледокол в последние годы.


И вообще, в рамках аспектов, обсуждаемых в статье, Rust — продолжение С.

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

В статье пару раз говорится о "грубых ошибках в дизайне языка". Можно дать несколько примеров этих ошибок? В чём они заключаются?

Сходу ошибки в языке еще в оригинальном K&R варианте:


  1. хитрые правила неявных преобразований типов.
  2. иерархия преобразований типов.
  3. приоритеты операторов.
  4. присваивание как оператор.
  5. многочисленные мутные моменты в работе массивов.

Стандарт ANSI — отдельная песня.

Может я, конечно, имею мало опыта программирования на С, но можно привести прям конкретный пример? Что не так, скажем, с приоритетами операторов? Потому что из перечисленного спсика я сталкивался только с 4-м пунктом.

Приоритет побитовых операторов меньше приоритета операторов сравнения.


a & b == 7 означает a & (b == 7)

  1. и 2. как базовые типы друг в друга превращаются. Переход от целочисленных типов к числам с плавающей запятой чреват проблемами. Переход от знаковых целых чисел к беззнаковым с потерей значений. Неопределенность поведения при переполении знаковых чисел.


  2. тут уже написали простейший пример.


  3. потеря типа массива при передаче в функцию. Невозможность передачи массива фиксированной длины без оборачивания в структуру.



Я бы еще добавил дебильный синтаксис объявления массивов, когда, например, int a[10] используется, хотя явно ж длина массива — часть типа. K&R тоже сказали, что это ошибка, но им хотелось, чтобы объявление типа было похоже на использование значения с этим типом, а потом было поздно менять.


Можно еще добавить про непонятный страх специальных ключевых слов. Тот же static в разных контекстах (глобальные и локальные объявления переменных) означает совершенно разные вещи.


Вот еще сильная ошибка: глобальные объявления по умолчанию имеют глобальную видимость.


Да там много всяких мелочей, к которым все привыкли, но у которых и объяснений-то нет, а K&R говорили в какой-то книжке, что "да, ошибки, но у нас тогда уже было 4 пользователя, поэтому мы решили ничего не менять ради обратной совместимости".


При всем при этом я люблю Си, да :-))

"Присаивание как выражение", конечно же, пардон. :-)))


Я всякого интересного начитался с присваиванием. Ещё радуют всякие хитрые сочетание инкерементов, разадресации и.д.

Интересного да, там вагон и маленькая тележка. Из-за неясностей с sequence point, или как там его теперь называть. Да и многомерные массивы/указатели добавляют, да. Главный принцип написания правильного C/C++ кода — не умничать, особенно там, где не следует. Но это так трудно…

Это сейчас принято как можно проще и чище. А сами K&R настаивали на компактном стиле кода:-) да и была такая своего культура шифрования кода....


Sequence points остались в Си, но их вроде отменили в плюсах недавно.

А сами K&R настаивали на компактном стиле кода:-) да и была такая своего культура шифрования кода....

Но мы любим их не только за это...;)
присваивание как оператор.

Это наследие Алгола. Есть еще, например, в PL/M.

В данно контексте совершенно не важно, кто и от кого заразился :-)

А какая собственно разница С высокоуровневый или нет?
Какие это задачи поможет решить ответ на этот вопрос?

Ну, тут дело в заочной дискуссии с криком души автора оригинальной статьи. Практический вопрос звучит так: можно ли опираться на Си как на самый низкий уровень при разработке приложений и ОС? Фактически язык сейчас именно так и используется, и очень много усилий вложено в оптимизацию даже железа под приложения на Си и Си-подобных языках.

Sign up to leave a comment.