Skip to content

Compiler support

Petr Senichenkov edited this page Jan 18, 2025 · 1 revision

Проблемы при сборке различными компиляторами

Сейчас Desbordante можно собрать несколькими различными компиляторами на Linux и macOS. Чтобы так продолжалось и дальше, нужно соблюдать несколько правил, описанных в этом документе. Также здесь собраны рекомендации по поиску проблем, связанных с особенностями различных компиляторов.

Aggregate initialization

Apple Clang пока что не поддерживает parenthesized aggregate initialization (см. List-initialization). Нужно использовать braced aggregate initialization. То есть, при инициализации простых структур нужно использовать фигурные скобки, а не круглые.

Например,

struct Complex {
    double re;
    double im;
};

roots.emplace_back(a, b);  // error: parenthesized aggregate initialization (compiler says "no suitable constructor for Complex")
roots.push_back({a, b});   // OK

foo(Complex(a, b));  // error: the same
foo(Complex{a, b});  // OK

GCC intrinsics

Нестандартные расширения GCC

В libstdc++ определено довольно много нестандартных расширений языка и STL. Понятно, что их нельзя использовать, если нужно, чтобы код компилировался не только GCC.

К расширениям языка относится, например, variable length array (который не рекомендуется использовать даже на GCC).

Расширения стандартной библиотеки можно легко отличить от стандартных функций и классов -- их имена начинаются с подчёркивания (одного или двух). Например, SGI extensions для std::bitset. Для двух из них -- _Find_first и _Find_next -- у нас уже есть кастомная реализация -- FindFirst и FindNext из файла util/bitset_extensions.h (ещё там есть несколько вспомогательных функций, использующих эти две).

Также с двух подчёркиваний начинаются feature-test macros. Их использовать можно (и очень желательно использовать именно их, а не _GNUG_ и __clang__).

Inline namespaces

В libstdc++ используются inline namespaces внутри std для того, чтобы выделить детали реализации из основного namespace. Их не нужно использовать даже на GCC:

The library uses a number of inline namespaces as implementation details that are not intended for users to refer to directly, these include std::__detail, std::__cxx11 and std::_V2.

(из документации к libstdc++)

template disambiguator

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

Но не все знают, что зависимые шаблонные имена должны использоваться с ключевым словом template, как в этом примере с cppreference:

template<typename T>
struct S
{
    template<typename U>
    void foo() {}
};
 
template<typename T>
void bar()
{
    S<T> s;
    s.foo<T>();          // error: < parsed as less than operator
    s.template foo<T>(); // OK
}

Ошибки в CI (и не только)

Даже при соблюдении предыдущих правил может получиться, что CI проходит на GCC под linux (run_tests(ubuntu-latest, gcc, ...)), но не проходит на другом компиляторе или ОС (например, run_tests(macos-latest, apple-clang, ...)). Ниже приведены рекомендации, что делать в таком случае.

Ошибки компиляции

Скорее всего, ошибки компиляции будут выглядеть примерно так:

X is not member of namespace std, non-constexpr X cannot be used in a constant expression, и т. д.

Некоторые возможности реализованы ещё не во всех версиях стандартной библиотеки. Скорее всего, возможность X относится к ним.

Найдите X в таблице "complier support" на cppreference ("C++20 library features" и "C++20 core language features") и посмотрите на столбцы GCC, Clang и Apple Clang (GCC libstdc++, Clang libc++ и Apple Clang в таблице library features).

Красная или жёлтая клеточка

Красные клеточки означают, что X ещё не реализована, жёлтые -- что X реализована частично. Сюда же относятся зелёные клеточки, в которых указана слишком высокая версия компилятора (на данный момент у нас используется GCC 14, Clang 16 и Apple Clang 15).

В таком случае нужно найти способ не использовать X. Скорее всего, вам пригодятся feature-test macros -- их можно найти внизу страницы X на cppreference и на странице feature testing.

У нас уже реализован JThread (файл util/auto_join_thread) -- замена std::jthread. Jthread использует feature-test macro, чтобы определить, доступен ли std::jthread и, если недоступен, использует кастомную реализацию. На GCC JThread просто будет zero-cost псевдонимом для std::jthread. Если понадобится замена для X, то лучше сделать так же.

Зелёная клеточка

