Комментарии 59
Одна оговорка — стандарт не гарантирует размещения переменной в ROM. Есть гарантия что произойдет константная инициализация если статическая переменная (variable with static storage duration) инициализируется константным выражением (в C++20 можно проверить с помощью std::is_constant_evaluated()
), но разницы в инициализации между static const Test test
и static Test test
нет с точки зрения стандарта. Усложняет ситуацию дефект языка CWG 2026, который был устранен лишь недавно, и баги в компиляторах (MSVC до сих пор не производит константную инициализацию constexpr конструктором).
Спасибо за ссылки и инфу…
Я хотел показать только размещение в ROM статической константы. А она размещается туда только по настройкам линкера. Т.е. компилятор её определяет в сегмент констант c атрибутом readonly, а этот сегмент констант в настройках линкера, я могу задать в ROM памяти.
И собственно важно, чтобы инициализация у такой константы была константная, что как вы и сказали, гарантируется стандартом, иначе она не попадет в сегмент констант с атрибутом readonly.
Статическая же переменная будет в другом сегменте (.data, например), который скорее всего в настройках линкера будет лежать в RAM.
Немного занудства. В C++ констант нет, есть литералы, которые в C действительно называются константами. Спецификатор const
это всего лишь контракт, за нарушение которого полагается undefined behavior. Компилятор, пользуясь этим неопределенным поведением, кладет эту переменную в ROM, но может и не положить, в том числе потому что есть легальные способы изменить const-qualified объект, например в деструкторе.
С первой частью согласен полностью, а как изменить const-qualified обьект легальным способом который изначально был обьявлен и является const? Разве это не будет автоматом UB?
Конструктором, деструктором, а так же любым способом его mutable
поля.
Еще раз спасибо, добавлю про mutable.
Если бы эти данные лежали в RAM, определить их целостность было бы проблематично или даже невозможно из-за того, неясно где в ОЗУ лежат неизменяемые данные, а где изменяемые, линкер размещает их как хочет, а защищать каждый объект ОЗУ контрольной суммой выглядит как паранойя.
Только в том случае, если вам дела нет, до того где и как они лежат.
Даже без инструкций линкеру вы можете сложить все эти данные в одну структуру (или даже класс, раз уж вы пишете отказоустойчивый код для микроконтроллеров на плюсах) и все они будут лежать рядышком.
И даже если такие константы у вас раскиданы по куче исходников, и лепить их в одну структуру неудобно, вы можете объявить свою секцию (в дополнение к .data .bss и т.п.), и уже потом рассказать линкеру куда вы хотите ее положить: хоть в RAM, хоть в ROM, хоть вообще абсолютный адрес указать.
+1
Хотел написать ровно тот же коммент. Структуры и отдельные секции памяти — техники стары как мир. А размещение данных в ром я всегда рассматривал исключительно как инструмент сокращения потребления памяти. Обычно это относится к строкам и крупным статическим конфигурационным структурам, типа системного меню или дескрипторов dma. Так в одном проекте цепочка дескрипторов для обработки данных с ADC занимала почти десяток килобайт, дублировать всё это в Ram ваще не комильфо.
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, сбрасывал вотчдог только при прохождении критических функций.
В результате код превраитился в кучу вызовов некрасивых макросов и стал ужасно труден в отладке.
Я пришел к выводу, что всякие надежные приемы, контроль целостности кода и данных должны поддерживаться на уровне компилятора или языка
Так же хотелось узнать, есть ли способ положить в ROM строку перед которой установлена её длинна? Что то типа const char* my_str = {sizeof(«Text»), «Text»}; // my_str = {5,'T','e','x','t','\0'};
int len;
char* string;
} some_struct = {sizeof(«Text»),«Text»};
IAR кладет во flash
struct TestStruct
{
int len;
char string[];
} ;
const TestStruct some_struct = {5,"Text"};
constexpr void InitArray()
{
int i = 0;
for (auto& it : k)
{
it = i++;
}
}
Допустим, я хочу сделать класс, который хранит калибровочный коэффициент во флеше. Изначально он хранит некоторое дефолтное значение. Но иногда требуется это значение изменять.
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 и класть его прагмой во флеш?
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();
}
После перезагрузки МК 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();
}
Если эту секцию не определить, что линкер с компилятором молча все прожуют и ни одной ошибки или предупреждения не выдадут.
private:
mutable int _data;
то
const FlashStorage koeff0(77 );
ложится в RAM
Значит чудес не бывает… :) Наверное самое правильное указывать конкретный сегмент. В таком случае можно обмануть компилятор, 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… через собаку.
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 на константе позволяет нам эту константу менять
А как вы снимите только const у const volatile? const_cast все снимет, а это UB, volatile тоже нельзя снимать.
https://www.viva64.com/en/w/v2567/
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 volatile переменную я могу менять любым способом, которым захочу.
Возьму и запишу магическое число в контроллер флеш-памяти, а он мне по адресу &_data что-то положит.
Возьму, ассембелрную вставку сделаю или вызову какую-нибудь функцию из загрузчика, которая заменит набор констант. Это не важно, потому что компилятор теперь знает, что const volatile переменная может измениться по независящим от программы причинам.
Это как мы хотели провернуть трюк с mutable.
Я могу ошибаться, но применение флеш-магии ничем, кроме ограничений (время выполнение, потребляемая память, запрет прерываний и т.д.) от 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();
}
Где хранятся ваши константы на микроконтроллере CortexM (на примере С++ IAR компилятора)