Pull to refresh

Comments 59

Занудства ради: Whant… -> Want. А то глаз спотыкается.
Ctrl+Enter не пробовали? Чтобы не засорять комменты. ;)

Одна оговорка — стандарт не гарантирует размещения переменной в ROM. Есть гарантия что произойдет константная инициализация если статическая переменная (variable with static storage duration) инициализируется константным выражением (в C++20 можно проверить с помощью std::is_constant_evaluated()), но разницы в инициализации между static const Test test и static Test test нет с точки зрения стандарта. Усложняет ситуацию дефект языка CWG 2026, который был устранен лишь недавно, и баги в компиляторах (MSVC до сих пор не производит константную инициализацию constexpr конструктором).

Спасибо за ссылки и инфу…

Да, но дело в том, что возможность размещения в ROM статической переменной мною и не рассматривалась :)
Я хотел показать только размещение в ROM статической константы. А она размещается туда только по настройкам линкера. Т.е. компилятор её определяет в сегмент констант c атрибутом readonly, а этот сегмент констант в настройках линкера, я могу задать в ROM памяти.
И собственно важно, чтобы инициализация у такой константы была константная, что как вы и сказали, гарантируется стандартом, иначе она не попадет в сегмент констант с атрибутом readonly.
Статическая же переменная будет в другом сегменте (.data, например), который скорее всего в настройках линкера будет лежать в RAM.

Немного занудства. В C++ констант нет, есть литералы, которые в C действительно называются константами. Спецификатор const это всего лишь контракт, за нарушение которого полагается undefined behavior. Компилятор, пользуясь этим неопределенным поведением, кладет эту переменную в ROM, но может и не положить, в том числе потому что есть легальные способы изменить const-qualified объект, например в деструкторе.

С первой частью согласен полностью, а как изменить const-qualified обьект легальным способом который изначально был обьявлен и является const? Разве это не будет автоматом UB?

Mutable это тоже контракт, насколько я понимаю, увидев mutable компилятор, конечно же не положит обьект в сегмент констант, по факту он уже физически не константа. А про конструктор и деструктор понял… Это получается изменение его модификатора до начала его времени жизни во время конструирования или уничтожения, когда правило UB и const сематики еще не работает. Но ведь constexpr конструктор такого не позволит…
Еще раз спасибо, добавлю про mutable.
Если бы эти данные лежали в RAM, определить их целостность было бы проблематично или даже невозможно из-за того, неясно где в ОЗУ лежат неизменяемые данные, а где изменяемые, линкер размещает их как хочет, а защищать каждый объект ОЗУ контрольной суммой выглядит как паранойя.

Только в том случае, если вам дела нет, до того где и как они лежат.

Даже без инструкций линкеру вы можете сложить все эти данные в одну структуру (или даже класс, раз уж вы пишете отказоустойчивый код для микроконтроллеров на плюсах) и все они будут лежать рядышком.

И даже если такие константы у вас раскиданы по куче исходников, и лепить их в одну структуру неудобно, вы можете объявить свою секцию (в дополнение к .data .bss и т.п.), и уже потом рассказать линкеру куда вы хотите ее положить: хоть в RAM, хоть в ROM, хоть вообще абсолютный адрес указать.

+1
Хотел написать ровно тот же коммент. Структуры и отдельные секции памяти — техники стары как мир. А размещение данных в ром я всегда рассматривал исключительно как инструмент сокращения потребления памяти. Обычно это относится к строкам и крупным статическим конфигурационным структурам, типа системного меню или дескрипторов dma. Так в одном проекте цепочка дескрипторов для обработки данных с ADC занимала почти десяток килобайт, дублировать всё это в Ram ваще не комильфо.

Да, согласен, единственное, что код в таком случае становится не очень переносимым, из-за использования специфических прагм. На IAR можно например сделать так:
int p = 10;
#define __safety _Pragma("location=\".myconstdata\"") 
__safety const int con = p; //если сегмент .myconstdata в RAM, то все Ок, переменная будет лежать в сегменте myconstdata, а если в ROM, то вас обманули, con лежит в RAM

И еще один момент, в случае, если инициализация не статическая, компилятор не положит вашу переменную в сегмент относящийся к ROM.
И даже если вы сделалете, специальный сегмент для хранения критических данных в ОЗУ, то надо иметь ввиду, что эти данные все таки иногда, очень редко изменяются. Например, те же коэффициенты калибровки, пользователь сделал калибровку на поверке, и они впервые за 5 лет поменялись, в этот момент надо будет пересчитать всю контрольную сумму всего сегмента. В принципе, вариант рабочий, но есть и другие способы, не задействующие спец. фичи типа #pragma location и @. и опять же от того, что ваша переменная попадет не туда, он не всегда защищает! Поэтому, мое мнение, что нужно просто правильно писать код :) используя сам язык.

Пытался я как-то писать "безопасный" код для МК.
Следил за стеком, контролировал адреса входа и выхода из функций, проверял содержимое flash/eeprom контрольной суммой и мажорированием 2 из 3, сбрасывал вотчдог только при прохождении критических функций.
В результате код превраитился в кучу вызовов некрасивых макросов и стал ужасно труден в отладке.


Я пришел к выводу, что всякие надежные приемы, контроль целостности кода и данных должны поддерживаться на уровне компилятора или языка

А у вас есть примеры такого компилятора или языка, которые это поддерживают?
Может, и нет. Не искал.
GreenHills упирает на то, что их компилятор имеет какие-то сертификаты и делает рантайм проверки, но я подробно его не изучал.
Спасибо за статью. Очень познавательно.
Так же хотелось узнать, есть ли способ положить в ROM строку перед которой установлена её длинна? Что то типа const char* my_str = {sizeof(«Text»), «Text»}; // my_str = {5,'T','e','x','t','\0'};
Ну там примеры почти про это :) Так как конструктор вы не объявили, он по умолчанию тут constexpr. Используя статическую агрегатную инициализацию. Получаете константу в ROM.
struct TestStruct
{
int len;
char string[];
} ;

const TestStruct some_struct = {5,"Text"};
Я как-то на С++ почти не пишу. Не знал, что у структур тоже могут быть конструкторы.
в С++ структура это тоже класс, только все поля публичные
только предупреждение будет :)
в последнем примере кода с инициализацей массива ошибка, в цикле надо использовать ссылку (auto& it)

constexpr void InitArray()
{
	int i = 0;
	for (auto& it : k)
	{
		it = i++;
	}
}
Не очень понятно про Undefined Behaviour
Допустим, я хочу сделать класс, который хранит калибровочный коэффициент во флеше. Изначально он хранит некоторое дефолтное значение. Но иногда требуется это значение изменять.

class FlashStorage {
public:
	constexpr FlashStorage(int init):_data(init){};
	const int& data() const {return _data;};
	
	void SetValue(int val) const {
		// магия с флеш контроллером и записью страниц
		//_data = val;
	}
	
private:
	int _data;
};

const FlashStorage koeff0(77); // ложится куда-то в о флеш

int main() {
  cout << koeff0.data();
  koeff0.SetValue(88);
  cout << koeff0.data();
}


Так вот SetValue(88) неявно меняет поле _data в константном объекте koeff0. По-видимому, это должно выливаться в UB.
Как сделать без UB?

Снимать с koeff0 const и класть его прагмой во флеш?

А для чего вы возвращаете ссылку на константный int, есть глубинный смысл?
Там в перспективе вместо int будет template T
Ну вот когда будет, тогда и задумывайтесь;) Да и вряд ли константа не будет trivaly relocatable…
Например, там будет какой-нибудь
strutct koeffs{
int k1,
int k2,
int k3
}

Что за trivaly relocatable?

Либо статик еще можно и сразу её положить во flash


#include <iostream>

class FlashStorage {
public:
    constexpr FlashStorage(){};
    const int& data() const {return _data;};

    void SetValue(int val) const {
        // магия с флеш контроллером и записью страниц
        _data = val;
    }

private:
    #pragma location = ".flashdata" //сегмент по которому вы хотите расположить данные
    inline static int _data = 77;
};

const FlashStorage koeff0; // ложится куда-то в о флеш

int main() {
  std::cout << koeff0.data();
  koeff0.SetValue(88);
  std::cout << koeff0.data();
}

Сделайте ссылку на _data


class FlashStorage {
public:
    constexpr FlashStorage(int& init):_data(init){};
    const int& data() const {return _data;};

    void SetValue(int val) const {
        // магия с флеш контроллером и записью страниц
        _data = val;
    }

private:
    int& _data;
};

int varInflash = 77;
const FlashStorage koeff0(varInflash ); // ложится куда-то в о флеш

int main() {
  cout << koeff0.data();
  koeff0.SetValue(88);
  cout << koeff0.data();
}
Тогда во флеш будет лежать ссылка, а само varInflash будет лежать в RAM.
После перезагрузки МК FlashStorage все равно будет содержать 77 а не 88

Так можно. Но тогда с прагмой и правкой линкер-файла
#pragma location = ".flashdata"
int varInflash = 77;
const FlashStorage koeff0(varInflash ); // ложится куда-то в о флеш

Тогда так, но это хак


#include <iostream>

class FlashStorage {
public:
    constexpr FlashStorage(int& init):_data(init){};
    const int& data() const {return _data;};

    void SetValue(int val) const {
        // магия с флеш контроллером и записью страниц
        _data = val;
    }

private:
    int& _data;
};

constexpr int varInflash = 77;
const FlashStorage koeff0(*(const_cast<int*>(&varInflash))); // ложится куда-то в о флеш

int main() {
  std::cout << koeff0.data();
  koeff0.SetValue(88);
  std::cout << koeff0.data();
}

Тогда лучше этот хак скрыть от пользователя… в классе:


#include <iostream>

class FlashStorage {
public:
    constexpr FlashStorage(const int& init):_data(init){};
    const int& data() const {return _data;};

    void SetValue(int val) const {
        // магия с флеш контроллером и записью страниц
        *const_cast<int*>(&_data) = val;
    }

private:
    const int& _data;
};

constexpr int varInflash = 77;
const FlashStorage koeff0(varInflash); // ложится куда-то в о флеш

int main() {
  std::cout << koeff0.data();
  koeff0.SetValue(88);
  std::cout << koeff0.data();
}

В данном случае да, но можно что сделать тогда:


