Спустя все эти годы мир по-прежнему основан на программировании на C
Опубликовано: 2022-03-11Многие из существующих сегодня проектов C были начаты несколько десятилетий назад.
Разработка операционной системы UNIX началась в 1969 году, а ее код был переписан на C в 1972 году. Фактически язык C был создан для переноса кода ядра UNIX с ассемблера на язык более высокого уровня, который выполнял бы те же задачи с меньшим количеством строк кода. .
Разработка базы данных Oracle началась в 1977 году, а ее код был переписан с ассемблера на C в 1983 году. Она стала одной из самых популярных баз данных в мире.
В 1985 году была выпущена Windows 1.0. Хотя исходный код Windows не является общедоступным, было заявлено, что его ядро в основном написано на C, а некоторые части находятся на ассемблере. Разработка ядра Linux началась в 1991 году, и оно также написано на C. В следующем году оно было выпущено под лицензией GNU и использовалось как часть операционной системы GNU. Сама операционная система GNU была запущена с использованием языков программирования C и Lisp, поэтому многие ее компоненты написаны на C.
Но программирование на C не ограничивается проектами, начатыми несколько десятилетий назад, когда языков программирования было не так много, как сегодня. Многие проекты на C все еще начаты сегодня; на это есть веские причины.
Как мир питается от C?
Несмотря на преобладание языков высокого уровня, C продолжает расширять возможности мира. Ниже приведены некоторые из систем, которые используются миллионами и запрограммированы на языке C.
Майкрософт Виндоус
Ядро Microsoft Windows разработано в основном на C, с некоторыми частями на языке ассемблера. На протяжении десятилетий самая используемая в мире операционная система, на долю которой приходится около 90% рынка, работала на ядре, написанном на C.
линукс
Linux также написан в основном на C, с некоторыми частями на ассемблере. Около 97 процентов из 500 самых мощных суперкомпьютеров мира работают под управлением ядра Linux. Он также используется во многих персональных компьютерах.
Мак
Компьютеры Mac также работают на C, поскольку ядро OS X написано в основном на C. Каждая программа и драйвер на Mac, как и на компьютерах с Windows и Linux, работают на ядре на C.
Мобильный
Ядра iOS, Android и Windows Phone также написаны на C. Это всего лишь мобильные адаптации существующих ядер Mac OS, Linux и Windows. Итак, смартфоны, которыми вы пользуетесь каждый день, работают на ядре C.
Базы данных
Самые популярные в мире базы данных, в том числе Oracle Database, MySQL, MS SQL Server и PostgreSQL, написаны на C (первые три из них на самом деле и на C, и на C++).
Базы данных используются во всех типах систем: финансовых, правительственных, медиа, развлекательных, телекоммуникационных, медицинских, образовательных, розничных, социальных сетей, Интернета и т.п.
3D фильмы
3D-фильмы создаются с помощью приложений, которые обычно написаны на C и C++. Эти приложения должны быть очень эффективными и быстрыми, поскольку они обрабатывают огромные объемы данных и выполняют множество вычислений в секунду. Чем они эффективнее, тем меньше времени требуется художникам и аниматорам для создания кадров фильма, и тем больше денег экономит компания.
Встроенные системы
Представьте, что однажды вы просыпаетесь и идете за покупками. Будильник, который будит вас, вероятно, запрограммирован на C. Затем вы используете микроволновую печь или кофеварку, чтобы приготовить себе завтрак. Они также являются встроенными системами и поэтому, вероятно, запрограммированы на C. Вы включаете телевизор или радио во время завтрака. Это также встроенные системы, работающие на C. Когда вы открываете дверь гаража с помощью пульта дистанционного управления, вы также используете встроенную систему, которая, скорее всего, запрограммирована на C.
Затем вы садитесь в свою машину. Если он имеет следующие функции, также запрограммированные на C:
- автоматическая коробка передач
- системы контроля давления в шинах
- датчики (кислорода, температуры, уровня масла и т.д.)
- память настроек сидений и зеркал.
- дисплей приборной панели
- антиблокировочная система тормозов
- автоматический контроль устойчивости
- круиз-контроль
- климат-контроль
- замки с защитой от детей
- вход без ключа
- сиденья с подогревом
- управление подушкой безопасности
Вы добираетесь до магазина, паркуете машину и идете к автомату за газировкой. Какой язык они использовали для программирования этого торгового автомата? Вероятно, С. Тогда вы покупаете что-то в магазине. Кассовый аппарат тоже запрограммирован на C. А когда вы платите кредитной картой? Вы уже догадались: считыватель кредитных карт, опять же, вероятно, запрограммирован на C.
Все эти устройства являются встроенными системами. Они похожи на небольшие компьютеры, внутри которых есть микроконтроллер/микропроцессор, который запускает программу, также называемую прошивкой, на встроенных устройствах. Эта программа должна обнаруживать нажатия клавиш и действовать соответствующим образом, а также отображать информацию для пользователя. Например, будильник должен взаимодействовать с пользователем, определяя, какую кнопку нажимает пользователь, а иногда и как долго она нажата, и соответствующим образом программировать устройство, одновременно отображая пользователю соответствующую информацию. Антиблокировочная тормозная система автомобиля, например, должна обнаруживать внезапную блокировку шин и на короткое время сбрасывать давление на тормоза, разблокируя их и тем самым предотвращая неконтролируемое занос. Все эти расчеты выполняются запрограммированной встроенной системой.
Хотя язык программирования, используемый во встроенных системах, может варьироваться от бренда к бренду, они чаще всего программируются на языке C из-за таких особенностей языка, как гибкость, эффективность, производительность и близость к аппаратному обеспечению.
Почему до сих пор используется язык программирования C?
Сегодня существует множество языков программирования, которые позволяют разработчикам работать более продуктивно, чем C, для различных типов проектов. Существуют языки более высокого уровня, которые предоставляют гораздо большие встроенные библиотеки, которые упрощают работу с JSON, XML, пользовательским интерфейсом, веб-страницами, клиентскими запросами, подключениями к базе данных, манипулированием мультимедиа и т. д.
Но, несмотря на это, есть много причин полагать, что программирование на C будет оставаться актуальным еще долгое время.
В языках программирования один размер не подходит всем. Вот несколько причин, по которым C непревзойден и почти обязателен для некоторых приложений.
Портативность и эффективность
C — это почти переносимый язык ассемблера . Он максимально приближен к машине и почти повсеместно доступен для существующих процессорных архитектур. Почти для каждой существующей архитектуры существует по крайней мере один компилятор C. И в настоящее время из-за высокооптимизированных двоичных файлов, генерируемых современными компиляторами, не так просто улучшить их вывод с помощью написанного вручную ассемблера.
Его переносимость и эффективность настолько велики, что «компиляторы, библиотеки и интерпретаторы других языков программирования часто реализуются на C». Интерпретируемые языки, такие как Python, Ruby и PHP, имеют свои основные реализации, написанные на C. Он даже используется компиляторами других языков для связи с машиной. Например, C является промежуточным языком, лежащим в основе Eiffel и Forth. Это означает, что вместо того, чтобы генерировать машинный код для каждой поддерживаемой архитектуры, компиляторы для этих языков просто генерируют промежуточный код C, а компилятор C обрабатывает генерацию машинного кода.
C также стал лингва-франка для общения между разработчиками. Как говорит Алекс Аллен, технический менеджер Dropbox и создатель Cprogramming.com:
C — отличный язык для выражения общих идей программирования удобным для большинства людей способом. Кроме того, многие принципы, используемые в C, например,
argc
иargv
для параметров командной строки, а также конструкции циклов и типы переменных, появятся во многих других языках, которые вы изучаете, так что вы сможете говорить людям, даже если они не знают C так, как это свойственно вам обоим.
Манипуляции с памятью
Доступ к произвольным адресам памяти и арифметика указателей — важная особенность, которая делает C идеально подходящим для системного программирования (операционные системы и встроенные системы).
На границе аппаратного и программного обеспечения компьютерные системы и микроконтроллеры отображают свои периферийные устройства и контакты ввода-вывода в адреса памяти. Системные приложения должны читать и записывать в эти пользовательские ячейки памяти для связи с миром. Таким образом, способность C манипулировать произвольными адресами памяти необходима для системного программирования.
Например, микроконтроллер можно спроектировать таким образом, чтобы байт в адресе памяти 0x40008000 посылался универсальным асинхронным приемником/передатчиком (или UART, общим аппаратным компонентом для связи с периферийными устройствами) каждый раз, когда устанавливается бит номер 4 адреса 0x40008001. в 1, и что после того, как вы установите этот бит, он будет автоматически сброшен периферийным устройством.
Это будет код для функции C, которая отправляет байт через этот UART:
#define UART_BYTE *(char *)0x40008000 #define UART_SEND *(volatile char *)0x40008001 |= 0x08 void send_uart(char byte) { UART_BYTE = byte; // write byte to 0x40008000 address UART_SEND; // set bit number 4 of address 0x40008001 }
Первая строка функции будет расширена до:
*(char *)0x40008000 = byte;
Эта строка говорит компилятору интерпретировать значение 0x40008000
как указатель на char
, затем разыменовать (дать значение, на которое указывает) этот указатель (с крайним левым оператором *
) и, наконец, присвоить byte
значение этому разыменованному указателю. Другими словами: запишите значение переменной byte
по адресу памяти 0x40008000
.

