Стандарты языков C и C++ и их компиляторы стремительно развиваются, но далеко не все новинки становятся нам доступны сразу.
Эту статью я решил подготовить по случаю недавнего (25 апреля 2025) релиза компилятора GCC 15.1.0. В нем стали доступны для использования некоторые фичи из C++26.
К сожалению, текущая версия Ubuntu (24.04.2 LTS) изначально содержит достаточно старый GCC 13.3.0. Чтобы использовать что-то поновее, нужен «ручной» подход.
Дальнейший наш план таков. Мы установим новейшую версию GCC из исходников, настроим ее работу в системе по умолчанию и на пробу скомпилируем С++ код, использующий элементы стандарта C++26.
1. Проводим общее обновление системы
Запускаем в терминале:
sudo apt update && sudo apt upgrade -y
Этой командой мы, действуя как администратор (sudo), даем знать системе о последних доступных через пакетный менеджер версиях компонентов (apt update).
Затем, если все прошло ок (&&), устанавливаем доступные обновления apt upgrade, заранее на это соглашаясь (-y).
2. Проверяем нашу версию GCC
Нет смысла продолжать, если у нас уже установлена последняя версия компилятора, правда?
sudo g++ --version && g++ --version
Этой командой мы узнаем, какая версия С++ компилятора используется у нас на обычном пользовательском аккаунте и на аккаунте администратора.
В ответ, как правило, терминал выдаст два идентичных ответа, где будет указано что-то вроде g++ (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0.
Если эта версия компилятора слишком стара для нас, идем дальше…
3. Устанавливаем необходимые зависимости
sudo apt install -y build-essential git make gawk flex bison libgmp-dev libmpfr-dev libmpc-dev python3 binutils perl libisl-dev libzstd-dev tar gzip bzip2
Это набор программ, который нам потребуется при сборке новейшего GCC на нашей системе.
Некоторые из этих компонентов уже предустановлены в Ubuntu. К примеру, build-essential, среди прочего, проверит и гарантирует наличие изначально присутствующих в системе gcc, g++, make.
export CONFIG_SHELL=/bin/bash
Данная команда устанавливает переменную среды CONFIG_SHELL в состояние /bin/bash. В результате скрипты, которые будут запускаться при последующей сборке GCC, будут обрабатываться именно через требуемый самими скриптами bash, а не через какую-то иную оболочку.
4. Скачиваем компоненты новейшего GCC
mkdir ~/gcc-15 cd ~/gcc-15 git clone https://gcc.gnu.org/git/gcc.git gcc-15-source cd gcc-15-source git checkout releases/gcc-15.1.0 ./contrib/download_prerequisites
На этой стадии мы создаем папки (gcc-15, gcc-15-source) и закачиваем туда исходники компилятора из репозитория (https://gcc.gnu.org/git/gcc.git) с необходимыми зависимостями (./contrib/download_prerequisites).
Мы также переключаем состояние скачанного репозитория на нужную нам версию компилятора. На конец апреля 2025 — это версия 15.1.0.
Команда git checkout releases/gcc-15.1.0 использует теги, которые, по сути, простые указатели на определенные коммиты со стабильными версиями исходников.
В дальнейшем выйдут новые версии. С полным списком доступных гит-тегов мы можем ознакомиться, введя команду git tag -l.
5. Настраиваем будущий билд
cd ~/gcc-15 mkdir gcc-15-build cd gcc-15-build ../gcc-15-source/configure --prefix=/opt/gcc-15 --disable-multilib --enable-languages=c,c++
Далее в директории gcc-15 мы создаем папку сборки gcc-15-build, в которой будет создан рабочий билд нашего компилятора, и переходим в нее.
Оттуда мы отправляем команду создания Make-файла с правилами, по которым будет проводиться сборка проекта.
--prefix=/opt/gcc-15 определяет, что GCC установится в папку /opt/gcc-15. Так новая версия будет существовать отдельно от уже установленного у нас GCC старой версии.
--disable-multilib предполагает, что собрана будет лишь версия компилятора, которая полностью совместима в текущей системой (к примеру, на x64 системе не будет создан x32 компилятор). Такой подход ускорит сборку и исключит возможные ошибки.
В --enable-languages=c,c++ мы перечисляем языки, которые будет поддерживать наш компилятор. GCC, к слову, может еще работать с иными языками, вроде fortran, go, d…
6. Собираем компилятор
make -j$(nproc)
Команда запускает сборку по правилам, которые мы определили в ранее созданном Make-файле.
Опция -j (полная форма --jobs) требует проводить компиляцию многопоточно для более быстрой сборки. В целом, эта стадия самая долгая и займет от получаса до нескольких часов, в зависимости от ресурсов машины.
Подстановка $(nproc) достанет из системы общее число ядер процессора. Соответственно, все они будут задействованы сборке GCC. При желании можно указать конкретное число (make -j8) или подойти чуть более творчески (make -j$(($(nproc) - 1))).
7. Инсталлируем и подключаем GCC
Команда sudo make install запустит инсталляцию собранного компилятора и сделает его готовым к работе.
Между тем, на данный момент запрос g++ --version выдаст нам номер старой версии GCC, а система в целом будет вести себя так, будто ничего не произошло.
Потому последним шагом нам следует объяснить системе, какой из наличных GCC отныне считается главным.
sudo update-alternatives --install /usr/bin/gcc gcc /opt/gcc-15/bin/gcc 100 sudo update-alternatives --install /usr/bin/g++ g++ /opt/gcc-15/bin/g++ 100
Эти команды зарегистрируют в системе бинарные файлы нового компилятора, расположенные в /opt/gcc-15/bin/.
Мы используем две команды, потому что g++ это обычный C++ компилятор, работающий по ISO стандартам, тогда как gcc — это C компилятор с поддержкой C++ и собственными GCC-расширениями.
Параметр 100 означает приоритет. Бинарники с большим приоритетом выбираются по умолчанию, если мы не предусмотрим иного.
sudo update-alternatives --config gcc sudo update-alternatives --config g++
Каждая из этих команд выведет в консоль нумерованный список зарегистрированных бинарников и предложит выбрать тот, который будет вызываться при обращении к g++ или gcc.
На этом все. Теперь команда g++ --version должна возвращать номер той самой последней версии компилятора GCC, которую мы все это время устанавливали.
В дальнейшем, если мы решим вернуться к использованию изначальной «системной» версии, нам помогут те же команды.
Если старый компилятор у нас на тот момент будет не зарегистрирован, нужно будет провести его регистрацию:
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 50 sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-13 50
8. Наслаждаемся новыми фичами
GCC 15.1 открыл нам доступ к целому ряду особенностей, запланированных для стандарта C++26. Теперь мы можем на практике ознакомиться с будущими новациями языка.
Для этого создадим отдельную директорию, перейдем туда и сделаем там .cpp файл.
mkdir gcc_test_folder cd gcc_test_folder touch test26.cpp
Запишем в файл следующий код:
#include <iostream> #include <format> // =================================================== // Feature 1: Pack Indexing // =================================================== template<typename... Types> void PrintFirstArg(Types... Args) { std::cout << std::format("{}\n\n", Args...[0]); } void TestPackIndexing() { puts("=== Feature 1: Pack Indexing ==="); PrintFirstArg('a', "two", 33); } // =================================================== // Feature 2: Attributes for Structured Bindings // =================================================== void TestAttributesForStructuredBindings() { puts("=== Feature 2: Attributes for Structured Bindings ==="); struct StructS1 { int A = 10, B = 20; } S; auto [A [[maybe_unused]], B [[deprecated]]] = S; std::cout << std::format("A = {}, B = {}\n\n", A, B); } // =================================================== // Feature 3: Variadic Friends // =================================================== template<typename... Ts> class Passkey { friend Ts...; Passkey() { } }; class ClassA; class ClassB; struct Widget { void Secret(Passkey<ClassA, ClassB>) const { puts("Secret accessed!"); } }; struct ClassA { void Access(const Widget& W) const { W.Secret({}); } }; struct ClassB { void Access(const Widget& W) const { W.Secret({}); } }; struct ClassD { // Compilation error: ClassD is not a friend // void Access(Widget& W) { W.Secret({}); } }; void TestVariadicFriends() { puts("=== Feature 3: Variadic Friends ==="); Widget W; ClassA A, B; A.Access(W); B.Access(W); puts(""); } // =================================================== // Feature 4: Constexpr Placement New // =================================================== constexpr int ConstexprPlacementNew() { std::allocator<int> Allocator; int* P = Allocator.allocate(16); new (P) int(33); int Value = *P; Allocator.deallocate(P, 16); return Value; } void TestConstexprPlacementNew() { puts("=== Feature 4: Constexpr Placement New ==="); constexpr int Result = ConstexprPlacementNew(); std::cout << std::format("Value constructed with placement new: {}\n\n", Result); } // =================================================== // Feature 5: Structured Binding as Condition // =================================================== struct StructS2 { int A, B; explicit operator bool() const noexcept { return A != B; } }; void Use(int A, int B) { std::cout << std::format("Using A = {}, B = {}\n", A, B); } void TestStructuredBindingAsCondition() { puts("=== Feature 5: Structured Binding as Condition ==="); StructS2 S{10, 20}; if (auto [A, B] = S) { Use(A, B); } puts(""); } // =================================================== // Feature 6: Deleted Function with Reason // =================================================== void OldFunction(char*) = delete("unsafe, use NewFunction instead"); void NewFunction(char*) { } void TestDeletedFunctionWithReason() { // error: use of deleted function ‘void OldFunction(char*)’: unsafe, use NewFunction instead // OldFunction("test"); } // =================================================== // Feature 7: Deleting Pointer to Incomplete Type // =================================================== struct IncompleteType; void TestDeletingIncompleteType(IncompleteType* Pointer) { // delete Pointer; // Compilation error in C++26 } // =================================================== // Feature 8: Oxford Variadic Comma // =================================================== void DeprecatedFunction(auto......) { } // omission of ‘,’ before varargs ‘...’ is deprecated in C++26 void OkayFunction(auto..., ...) { } void DeprecatedG(int...) { } // Deprecated in C++26 void OkayG(int, ...) { } void DeprecatedH(int X...) { } // Deprecated in C++26 void OkayH(int X, ...) { } // =================================================== // Main Function // =================================================== int main() { TestPackIndexing(); TestAttributesForStructuredBindings(); TestVariadicFriends(); TestConstexprPlacementNew(); TestStructuredBindingAsCondition(); TestDeletedFunctionWithReason(); TestDeletingIncompleteType(nullptr); }
Этот код содержит все основные поддерживаемые GCC 15.1.0 фичи стандарта С++26, которые мы можем испытать в рамках одного компилируемого кода.
Далее, пребывая в той же испытательной директории, проведем компиляцию нашего файла без GNU расширений, но с поддержкой C++26 (-std=c++2c или -std=c++26) и запустим полученную в результате программу (./test26):
g++ -std=c++26 test26.cpp -o test26 ./test26
Если файл успешно скомпилировался, в процессе выдав нам несколько запланированных предупреждений, у нас все получилось.
Источники
ссылка на оригинал статьи https://habr.com/ru/articles/905528/
Добавить комментарий