Самая надежная гарантия отсутствия исключений
#опытным
Исключения не любят не только и не столько потому, что они нарушают стандартный поток исполнения программы, могут привести к некорректному поведению системы и приходится везде писать try-catch блоки. Исключения - это не zero-cost абстракция. throw требуют динамические аллокации, catch - RTTI, а в машинном коде компилятор обязан генерировать инструкции на случай вылета исключений. Плюс обработка исключений сама по себе медленная.
Поэтому некоторые и стараются минимизировать использование исключений и максимально использовать noexcept код.
Но можно решить проблему накорню. Так сказать отрезать ее корешок под самый корешок.
Есть такой флаг компиляции -fno-exceptions. Он запрещает использование исключений в программе. Но что значит запрет на использование исключений?
👉🏿 Ошибка компиляции при выбросе исключения. А я говорил, что под корень рубим. Вы просто не соберете программу, которая кидает исключения.
👉🏿 Ошибка компиляции при попытке обработать исключение. Ну а че, если вы живете в мире без исключений, зачем вам их обрабатывать?
👉🏿 Можно конечно сколько угодно жить в розовом мире без исключений, но рано или поздно придется использовать чужой код. Что будет, если он выкинет исключение?
Моментальное завершение работы. Оно как бы и понятно. Метод мапы at() кидает std::out_of_range исключение, если ключа нет в мапе. Обрабатывать исключение нельзя, поэтому чего вола доить, сразу терминируемся. И никакой вам раскрутки стека и graceful shutdown. Просто ложимся и умираем, скрестив ручки.
То есть вы накорню запрещаете упоминание исключений в вашем коде, а если что-то пошло не по плану, то оно пойдет по п...
Зато получаете стабильно высокую производительность и предсказуемый флоу программы.
Как тогда код писать? А об этом через пару постов.
Handle errors. Stay cool.
#cppcore #compiler
#опытным
Исключения не любят не только и не столько потому, что они нарушают стандартный поток исполнения программы, могут привести к некорректному поведению системы и приходится везде писать try-catch блоки. Исключения - это не zero-cost абстракция. throw требуют динамические аллокации, catch - RTTI, а в машинном коде компилятор обязан генерировать инструкции на случай вылета исключений. Плюс обработка исключений сама по себе медленная.
Поэтому некоторые и стараются минимизировать использование исключений и максимально использовать noexcept код.
Но можно решить проблему накорню. Так сказать отрезать ее корешок под самый корешок.
Есть такой флаг компиляции -fno-exceptions. Он запрещает использование исключений в программе. Но что значит запрет на использование исключений?
👉🏿 Ошибка компиляции при выбросе исключения. А я говорил, что под корень рубим. Вы просто не соберете программу, которая кидает исключения.
int main() {
throw 1; // even this doesn't compile
}👉🏿 Ошибка компиляции при попытке обработать исключение. Ну а че, если вы живете в мире без исключений, зачем вам их обрабатывать?
int main() {
// even this doesn't compile
try {
} catch(...) {
}
}👉🏿 Можно конечно сколько угодно жить в розовом мире без исключений, но рано или поздно придется использовать чужой код. Что будет, если он выкинет исключение?
std::map<int, int> map;
std::cout << map.at(1) << std::endl;
Моментальное завершение работы. Оно как бы и понятно. Метод мапы at() кидает std::out_of_range исключение, если ключа нет в мапе. Обрабатывать исключение нельзя, поэтому чего вола доить, сразу терминируемся. И никакой вам раскрутки стека и graceful shutdown. Просто ложимся и умираем, скрестив ручки.
То есть вы накорню запрещаете упоминание исключений в вашем коде, а если что-то пошло не по плану, то оно пойдет по п...
Зато получаете стабильно высокую производительность и предсказуемый флоу программы.
Как тогда код писать? А об этом через пару постов.
Handle errors. Stay cool.
#cppcore #compiler
👍27❤11🔥6😁3❤🔥2🤔1
Как стандартная библиотека компилируется с -fno-exceptions?
#опытным
В прошлом посте мы поговорили о том, что использование флага -fno-exceptions фактически трансформирует ваш код в диалект С++, в котором упоминание мира исключений карается ошибкой компиляции. Но каким образом компилируется код из стандартных заголовочных файлов? Там же повсюду обработка исключений?
Ответ прост. Макросы, товарищи. Вся магия в них. Вот на что заменяется обработка исключений:
При запрете исключений, обработка заменяется на максимально безобидные инструкции, а проброс исключения дальше превращается в ничто.
Ну и для большинства классов, унаследованных от
Тогда любая функция, которая бросает исключения должна триггерить std::abort. Или нет?
Нет. Вот примерчик.
Когда вы запрещаете исключения для своей программы, то это затрагивает только хэдэры стадартной либы. Но хэдэры определяют лишь интерфейс. Реализация std еще и неявно динамически линкуется к каждой программе. И по дефолту она собирается с использованием исключений.
Чтобы это исправить, можно собрать ее с запретом исключений. Примерно так:
Тогда у вас действительно всегда будет вызываться abort. Потому что все эти макросы также находятся в сорс файлах.
Extend your limits. Stay cool.
#compiler
#опытным
В прошлом посте мы поговорили о том, что использование флага -fno-exceptions фактически трансформирует ваш код в диалект С++, в котором упоминание мира исключений карается ошибкой компиляции. Но каким образом компилируется код из стандартных заголовочных файлов? Там же повсюду обработка исключений?
Ответ прост. Макросы, товарищи. Вся магия в них. Вот на что заменяется обработка исключений:
#if __cpp_exceptions
# define __try try
# define __catch(X) catch(X)
# define __throw_exception_again throw
#else
# define __try if (true)
# define __catch(X) if (false)
# define __throw_exception_again
#endif
При запрете исключений, обработка заменяется на максимально безобидные инструкции, а проброс исключения дальше превращается в ничто.
Ну и для большинства классов, унаследованных от
exception, существуют соответствующие функции с C-линковкой:#if __cpp_exceptions
void __throw_bad_exception()
{ throw bad_exception(); }
#else
void __throw_bad_exception()
{ abort(); }
#endif
Тогда любая функция, которая бросает исключения должна триггерить std::abort. Или нет?
Нет. Вот примерчик.
Когда вы запрещаете исключения для своей программы, то это затрагивает только хэдэры стадартной либы. Но хэдэры определяют лишь интерфейс. Реализация std еще и неявно динамически линкуется к каждой программе. И по дефолту она собирается с использованием исключений.
Чтобы это исправить, можно собрать ее с запретом исключений. Примерно так:
git clone git://gcc.gnu.org/git/gcc.git
cd gcc
git checkout <target_release_tag>
./configure
--disable-libstdcxx-exceptions
CXXFLAGS="-fno-exceptions <all_flags_that_you_need>"
make -j$(nproc)
make install
Тогда у вас действительно всегда будет вызываться abort. Потому что все эти макросы также находятся в сорс файлах.
Extend your limits. Stay cool.
#compiler
❤16👍9🔥9❤🔥3🤔2
Что не так с модулями?
#опытным
Модули появились как одна из мажорных фич С++20, которая предоставляет чуть ли не другой подход к написанию С++ кода.
Модули - это новая фундаментальная единица организации кода, которая должна дополнить и в идеале(в мечтах комитета) заменить старую концепцию заголовочных файлов.
Если по простому, то модуль - это такой бинарный черный ящик, у которого четко определен интерфейс, который он экспортирует наружу.
Экспортируемые сущности явно помечаются в коде модуля. Затем модуль компилируется и из бинарного его представления можно дергать только эти экспортируемые сущности.
Короткий пример:
и его использование:
Модули призваны решать следующие проблемы:
✅ Одни и те же заголовки могут сотни раз обрабатываться компилятором при компиляции программ из многих единиц трансляции. Модули же компилируются один раз, в них кэшируется информация, необходимая для нормальной компиляции cpp файлов и потом эта информация просто используется при компиляции. Никакой повторной работы!
Это значит, что время компиляции должно заметно уменьшиться.
✅ В хэдэрах зачастую нужно оставлять некоторые детали реализации, которые не нужны пользователю, но нужны для корректной компиляции. Модули же явно экспортируют только нужный интерфейс.
✅ Никакой макросятины! Ни один макрос не прошмыгнет внутрь клиентского кода из модуля, потому что он уже скомпилирован.
На словах - прекрасные плюсы будущего. Но на словах мы все Львы Толстые, а на деле...
А на деле это все до сих пор работает довольно костыльно. До 23, а скорее 24 года использовать модули было совсем никак нельзя. Сейчас все немного лучше, но реализации все еще пропитаны проблемами. А проекты не спешат переходить на модули. Но почему?
😡 Модули - довольно сложная штука в реализации. Не будем вдаваться в нюансы, но компилятор должен сильно измененить свое поведение и преобрести свойства системы сборки, чтобы нормально компилировать модули. А делать они этого не хотят. Плюс многие компиляторы опенсорсные и не так-то просто в опенсорсе реализовывать такие масштабные идеи. На винде с этим попроще, потому что во главе всего Microsoft и они завезли модули раньше всех.
😡 Бинарный формат модулей нестандартизирован. Каждый компилятор выдумывает свое представление, которое несовместимо между компиляторами или даже версиями одного компилятора.
😡 Из-за этого в том числе хромает тулинг. Дело в том, что модуль - это бинарный файл и программист просто так не может, например, посмотреть сигнатуру метода в каком-то файле. Это большая проблема, которую должны решить редакторы и анализаторы кода. Но отсутствие стандартизации формата мешает интеграции модулей в них.
😡 Очень много усилий нужно потратить на переработку архитектуры и кода существующих проектов, чтобы перевести их на модули.
😡 Ускорение компиляции может неоправдать затрат. В среднем ускорение составляет порядка 30%. И это просто не стоит усилий.
😡 Нужны новейшие версии систем сборки, компиляторов и других инструментов, чтобы заработали модули.
😡 Пока популярные библиотеки не начнут распространяться через модули, существующие проекты не будут иметь большое желание переезжать на модули, потому что получится частичное внедрение.
Тем не менее, если у вас есть самые актуальные инструменты, вы запускаете новый проект или решили в тестовом режиме обновлять уже существующий, то пользоваться модулями уже можно, хоть и осторожно и с ожиданием возможных проблем.
Use new features. Stay cool.
#cppcore #compiler #tools
#опытным
Модули появились как одна из мажорных фич С++20, которая предоставляет чуть ли не другой подход к написанию С++ кода.
Модули - это новая фундаментальная единица организации кода, которая должна дополнить и в идеале(в мечтах комитета) заменить старую концепцию заголовочных файлов.
Если по простому, то модуль - это такой бинарный черный ящик, у которого четко определен интерфейс, который он экспортирует наружу.
Экспортируемые сущности явно помечаются в коде модуля. Затем модуль компилируется и из бинарного его представления можно дергать только эти экспортируемые сущности.
Короткий пример:
// math.cppm - файл модуля
export module math; // Объявление модуля
import <vector>; // Импорт, а не включение
// Макросы НЕ экспортируются!
#define PI 3.14159
// Явный экспорт - только то, что нужно
export double calculate_circle_area(double radius);
// Внутренние функции скрыты
void internal_helper();
и его использование:
// main.cpp - обычный С++ файл
import math; // Импорт интерфейса, не всего кода
// Используем экспортированную функцию
double area = calculate_circle_area(10);
// internal_helper(); // ERROR! функция скрыта
// double x = PI; // ERROR! макросы не экспортируются
Модули призваны решать следующие проблемы:
✅ Одни и те же заголовки могут сотни раз обрабатываться компилятором при компиляции программ из многих единиц трансляции. Модули же компилируются один раз, в них кэшируется информация, необходимая для нормальной компиляции cpp файлов и потом эта информация просто используется при компиляции. Никакой повторной работы!
Это значит, что время компиляции должно заметно уменьшиться.
✅ В хэдэрах зачастую нужно оставлять некоторые детали реализации, которые не нужны пользователю, но нужны для корректной компиляции. Модули же явно экспортируют только нужный интерфейс.
✅ Никакой макросятины! Ни один макрос не прошмыгнет внутрь клиентского кода из модуля, потому что он уже скомпилирован.
На словах - прекрасные плюсы будущего. Но на словах мы все Львы Толстые, а на деле...
А на деле это все до сих пор работает довольно костыльно. До 23, а скорее 24 года использовать модули было совсем никак нельзя. Сейчас все немного лучше, но реализации все еще пропитаны проблемами. А проекты не спешат переходить на модули. Но почему?
😡 Модули - довольно сложная штука в реализации. Не будем вдаваться в нюансы, но компилятор должен сильно измененить свое поведение и преобрести свойства системы сборки, чтобы нормально компилировать модули. А делать они этого не хотят. Плюс многие компиляторы опенсорсные и не так-то просто в опенсорсе реализовывать такие масштабные идеи. На винде с этим попроще, потому что во главе всего Microsoft и они завезли модули раньше всех.
😡 Бинарный формат модулей нестандартизирован. Каждый компилятор выдумывает свое представление, которое несовместимо между компиляторами или даже версиями одного компилятора.
😡 Из-за этого в том числе хромает тулинг. Дело в том, что модуль - это бинарный файл и программист просто так не может, например, посмотреть сигнатуру метода в каком-то файле. Это большая проблема, которую должны решить редакторы и анализаторы кода. Но отсутствие стандартизации формата мешает интеграции модулей в них.
😡 Очень много усилий нужно потратить на переработку архитектуры и кода существующих проектов, чтобы перевести их на модули.
😡 Ускорение компиляции может неоправдать затрат. В среднем ускорение составляет порядка 30%. И это просто не стоит усилий.
😡 Нужны новейшие версии систем сборки, компиляторов и других инструментов, чтобы заработали модули.
😡 Пока популярные библиотеки не начнут распространяться через модули, существующие проекты не будут иметь большое желание переезжать на модули, потому что получится частичное внедрение.
Тем не менее, если у вас есть самые актуальные инструменты, вы запускаете новый проект или решили в тестовом режиме обновлять уже существующий, то пользоваться модулями уже можно, хоть и осторожно и с ожиданием возможных проблем.
Use new features. Stay cool.
#cppcore #compiler #tools
❤18👍7🔥6
Парсим ужас
#новичкам
Вот мы и рассмотрели все необходимые компоненты, чтобы понять, что написано здесь:
Если включить clang-format, то код преобразится во что-то такое:
Давайте посмотрим, откуда так много скобок:
1️⃣ Перед типом возвращаемого значения main определены 2 пустые области для указания атрибутов функции main.
2️⃣ Перед телом функции main определена пустая область для атрибутов, применяемых к типу функции main.
3️⃣ После блока захвата лямбды определена пустая область для атрибутов, применяемых к самой лямбде.
4️⃣ Внутри списка параметров лямбды определена пустая область для атрибутов, применяемых к единственному параметру лямбды.
5️⃣ Сама лямбда является generic и принимает массив неизвестного типа.
6️⃣ Перед телом лямбды определены 2 пустые области для атрибутов, применяемых к типу лямбды.
7️⃣ Вызываем лямбду с помощью указателя на функцию main.
8️⃣ Ну и разбавили это дело несколькими лишними скоупами по пути.
Не так уж и сложно оказалось)
Так, новичковая часть закончилась.
#опытным
Интересно, что этот код компилируется на gcc, но не на clang.
cppinsights показывает, что лямбда раскрывается во что-то такое:
То есть по факту мы имеем шаблонный оператор с auto параметром.
Как интерпретировать эту штуку - дело нетривиальное и по ходу компиляторы это делают по-разному. Видимо gcc при попытке инстанцировать шаблон с параметром int() выводит auto как тот же самый тип функции int() и в итоге лямбда принимает указатель на функцию. А clang при попытке инстанцировать шаблон выводит тип параметра функции как массив функций int() и не может принять main в качестве такого параметра.
Пишите ваше мнение, кто прав, кто виноват)
Deal with horrible things step by step. Stay cool.
#cppcore #compiler
#новичкам
Вот мы и рассмотрели все необходимые компоненты, чтобы понять, что написано здесь:
[[]][[]]int main()[[]]{{[][][[]][[]]{{{}}}(main);}}Если включить clang-format, то код преобразится во что-то такое:
/*1*/[[]][[]] int main()/*2*/[[]] {
{
[]/*3*/[[]](/*4*/[[]] /*5*/auto [])/*6*/[[]][[]] {
{
{}
}
}/*7*/(main);
}
}Давайте посмотрим, откуда так много скобок:
1️⃣ Перед типом возвращаемого значения main определены 2 пустые области для указания атрибутов функции main.
2️⃣ Перед телом функции main определена пустая область для атрибутов, применяемых к типу функции main.
3️⃣ После блока захвата лямбды определена пустая область для атрибутов, применяемых к самой лямбде.
4️⃣ Внутри списка параметров лямбды определена пустая область для атрибутов, применяемых к единственному параметру лямбды.
5️⃣ Сама лямбда является generic и принимает массив неизвестного типа.
6️⃣ Перед телом лямбды определены 2 пустые области для атрибутов, применяемых к типу лямбды.
7️⃣ Вызываем лямбду с помощью указателя на функцию main.
8️⃣ Ну и разбавили это дело несколькими лишними скоупами по пути.
Не так уж и сложно оказалось)
Так, новичковая часть закончилась.
#опытным
Интересно, что этот код компилируется на gcc, но не на clang.
cppinsights показывает, что лямбда раскрывается во что-то такое:
class __lambda_5_17 {
public:
template <class type_parameter_0_0>
inline /*constexpr */ auto operator()(auto *) const {
{ {}; };
}
private:
template <class type_parameter_0_0>
static inline /*constexpr */ auto __invoke(auto *__param0) {
return __lambda_5_17{}.operator()<type_parameter_0_0>(__param0);
}
public:
// /*constexpr */ __lambda_5_17() = default;
};То есть по факту мы имеем шаблонный оператор с auto параметром.
Как интерпретировать эту штуку - дело нетривиальное и по ходу компиляторы это делают по-разному. Видимо gcc при попытке инстанцировать шаблон с параметром int() выводит auto как тот же самый тип функции int() и в итоге лямбда принимает указатель на функцию. А clang при попытке инстанцировать шаблон выводит тип параметра функции как массив функций int() и не может принять main в качестве такого параметра.
Пишите ваше мнение, кто прав, кто виноват)
Deal with horrible things step by step. Stay cool.
#cppcore #compiler
Telegram
Грокаем C++
И это все компилируется!
Сможете сказать, откуда каждая скобка взялась?)
Сможете сказать, откуда каждая скобка взялась?)
❤🔥21😁12❤7👍3🔥2