Друзья! В данной статье мы бы хотели порассуждать на тему использования инструментария языка C в C++, и как это может повлиять на исходную программу.
Ссылки на полезные ресурсы вы сможете увидеть в конце статьи, и обязательно делитесь своим мнением в комментариях, нам будет очень интересно с ним ознакомиться!
История C++
Чтобы понять, почему C и C++ часто используют в одном коде и к чему это может привести, начнём с истории создания языка C++.
В 70-е годы язык C стал революцией в мире программирования, предоставив разработчикам гибкость и мощь для низкоуровневого управления системой. Однако с увеличением сложности программ возникла необходимость в языках, поддерживающих абстракции. Это понимание и привело к созданию языка C++.
В конце 70-х годов, будучи аспирантом Кембриджского университета, Бьёрн Страуструп задался целью создать язык, который бы сочетал производительность C с поддержкой высокоуровневых абстракций. Этот язык он изначально назвал «C with Classes» — C с классами. На основе C он добавил концепцию классов и поддержал инкапсуляцию, что позволяло создавать более сложные структуры.
В 1982 году Страуструп развил язык, добавив новые возможности для решения проблем распределённых вычислений. Он назвал обновлённый язык C++. Ключевые новшества включали классы, функции-члены и основную поддержку объектно-ориентированного программирования (ООП). C++ всё ещё требовал компиляции в C-код, поэтому до появления специализированных компиляторов работал как препроцессор к C.
Развитие компиляторов C++
Первая версия компилятора C++, известная как Cfront, появилась в 1983 году. Это был инструмент, который переводил код на C++ в код на C. Первый публичный релиз — Cfront 1.0 — появился в 1985 году и уже был способен компилировать достаточно сложные программы, однако для работы с ним нужно было досконально знать язык C, поскольку ошибки или неполадки легко могли возникнуть из-за сочетания C и C++ конструкций.
С ростом популярности C++ начали разрабатываться независимые компиляторы. В 1987 году GCC (GNU Compiler Collection) добавил поддержку C++, а в 1989 году вышел Cfront 2.0, с более устойчивой поддержкой нового синтаксиса и улучшениями компиляции.
К 1990 году начал работу комитет по стандартизации ANSI C++, а в 1991 году — международный комитет ISO C++, что привело к появлению стандарта C++98, а позднее и его более новых версий: C++03, C++11, C++14, C++17, C++20, каждая из которых вносила дополнительные возможности и улучшения. Современный C++ далеко ушёл от своего предшественника, включив в себя поддержку шаблонов, многопоточности, систем обработки ошибок, стандартной библиотеки STL и множество других возможностей.
Однако, даже спустя годы, C++ сохраняет обратную совместимость с C. Это даёт разработчикам C++ доступ к функционалу C, но часто использование функций и подходов из C вредит чистоте и безопасности кода.
Почему использование C в C++ может быть вредным?
1. Управление памятью: char[] и malloc вместо std::string и new
В C++ предусмотрено множество средств для безопасного управления памятью, таких как умные указатели (std::unique_ptr
, std::shared_ptr
), класс std::string
, контейнеры из библиотеки STL, и ключевое слово new
. Однако, иногда разработчики, знакомые с C, продолжают использовать низкоуровневые конструкции C:
-
Необработанные массивы
char[]
вместоstd::string
Использование
char[]
требует ручного управления памятью, что повышает вероятность ошибок, особенно при динамическом выделении и освобождении памяти. -
Функции
malloc
иfree
вместоnew
иdelete
В C++
new
иdelete
интегрированы в систему типов языка, что делает их безопаснее. При использованииmalloc
иfree
в C++ отсутствует автоматическая инициализация и проверка типов, что может привести к неопределённому поведению.
Пример
// Небезопасно: низкоуровневый массив и malloc char* text = (char*) malloc(100); // необходимо вручную освобождать память strcpy(text, "Hello, world"); // Безопаснее: использование std::string std::string text = "Hello, world"; // автоматическое управление памятью
2. Ввод и вывод: scanf/printf вместо std::cin/std::cout
C++ предоставляет удобные и безопасные потоки для ввода и вывода, но иногда можно встретить scanf
и printf
, что несёт следующие риски:
-
Типобезопасность
std::cin
иstd::cout
проверяют типы при компиляции, тогда какscanf
иprintf
полагаются на форматные строки, что может привести к ошибкам на этапе выполнения. -
Читаемость и удобство.
Потоки ввода-вывода C++ интуитивнее и легче читаются благодаря синтаксису
<<
и>>
. -
Управление форматированием.
С помощью манипуляторов (
std::fixed
,std::setprecision
) легко управлять выводом, чего трудно достичь вprintf
.
Пример
// В стиле C++ int number; std::cout << "Enter a number: "; std::cin >> number; // безопасно и проверяет типы // В стиле C printf("Enter a number: "); scanf("%d", &number); // типобезопасности нет, возможны ошибки
3. Заголовочные файлы: .h и .hpp
Смешивание заголовочных файлов C и C++ может привести к путанице:
-
Форматирование кода.
В IDE часто настраивают форматирование под
.h
и.hpp
файлы по-разному. Если использовать.h
для C++-заголовков, можно случайно применить стилизацию C. -
Путаница в имёнованиях.
Заголовки C и C++ с похожими именами (например,
MyClass.h
иMyClass.hpp
) помогают быстро различать файлы для C и C++, что особенно важно при использовании обёрток для библиотек на C. -
Ошибки при подключении C-заголовков в C++.
При подключении заголовков на C нужно оборачивать их в
extern "C"
, чтобы избежать конфликтов вызовов из-за различного манглинга имён. Если не делать этого, могут возникнуть ошибки компиляции.
Пример
// C-заголовок, например, library.h #ifdef __cplusplus extern "C" { #endif void someFunction(); #ifdef __cplusplus } #endif // C++ заголовок library.hpp можно подключать без дополнительных настроек class MyClass { public: void someMethod(); };
Манглинг в языках программирования C и C++
Манглинг (или мэнглинг имен, от англ. name mangling) — это процесс преобразования имен функций и переменных, происходящий на этапе компиляции в языках C и C++. Он служит для создания уникальных имен функций и переменных, особенно когда используется перегрузка функций. Манголинг добавляет к именам дополнительную информацию, такую как типы параметров, пространство имен и т.д., чтобы различать функции с одинаковыми именами, но разными параметрами.
В C, где перегрузка функций отсутствует, манглинг минимален: компилятор сохраняет имена функций в том виде, как они заданы в исходном коде. Однако в C++ манглинг становится необходимостью для поддержки перегрузки функций, пространств имен и других возможностей. Например, при перегрузке двух функций с одинаковым именем, но разными параметрами, компилятор C++ создаст уникальные идентификаторы для каждой версии функции.
Пример манглинга в C и C++
Рассмотрим простую функцию на C:
// example.c void print_message(const char* message) { printf("%s\n", message); }
В этом примере компилятор C создаст неизмененное имя print_message
, так как перегрузка отсутствует, и единственное имя для функции достаточно уникально.
Скомпилированный ассемблерный код будет выглядеть примерно так:
print_message: push rbp mov rbp, rsp sub rsp, 16 mov rdi, rsi ; аргумент для printf копируется в rdi call printf ; вызов функции printf leave ret
Здесь имя функции print_message
остается неизменным в ассемблерном коде, и это имя будет использоваться при линковке.
Теперь рассмотрим аналогичную функцию на C++, но добавим к ней перегрузку:
// example.cpp #include <iostream> void print_message(const char* message) { std::cout << message << std::endl; } void print_message(int number) { std::cout << number << std::endl; }
Компилятор C++ создаст уникальные имена для каждой версии print_message
, учитывая тип их параметров. Например, в ассемблере имена функций могут выглядеть так:
_Z13print_messagePKc: ; "print_message" для const char* (строка) push rbp mov rbp, rsp sub rsp, 16 ; код вывода строки leave ret _Z13print_messagei: ; "print_message" для int (число) push rbp mov rbp, rsp sub rsp, 16 ; код вывода числа leave ret
Эти имена _Z13print_messagePKc
и _Z13print_messagei
— сманглированные. Здесь содержатся:
-
_Z
— префикс, указывающий, что это сманглированное имя. -
13
— длина имени функцииprint_message
. -
PKc
— код для указателя наconst char
. -
i
— код дляint
.
Этот код служит для различения перегруженных функций, обеспечивая корректное связывание на этапе линковки.
Использование extern «C» для интеграции кода C и C++
Проблемы могут возникнуть, если вы пытаетесь использовать C-код в C++, поскольку компилятор C++ манглирует имена функций, а компилятор C — нет. Это может привести к ошибкам линковки: компилятор C++ не найдет нужную функцию с неманглированным именем. Чтобы решить эту проблему, в C++ используется спецификатор extern "C"
, который отключает манглинг и позволяет компилятору сохранить «чистое» имя, совместимое с C.
Рассмотрим, как extern "C"
поможет избежать ошибок:
// Подключение C-кода в C++ extern "C" { #include "some_c_library.h" // библиотека на C }
Этот блок указывает компилятору C++, что все функции внутри него нужно компилировать без манглинга, сохраняя их имена как в C. Это гарантирует совместимость C и C++ кода.
Пример ошибки линковки без extern «C»
Допустим, у нас есть функция на C:
// example.c void print_message(const char* message) { printf("%s\n", message); }
При попытке вызвать эту функцию из C++ без extern "C"
могут возникнуть проблемы:
// main.cpp #include "example.h" // файл с объявлением print_message int main() { print_message("Hello, World!"); return 0; }
Компилятор C++ ожидает найти сманглированное имя для print_message
, но функция была скомпилирована как обычное print_message
в C. В результате это приведет к ошибке линковки:
undefined reference to `print_message`
Чтобы избежать этой ошибки, добавим extern "C"
к объявлению функции:
// example.h #ifdef __cplusplus extern "C" { #endif void print_message(const char* message); #ifdef __cplusplus } #endif
Теперь при компиляции C++ код будет воспринимать print_message
как неманглированное имя, как в C, и ошибка исчезнет.
Полезные ресурсы
История C++ — https://www.geeksforgeeks.org/history-of-c/
Наше руководство по стилизации кода на C++ — https://case-technologies.ru/guides.php
Манглирование — https://en.wikipedia.org/wiki/Name_mangling
Наши ссылки
Официальный сайт — https://case-technologies.ru/
Наш GitHub — https://github.com/case-tech
ссылка на оригинал статьи https://habr.com/ru/articles/858366/
Добавить комментарий