Как использовать c
Перейти к содержимому

Как использовать c

Языки C и C++. Где их используют и зачем?

C++

Сфера применения C и C++ на удивление широка. Даже в сегодняшнем мире, несмотря на всё многообразие современных надёжных инструментов, эти два языка программирования остаются незаменимыми. Давайте же выясним, какие популярные приложения написаны на этих языках и зачем нам использовать их сегодня.

Когда и зачем используются C и C++ ?

1. Операционные системы

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

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

Вот почему он идеален для разработки операционных систем.

Любая операционная система в первую очередь должна быстро запускаться и эффективно управлять системными ресурсами. Например, Linux, Microsoft Windows, Mac OS полностью написаны на языке C, а Android и iOS — частично.

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

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

2. Программирование встроенных систем

Язык программирования C просто необходим при проектировании встроенных систем.

Главным образом из-за высокой производительности и простоты использования. Язык C очень экономичен в потреблении ресурсов системы, благодаря чему программа выполняется быстро. В результате встроенные системы работают в реальном времени без замедления.

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

Вот почему беспилотные автомобили, умные часы, сенсоры и устройства, использующие технологию Интернета вещей (например, кофемашины), имеют встроенное ПО, целиком написанное на C или C++. В связанных системах C позволяет работать непосредственно с ресурсами памяти и править любую часть кода.

3. Разработка игр и игрового движка

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

Являясь самым быстрым на сегодняшний день языком программирования, С++ оказывается одним из лучших для игр в 3D, многопользовательских и других.

Например, Counter-Strike, StarCraft: Brood War, Diablo I, World of Warcraft — все эти игры написаны на C++. Не говоря уже о консолях Xbox и PlayStation, в основе которых лежит программирование C++.

В ядре игрового движка Unity — самого популярного движка для создания видеоигр под несколько операционных систем одновременно — также использовался C++.

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

4. Разработка настольных и кроссплатформенных приложений

C++ также можно использовать для создания настольных приложений. Всё благодаря превосходным кроссплатформенным средствам разработки (иногда называемым фреймворками), таким как Qt. Оно позволяет нацелиться на Windows, Linux, macOS, Android и встроенные системы — все c единой кодовой базой. Так что разработка приложений с помощью Qt оказывается отличным решением для тех, кто хочет сэкономить на времени и стоимости программирования.

Стоит упомянуть и о библиотеке SDL, нашпигованной функциями, позволяющими создавать приложения одновременно для Windows, Linux, Android, MacOS и iOS.

Кстати, Photoshop, Illustrator и Adobe Premiere целиком написаны на C++.

Какие ещё есть приложения?

В Facebook перевели часть кода из PHP на язык C++, чтобы сократить затраты электроэнергии в расчёте на одного пользователя. Возможно, облачные системы хранения, базы данных, драйверы устройств и другие виды ПО тоже используют C++.

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

Выводы

Принимая во внимание всё вышесказанное, будем считать, что языки C/C++ вполне годятся для:

  1. разработки операционных систем,
  2. программирования встроенных систем,
  3. разработки игр,
  4. разработки баз данных,
  5. разработки настольных и кроссплатформенных приложений (с помощью средств C++).

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

Так что, если вы спросите:

Почему мы до сих пор используем C/C++?

Есть короткий ответ: C и C++ гибкие, очень быстрые и эффективные. К тому же они ближе к машинному коду, чем любой другой язык программирования. Именно поэтому в некоторых случаях эти языки просто незаменимы.

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

Для чего хорош Си?

Си применяют в сферах, где важен код, наиболее тесно взаимодействующий с «железом». Под влиянием Си появились языки C++, C#, Java и Objective-C.

Си иногда называют подмножеством C++ или «C++ без классов», но это не совсем верно. Почему это не так, можно узнать в статье про C++.

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

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

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

Оптимизация участков кода на C++

Объектно-ориентированные возможности C++ часто обходятся дороже, чем «чистый Си», так как расходуют больше ресурсов (в частности оперативной памяти). Поэтому иногда код в стиле Си может быть эффективнее. Если нужно заставить какой-либо алгоритм работать быстрее — используйте процедурный стиль и откажитесь от встроенных инструментов C++ для ООП, например от полиморфизма.

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

Информационная безопасность