Следующая строка будет расширена до:
*(volatile char *)0x40008001 |= 0x08;
В этой строке мы выполняем побитовую операцию ИЛИ над значением по адресу 0x40008001
и значением 0x08
( 00001000
в двоичном формате, т. е. 1 в бите номер 4) и сохраняем результат обратно по адресу 0x40008001
. Другими словами: мы устанавливаем бит 4 байта, который находится по адресу 0x40008001. Мы также объявляем, что значение по адресу 0x40008001
является volatile . Это сообщает компилятору, что это значение может быть изменено процессами, внешними по отношению к нашему коду, поэтому компилятор не будет делать никаких предположений о значении в этом адресе после записи в него. (В данном случае этот бит сбрасывается аппаратно UART сразу после того, как мы устанавливаем его программно.) Эта информация важна для оптимизатора компилятора. Если бы мы сделали это внутри цикла for
, например, не указав, что значение является изменчивым, компилятор мог бы предположить, что это значение никогда не изменяется после его установки, и пропустить выполнение команды после первого цикла.
Детерминированное использование ресурсов
Распространенной языковой функцией, на которую системное программирование не может полагаться, является сборка мусора или даже просто динамическое выделение памяти для некоторых встраиваемых систем. Встроенные приложения очень ограничены по времени и ресурсам памяти. Они часто используются для систем реального времени, где недетерминированный вызов сборщика мусора невозможен. И если динамическое выделение нельзя использовать из-за нехватки памяти, очень важно иметь другие механизмы управления памятью, такие как размещение данных по настраиваемым адресам, как это позволяют указатели C. Языки, сильно зависящие от динамического выделения памяти и сборки мусора, не подходят для систем с ограниченными ресурсами.
Размер кода
C имеет очень маленькое время выполнения. И объем памяти для его кода меньше, чем для большинства других языков.
По сравнению с C++, например, двоичный файл, сгенерированный C, который отправляется на встроенное устройство, примерно вдвое меньше двоичного файла, созданного аналогичным кодом C++. Одной из основных причин этого является поддержка исключений.
Исключения — отличный инструмент, добавленный C++ поверх C, и, если их не активировать и не реализовать разумно, они практически не несут дополнительных затрат времени на выполнение (но за счет увеличения размера кода).
Давайте посмотрим пример на C++:
// Class A declaration. Methods defined somewhere else; class A { public: A(); // Constructor ~A(); // Destructor (called when the object goes out of scope or is deleted) void myMethod(); // Just a method }; // Class B declaration. Methods defined somewhere else; class B { public: B(); // Constructor ~B(); // Destructor void myMethod(); // Just a method }; // Class C declaration. Methods defined somewhere else; class C { public: C(); // Constructor ~C(); // Destructor void myMethod(); // Just a method }; void myFunction() { A a; // Constructor aA() called. (Checkpoint 1) { B b; // Constructor bB() called. (Checkpoint 2) b.myMethod(); // (Checkpoint 3) } // b.~B() destructor called. (Checkpoint 4) { C c; // Constructor cC() called. (Checkpoint 5) c.myMethod(); // (Checkpoint 6) } // c.~C() destructor called. (Checkpoint 7) a.myMethod(); // (Checkpoint 8) } // a.~A() destructor called. (Checkpoint 9)
Методы классов A
, B
и C
определены где-то еще (например, в других файлах). Поэтому компилятор не может их анализировать и не может знать, вызовут ли они исключения. Поэтому он должен быть готов к обработке исключений, вызванных любым из их конструкторов, деструкторов или вызовов других методов. Деструкторы не должны генерировать исключение (очень плохая практика), но пользователь все равно может генерировать исключение, или они могут вызывать генерацию косвенно, вызывая какую-либо функцию или метод (явно или неявно), которые генерируют исключение.
Если какой-либо из вызовов в myFunction
вызывает исключение, механизм раскручивания стека должен иметь возможность вызывать все деструкторы для уже созданных объектов. Одна реализация механизма раскручивания стека будет использовать адрес возврата последнего вызова этой функции для проверки «номера контрольной точки» вызова, вызвавшего исключение (это простое объяснение). Он делает это, используя вспомогательную автоматически сгенерированную функцию (своего рода справочную таблицу), которая будет использоваться для раскручивания стека в случае возникновения исключения из тела этой функции, которое будет похоже на это:
// Possible autogenerated function void autogeneratedStackUnwindingFor_myFunction(int checkpoint) { switch (checkpoint) { // case 1 and 9: do nothing; case 3: b.~B(); goto destroyA; // jumps to location of destroyA label case 6: c.~C(); // also goes to destroyA as that is the next line destroyA: // label case 2: case 4: case 5: case 7: case 8: a.~A(); } }
Если исключение выдается из контрольных точек 1 и 9, уничтожение объекта не требуется. Для контрольной точки 3 b
и a
должны быть уничтожены. Для контрольной точки 6, c
и a
должны быть уничтожены. Во всех случаях порядок уничтожения должен соблюдаться. Для контрольных точек 2, 4, 5, 7 и 8 необходимо уничтожить только объект a
.
Эта вспомогательная функция увеличивает размер кода. Это часть служебного пространства, которое C++ добавляет к C. Многие встраиваемые приложения не могут позволить себе такое дополнительное пространство. Поэтому компиляторы C++ для встраиваемых систем часто имеют флаг для отключения исключений. Отключение исключений в C++ не является бесплатным, поскольку стандартная библиотека шаблонов в значительной степени зависит от исключений для информирования об ошибках. Использование этой модифицированной схемы без исключений требует дополнительной подготовки разработчиков C++ для выявления возможных проблем или поиска ошибок.
И мы говорим о C++, языке, принцип которого: «Вы не платите за то, чем не пользуетесь». Это увеличение размера двоичного файла усугубляется для других языков, которые добавляют дополнительные накладные расходы с другими функциями, которые очень полезны, но не могут быть предоставлены встроенными системами. Хотя C не дает вам возможности использовать эти дополнительные функции, он позволяет значительно сократить размер кода по сравнению с другими языками.
Причины изучать C
C не сложный для изучения язык, поэтому все преимущества его изучения обойдутся довольно дешево. Давайте посмотрим на некоторые из этих преимуществ.
Лингва франка
Как уже упоминалось, C — это язык общения для разработчиков. Многие реализации новых алгоритмов в книгах или в Интернете впервые (или только) доступны на языке C их авторами. Это дает максимально возможную переносимость реализации. Я видел, как программисты в Интернете изо всех сил пытались переписать алгоритм C для других языков программирования, потому что они не знали самых основных концепций C.
Имейте в виду, что C — старый и широко распространенный язык, поэтому в Интернете можно найти всевозможные алгоритмы, написанные на C. Поэтому вам, скорее всего, будет полезно знать этот язык.
Поймите машину (мыслите на C)
Когда мы обсуждаем поведение определенных частей кода или определенных функций других языков с коллегами, мы заканчиваем тем, что «говорим на C»: эта часть передает «указатель» на объект или копирует весь объект? Мог ли здесь происходить какой-то «каст»? И так далее.
Мы редко обсуждали (или думали) об инструкциях ассемблера, которые выполняет часть кода, анализируя поведение части кода языка высокого уровня. Вместо этого, обсуждая, что делает машина, мы довольно четко говорим (или думаем) на C.
Более того, если вы не можете остановиться и подумать таким образом о том, что вы делаете, вы можете закончить тем, что программируете с каким-то суеверием относительно того, как (волшебным образом) что-то делается.
Работа над многими интересными проектами на C
Многие интересные проекты, от больших серверов баз данных или ядер операционных систем до небольших встроенных приложений, которые вы даже можете делать дома для собственного удовольствия и развлечения, выполняются на C. Нет причин прекращать делать то, что вам может нравиться, только по одной причине. что вы не знаете такого старого и маленького, но сильного и проверенного временем языка программирования, как Си.
Заключение
Язык программирования C, похоже, не имеет срока годности. Его близость к аппаратному обеспечению, отличная переносимость и детерминированное использование ресурсов делают его идеальным для низкоуровневой разработки таких вещей, как ядра операционных систем и встроенное программное обеспечение. Его универсальность, эффективность и хорошая производительность делают его отличным выбором для программ обработки данных высокой сложности, таких как базы данных или 3D-анимация. Тот факт, что сегодня многие языки программирования лучше, чем C, для предполагаемого использования, не означает, что они превосходят C во всех областях. C по-прежнему непревзойден, когда производительность является приоритетом.
Мир работает на устройствах на базе C. Мы используем эти устройства каждый день, осознаем мы это или нет. C — это прошлое, настоящее и, насколько мы можем видеть, будущее для многих областей программного обеспечения.