Если все клеточки напротив X зелёные, или в таблице нет X, то, скорее всего, вы делаете что-то не так, но, по воле случая, это работает на GCC.

Например, транзитивные include могут работать или не работать в зависимости от стандартной библиотеки:

#include <stdexcept>
// On GCC and LLVM Clang, <stdexcept> transitively includes <string>.
// But on Apple Clang, it won't compile until you uncomment the next line:
// #include <string>

class Exception: std::exception {
    std::string what();
};

Unused private field

Clang понимает, когда приватные поля не используются и выдаёт предупреждение Wunused-private-field (это правильное поведение). Чтобы избежать этого, надо помечать такие поля как [[maybe_unused]]. Но GCC не умеет определять, когда приватные поля не используются, и на всякий случай выдаёт предупреждение Wattributes: "приватные поля и так не помечаются как unused, так что атрибут проигнорирован". Поэтому такие поля нужно помечать как MAYBE_UNUSED_PRIVATE_FIELD из файла util/maybe_unused_private_field.h.

Ошибки компоновки

Здесь речь идёт про ошибки компоновки при сборке разными компиляторами локально. Если Вы не меняли конфигурацию CI (файлы *.yml), а она падает с ld error, то, вероятно, ошибка не связана с различиями в компиляторах, либо это хитро замаскированная ошибка из предыдущего раздела.

Если Вы видите "невозможные" ошибки ld (например, не найден std::basic_string), скорее всего, дело в несовместимости ABI. В нашем случае в первую очередь надо проверить CXXFLAGS и LDFLAGS -- они долны указывать на одну и ту же версию стандартной библиотеки, совместимую с вашей версией компилятора. Также стандратная библиотека должна быть совместима с библиотекой C++ ABI. Значения CXXFLAGS и LDFLAGS для различных компиляторов можно найти в README.md в разделе Dependencies. Проверить текущие значения можно командой echo $CXXFLAGS (echo $LDFLAGS), очистить -- командой export -n CXXFLAGS (export -n LDFLAGS).

Если у Вас установлено несколько версий одного компилятора, обязательно указывайте полные пути (например, export CXXFLAGS="-stdlib=/usr/lib/llvm19/libc++").

Если "невозможные" ошибки указывают на отсутствие чего-либо в boost, то, почти наверняка, boost у Вас собран не тем компилятором.

Не проходят тесты

Если тесты проходят на одной конфигурации, но не проходят на другой, то наиболее вероятно, вы где-то полагаетесь на implementation-defined behaviour (например, порядок эквивалентных элементов после std::sort) или какой-то специфический вид undefined behaviour, который пропускают UB санитайзеры (например, сужающие преобразования из float в int).

В любом случае, это поведение нужно найти (вам может помочь gdb и ubbook) и заменить на что-то определённое.

Не проходят UB sanitizer или ADDRESS sanitizer

Санитайзеры Clang могут найти проблемы, которые пропустили санитайзеры GCC. Это совершенно нормально. Надо просто исправить проблемы.

UB sanitizer чаще всего довольно подробно сообщает, в чём проблема. ADDRESS sanitizer может оказаться удобным запустить локально (см. инструкции по сборке Clang в README.md), возможно, с gdb.

False positives

Санитайзеры Clang почти никогда не выдают false positives:

If you see one [false positive], look again; most likely it is a true positive!

(из документации Clang Address Sanitizer)

Однако, такое всё же случается: например, ошибка в пакете для Ubuntu.

Если вы уверены, что перед вами false positive, можно использовать __attribute__((no_sanitize("address"))) (__attribute__((no_sanitize("undefined")))) или sanitizer ignore list (ADDRESS) (sanitizer ignore list (UB)). Постарайтесь отключить как можно меньше проверок и убедитесь, что аналогичные проверки в санитайзерах GCC включены.

Заключение

Все эти правила основаны на проблемах, которые я исправлял при портировании Desbordante на Clang для Linux и macOS. Вы можете найти реальные примеры в PR#507.

Если Вы не соблюдали какое-то из этих правил, но всё собралось и прошли тесты, то, возможно, соответствующую проблему исправили в новой версии компилятора (см. compiler support). Это очень хорошая новость -- обязательно сообщите об этом всем!

Если у Вас возникнут вопросы, спрашивайте в Технические вопросы или пишите мне в Telegram: @petua41.