Сюда относятся сложные хакерские приёмы. Среди них:

  • Использование уязвимостей: переполнения буфера, двойные удаления (повреждения кучи).
  • Инъекция (сокрытие) кода. Если получить доступ к другому процессу, используя уязвимость, то можно спрятать свой код внутри чужого и заставить процесс выполнять его. Теперь скрытый код будет жить в «невинном» процессе, спрятанном от глаз пользователя.
  • Перехват (hooking). Если вы хотите мониторить чьи-то взаимодействия с системой (нажатие клавиш, открытие файлов), вам, как правило, нужно вызывать отслеживающий код всякий раз, когда пользователь что-то делает. Для этого вы заменяете какой-либо фрагмент API операционной системы вашим кодом.

Почти для всех этих применений используется PIC код (position-independent code — код, не зависящий от адреса). Он может выполняться в любом месте памяти, независимо от того, где находится и кто его запустил. У PIC-кода нет доступа к глобальным переменным и таблицам, поэтому C++ для его написания не подойдёт (классам C++ нужны глобальные таблицы для реализации наследования).

Код ядра

Код, который выполняется в режиме ядра (kernel mode) имеет полный доступ к памяти и оборудованию: RAM, GPU, жёсткому диску. В режиме ядра работают:

  • Аппаратные драйверы — здесь без доступа к железу не обойтись. Драйверы являются посредниками между пользовательским кодом (не в режиме ядра) и оборудованием.
  • Ядро операционной системы. На Си, кстати, написано множество ядер ОС, в том числе Unix и Android.

Использовать для всего этого код на C++ почти невозможно, поскольку в режиме ядра нет доступа к тем же глобальным таблицам, о которых говорилось выше. Иногда в режиме ядра тоже необходим PIC код — например для загрузчика (bootloader). Загрузчик — самая первая программа, выполняющаяся при запуске ПК. Биос извлекает её из жёсткого диска, помещает в память и говорит процессору запустить эту часть памяти.

Embedded-разработка

Для программирования встраиваемых систем часто используется как Си, так и C++. Но Си имеет преимущество, поскольку позволяет разрабатывать встроенное ПО при ограниченных ресурсах — например когда у микроконтроллера очень мало RAM. Помимо Си также может пригодиться знание ассемблера (как вариант, ARM-ассемблера) для написания ассемблерных вставок, чтобы ещё больше оптимизировать код и получить доступ к специфичным инструкциям процессора.

Как использовать c

Что нужно знать для смешивания в проекте кода C и C++? Вот несколько основных моментов (хотя некоторые производители компилятора могут этого не требовать, см. документацию на конкретный компилятор):

• Вы должны использовать компилятор C++, когда компилируете тело функции main() (например, для статической инициализации).
• Ваш компилятор C++ должен управлять процессом линковки (например, могут использоваться его специальные библиотеки).
• Ваши компиляторы C и C++ возможно должны быть от одного производителя, и иметь совместимые друг с другом версии (например, должны использовать одинаковые соглашения о вызовах функций).

Кроме того, Вам следует разобраться (см. далее), как сделать свои функции C вызываемыми из кода C++, и/или свои функции C++ вызываемыми из C.

На пути решения этой проблемы есть другой способ добиться желаемого: просто компилировать весь код (даже код в стиле C) компилятором C++. Это в значительной степени избавляет от необходимости смешивать код C и C++, и также бонусом получите некую проверку качества кода C и возможно (на что есть шанс надеяться!) обнаружить некоторые ошибки кода C. Недостаток такого решения в том, что нужно некоторым образом обновить код C, в основном по той причине, что компилятор C++ более придирчив, чем компилятор C. В результате усилия, которые надо потратить для чистки кода C (особенно актуально для больших проектов), могут оказаться больше, чем усилия на организацию смеси в проекте кода C и C++. В некоторых случаях у Вас просто может не быть выбора, например когда код на языке C поставляется из стороннего источника.

Как вызывать функцию C из C++? Просто декларируйте C-функцию в коде C++ с помощью директивы extern «C», и вызовите её (из кода C или кода C++). Пример:

Реализация функций может быть совершенно стандартная для C:

Чаще всего директива extern «C» используется в только заголовочных файлах. Чтобы сделать заголовочный файл универсальным, используют общепринятое обрамление блока декларации функций через extern «C» с помощью операторов условной компиляции, которые проверяют режим компиляции (какой используется компилятор — C++ или C).

Здесь директива условной компиляции #ifdef __cplusplus сработает, если активен компилятор C++, и будет подключен блок директивы extern «C». В случае компиляции в режиме C блок директивы extern «C» подключаться не будет.

Также имейте в виду, что действуют правила типов C++, не правила типов C. Таким образом, Вы не сможете вызвать функцию, которая определена через extern «C», с неправильным количеством аргументов. Например:

