В принципе и просто работы с выравниванием хватает, но это (как и Ваше, тоже) решение, которое оставляет простор для граблей. А мы ведь хотим исключить грабли как класс.
Господь с вами. Хотел бы , чтобы он был int. А кто место экономит - пущай переопределяет или свой тип создает
Скажите спасибо, что не unsigned int. Язык это вполне допускает:
Дык так оно и есть везде на нормальных процессорах. Это только на микроконтроллерах такое. Я ж говорю - первый раз в жизни я столкнулся с тем, что bool - не int.
Вот так работает в продакшене, не на макете и не на столе… без всяких упаковок.
Спойлер
class SysState
{
public:
int mode;
int uptime;
bool isDamp; //режим успокоения
int errorCode;
int progN;
float tSet;
int timeSet;
int delaySet;
float t;
float tSystem;
float tCorrection;
bool waterOK;
bool isASset;
bool isAfterAS;
int timeNow;
int timeDStart;
int timeCookStart;
int timeCookEnd;
int timeCookLeft;
int timeLWstart;
int timeLW;
bool isLowT;
bool startWith1;
bool isWiFi;
bool isTimer;
bool isHeater;
bool isPasterizationStarted;
bool isBath;
SysState() : mode(0),
uptime(0),
isDamp(true),
errorCode(0),
progN(1),
tSet(25),
timeSet(3600),
delaySet(0),
t(25),
tSystem(25),
tCorrection(0),
waterOK(true),
isASset(true),
isAfterAS(false),
timeNow(0),
timeDStart(0),
timeCookStart(0),
timeCookEnd(0),
timeCookLeft(0),
timeLWstart(0),
isLowT(false),
startWith1(true),
isWiFi(false),
isTimer(false),
isHeater(false),
isPasterizationStarted(false),
isBath(false)
{
}
void loadState(void);
void saveState(void);
};
void loadProg(void);
void saveProg(void);
extern SysState s;
//==================================
// В другом файле:
SysState s;
Preferences pref;
SemaphoreHandle_t xMutexState;
void SysState::loadState(void) {
//xSemaphoreTake( xMutexState, portMAX_DELAY );
pref.getBytes("sysState", (void *)(this), sizeof(SysState));
//xSemaphoreGive( xMutexState);
}
void SysState::saveState(void) {
xSemaphoreTake( xMutexState, portMAX_DELAY );
pref.putBytes("sysState", (const void *)(this), sizeof(SysState));
Serial.println("State saved");
xSemaphoreGive( xMutexState);
}
Семафоры там не работают реально, это грязный кусок, но он именно из продакшена како-то-там версии.
Это ЕСП32 и никаких проблем не возникает. При изменении полей с структуре состояния системы, КОНЕЧНО нужно перезалить с форматированием и очисткой всего ЕЕПРОМ.
В общем, мужики, идея в том, чтобы писать не структуру целиком, а отдельные поля хороша по многим причинам. Но решение, которое предложил @Мишутк оставляет просто для всё тех же граблей. Что будет, если полем структуры является другая структура? Да, ничего, нормально запишется, только если в ней возникнут грабли с разным выравниванием, то мы прямо на них и наступим!
Можно, конечно, потребовать от программиста, чтобы он не использовал этот приём для полей, не являющихся тривиальными типами. Но я очень не люблю, когда что-то остаётся зависеть от внимательности и аккуратности программиста. Это и есть грабли! Нет, я вполне допускаю, что где-то существуют внимательные программисты, которые всегда делают всё как положено, но мне таковых встречать не доводилось.
Решением здесь может служить такой приём: пишем программу записи / чтения так, чтобы она нормально записывала / читала тривиальные типы, но вызывала ошибку компиляции при попытке читать / писать что-то иное (например, структуру). Тогда, даже если программист и попытается наступить на грабли, компилятор сам стукнет его по лбу и заставит переписать потенциально опасный участок.
Как сделать, что с какими-то типами программа работала, а со всеми остальными вызывал ошибку? Один из способов – определить шаблон, который можно специализировать для всех допустимых типов, и сделать так, чтобы попытка использовать общий (неспециализированный) вариант вызывал ошибку компиляции.
Ну, и запись / чтение предпочтительнее делать методами самой структуры. Это очень ООП’но, когда тип сам знает как ему записываться, читаться и делать иные операции. Тогда, в случае чего, всё, связанное с типом меняется в одном месте (в нём самом) и не ищется по всей программе.
Вот пример, как это можно сделать. Шаблон у меня определён в отдельном файле, там много однотипных строк для разных типов.
Файл 'eepromablestruct.h'
#ifndef eepromablestruct_h
#define eepromablestruct_h
#include <avr/eeprom.h>
//
// Шаблоны для чтения и записи в/из EEPROM различных базовых типов
//
// Попытка чтения / записи типа, для которого явно не определена такая операция
// приврдит к ошибке времени компиляции с диагностикой:
// "Попытка использования неизвестного типа для записи/чтения EEPROM"
//
// Все функции принимают адрес и значение и возвращают количество
// записанных / прочитанных байтов.
//
//////////////////////////
//
// ШАБЛОН ЗАПИСИ
//
// Все поддерживаемые типы будут специализированы, а попытка использовать базовый шаблон
// является признаком попытки записи неизвестного типа и должна приводить к ошибке компиляции
//
template<typename T> size_t eeprom_update(const size_t addr, const T f) {
((void)(addr));
((void)(f));
static_assert(sizeof(f)!=sizeof(f), "Попытка использования неизвестного типа для записи в EEPROM");
return 0;
}
//
// Специализируем самые ходовые типы (всегда можно расширить перечень типов)
//
template<> size_t eeprom_update(const size_t addr, const float f) {
eeprom_busy_wait();
eeprom_update_float(reinterpret_cast<float *>(addr), f);
return sizeof(float);
}
template<> size_t eeprom_update(const size_t addr, const uint8_t u8) {
eeprom_busy_wait();
eeprom_update_byte(reinterpret_cast<uint8_t *>(addr), u8);
return sizeof(uint8_t);
}
template<> size_t eeprom_update(const size_t addr, const uint16_t w) {
eeprom_busy_wait();
eeprom_update_word(reinterpret_cast<uint16_t *>(addr), w);
return sizeof(uint16_t);
}
template<> size_t eeprom_update(const size_t addr, const uint32_t dw) {
eeprom_busy_wait();
eeprom_update_dword(reinterpret_cast<uint32_t *>(addr), dw);
return sizeof(uint32_t);
}
//
// Производные типы
//
template<> size_t eeprom_update(const size_t addr, const bool b) {
return eeprom_update(addr, static_cast<const uint8_t>(b));
}
template<> size_t eeprom_update(const size_t addr, const int16_t w) {
return eeprom_update(addr, static_cast<const uint16_t>(w));
}
template<> size_t eeprom_update(const size_t addr, const int32_t dw) {
return eeprom_update(addr, static_cast<const uint32_t>(dw));
}
//////////////////////////
//
// ШАБЛОН ЧТЕНИЯ
//
// Все поддерживаемые типы будут специализированы, а попытка использовать базовый шаблон
// является признаком попытки чтения неизвестного типа и должна приводить к ошибке компиляции
//
template<typename T> size_t eeprom_read(const size_t addr, T & f) {
((void)(addr));
((void)(f));
static_assert(sizeof(f)!=sizeof(f), "Попытка использования неизвестного типа для чтения из EEPROM");
return 0;
}
//
// Специализируем самые ходовые типы (всегда можно расширить перечень типов)
//
template<> size_t eeprom_read(const size_t addr, float & f) {
eeprom_busy_wait();
f = eeprom_read_float(reinterpret_cast<float *>(addr));
return sizeof(float);
}
template<> size_t eeprom_read(const size_t addr, uint8_t & u8) {
eeprom_busy_wait();
u8 = eeprom_read_byte(reinterpret_cast<uint8_t *>(addr));
return sizeof(uint8_t);
}
template<> size_t eeprom_read(const size_t addr, uint16_t & w) {
eeprom_busy_wait();
w = eeprom_read_word(reinterpret_cast<uint16_t *>(addr));
return sizeof(uint16_t);
}
template<> size_t eeprom_read(const size_t addr, uint32_t & dw) {
eeprom_busy_wait();
dw = eeprom_read_dword(reinterpret_cast<uint32_t *>(addr));
return sizeof(uint32_t);
}
//
// Производные типы
//
template<> size_t eeprom_read(const size_t addr, bool & b) {
eeprom_busy_wait();
b = eeprom_read_byte(reinterpret_cast<uint8_t *>(addr));
return sizeof(uint8_t);
}
template<> size_t eeprom_read(const size_t addr, int16_t & w) {
eeprom_busy_wait();
w = eeprom_read_word(reinterpret_cast<uint16_t *>(addr));
return sizeof(uint16_t);
}
template<> size_t eeprom_read(const size_t addr, int32_t & dw) {
eeprom_busy_wait();
dw = eeprom_read_word(reinterpret_cast<uint16_t *>(addr));
return sizeof(uint16_t);
}
#endif // eepromablestruct_h
Ну, а основной файл, который это вызывает вот такой:
Файл 'structsample.ino'
#include "eepromablestruct.h"
struct Point : public Printable {
uint8_t id;
uint32_t signature;
float x;
float y;
Point(void) : id(0), signature(0), x(0), y(0) {}
Point(const uint8_t _id, const uint32_t _signature, const float _x, const float _y) : id(_id), signature(_signature), x(_x), y(_y) {}
size_t printTo(Print& p) const {
size_t res = p.print("Id: ");
res += p.println(id);
res += p.print("Signature: ");
res += p.println(signature);
res += p.print("X: ");
res += p.println(x, 6);
res += p.print("Y: ");
res += p.println(y, 6);
return res;
}
size_t toEeprom(const size_t addr) const {
size_t totalWritten = 0;
totalWritten += eeprom_update(addr + totalWritten, signature);
totalWritten += eeprom_update(addr + totalWritten, id);
totalWritten += eeprom_update(addr + totalWritten, x);
totalWritten += eeprom_update(addr + totalWritten, y);
return totalWritten;
}
size_t fromEeprom(const size_t addr) {
size_t totalRead = 0;
totalRead += eeprom_read(addr + totalRead, signature);
totalRead += eeprom_read(addr + totalRead, id);
totalRead += eeprom_read(addr + totalRead, x);
totalRead += eeprom_read(addr + totalRead, y);
return totalRead;
}
};
void setup() {
Serial.begin(9600);
Point point0(3, 321, 3.141592, 2.718282);
point0.toEeprom(100);
Serial.println("Structure point0");
Serial.println(point0);
Point point1;
Serial.println("Structure point1 (before reading)");
Serial.println(point1);
point1.fromEeprom(100);
Serial.println("Structure point1 (after reading)");
Serial.println(point1);
}
void loop(void) {}
и, да, я совершенно согласен с теми, кто говорит, что такие структуры (как у меня здесь) создавать не нужно. Надо идти от “больших” типов к маленьким, тогда и проблем меньше будет.
У меня в большом проекте на работе есть хитрый контейнер, к которому можно обращаться как к базе данных. Все клиенты знают только типы полей (и то не всех). А для чтения и записи обращаются произвольными типами структур, которые содержат любое подмножество полей. Таким образом при добавлении клиентов, которым нужно еще новых полей, старым клиентам не требуется перекомпиляция (это отдельные модули). И вот там как раз применен похожий подход, где регистрируются смещения и размеры каждого поля в разных типах структур. По факту я имею возможность сделать копирование между двумя структурами разных типов, будут скопированы только пересекающиеся поля. И сам контейнер распознает разметку структур по размещению полей.
Это очень удобно, когда сотня уже скомпилированных модулей работает с одним контейнером и никак не зависит от формата хранимой структуры, которая в процессе разработки постоянно видоизменяется. Для маленьких проектов это не так актуально, но прием со смещением полей я таскаю за собой повсюду.