Pull to refresh

Comments 21

В свое время задумывался об этом, но к сожалению уровня знания не хватало даже для возникновения идей реализации. Спасибо за предложенное решение, на досуге попробую в деле.
Вроде как variant для избавления от union в основном был сделан, немного другая задача им решалась. Но честно, с ним не разбирался, он из С++17 идет, а мой компилятор его не поддерживает.
Всё очень здорово. Но я вот только не очень понимаю чем это для микроконтроллеров сильно лучше обычного сишного кода:
// categories.h
enum { error_category_shift=16, error_code_mask=(1<<error_category_shift)-1  };

enum { cpu_category, measure_category, my_category };

// cpu.h #include "categories.h"
enum {
	cpu_ok=cpu_category << error_category_shift,
	cpu_alu,
	cpu_rom,
	cpu_ram
};

// measure.h #include "categories.h"
enum {
	measure_ok=measure_category << error_category_shift,
	measure_outoflimits,
	measure_badcode
};

// my.h #include "categories.h"
enum {
	my_ok=my_category << error_category_shift,
	my_error1,
	my_error2,
	my_error3
};

// errorcodes.h
int any_error(int code);
int get_error_category(int code);
const char* get_category_name(int category);
const char* get_error_text(int code);

int error_code;
...
error_code=some_function();
if (any_error(error_code)) {
	log_error(error_code);
}
...

impl
// in cpu
const char* get_error_text_cpu(int code) {
	switch(code) {
	case cpu_ok:  return "cpu_ok";
	case cpu_alu: return "cpu_alu";
	case cpu_rom: return "cpu_rom";
	case cpu_ram: return "cpu_ram";
	}
	return 0;
}
// in measure
const char* get_error_text_measure(int code) {
	switch(code) {
	case measure_ok:  return "measure_ok";
	case measure_outoflimits: return "measure_outoflimits";
	case measure_badcode: return "measure_badcode";
	}
	return 0;
}
// in my
const char* get_error_text_my(int code) {
	switch(code) {
	case my_ok:  return "my_ok";
	case my_error1: return "my_error1";
	case my_error2: return "my_error2";
	case my_error3: return "my_error3";
	}
	return 0;
}
// in errorcodes
int get_error_category(int code) { return code >> error_category_shift; }
int any_error(int code) { return code & error_code_mask; }
const char* get_category_name(int category) {
	switch(category) {
		case cpu_category: return "cpu_category";	
		case measure_category: return "measure_category";
		case my_category: return "my_category";
	};
	return 0;
}
const char* get_error_text(int code) {
	const char* res;
	res=get_error_text_cpu(code); if (res) return res;
	res=get_error_text_measure(code); if (res) return res;
	res=get_error_text_my(code); if (res) return res;
	return 0;
}

Подобный код элементарно генерировать скриптом что бы не писать руками.

Тут много отличий.
1. Надо писать скрипт
2. Каждый такой метод get_error, даже если он будет возвращать просто номер придется юнит тестить. В вашем примере, вообще ад для юнит теста — каждую ветку switchа, хотя она и сгенерирована автоматически. Но покрытие юнит тестами должно 100% иначе не пройдет сертификацию на безопасность. В примере же на С++ там этого делать не надо, так как методов(GetEnumPosition() аналог) таких нет в коде, они все посчитают на этапе компиляции.
3. Ну и минорное методы занимают место, так как существуют в рантайме. Хотя справедливости ради, надо сказать, что их место на С++ занимает конструктор, который типизирован, но там только инициализация идет, т.е. по сути кода нет. Т.е. я полагаю, что на Си кода получится больше. Надо проверить.
Таки да есть еще отличия:
4. Не требуется современный компилятор C++
5. Функции с текстом можно разложить по отдельным объектникам и сложить в библиотеку, и если они не используются то и не прилинкуются.
6. Скрипт это для ленивых («моется только тот кому лень чесаться»), остальные могут писать руками. (скриптом можно и тесты генерить, причем много и быстро)
7. Проще, короче, нагляднее и собирается быстрее.

«сертификация на безопасность» это обязательное требование для любого кода?
ps: компилятор тоже сертифицированный?
4. Ну С++11 не такой уже и современный. Хотя конечно, согласен, есть еще куча компиляторов, которые его не поддерживают
5. Это если енумы совпадают, а если в новом проекте надо расширить енум, или ввести новый. В одном проекте один объектник, в другом — другой.
6. Согласен, либо скрипт, либо руками писать. Одна из задач звучала, как практически «ничего не делать».
7. На Си проще — согласен, также как и быстрее собирается. На С++ код выглядит страшненько, для нормального программиста встроенного софта.
Плюсы С++ все равно перевешивают :) Кода то меньше в итоге в ПЗУ.
Сертификация, конечно не обязательна для любого кода. Только для устройств используемых в системах на которые распространяется IEC 61508.
Компилятор, да сертифицированный. IAR Certified by TÜV SÜD

Кстати на C++14 можно тоже без рекурсии через constexpr.


template <typename... Ts> class TypeList {
private:
  static constexpr auto no_index = std::numeric_limits<size_t>::max();

  template <typename T> static constexpr size_t getIndexImpl() {
    auto seq = {same<T, Ts>()...};
    size_t i = 0u;
    for (auto s : seq) {
      if (s) {
        return i;
      }
      i++;
    }
    return no_index;
  }
  template <typename U, typename V> static constexpr bool same() {
    return std::is_same<U, V>::value;
  }

public:
  template <typename T> static constexpr size_t getIndex() {
    constexpr bool found = getIndexImpl<T>() != no_index;
    static_assert(found, "Type T not found in TypeList");
    return getIndexImpl<T>();
  }
};

using ClassList = TypeList<C1, C2>;

static_assert(ClassList::getIndex<C1>() == 0, "Test failed");
static_assert(ClassList::getIndex<C2>() == 1, "Test failed");

Код: https://godbolt.org/z/FyR-Lw

Согласен, без рекурсии понятнее. И вообще по идее ей нельзя в наших приложения использовать, в данном случае использовали, только потому, что она выполняется во время компиляции и в код не попадает.

Да, но я не до конца понял как работает constexpr. Вроде не обязательно всегда именно if constexpr писать, потому что это больше для замены enable_if и свинае ада. Но я проверил что действительно в моем примере вызовы заменяются на константу. Но в общем случае это уже на усмотрение компилятора, т.е. если я присваиваю constexpr value что то, то выражение справа вычислится во время компиляции. Но всегда ли constexpr функции во время компиляции считается?

Отвечу сам себе. Да constexpr функция не обязательно вызывается во время компиляции. Её можно вызывать и во время выполнения. В случае если аргументы известны только в ран тайм, это удобно чтобы не писать две версии одной и той же функции.

Нет, далеко не всегда. Чтобы быть уверенным, что твоя функция отработает в compile-time нужно присваивать результат либо constexpr-переменным, либо передавать его в параметры шаблона. Мало того, функции из stl, например std::char_traits<>::length, будут вычислятся в compile-time, а твоя, точно такая же функция — в runtime.

Да, тогда в моем коде лучше получать index, потом сверять его в static_assert с no_index а потом return index. Тогда можно быть уверенным что оно посчитает в compile time.

Еще, это можно проверить оператором noexcept. Если функция не сможет бросить исключение, то это costrexpr функция. Но для микроконтроллера это плохо, там обычно экспешены отключены, поэтому static_assert нормальное решение.

«При разработке ПО для микроконтроллеров на С++ очень часто можно столкнуться с тем, что использование стандартной библиотеки может привести к нежелательным дополнительным расходам ресурсов, как ОЗУ, так и ПЗУ. Поэтому зачастую классы и методы из библиотеки std не совсем подходят для реализации в микроконтроллере.» — поэтому люди некоторое время назад написали библиотеку etl (embedded template library), предназначенную заместить std в embedded systems.
Да, я смотрел её, к сожалению она не подходит нам по причине отсутствия сертификата SIL 3 по, ГОСТ IEC 61508-3-2018. IAR компилятор вместе с поставляемой с ним библиотекой имеет такой сертификат.
Да, этого момента я не учёл. Тогда да, нужно изобретать некоторые собственные аналоги.
получается вы свой код верифицируете по этому стандарту?
Сертифицируем ПО. Там не только верификация, но нужно еще следовать и процессам, например, написано, что должно быть юнит тестирование, выходом является отчет о юнит тестировании с логами, или например, должен быть процесс ревью кода, выход логи ревью, должна быть проверка стическим анализатором кода, на выходе отчет анализатора и описание каждого предупреждения. Должна быть прослеживаемость между требования к безопасности и архитектуры, покажите как это у вас сделано.Кроме того проверяется, что например нет рекурсий, динамически выделяемой памяти, есть диагностики ПЗУ, ОЗУ, АЛУ и так далее. Эксперт это все проверяет, за каждый пункт бал дается, чем больше пунктов выполнено, тем больше балов. Есть обязательные пункты…
Есть вариант проще, называется проверено временем… Как например РТОС сертифицируется, основывается на расчете отказов у большого количества пользователей за определенное время, скажем 3-5 лет. Но тоже надо чтобы на предприятии была система отслеживания отказов.
Хорошая идея. Вместо EnumTypeRegister можно использовать std::tuple, вместо GetEnumPosition что-то типа
template <typename Tuple, typename T, template <typename, typename> typename Pred = std::is_same, size_t I = 0>
constexpr size_t tuple_index() noexcept
{
	if constexpr(I >= std::tuple_size_v<Tuple>)
		return I;
	else if constexpr(Pred<T, std::tuple_element_t<I, Tuple>>::value)
		return I;
	else 
		return tuple_index<Tuple, T, Pred, I + 1>();
}
Sign up to leave a comment.

Articles