Как вызывать функцию C++ из кода C? Просто декларируйте функцию C++ с директивой extern «C» (в коде C++) и вызовите её (из кода C или C++). Пример:

Теперь функция f() может использоваться следующим образом:

В реальности это работает только для функций, которые не являются членами класса. Если Вы хотите вызвать функцию — члена класса (включая функции virtual) из C, то понадобится реализовать простую обертку. Например:

Теперь функция C::f() может быть вызвана следующим образом:

Если Вы хотите вызвать перегруженные (overloaded) функции из C, то нужно предоставить обертки с разными именами для каждой реализации перегруженной функции, чтобы их можно было использовать в коде C. Например:

Теперь разные перегрузки функции f() можно вызвать следующим образом:

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

Как можно подключить стандартный заголовочный файл C в код C++? Для подключения стандартного хедера (такого как < cstdio>) Вы не должны делать ничего необычного. Например:

Если Вы думаете, что часть std:: в вызове std::printf() необычным, то лучшее, что следует сделать, это просто привыкнуть к этому. Другими словами, это стандартный способ использовать имена в стандартной библиотеке, так что начинайте к этому привыкать.

Однако если Вы компилируете код C компилятором C++, то скорее всего не захотите переделывать все эти вызовы из printf() в std::printf(). К счастью, в этом случае код C будет использовать заголовочный файл старого стиля < stdio.h> вместо заголовочного файла нового стиля < cstdio>, и обо всем остальном позаботится магия пространства имен (namespaces):

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

Как можно подключить не системный заголовочный файл C в код C++? Если Вы используете заголовочный файл C, который не предоставляется вместе с системой программирования, то может понадобиться сделать обрамление строки #include конструкцией блока extern «C» < /*. */ >. Это укажет компилятору C++, что функции, декларируемые в заголовке, являются C-функциями.

Как изменить файл заголовка C, чтобы его было проще подключать в код C++? Если Вы подключаете хедер C, который не предоставляется системой программирования («нестандартный» заголовок, т. е. не относящийся к стандартной библиотеке), и если Вы можете поменять этот хедер C, то обязательно следует добавить логику extern «C» <. >в хедер, чтобы пользователям C++ было проще подключать его в свой код C++. Поскольку компилятор C не захочет понимать конструкцию extern «C», то Вы должны обернуть строки extern «C» < и >в условный оператор препроцессора #ifdef, чтобы они не были видны обычным компилятором C.

Шаг 1: поместите следующие строки в начало заголовочного файла C (символ __cplusplus автоматически определяется компилятором C++, компилятор C этот символ не определяет):

Шаг 2: Поместите следующие строки в конец заголовочного файла C:

После этого Вы можете свободно подключать оформленный таким образом заголовок как из кода C, так и из кода C++, не заботясь ни о каких директивах extern «C»:

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