```cpp
#include <iostream>

class FlashStorage {
public:
    constexpr FlashStorage(int init):_data(init){};
    const int& data() const {return _data;};

    void SetValue(int val) const {
        // магия с флеш контроллером и записью страниц
        _data = val;
    }

private:
    mutable int _data;
};

//#pragma location = ".flashdata" //сегмент во флеш.
//int varInflash = 77;
const FlashStorage koeff0(77 ); // ложится куда-то в о флеш

int main() {
  std::cout << koeff0.data();
  koeff0.SetValue(88);
  std::cout << koeff0.data();
}

Кстати, а есть ли какой-то способ проверить, что .flashdata существует?
Если эту секцию не определить, что линкер с компилятором молча все прожуют и ни одной ошибки или предупреждения не выдадут.
Как только в классе появляется mutable член
private:
mutable int _data;
то
const FlashStorage koeff0(77 );
ложится в RAM
Память глобальных переменных по сегментам раскидывает компоновщик, указать ему явно? Не захочет создавать в области видимости файл — в мейн создать в фиксированной памяти? Или у Вас таких констант видимо-невидимо и одной static/unnamed namespace переменной и «окружающей» ее классом никак не обойтись?

Значит чудес не бывает… :) Наверное самое правильное указывать конкретный сегмент. В таком случае можно обмануть компилятор, UB не будет, переменная для компилятора изменяемая, просто он её должен положить в другой сегмент. А линкеру указать, чтобы этот сегмент по адресам во флеше разместил.
Как проверить, что сегмента нет — не знаю...

Похоже, что только так
Вот нашел кое-что
В IAR можно сделать так:
The __ro_placement attribute specifies that a data object should be placed in
read-only memory. There are two cases where you might want to use this object
attribute:
● Data objects declared const volatile are by default placed in read-write
memory. Use the __ro_placement object attribute to place the data object in
read-only memory instead.
● In C++, a data object declared const and that needs dynamic initialization is placed
in read-write memory and initialized at system startup. If you use the
__ro_placement object attribute, the compiler will give an error message if the
data object needs dynamic initialization.
You can only use the __ro_placement object attribute on const objects.
You can use the __ro_placement attribute with C++ objects if the compiler can
optimize the C++ dynamic initialization of the data objects into static initialization. This
is possible only for relatively simple constructors that have been defined in the header
files of the relevant class definitions, so that they are visible to the compiler. If the
compiler cannot find the constructor, or if the constructor is too complex, an error
message will be issued (Error[Go023]) and the compilation will fail.
Example __ro_placement const volatile int x = 10;


только члены класса нельзя объявит с таким аттрибутом

Это __ro_placement практически равносилен constexpr, по-крайней мере, все где не компилятор не ругается на невозможность constexpr ложится в __ro_placement, а где ругается, там не ложится…
Можно еще конечно явно адрес в ПЗУ указать переменной varInflash… через собаку.

Ну и сделайте CRTP, это имхо лучший способ создать сколько угодно классов со статической переменной и парой статических методов. Вот и разместите где-то во флеше шаблонный параметр и будете его читать/писать в свое удовольствие. Вроде такого:
template <typename T> base{
static T data_ __attribute__((section("flash")));
static T get() const;
static void put(T const &t;);
};

struct Coeff : public base<Coeff> {};


Ну, в cpp-файле может и придется что-то еще добавить.

Я работу с записью во флеш тоже сделал через статический класс, ну т.е. все методы и атрибуты статические у него.

Вот нашел:

A program may not modify its own object defined with a const-qualified type, per C 2011 (N1570) 6.7.3 6: “If an attempt is made to modify an object defined with a const-qualified type through use of an lvalue with non-const-qualified type, the behavior is undefined.” An external agent may modify an object that has volatile-qualified type, per 6.7.3 7: “An object that has volatile-qualified type may be modified in ways unknown to the implementation or have other unknown side effects


Т.е. если мы неявно меняем const, то это UB.
А если меняем const volatile, то уже не UB

volatile на константе позволяет нам эту константу менять
class FlashStorage {
public:
	constexpr FlashStorage(int init) :_data(init)  {};
	const volatile int& data() const volatile {return _data;};
	
	void SetValue(int val) const volatile{
		// магия с флеш контроллером и записью страниц флеш
		//int* ptr = (int*)&_data;
		//*ptr = val;
		//_data=val;
	}
	
private:
	 const volatile int _data;
};

const FlashStorage koeff0(77);

int main() {

	koeff0.SetValue(88);
  	koeff0.data();
}


При этом FlashStorage попадает в ROM.
А объявление constexpr FlashStorage koeff0(77); не хочет компилироваться.

А где мне нужно снимать const у const volatile?
А объявление constexpr FlashStorage koeff0(77); не хочет компилироваться.

Да все верно, так как volatile в constexpr запрещены


А где мне нужно снимать const у const volatile?

вот тут у вас, вы приводите указатель const volatile int к указателю int, там как раз и выполнился const_cast...


//int ptr = (int)&_data; /
Это не важно. Пусть привидение снимает const.
Насколько я понял, const volatile переменную я могу менять любым способом, которым захочу.
Возьму и запишу магическое число в контроллер флеш-памяти, а он мне по адресу &_data что-то положит.
Возьму, ассембелрную вставку сделаю или вызову какую-нибудь функцию из загрузчика, которая заменит набор констант. Это не важно, потому что компилятор теперь знает, что const volatile переменная может измениться по независящим от программы причинам.

Это как мы хотели провернуть трюк с mutable.
ЗЫ: я бы в Вашем случае не слишком опасался UB. В тот момент, как вы используете первый платформо-зависимый символ или расширение компилятора — UB исчезнет и станет well defined platform specific/implementation defined поведением. Прям как бордюр в поребрик…
а если мы mutable константу изменяем, тоже получаем UB? Чем отличается? Тем, что у нас десяток строк вместо одной + компиляторно-зависимого помещения в конкретный сегмент?
Я могу ошибаться, но применение флеш-магии ничем, кроме ограничений (время выполнение, потребляемая память, запрет прерываний и т.д.) от mutable константы-члена не отличается.

Кстати да...


#include <iostream>

class FlashStorage {
public:
    constexpr FlashStorage(int init):_data(init){};
    const int& data() const {return _data;};

    void SetValue(int val) const {
        // магия с флеш контроллером и записью страниц
        _data = val;
    }

private:
    mutable int _data;
};

//#pragma location = ".flashdata" //сегмент во флеш.
//int varInflash = 77;
const FlashStorage koeff0(77 ); // ложится куда-то в о флеш

int main() {
  std::cout << koeff0.data();
  koeff0.SetValue(88);
  std::cout << koeff0.data();
}
Sign up to leave a comment.

Articles