-
Notifications
You must be signed in to change notification settings - Fork 72
Compiler support
Сейчас Desbordante можно собрать несколькими различными компиляторами на Linux и macOS. Чтобы так продолжалось и дальше, нужно соблюдать несколько правил, описанных в этом документе. Также здесь собраны рекомендации по поиску проблем, связанных с особенностями различных компиляторов.
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
В 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__
).
В 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
andstd::_V2
.
(из документации к libstdc++)
Известно, что зависимые имена типов должны использоваться с ключевым словом 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 проходит на 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();
};
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) и заменить на что-то определённое.
Санитайзеры Clang могут найти проблемы, которые пропустили санитайзеры GCC. Это совершенно нормально. Надо просто исправить проблемы.
UB sanitizer чаще всего довольно подробно сообщает, в чём проблема.
ADDRESS sanitizer может оказаться удобным запустить локально (см. инструкции по сборке Clang в README.md
), возможно, с gdb
.
Санитайзеры 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.