Как вызывать не системную C-функцию f(int,char,float) из кода C++? Если есть отдельная C-функция, которую Вы хотите вызвать, и по какой-либо причине не хотите подключать (директивой #include) заголовочный файл C, в котором эта функция декларирована, то можно декларировать эту отдельную функцию C в коде C++, используя синтаксис extern «C». Для этого надо использовать полный прототип функции:

Несколько таких функций можно сгруппировать в блок фигурными скобками:

После этого просто вызовите функцию, как если бы это была функция C++:

Как создать C++ функцию f(int,char,float), которую можно вызывать из кода C? Компилятор C++ должен четко знать, что функция f(int,char,float) будет вызываться из кода компилятора C. Такое указание компилятору C++ делает конструкция extern «C»:

Строка extern «C» говорит компилятору C++, что информация, посылаемая линкеру, должна использовать соглашения о вызовах C, и также должна использоваться декорация имен языка C (name mangling, т. е. присоединение к имени функции префикса одиночного нижнего подчеркивания). Поскольку перегрузка имен функций не поддерживается языком C, то Вы не можете сделать некоторые перегружаемые функции одновременно вызываемыми из программы на C.

Почему линкер выдает ошибки на функции C/C++, которые вызываются из кода C++/C? Если Вы не всегда правильно используете extern «C» (если вообще делаете это), то будете иногда сталкиваться с ошибками линкера вместо ошибок компиляции, когда смешиваете код C и C++. Причина здесь в том, что фактически компиляторы C++ обычно декорируют («mangle») имена функций не так, как это делают компиляторы C, и поэтому линкер не находит ожидаемые имена в объектных или библиотечных файлах.

См. два предыдущих вопроса о том, как использовать extern «C».

Как передать объект класса C++ в функцию C и из неё? Ниже дан пример (как использовать extern «C», см. предыдущие вопросы).

Заголовочный файл Fred.h:

В отличие от кода C++, коду на C нельзя указать, что два указателя ссылаются на тот же самый объект, если указатели не будут иметь совершенно одинаковый тип. Например, на C++ просто проверить, указывает ли dp (указатель на объект производного класса Derived*) на тот же объект, что и указатель bp (указатель на объект базового класса Base*), это делается обычной проверкой if (dp == bp) . Компилятор C++ автоматически преобразует оба указателя в одинаковый тип, в этом случае к базовому типу, после чего проверит их равенство. В зависимости от подробностей реализации компилятора C++, это преобразование иногда меняет биты значения указателя.

Технический момент: большинство компиляторов C++ используют двоичную структуру объекта, которая заставляет это преобразование происходить с поддержкой множественного наследования (multiple inheritance) и/или виртуального наследования (virtual inheritance). Однако язык C++ не навязывает ограничения на реализацию этого принципа преобразования, так что оно может произойти даже с не виртуальным одиночным наследованием (non-virtual single inheritance).

Все просто: компилятор C ничего не знает о том, как сделать преобразование указателя, т. е. преобразование указателя из производного объекта в базовый. Так что преобразование должно произойти при компиляции C++, но не при компиляции C.

Важное замечание: особенно осторожным следует быть при преобразовании обоих указателей в void*, поскольку такое приведение типов не даст возможности ни компилятору C, ни компилятору C++, выполнить правильное преобразование! Сравнение (x == y) может быть false даже если (b == d) равно true:

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

Если Вы все-таки очень хотите использовать указатели void*, вот безопасный способ:

Может ли моя функция C получить прямой доступ к данным объекта класса C++? Не всегда. Базовые инструкции по передаче данных объектов C++ в функцию C и из функции C см. в предыдущих вопросах.

Вы можете безопасно получить доступ к данным объекта C++ из функции C, если этот класс C++:

• Не имеет виртуальных функций (включая унаследованные виртуальные функции).
• Помещает все свои данные на одном и том же уровне доступа (private/protected/public).
• Не имеет полностью встроенных подчиненных объектов (fully-contained subobjects) с виртаульными функциями.

Если класс C++ основан на любом базовом классе (или если любые полностью встроенные подчиненные объекты основаны на базовом класса), то доступ к данным класса технически будет не портируемым, потому что двоичная структура наследуемых объектов не не определяется синтаксисом языка. Однако на практике все компиляторы C++ делают это одинаково: объект базового класса появляется первым (в порядке слева направо при событии множественного наследования), и за ним идут объекты члены класса.

Кроме того, если класс (или любой его базовый класс) содержит любые виртуальные функции, почти все компиляторы C++ помещают указатель void* в объект либо на месте первой виртуальной функции, или в самом начале объекта. Опять-таки, этот принцип не определяется языком, просто «все делают это».

Если класс имеет любые виртуальные базовые классы, то все становится еще сложнее и менее портируемо. Общая техника реализации для объектов — помещать объект виртуального базового класса (V) последним (независимо от того, где обнаруживается V как виртуальный базовый класс в иерархии наследования). Остальная часть объекта появляется в нормальном порядке. Каждый производный класс, для которого V является виртуальным базовым классом, в реальности имеет указатель на V конечного объекта.

Почему с языком C++ кажется, что я «удаляюсь от машины», в отличие от языка C? Ни почему, просто это так и есть.

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

Сила языка C в том, что у него фактически нет «скрытых механизмов»: что видите, то и получаете. Вы можете читать программу C и «видеть», что в ней происходит на каждом такте процессора. С языком C++ все не так; старые C-программисты (которыми почти все мы когда-то были) часто настороженно (можно даже сказать — «враждебно»?) относятся к этой особенности C++. Однако после того, как они сделали переход к объектно-ориентированному мышлению, они часто понимают, что хотя C++ скрывает многие механизмы выполнения кода от программиста, C++ также дает новый удобный уровень абстракции, экономный в выражении и снижающий усилия на поддержку кода без снижения его рабочей производительности.

В сущности Вы можете писать плохой код на любом языке; C++ не гарантирует какой-то определенный уровень качества, возможности повторного использования кода, абстракции, или любых подобных полезных «плюшек».

C++ не пытается запретить плохим программистам писать плохие программы. Вместо этого C++ дает возможность разумным разработчикам создавать превосходные программы.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *