Всегда приятно, когда громко рассказывают об успехах отечественных технологий. Я к этому хоть отношения никакого не имею, но все равно на душе как-то приятно становится.
И двигают ее вперед в первую очередь энтузиасты, которым по кайфу работать в той области, где еще очень неуверено ступала нога массового пользователя, бизнеса и тд.
Сегодня хочется рассказать об одном необычном для русского айти событии. Программист-любитель просто взял и организовал соревнование по алгоритмическому программированию на C/C++ под «Эльбрусы» (e2k). Соревы собрали 31 студентов со всей России в онлайн-формате, призовым фондом были личные 215 тысяч рублей организатора.
Какие были задачи, как происходила подготовка инфраструктуры совернований, откуда взялись эти бравые студенты - обо всем этом вы можете почитать в статье организатора на Хабре. Там расписаны буквально все подробности от подготовки до юридичыеских тонкостей.
Обязательно зайдите и поддержите автора.
А еще в марте следующего года будет еще одно такое мероприятие. Так что если вы студент и знакомы с Эльбрусами, вас будут там ждать)
И двигают ее вперед в первую очередь энтузиасты, которым по кайфу работать в той области, где еще очень неуверено ступала нога массового пользователя, бизнеса и тд.
Сегодня хочется рассказать об одном необычном для русского айти событии. Программист-любитель просто взял и организовал соревнование по алгоритмическому программированию на C/C++ под «Эльбрусы» (e2k). Соревы собрали 31 студентов со всей России в онлайн-формате, призовым фондом были личные 215 тысяч рублей организатора.
Какие были задачи, как происходила подготовка инфраструктуры совернований, откуда взялись эти бравые студенты - обо всем этом вы можете почитать в статье организатора на Хабре. Там расписаны буквально все подробности от подготовки до юридичыеских тонкостей.
Обязательно зайдите и поддержите автора.
А еще в марте следующего года будет еще одно такое мероприятие. Так что если вы студент и знакомы с Эльбрусами, вас будут там ждать)
Хабр
к.т.н. Страннолюбов, или Как я перестал бояться и провёл соревнование по программированию на Эльбрусе
Здравствуйте, друзья, меня зовут Ерохин Кирилл, я программист‑любитель, и в этом сентябре я втихаря провёл соревнование по алгоритмическому программированию на C/C++ под платформу...
❤16👍14🔥7👎3🗿1
Уплощаем многомерный массив
#опытным
Иногда у вас есть коллекция элементов, для каждого из которых вы выполняете операцию, возвращающую вектор значений:
Итоговое отображение result_view - это по факту набор векторов. Чтобы сложить это все в один массив нужен двойной цикл. А можно как-то удобно и лаконично получить плоский вектор интов?
С помощью С++20 отображения std::views::join:
Это все сработает и на экране появлятся заветные чиселки.
Здесь используется std::ranges::to и std::print, которые добавлены в 23-м стандарте
Если у вас элементы, которые хотелось бы переместить, а не скопировать, то можно добавить еще с++23 отображение as_rvalue:
Если хочется чистого кода без циклов, то рэнджи для этого и сделаны.
Don't stuck in a loop. Stay cool.
#cpp20 #cpp23
#опытным
Иногда у вас есть коллекция элементов, для каждого из которых вы выполняете операцию, возвращающую вектор значений:
std::vector<int> Process(const std::string& str);
std::vector<std::string> elems = ...;
auto result_view = elems | std::views::transform([](const std::string& str) {
return Process(str);
})
Итоговое отображение result_view - это по факту набор векторов. Чтобы сложить это все в один массив нужен двойной цикл. А можно как-то удобно и лаконично получить плоский вектор интов?
С помощью С++20 отображения std::views::join:
std::vector<int> Process(const std::string& str);
std::vector<std::string> elems = ...;
auto result = elems | std::views::transform([](const std::string &str) {
return Process(str);
}) |
std::views::join | std::ranges::to<std::vector>();
std::print("{}", result);
Это все сработает и на экране появлятся заветные чиселки.
Здесь используется std::ranges::to и std::print, которые добавлены в 23-м стандарте
Если у вас элементы, которые хотелось бы переместить, а не скопировать, то можно добавить еще с++23 отображение as_rvalue:
auto result = elems | std::views::transform([](const auto & elem) {
return Process(elem);
}) |
std::views::join | std::views::as_rvalue |
std::ranges::to<std::vector>();Если хочется чистого кода без циклов, то рэнджи для этого и сделаны.
Don't stuck in a loop. Stay cool.
#cpp20 #cpp23
❤19👍13🔥7
Константная мапа
#новичкам
Определяете вы какое-нибудь отображение в коде:
Никаких больше команд вы не обрабатываете, отображение константно.
И вот вы хотите получить доступ к элементам мапы:
И тут бац! И ошибка компиляции о том, что нет такого оператора[], который бы принимал константную std::unordered_map.
Почему так? У вектора же есть.
Проблема тут комплексная.
Здесь мы рассказали о том, что operator[] у мапы имеет одну особенность. Если вы ему передаете новый ключ, то он изменяет мапу и вставляет в нее элемент с этим новым ключом и дефолтным значением.
Это сделано по всей видимости по причине универсализации поведения между операторами[] для большинства контейнеров. Обычно operator[] не бросает никаких исключений. Он может приводить к неопределенному поведению, как например у вектора при доступе за границу массива. Но он не бросает.
И в случае мапы не очень понятно, что делать, если переданного ключа нет, бросать нельзя и хочется сохранить идентичность интерфейса по всему STL с возвратом ссылки.
Вот и решили конструировать объект налету.
Но для константного оператора вообще непонятно, что делать, если ключа нет, бросать нельзя, нужно возвращать ссылку, да еще и изменять мапу нельзя. И UB тоже не хочется, чем меньше его в стандарте, тем лучше.
Поэтому решили проблему гениально: вообще не вводить эту версию оператора.
В условиях отсутствия константного operator[] для std::map и std::unordered_map вы можете использовать либо метод at(), который бросает std::out_of_range, если ключа нет. Или find(), который вернет итератор на конец мапы:
Find compromis. Stay cool.
#STL
#новичкам
Определяете вы какое-нибудь отображение в коде:
using CommandCreator = std::function<std::unique_ptr<ICommand>(const std::vector<std::string>&)>;
const std::unordered_map<std::string, CommandCreator> commands = {
{"create", [](const std::vector<std::string>& vec){return std::make_unique<CreateCommand>(vec)}},
{"delete", [](const std::vector<std::string>& vec){return std::make_unique<DeleteCommand>(vec)}},
{"save", [](const std::vector<std::string>& vec){return std::make_unique<SaveCommand>(vec)}}
};
Никаких больше команд вы не обрабатываете, отображение константно.
И вот вы хотите получить доступ к элементам мапы:
auto command = commands[command_name](vec);
command->Execute();
И тут бац! И ошибка компиляции о том, что нет такого оператора[], который бы принимал константную std::unordered_map.
Почему так? У вектора же есть.
Проблема тут комплексная.
Здесь мы рассказали о том, что operator[] у мапы имеет одну особенность. Если вы ему передаете новый ключ, то он изменяет мапу и вставляет в нее элемент с этим новым ключом и дефолтным значением.
Это сделано по всей видимости по причине универсализации поведения между операторами[] для большинства контейнеров. Обычно operator[] не бросает никаких исключений. Он может приводить к неопределенному поведению, как например у вектора при доступе за границу массива. Но он не бросает.
И в случае мапы не очень понятно, что делать, если переданного ключа нет, бросать нельзя и хочется сохранить идентичность интерфейса по всему STL с возвратом ссылки.
Вот и решили конструировать объект налету.
Но для константного оператора вообще непонятно, что делать, если ключа нет, бросать нельзя, нужно возвращать ссылку, да еще и изменять мапу нельзя. И UB тоже не хочется, чем меньше его в стандарте, тем лучше.
Поэтому решили проблему гениально: вообще не вводить эту версию оператора.
В условиях отсутствия константного operator[] для std::map и std::unordered_map вы можете использовать либо метод at(), который бросает std::out_of_range, если ключа нет. Или find(), который вернет итератор на конец мапы:
auto command = commands.at(command_name)(vec);
command->Execute();
// or
if (auto it = commands.find(command_name); it != commands.emd()) {
auto command = it->second();
command->Execute();
} else {
std::cout << "ERROR" << std::endl;
}
Find compromis. Stay cool.
#STL
❤20👍14🔥8😱1
pointer to data member
#опытным
В этом посте мы рассказывали о том, что с помощью ranges и и параметра проекции можно кастомизировать алгоритмы с соответствии с определенным полем класса. Например, чтобы найти в коллекции элемент с максимальным определенным полем, то можно сделать так:
max в этом случае будет транзакцией с максимальным размером платежа.
В последней строчке используется
Если про указатели на конкретные мемберы знают не только лишь все, то это совсем дебри плюсов.
Явный тип указателя на поле класса используется так:
По сути это особый тип указателя, который хранит смещение поля относительно начала объекта в байтах. Это не специфицировано в стандарте, но примерно везде так работает.
Мы обязательно должны указать, на какой тип полей этот указатель может указывать. Таким образом указатель
Указателю на интовое поле нельзя присвоить указатель на флотовое. И наоборот, указатель
Если вы подумали, что очень узкоспециализированная вещь, то вы правы. Чуть больше универсализации здесь могут дать шаблоны:
Walk through the nooks and crannies. Stay cool.
#cppcore #memory
#опытным
В этом посте мы рассказывали о том, что с помощью ranges и и параметра проекции можно кастомизировать алгоритмы с соответствии с определенным полем класса. Например, чтобы найти в коллекции элемент с максимальным определенным полем, то можно сделать так:
struct Payment {
double amount;
std::string category;
};
auto max = *std::ranges::max_element(payments, {}, &Payment::amount);max в этом случае будет транзакцией с максимальным размером платежа.
В последней строчке используется
&Payment::amount - указатель на поле amount в классе Payment. Но если это параметр функции, то это значение какого-то типа. Но какой тип у этого указателя?Если про указатели на конкретные мемберы знают не только лишь все, то это совсем дебри плюсов.
Явный тип указателя на поле класса используется так:
struct Payment {
double amount;
std::string category;
};
double Payment::*ptr = &Payment::amount; // Here!
Payment payment{3.14, "Groceries"};
std::cout << payment.*ptr << std::endl;
// OUTPUT:
// 3.14double Payment::* ptr = &Payment::amount;
// тип указателя имя указателя инициализатор
По сути это особый тип указателя, который хранит смещение поля относительно начала объекта в байтах. Это не специфицировано в стандарте, но примерно везде так работает.
Мы обязательно должны указать, на какой тип полей этот указатель может указывать. Таким образом указатель
ptr может указывать на любое поле класса Payment, имеющее тип double. То есть:struct Type {
int a;
int b;
float c;
};
int Type::*p = nullptr;
p = &Type::a; // OK, a is int
p = &Type::b; // OK, b is int
p = &Type::c; // ERROR! c is floatУказателю на интовое поле нельзя присвоить указатель на флотовое. И наоборот, указатель
p работает с любыми полями типа int.Если вы подумали, что очень узкоспециализированная вещь, то вы правы. Чуть больше универсализации здесь могут дать шаблоны:
// Takes pointer to any data member for any type
template<typename T, typename FieldType>
void print_field(const T& obj, FieldType T::*field) {
std::cout << obj.*field << std::endl;
}
Payment payment{3.14, "Groceries"};
Type t(42, 69, 3.14);
print_field(payment, &Payment::amount);
print_field(payment, &Payment::category);
print_field(t, &Type::a);
print_field(t, &Type::b);
print_field(t, &Type::c);
// OUTPUT
// 3.14
// Groceries
// 42
// 69
// 3.14
print_field может печатать значение любого поля любого класса по его указателю. Обратите внимание на шаблонную сигнатуру.Walk through the nooks and crannies. Stay cool.
#cppcore #memory
3❤23🔥14👍11
WAT
#опытным
Спасибо, @Ivaneo, за любезно предоставленный примерчик в рамках рубрики #ЧЗХ.
Всегда ли nullptr указатель равен нулю?
Казалось бы в названии дан ответ:
Но в общем случае это неправда! Смотрим на пример:
nullptr указатель равен совсем не нулю, как декларировалось в начале main.
WAT? Что за фокусы с пропажей нуля?
Во вчерашнем посте мы рассказывали об особом типе указателя - pointer to data member. Этот указатель, которым и является
И в большинстве случаев эта информация представляет собой просто смещение поля относительно начала объекта в байтах.
Однако нулевое смещение используется для локации самого первого поля класса. Поэтому в байтовом представлении неинициализированный указатель не может быть нулем.
Вместо этого обычно используется число -1, которое в байтовом представлении как раз выглядит как все единички:
С помощью указателей на поля класса можно кстати наглядно изучать выравнивание и упаковку полей с объект:
Опять же, интересный уголок плюсов.
Walk through the nooks and crannies. Stay cool.
#cppcore #memory
#опытным
Спасибо, @Ivaneo, за любезно предоставленный примерчик в рамках рубрики #ЧЗХ.
Всегда ли nullptr указатель равен нулю?
Казалось бы в названии дан ответ:
int * p = nullptr;
std::cout << std::boolalpha << (p == nullptr) << "\n";
std::cout << std::hex << std::bit_cast<std::uintptr_t>(p) << "\n";
// OUTPUT
// true
// 0
Но в общем случае это неправда! Смотрим на пример:
struct A {
int i;
};
int main() {
int A::* p = 0;
std::cout << std::boolalpha << (p == nullptr) << "\n";
std::cout << std::hex << std::bit_cast<std::uintptr_t>(p) << "\n";
std::cout << std::boolalpha << (std::bit_cast<std::uintptr_t>(p) == 0xffffffffffffffff) << "\n";
}
// OUTPUT:
// true
// ffffffffffffffff
// truenullptr указатель равен совсем не нулю, как декларировалось в начале main.
WAT? Что за фокусы с пропажей нуля?
Во вчерашнем посте мы рассказывали об особом типе указателя - pointer to data member. Этот указатель, которым и является
p из примера, по сути хранит информацию о том, как в объекте найти нужное поле класса.И в большинстве случаев эта информация представляет собой просто смещение поля относительно начала объекта в байтах.
Однако нулевое смещение используется для локации самого первого поля класса. Поэтому в байтовом представлении неинициализированный указатель не может быть нулем.
Вместо этого обычно используется число -1, которое в байтовом представлении как раз выглядит как все единички:
std::cout << std::hex << static_cast<long long int>(-1) << "\n";
// OUTPUT:
// ffffffffffffffff
С помощью указателей на поля класса можно кстати наглядно изучать выравнивание и упаковку полей с объект:
struct Type {
double a;
char b;
float c;
long long d;
short e;
unsigned f;
};
std::cout << std::dec << std::bit_cast<std::uintptr_t>(&Type::a) << "\n";
std::cout << std::dec << std::bit_cast<std::uintptr_t>(&Type::b) << "\n";
std::cout << std::dec << std::bit_cast<std::uintptr_t>(&Type::c) << "\n";
std::cout << std::dec << std::bit_cast<std::uintptr_t>(&Type::d) << "\n";
std::cout << std::dec << std::bit_cast<std::uintptr_t>(&Type::e) << "\n";
std::cout << std::dec << std::bit_cast<std::uintptr_t>(&Type::f) << "\n";
// OUTPUT:
// 0
// 8
// 12
// 16
// 24
// 28Опять же, интересный уголок плюсов.
Walk through the nooks and crannies. Stay cool.
#cppcore #memory
❤21👍11🔥8🤯4❤🔥2
Сколько весит объект полиморфного класса?
#новичкам
Частый вопрос с собеседований про размеры объектов различных классов. Даже в бэкэндерских конторах, в которых никогда в жизни не учитывали эти размеры. Но такие вопросы раскрывают знание базы, а считается, что без ее знания вы не можете писать нормальный код.
У нас уже был пост про размер объекта пустого класса.
А что если это будет класс с виртуальными методами? Сколько тогда будет весить этот класс?
Мы знаем, что для каждого класса, имеющего виртуальные методы, создается глобальная таблица виртуальных функций. В ней находятся конкретные адреса виртуальных методов конкретно этого класса. И именно к ней обращаются, когда хотят порешать вопросики, какой метод вызвать.
Таблица одна на все объекты заданного класса. И им каким-то образом нужно получить доступ к этой таблице.
Учитывайте, что нельзя захардкодить эту информацию по статическому типу объекта(например SomeClass& или SomeClass*), потому что под его личиной может скрываться наследник.
Значит надо ее класть в каждый объект. И самое простое - положить в них указатель на свою таблицу виртуальных функций. Так и делают на самом деле. Этот указатель называют vptr.
Соответственно размер класса зависит от битности системы. Для 64-бит указатель имеет размер 8 байт(64 бита) поэтому и размер класса SomeClass будет 8 байт.
Пустые наследники SomeClass кстати тоже будут иметь размер 8 из-за того, что им нужно лишь другое значения указателя.
Если вы добавите еще полей, то размер увеличится в соответствии с размером дополнительных полей и выравниванием. Если вы используете множественное наследование, то тоже увеличится, но об этом как-нибудь потом поговорим.
Be lightweight. Stay cool.
#cppcore #interview
#новичкам
Частый вопрос с собеседований про размеры объектов различных классов. Даже в бэкэндерских конторах, в которых никогда в жизни не учитывали эти размеры. Но такие вопросы раскрывают знание базы, а считается, что без ее знания вы не можете писать нормальный код.
У нас уже был пост про размер объекта пустого класса.
А что если это будет класс с виртуальными методами? Сколько тогда будет весить этот класс?
struct SomeClass {
virtual ~SomeClass() = default;
virtual void Process() {
std::cout << "Process" << std::endl;
}
};Мы знаем, что для каждого класса, имеющего виртуальные методы, создается глобальная таблица виртуальных функций. В ней находятся конкретные адреса виртуальных методов конкретно этого класса. И именно к ней обращаются, когда хотят порешать вопросики, какой метод вызвать.
Таблица одна на все объекты заданного класса. И им каким-то образом нужно получить доступ к этой таблице.
Учитывайте, что нельзя захардкодить эту информацию по статическому типу объекта(например SomeClass& или SomeClass*), потому что под его личиной может скрываться наследник.
Значит надо ее класть в каждый объект. И самое простое - положить в них указатель на свою таблицу виртуальных функций. Так и делают на самом деле. Этот указатель называют vptr.
Соответственно размер класса зависит от битности системы. Для 64-бит указатель имеет размер 8 байт(64 бита) поэтому и размер класса SomeClass будет 8 байт.
std::cout << sizeof(SomeClass) << std::endl;
// OUTPUT
// 8
Пустые наследники SomeClass кстати тоже будут иметь размер 8 из-за того, что им нужно лишь другое значения указателя.
Если вы добавите еще полей, то размер увеличится в соответствии с размером дополнительных полей и выравниванием. Если вы используете множественное наследование, то тоже увеличится, но об этом как-нибудь потом поговорим.
Be lightweight. Stay cool.
#cppcore #interview
1❤19👍14🔥7😁7🆒1
Комьюнити, полезное для всех бекенд-разработчиков
Как работает VK изнутри? Что происходит за интерфейсами, когда миллионы пользователей одновременно отправляют сообщения, загружают фото и смотрят клипы?
В канале Backend VK Hub мы рассказываем о работе всех наших сервисах: от VK Play до Tarantool. Делимся подходами к масштабированию, оптимизации и новым архитектурным решениям. Открыто дискутируем, а также регулярно публикуем вакансии в нашу команду.
Здесь — реальные кейсы, технические разборы, советы от наших экспертов и возможность поговорить с ними в любой момент.
Подписывайся!
Как работает VK изнутри? Что происходит за интерфейсами, когда миллионы пользователей одновременно отправляют сообщения, загружают фото и смотрят клипы?
В канале Backend VK Hub мы рассказываем о работе всех наших сервисах: от VK Play до Tarantool. Делимся подходами к масштабированию, оптимизации и новым архитектурным решениям. Открыто дискутируем, а также регулярно публикуем вакансии в нашу команду.
Здесь — реальные кейсы, технические разборы, советы от наших экспертов и возможность поговорить с ними в любой момент.
Подписывайся!
❤4👎4🔥4👍3
join
#опытным
Как прекрасно сделан в питоне метод join у строки. Чтобы соединить список строк разделителем нужно просто написать:
И как же сложно того же результата достичь в плюсах!
То делают через потоки:
то через std::accumulate:
Ну вы что! Стандартная строка же себе не может позволить иметь метод join, принимающий коллекцию строк и возвращающий объединенную строку с разделителями. Это же не универсально и никому не надо...
Но в С++23 наконец-то появилось хоть что-то похожее на адекватное решение. Используем std::views::join_with:
Можете обмазать все это шаблонами с головы до пят, чтобы получить универсальное решение, либо использовать этот код прям inplace, он и так довольно понятный.
И жизнь стала чуть-чуть счастливее...
Make thing simple. Stay cool.
#cpp23
#опытным
Как прекрасно сделан в питоне метод join у строки. Чтобы соединить список строк разделителем нужно просто написать:
my_list = ["John", "Peter", "Vicky"]
x = " ".join(my_list)
print(x)
# OUTPUT
# John Peter Vicky
И как же сложно того же результата достичь в плюсах!
То делают через потоки:
std::string join(const std::vector<std::string>& vec, const std::string& delimiter) {
if (vec.empty()) return "";
std::ostringstream oss;
oss << vec[0];
for (size_t i = 1; i < vec.size(); ++i) {
oss << delimiter << vec[i];
}
return oss.str();
}то через std::accumulate:
std::string join(const std::vector<std::string>& vec, const std::string& delimiter) {
if (vec.empty()) return "";
return std::accumulate(
std::next(vec.begin()), vec.end(),
vec[0],
[&delimiter](const std::string& a, const std::string& b) {
return a + delimiter + b;
}
);
}Ну вы что! Стандартная строка же себе не может позволить иметь метод join, принимающий коллекцию строк и возвращающий объединенную строку с разделителями. Это же не универсально и никому не надо...
Но в С++23 наконец-то появилось хоть что-то похожее на адекватное решение. Используем std::views::join_with:
std::string join(const std::vector<std::string> &vec,
const std::string &delimiter) {
return vec | std::views::join_with(delimiter) |
std::ranges::to<std::string>();
}
Можете обмазать все это шаблонами с головы до пят, чтобы получить универсальное решение, либо использовать этот код прям inplace, он и так довольно понятный.
И жизнь стала чуть-чуть счастливее...
Make thing simple. Stay cool.
#cpp23
❤24👍10🔥9😁5
Как динамически выделить память на стеке?
#опытным
В книжке "Вредные советы для С++ программистов" от PVS-студии есть такой вредный совет: "массив на стеке - это лучшее решение"
Типа выделение памяти в куче - это зло. char c[256] хватит всем, а если не хватит, то потом поменяем на 512. В крайнем случае – на 1024.
Да, использование буферов, фиксированного размера действительно может привести в проблемам в коде. Запилили новую фичу, изменили размер данных, а забыли поменять размер массива. Пожалуйста, UB.
Но возникает вопрос: а как тогда можно динамически выделять память на стеке? Ведь стандартные C-style массивы работают только с известными в compile-time размерами.
Пара способов есть, но они с "нюансом":
1️⃣ Variable Length Array. Вы просто берете и создаете массив переменного размера:
Круто!
Да, но это не часть стандарта С++) Это фича языка С, доступная с С99. Однако GCC например поддерживает ее, как компиляторное расширение языка и вы сможете g++'ом сгенерировать код выше.
2️⃣ alloca. Функция, которая аллоцирует заданное количество байт на стеке:
Память, выделенная alloca автоматически освобождается при выходе из функции.
Эта также нестандартная функция, которая не является даже частью ANSI-C стандарта, но поддерживается многими компиляторами(в т.ч. является частью Linux API).
Ну и на этом все.
Стандартных решений нет. И на это есть причина. Дело в том, что такой код провоцирует возникновение в программах уязвимостей.
В стандарте MISRA C есть правило MISRA-C-18.8, указывающее не использовать VLA. Другие руководства, такие как SEI CERT C Coding Standard (ARR32-C) и Common Weakness Enumeration (CWE-129), не запрещают использовать эти массивы, но призывают проверять перед созданием их размер.
Не учли, что могут прийти неожиданные данные, выделили охренелион байт и получили переполнение стека. Это вариант DoS-атаки уровня приложения.
На счет alloca вообще есть интересные интересности. Представьте себе, что будет если компилятор попытается встроить код этой функции:
в вызывающий код:
Так как память, выделенная alloca освобождается только после завершения функции, а не выходе из скоупа, то получает взрыв размера стека.
И теперь представьте лицо программиста, который написал этот код с учетом вызова alloca именно во фрейме функции DoSomething.
Манипуляции со стеком - не очень безопасно, поэтому ничего такого и не вводят в плюсы.
Be safe. Stay cool.
#NONSTANDARD #goodoldc
#опытным
В книжке "Вредные советы для С++ программистов" от PVS-студии есть такой вредный совет: "массив на стеке - это лучшее решение"
Типа выделение памяти в куче - это зло. char c[256] хватит всем, а если не хватит, то потом поменяем на 512. В крайнем случае – на 1024.
Да, использование буферов, фиксированного размера действительно может привести в проблемам в коде. Запилили новую фичу, изменили размер данных, а забыли поменять размер массива. Пожалуйста, UB.
Но возникает вопрос: а как тогда можно динамически выделять память на стеке? Ведь стандартные C-style массивы работают только с известными в compile-time размерами.
Пара способов есть, но они с "нюансом":
1️⃣ Variable Length Array. Вы просто берете и создаете массив переменного размера:
void foo(size_t n) {
float array[n];
// ....
}Круто!
Да, но это не часть стандарта С++) Это фича языка С, доступная с С99. Однако GCC например поддерживает ее, как компиляторное расширение языка и вы сможете g++'ом сгенерировать код выше.
2️⃣ alloca. Функция, которая аллоцирует заданное количество байт на стеке:
void *alloca(size_t size);
void foo(size_t n) {
float array = (float)alloca(sizeof(float) * n);
}
Память, выделенная alloca автоматически освобождается при выходе из функции.
Эта также нестандартная функция, которая не является даже частью ANSI-C стандарта, но поддерживается многими компиляторами(в т.ч. является частью Linux API).
Ну и на этом все.
Стандартных решений нет. И на это есть причина. Дело в том, что такой код провоцирует возникновение в программах уязвимостей.
В стандарте MISRA C есть правило MISRA-C-18.8, указывающее не использовать VLA. Другие руководства, такие как SEI CERT C Coding Standard (ARR32-C) и Common Weakness Enumeration (CWE-129), не запрещают использовать эти массивы, но призывают проверять перед созданием их размер.
Не учли, что могут прийти неожиданные данные, выделили охренелион байт и получили переполнение стека. Это вариант DoS-атаки уровня приложения.
На счет alloca вообще есть интересные интересности. Представьте себе, что будет если компилятор попытается встроить код этой функции:
void DoSomething() {
char pStr = alloca(100);
//......
}в вызывающий код:
void Process() {
for (i = 0; i < 1000000; i++) {
DoSomething();
}
}Так как память, выделенная alloca освобождается только после завершения функции, а не выходе из скоупа, то получает взрыв размера стека.
И теперь представьте лицо программиста, который написал этот код с учетом вызова alloca именно во фрейме функции DoSomething.
Манипуляции со стеком - не очень безопасно, поэтому ничего такого и не вводят в плюсы.
Be safe. Stay cool.
#NONSTANDARD #goodoldc
👍22❤11🔥7
Двойной unlock
#опытным
Если не пользоваться RAII, то можно наткнуться на массу проблем. Все знают про double free. Но менее известна проблема double unlock.
Все просто, вы используете ручной lock-unlock мьютекса и возможно попадаете в ситуацию двойного освобождения:
Практически всегда двойной unlock происходит из-за некорректного кода в той или иной степени. Забыть вызвать return кажется детской проблемой, но если вы например не написали тесты на эту ветку, то возможно вы наткнетесь на проблемы только в проде.
А проблемы могут быть примерно любыми. Потому что двойной unlock мьютекса - UB по стандарту. Соответственно, можете получить много непрятностей, от сегфолта до бесконечного ожидания.
Поэтому просто используйте RAII и спина болеть не будет:
Use safe technics. Stay cool.
#concurrency #cpp11
#опытным
Если не пользоваться RAII, то можно наткнуться на массу проблем. Все знают про double free. Но менее известна проблема double unlock.
Все просто, вы используете ручной lock-unlock мьютекса и возможно попадаете в ситуацию двойного освобождения:
void unsafe_function(int value) {
mtx.lock();
if (value < 0) {
std::cout << "Error: negative value\n";
mtx.unlock();
// forget to return!
}
shared_data = value;
std::cout << "Data has updated: " << shared_data << std::endl;
mtx.unlock(); // second unlock
}Практически всегда двойной unlock происходит из-за некорректного кода в той или иной степени. Забыть вызвать return кажется детской проблемой, но если вы например не написали тесты на эту ветку, то возможно вы наткнетесь на проблемы только в проде.
А проблемы могут быть примерно любыми. Потому что двойной unlock мьютекса - UB по стандарту. Соответственно, можете получить много непрятностей, от сегфолта до бесконечного ожидания.
Поэтому просто используйте RAII и спина болеть не будет:
void safe_function(int value) {
std::lock_guard lg{mtx};
if (value < 0) {
std::cout << "Error: negative value\n";
return;
}
shared_data = value;
std::cout << "Data has updated: " << shared_data << std::endl;
}Use safe technics. Stay cool.
#concurrency #cpp11
❤9👍7🔥3