PROGMEM tricks

sadman41
Offline
Зарегистрирован: 19.10.2016

Думаю, что ни для кого не является секретом, что любой отвязный ардуинщик рано или поздно пытается засунуть в самый дешёвый микроконтроллер если не веб-сервер, то хотя бы "умный дом". Ну, а какой умный дом обходится без вложенных в него автором красивых фраз, названий праздников и матросских ругательств? Но полёт мысли и приложенное к нему творчество быстро пресекается безжалостным фактом - оперативная память микроконтроллера не бесконечна. Кто-то этот намёк понимает и останавливается, кто-то приделывает к Ардуине внешнее хранилище, но многие начинают свой тернистый путь с простого ключевого слова PROGMEM, активирующее механизмы, которые на поверку оказываются не таким уж и простым для понимания.

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

sadman41
Offline
Зарегистрирован: 19.10.2016

Для начала приведу небольшое исследование "типовых приёмов", скопированное отсюда

 

№1 (классический)
 
Ключевая особенность: в PGM структуре хранятся ссылки на PGM строки, т.е. в RAM ничего не подгружается
 
Плюсы: строки одного поля структуры могут быть разной длины, и это не дает оверхеда, каков мог быть в №3. Так же можно одну строку (указатель на нее) использовать в разных полях структуры и повторять в одних и тех же совершенно безнаказанно (без какого-либо пенальти).
Минусы: много писанины.
 
Sketch uses 1,138 bytes (3%) of program storage space. Maximum is 32,256 bytes.
Global variables use 21 bytes (1%) of dynamic memory, leaving 2,027 bytes for local variables. Maximum is 2,048 bytes.
 
#pragma GCC optimize ("O0")
struct listUsr_t
{
  const char* phoneNum;
  const char* phonePass;
  const char* phonePDU;
};

const char number_70000000000[] PROGMEM = "70000000000";
const char number_70000000001[] PROGMEM = "70000000002";
const char number_70000000002[] PROGMEM = "70000000003";

const char pass_70000000000[] PROGMEM = "00000";
const char pass_70000000001[] PROGMEM = "00001";
const char pass_70000000002[] PROGMEM = "00002";

const char pdu_70000000000[] PROGMEM = "000000000000";
const char pdu_70000000001[] PROGMEM = "000000000000";
const char pdu_70000000002[] PROGMEM = "000000000000";

const listUsr_t PROGMEM users[] =
{
  { number_70000000000, pass_70000000000, pdu_70000000000},
  { number_70000000001, pass_70000000001, pdu_70000000001},
  { number_70000000002, pass_70000000002, pdu_70000000002},
};

void setup() {
  char incomingNumber[] = "70000000002";
  pinMode(13, OUTPUT);
  digitalWrite(13, LOW);

  for (int i = 0; i < 3; i++) {
    // if (0x00 == strncmp_P(incomingNumber, (const char*) pgm_read_word(&(users[i].phoneNum)), 12))
    if (0x00 == strncmp_P(incomingNumber, (const char*) users[i].phoneNum, 12))
    {
      digitalWrite(13, HIGH);
    }
  }
}
void loop() {}
 
№2 (без фиксированной размерности поля структуры)
 
Ключевая особенность: в PGM структуре хранятся ссылки на строки, которые хранятся в RAM, ибо "Unfortunately, with GCC attributes, they affect only the declaration that they are attached to. So in this case, we successfully put the string_table variable, the array itself, in the Program Space. This DOES NOT put the actual strings themselves into Program Space. "
 
Плюсы: "красота" кода.
Минусы: бессмысленный оверхед по RAM, ожидаемый эффект практически не достигается
 
Sketch uses 1,088 bytes (3%) of program storage space. Maximum is 32,256 bytes.
Global variables use 65 bytes (3%) of dynamic memory, leaving 1,983 bytes for local variables. Maximum is 2,048 bytes.
 
#pragma GCC optimize ("O0")
struct listUsr_t
{
  const char* phoneNum;
  const char* phonePass;
  const char* phonePDU;
};

const listUsr_t PROGMEM users[] =
{
  { "70000000000", "00000", "000000000000"},
  { "70000000001", "00001", "000000000000"},
  { "70000000002", "00002", "000000000000"},
};

void setup() {
  char incomingNumber[] = "70000000001";
  pinMode(13, OUTPUT);
  digitalWrite(13, LOW);

  for (int i = 0; i < 3; i++) {
    //if (0x00 == strncmp(incomingNumber, (const char*) pgm_read_word(&(users[i].phoneNum)), 12)) 
    if (0x00 == strncmp_P(incomingNumber, (const char*) users[i].phoneNum, 12)) 
    {
      digitalWrite(13, HIGH);
    }
  }
}
void loop() {}

№3 (изначальный)

Ключевая особенность: в PGM структуре строки хранятся сразу как массивы символов (последовательности байт), а не через указатели на таковые массивы, поэтому нет необходимости в предварительном объявлении строк, как PGM-ресурса. Доступ к первой ячейке какой-либо строки осуществляется по простому смещению от начального адреса массива структур.
 
Плюсы: красота кода.
Минусы: строки с разной длиной принесут оверхед по progmem space, так как компилятор будет резервировать блоки объявленной размерности в любом случае.
 
Sketch uses 1,114 bytes (3%) of program storage space. Maximum is 32,256 bytes.
Global variables use 21 bytes (1%) of dynamic memory, leaving 2,027 bytes for local variables. Maximum is 2,048 bytes.
 
#pragma GCC optimize ("O0")
struct listUsr_t
{
  const char phoneNum[12];
  const char phonePass[6];
  const char phonePDU[13];
};

const listUsr_t PROGMEM users[] =
{
  { "70000000000", "00000", "000000000000"},
  { "70000000001", "00001", "000000000000"},
  { "70000000002", "00002", "000000000000"},
};

void setup() {
  char incomingNumber[] = "70000000001";
  pinMode(13, OUTPUT);
  digitalWrite(13, LOW);

  for (int i = 0; i < 3; i++) {
     if (0x00 == strncmp_P(incomingNumber, (const char*) users[i].phoneNum, 12)) 
    {
      digitalWrite(13, HIGH);
    }
  }
}
void loop() {}

P.S. В фрагментах кода закомментированы работающие методы получения правильного указателя на строку для каждого случая, так как сначала я оставил во всех трех один и те же, чтобы исследовать изменения только в объемах хранимых данных. 

sadman41
Offline
Зарегистрирован: 19.10.2016

Всё фокусы с PROGMEM выглядят достаточно просто, пока на сцену не выходит Enum, размер элемента которого выбирается компилятором самостоятельно. Хотя, справедливости ради, необходимо отметить, что задать данный размер всё же можно вручную.

Как загнать в PROGMEM enum к тому же находящийся внутри struct, а также как получить доступ к полям таковой структуры, продемонстрированно в коде, находящемся ниже. Строки, отвечающие за доступ к PROGMEM-данным, выделены цветом.

Для самостоятельной проверки вы можете выбрать один из трёх вариантов компиляции и увидеть, как начинает освобождаться оперативная память:
 
1) PROGMEM_NONE - PROGMEM не используется вообще.
  Sketch uses 2,706 bytes (8%) of program storage space. Maximum is 32,256 bytes.
  Global variables use 344 bytes (16%) of dynamic memory, leaving 1,704 bytes for local variables. Maximum is 2,048
 
2) PROGMEM_SIMPLE - PROGMEM применён ко всем данным, за исключением массива структур (так, как в нём импользуется enum, который ведёт себя непослушно)
  Sketch uses 2,708 bytes (8%) of program storage space. Maximum is 32,256 bytes.
  Global variables use 288 bytes (14%) of dynamic memory, leaving 1,760 bytes for local variables. Maximum is 2,048 bytes.
 
3) PROGMEM_FULL - все статические данные уложены в PROGMEM.
  Sketch uses 2,820 bytes (8%) of program storage space. Maximum is 32,256 bytes.
  Global variables use 252 bytes (12%) of dynamic memory, leaving 1,796 bytes for local variables. Maximum is 2,048 bytes.
 
#pragma GCC optimize ("O0")
#include <avr/pgmspace.h>

// No PROGMEM used for data 
#define PROGMEM_NONE
// No PROGMEM used for struct's array
//#define PROGMEM_SIMPLE
// PROGMEM used for all data
//#define PROGMEM_FULL

typedef enum {diMonday, diTuesday, diWednesday, diThursday, diFriday, diSaturday, diSunday} dayId_t;
#define arraySize(_array) ( sizeof(_array) / sizeof(*(_array)) )

// ***********************************************************
#ifdef PROGMEM_NONE
typedef struct {
  char* name;
  dayId_t id;
  int8_t temperature;
} daysOfWeek_t;

char name_DAY_MONDAY[]               = "Monday";
char name_DAY_TUESDAY[]              = "Tuesday";
char name_DAY_WEDNESDAY[]            = "Wednesday";
char name_DAY_THURSDAY[]             = "Thursday";
char name_DAY_FRIDAY[]               = "Friday";
char name_DAY_SATURDAY[]             = "Saturday";
char name_DAY_SUNDAY[]               = "Sunday";

daysOfWeek_t daysOfWeek[]            = {
  { name_DAY_MONDAY,    diMonday,   +10},
  { name_DAY_TUESDAY,   diTuesday,   +6},
  { name_DAY_WEDNESDAY, diWednesday, +1},
  { name_DAY_THURSDAY,  diThursday,  -3},
  { name_DAY_FRIDAY,    diFriday,    -1},
  { name_DAY_SATURDAY,  diSaturday,  +0},
  { name_DAY_SUNDAY,    diSunday,    +2}
};

dayId_t getDayIdByName(char* _dayName) {
  dayId_t dayId = NULL;
  for (uint8_t i = 0; i < arraySize(daysOfWeek); i++) {
    if (!strcmp(_dayName, daysOfWeek[i].name)) {
      dayId = daysOfWeek[i].id;
      break;
    }
  }
  return dayId;
}

int8_t getTemperatureByDayId(dayId_t _dayId) {
  int8_t temperature = -127;
  for (uint8_t i = 0; i < arraySize(daysOfWeek); i++) {
    if (_dayId == daysOfWeek[i].id) {
      temperature = daysOfWeek[i].temperature;
      break;
    }
  }
  return temperature;
}

#endif

// ***********************************************************
#ifdef PROGMEM_SIMPLE
typedef struct {
  PGM_P name;
  dayId_t id;
  int8_t temperature;
} daysOfWeek_t;

const char name_DAY_MONDAY[]       PROGMEM = "Monday";
const char name_DAY_TUESDAY[]      PROGMEM = "Tuesday";
const char name_DAY_WEDNESDAY[]    PROGMEM = "Wednesday";
const char name_DAY_THURSDAY[]     PROGMEM = "Thursday";
const char name_DAY_FRIDAY[]       PROGMEM = "Friday";
const char name_DAY_SATURDAY[]     PROGMEM = "Saturday";
const char name_DAY_SUNDAY[]       PROGMEM = "Sunday";

const daysOfWeek_t daysOfWeek[]    = {
  { name_DAY_MONDAY,    diMonday,   +10},
  { name_DAY_TUESDAY,   diTuesday,   +6},
  { name_DAY_WEDNESDAY, diWednesday, +1},
  { name_DAY_THURSDAY,  diThursday,  -3},
  { name_DAY_FRIDAY,    diFriday,    -1},
  { name_DAY_SATURDAY,  diSaturday,  +0},
  { name_DAY_SUNDAY,    diSunday,    +2}
};

dayId_t getDayIdByName(char* _dayName) {
  dayId_t dayId = NULL;
  for (uint8_t i = 0; i < arraySize(daysOfWeek); i++) {
    if (!strcmp_P(_dayName, daysOfWeek[i].name)) {
      dayId = daysOfWeek[i].id;
      break;
    }
  }
  return dayId;
}

int8_t getTemperatureByDayId(dayId_t _dayId) {
  int8_t temperature = -127;
  for (uint8_t i = 0; i < arraySize(daysOfWeek); i++) {
    if (_dayId == daysOfWeek[i].id) {
      temperature = daysOfWeek[i].temperature;
      break;
    }
  }
  return temperature;
}
#endif

// ***********************************************************
#ifdef PROGMEM_FULL
typedef struct {
  PGM_P name;
  dayId_t id;
  int8_t temperature;
} daysOfWeek_t;

const char name_DAY_MONDAY[]       PROGMEM = "Monday";
const char name_DAY_TUESDAY[]      PROGMEM = "Tuesday";
const char name_DAY_WEDNESDAY[]    PROGMEM = "Wednesday";
const char name_DAY_THURSDAY[]     PROGMEM = "Thursday";
const char name_DAY_FRIDAY[]       PROGMEM = "Friday";
const char name_DAY_SATURDAY[]     PROGMEM = "Saturday";
const char name_DAY_SUNDAY[]       PROGMEM = "Sunday";

const daysOfWeek_t daysOfWeek[]    PROGMEM = {
  { name_DAY_MONDAY,    diMonday,   +10},
  { name_DAY_TUESDAY,   diTuesday,   +6},
  { name_DAY_WEDNESDAY, diWednesday, +1},
  { name_DAY_THURSDAY,  diThursday,  -3},
  { name_DAY_FRIDAY,    diFriday,    -1},
  { name_DAY_SATURDAY,  diSaturday,  +0},
  { name_DAY_SUNDAY,    diSunday,    +2}
};

dayId_t getDayIdByName(char* _dayName) {
  dayId_t dayId = NULL;
  for (uint8_t i = 0; i < arraySize(daysOfWeek); i++) {
    PGM_P ptrName = pgm_read_word(&(daysOfWeek[i].name));
    if (!strcmp_P(_dayName, ptrName)) {
      memcpy_P(&dayId, &daysOfWeek[i].id, sizeof(dayId_t));
      break;
    }
  }
  return dayId;
}

int8_t getTemperatureByDayId(dayId_t _dayId) {
  int8_t temperature = -127;
  dayId_t currentDayId = NULL;
  for (uint8_t i = 0; i < arraySize(daysOfWeek); i++) {
    memcpy_P(&currentDayId, &daysOfWeek[i].id, sizeof(dayId_t));
    if (_dayId == currentDayId) {
      temperature = pgm_read_byte(&daysOfWeek[i].temperature);
      break;
    }
  }
  return temperature;
}
#endif

void setup() {
  Serial.begin(115200);
  Serial.println("PROGMEM tricks");
  
  char dayName[] = "Thursday";
  dayId_t dayId;

  dayId = getDayIdByName(dayName);
  Serial.print("["); Serial.print(dayId); Serial.print("]"); Serial.print(dayName); Serial.print(" => "); Serial.print(getTemperatureByDayId(dayId)); Serial.println("C");
}

void loop() { }

Пояснение: трюком, позволяющим получить значение enum, хранимое в PROGMEM, является копирование данных функцией memcpy_P() области памяти размером, эквивалентным размерности одиночного элемента enum.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

А как насчёт хранения в PROGMEM данных, запакованных обобщённым zip'ом, с распаковкой на лету по мере чтения. Имеет право на жизнь?

Как минимум, вдвое получается выигрыш. Проигрыш по времени вытаскивания из PROGMEM, конечно значительный, но некритичный для львиной доли приложений.

Могу подогнать пример (только не прямо сейчас, он у меня есть, но «в домашних тапочках» – нужно привести его к демонстрабельному виду). Или сами пример сделайте. Упаковку можно делать внешней программой (и плевать на её скорость и пр.), в код вставлять готовый массив байтов, а распаковывать на лету.

sadman41
Offline
Зарегистрирован: 19.10.2016

Я только "за". Это ж можно будет не только умный дом засунуть в ардуину, но и пару чясиков с календарём.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

sadman41 пишет:

но и пару чясиков с календарём.

Ну, да. И в цыкле их перебирать, включая рыле в нужное время.

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

А еще говоря о PROGMEM народ стыдливо умалчивает о const __FlashStringHelper* .  А ведь умение с ней работать очень упрощает работу с хранением данных во флеш.

b707
Offline
Зарегистрирован: 26.05.2017

qwone пишет:

А еще говоря о PROGMEM народ стыдливо умалчивает о const __FlashStringHelper* .  А ведь умение с ней работать очень упрощает работу с хранением данных во флеш.

причина проста - в примере использования ПРОГМЕМ на сайте ардуино какой-то умник вставил печать строки через буфер - вот все и копируют оттуда, хотя оператор print() умеет печатать строчки из флеша без промежуточного копирования

у меня const __FlashStringHelper* почти в каждой программе

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

b707, вот как только научаться писать 1 !!! функции , а потом 2 !!! писать функции с параметром const __FlashStringHelper*то те костыли которые показывает sadman41 станут не очень нужны.

sadman41
Offline
Зарегистрирован: 19.10.2016

Квон, у меня возражений нет: пости пример с хелпером, но без лямбда-функций.

b707
Offline
Зарегистрирован: 26.05.2017

qwone пишет:

b707, вот как только научаться писать 1 !!! функции , а потом 2 !!! писать функции с параметром const __FlashStringHelper*то те костыли которые показывает sadman41 станут не очень нужны.

так вы вглядитесь в код - Садман как раз использует функции с этим параметром.

 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

sadman41 пишет:

но без лямбда-функций.

Это как же? Не, так по правилам!

Нельзя ограничивать свободу художника!

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Итак программировать const __FlashStringHelper* очень просто. Но надо знать язык Си, а не ограничиваться кухаркиным блинк програмированием.

template <typename T> inline Print & operator << (Print &s, T n) {
  s.print(n);
  return s;
}
template <> inline Print & operator << (Print &s, byte n) {
  if (n < 10)s.print("0");
  s.print(n);
  return s;
}
//-------------------------------------------
byte hh = 1, mm = 2, ss = 3;
//--------------------------------------------
void setup() {
  Serial.begin(9600);
  //Serial << "T:" << hh << ":" << mm << ":" << ss << "\n";//<- а здесь его нет
  Serial << F("T:") << hh << F(":") << mm << F(":") << ss << F("\n"); //< здесь скрыт __FlashStringHelper*

  Serial << F("Time:") << (F(__TIME__)); //<--__FlashStringHelper*
}
void loop() {
}

А вот так можно написать свою функцию

template <typename T> inline Print & operator << (Print &s, T n) {
  s.print(n);
  return s;
}
template <> inline Print & operator << (Print &s, byte n) {
  if (n < 10)s.print("0");
  s.print(n);
  return s;
}
//-------------------------------------------
byte hh = 1, mm = 2, ss = 3;

uint8_t conv2d(const char* p) { // конвертирование строки из 2-х чисел в байт
  uint8_t v = 0;
  if ('0' <= *p && *p <= '9')
    v = *p - '0';
  return 10 * v + *++p - '0';
}
void setTime(const __FlashStringHelper* time) { //<-- сама функция с __FlashStringHelper*
  char buff[8];   //<-- создаем временный буфер
  memcpy_P(buff, time, 8); //<-- переносим флаш строку
  hh = conv2d(buff);
  mm = conv2d(buff + 3);
  ss = conv2d(buff + 6);
}
void setTime(const char* time) {//<-- таже функция но без __FlashStringHelper*
  hh= conv2d(time);
  mm = conv2d(time + 3);
  ss = conv2d(time + 6);
}
//--------------------------------------------
void setup() {
  Serial.begin(9600);
  setTime(F("12:13:14")); //<-через __FlashStringHelper*
  Serial << F("Time:") << hh << F(":") << mm << F(":") << ss << F("\n");
  setTime("01:02:03");//<-без __FlashStringHelper*
  Serial << F("Time:") << hh << F(":") << mm << F(":") << ss << F("\n");
}
void loop() {
}

Теперь вопрос с проверкой у флешхеперов. У них ее нет. Да и флешхеперы используют для написания посланий на других языках - это и человеческие сообщения в различных меню , а так же создания и общения на серверах. И все это находится за пределами языка Си. Вы же не требуете от среды программирования что бы она проверяла правильность  вашего телефоного номера для послания вам СМС по какому-то событию.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

qwone пишет:

Итак программировать const __FlashStringHelper* очень просто. Но надо знать язык Си

Позвольте узнать, какое отношение к языку Си имеет код, приведённый в Вашем посте?

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

ЕвгенийП пишет:
Позвольте узнать, какое отношение к языку Си имеет код, приведённый в Вашем посте?

А это Вы сами решайте на "чесном" или "нечесном" Си я пишу.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Спасибо. Я уже решил - вообще не на Си - ни на каком :)

sadman41
Offline
Зарегистрирован: 19.10.2016

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

Итак, чудесный класс __FlashStringHelper описан в WString.h как:

class __FlashStringHelper;

И является, в сущности, синтаксическим сахаром, который позволяет сообщить вызываемой функции, что ей на вход передаётся  указатель не на строку в RAM (const char*), а на строку, расположенную в Program Space. В функции, соответственно, следует использовать соответствующие приёмчики: вызывать *_P(), pgm_read_*() и пр. и др. (полный список).

Макрос F() описан рядом с волшебным классом __FlashStringHelper и делает только одно: приводит строку, расположенную в макросе PSTR() к типу __FlashStringHelper*:

#define F(string_literal) (reinterpret_cast<const __FlashStringHelper *>(PSTR(string_literal)))

PSTR() же просто создаёт из переданного ему аргумента строку, обладающую свойством PROGMEM-ресурса:

# define PSTR(s) (__extension__({static const char __c[] PROGMEM = (s); &__c[0];}))

Такая вот матрёшка.

Недостатком применения макросов F()/PSTR() являетсято, что создаваемые ими строки никак не оптимизируются и десять одинаковых строк дадут девятикратный оверхед по Program Space. Выходом из сложившейся ситуации может стать смешанное использование строк, сохраняемых как через макрос F(), так и напрямую определяемых, как PROGMEM-ресурс. 

Продемонстрирую объяснение кодом воображаемого веб-сервера, в котором:

1) Раскомментированный USE_F_MACRO_FOR_ALL даёт полную власть в руки F()

Sketch uses 2200 bytes (6%) of program storage space. Maximum is 32256 bytes.
Global variables use 206 bytes (10%) of dynamic memory, leaving 1842 bytes for local variables. Maximum is 2048 bytes.

2) Закомментированный USE_F_MACRO_FOR_ALL заменяет некоторые строки, определяемые в F() на прямо заданные PROGMEM-строки.

Sketch uses 2124 bytes (6%) of program storage space. Maximum is 32256 bytes.
Global variables use 206 bytes (10%) of dynamic memory, leaving 1842 bytes for local variables. Maximum is 2048 bytes.
 
#pragma GCC optimize ("O0")

#define USE_F_MACRO_FOR_ALL

#ifndef USE_F_MACRO_FOR_ALL
const char html_TR_TD[] PROGMEM = "<tr><td class=\"datetime\">";
const char html_TD_TD[] PROGMEM = "</td><td class=\"datetime\">";
const char html_TD_TR[] PROGMEM = "</td><tr>\n";
#endif

void setup() {
  Serial.begin(115200);
  Serial.print(F("<html><body>\n")); 
  Serial.print(F("<header>FlashStringHelper demo</header>\n")); 

  Serial.print(F("<table>\n"));
  
#ifdef USE_F_MACRO_FOR_ALL
  Serial.print(F("<tr><td class=\"datetime\">"));
#else
  Serial.print((const __FlashStringHelper*) html_TR_TD);
#endif

  Serial.print(F("Time:"));

#ifdef USE_F_MACRO_FOR_ALL
  Serial.print(F("</td><td class=\"datetime\">"));
#else
  Serial.print((const __FlashStringHelper*) html_TD_TD);
#endif

  Serial.print(__TIME__);

#ifdef USE_F_MACRO_FOR_ALL
  Serial.print(F("</td><tr>\n"));
#else
  Serial.print((const __FlashStringHelper*) html_TD_TR);
#endif

#ifdef USE_F_MACRO_FOR_ALL
  Serial.print(F("<tr><td class=\"datetime\">"));
#else
  Serial.print((const __FlashStringHelper*) html_TR_TD);
#endif
  
  Serial.print(F("Date:"));

#ifdef USE_F_MACRO_FOR_ALL
  Serial.print(F("</td><td class=\"datetime\">"));
#else
  Serial.print((const __FlashStringHelper*) html_TD_TD);
#endif

  Serial.print(__DATE__);

#ifdef USE_F_MACRO_FOR_ALL
  Serial.print(F("</td><tr>\n"));
#else
  Serial.print((const __FlashStringHelper*) html_TD_TR);
#endif

  Serial.print(F("</table>\n<body><html>"));
}

void loop() {}

 

 

Алексей.
Алексей. аватар
Offline
Зарегистрирован: 02.02.2018

sadman41 пишет:
Думаю, что ни для кого не является секретом, что любой отвязный ардуинщик рано или поздно пытается засунуть в самый дешёвый микроконтроллер если не веб-сервер, то хотя бы "умный дом". Ну, а какой умный дом обходится без вложенных в него автором красивых фраз, названий праздников и матросских ругательств? Но полёт мысли и приложенное к нему творчество быстро пресекается безжалостным фактом - оперативная память микроконтроллера не бесконечна.
Засовывать веб-сервер в контроллер с весьма ограниченными ресурсами спорное решение, а вот использовать как бек-енд устройства и по http реcтОм выполнять запросы вполне нормально.
Типа запрашиваем состояния датчиков, включаем лампочки моторчики, выводим чего либо на дисплейкики и т.п.
Любимый интерфейс (для меня) командная строка, ни каких проблем curl-ом выполнять запросы на эти устройства.
Если захотели всяких бантиков с плюшечками (вся статика), положили на роутер и не паримся.

Повелся таки я на эти понты с веб мордой. На esp8266 можно для хранения статики использовать spiffs, но грузить приходится отдельно от скетча, да и загрузка не очень быстрая.
Во время сборки, нелету, преобразую хтмл-контент в исходник, и копирую в директорию билда.
Засунул кроме страниц стилей классов ещё бутстрап с джиквери, без прогмема скетч уже просто не собирается, не лезет в dram0_0_seg.

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

sadman41.Я конечно понимаю Вера она у каждого индивидульная. Но если у вас много одинаковых строк , что надо выносить их в начало. Но можно эти строки упаковать в отдельные функции и вызывать уже их в нужном месте программы. Или вы после каждого использования написания своей функции каететесь в грехе.

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

Пух в нас тычет пальцем, наганяя страх

Толька слишком рано каяца в грехах... 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Не согрешишь - не покаешься.

sadman41
Offline
Зарегистрирован: 19.10.2016

Так, как я из поста qwone не совсем понял кого, куда и за что необходимо, постфиксно покаявшись, выносить, но узнал слова "отдельные" и "функции", то приведу пример упаковки PROGMEM-строк с оператором семейства printf() в отдельную функцию и вызова оной в нужном месте. Надеюсь, что это, учитывая упоминание святого millis()-а, искупит мои грехи.

Испытательным объектом послужит всё тот же воображаемый веб-сервер, который имеет не только метод print(), но и, так же, метод write(). К слову - на месте Serial может оказаться любой наследник класса Stream... даже EthernetClient. Как видно из кода - предполагается, что у МК можно временно позаимствовать несколько сотен байт, сэкономленных за счёт использования строк как PROGMEM-ресурса. 

#pragma GCC optimize ("O0")

const char html_TR_TD[] PROGMEM = "<tr><td class=\"datetime\">";
const char html_TD_TD[] PROGMEM = "</td><td class=\"datetime\">";
const char html_TD_TR[] PROGMEM = "</td><tr>\n";

void printPage(Stream& _stream) {
  char buffer[100];
  uint16_t realSize;

  snprintf_P(buffer, sizeof(buffer), PSTR("<html><body>\n<header>snprintf_P demo</header>\n<table>\n"));
  _stream.print(buffer);

  snprintf_P(buffer, sizeof(buffer), PSTR("%S __TIME__: %S%S%S"), html_TR_TD, html_TD_TD, PSTR(__TIME__), html_TD_TR);
  _stream.print(buffer);

  snprintf_P(buffer, sizeof(buffer), PSTR("%S __DATE__: %S %S %S"), html_TR_TD, html_TD_TD, PSTR(__DATE__), html_TD_TR);
  _stream.print(buffer);

  snprintf_P(buffer, sizeof(buffer), PSTR("%S millis(): %S %lu %S"), html_TR_TD, html_TD_TD, millis(), html_TD_TR);
  _stream.print(buffer);

  realSize = snprintf_P(buffer, sizeof(buffer), PSTR("</table>\n<body><html>\n") );
  _stream.write(buffer, realSize);
}

void setup() {
  Serial.begin(115200);
  printPage(Serial);
}

void loop() {}

Примечание: передаваемые в функции *printf*() Си-строки обозначаются спецификатором %s, PROGMEM-строки - спецификатором %S

P.S. За флейм насчёт printf() знаю.

Beginer123
Offline
Зарегистрирован: 23.11.2018

Спасибо за тему.

sadman41
Offline
Зарегистрирован: 19.10.2016

Совсем забыл про, наверняка волнующий всех, вопрос: а что там с быстродействием?

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

#pragma GCC optimize ("O0")

const char ramAsciiTable[] = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
const char romAsciiTable[] PROGMEM = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";

const uint32_t samples = 100000;

void setup() {
  char buffer[100];
  uint32_t startTime, endTime, testDuration;

  Serial.begin(115200);
  Serial.println(F("Need for speed: RAM vs ROM\n--------------------------\n\nwait please...\n"));

  startTime = micros();
  for (uint32_t i = 0; i < samples; i++) {
    memcpy(buffer, ramAsciiTable, sizeof(ramAsciiTable));
  }
  endTime = micros();
  testDuration = endTime  - startTime;
  Serial.print(F("RAM: ")); Serial.print(samples); Serial.print(F(" cycles => ")); Serial.print(testDuration); Serial.print(F(" uS, 1 cycle => ")); Serial.print((float)testDuration / samples); Serial.println(" uS");

  startTime = micros();
  for (uint32_t i = 0; i < samples; i++) {
    memcpy_P(buffer, romAsciiTable, sizeof(romAsciiTable));
  }
  endTime = micros();
  testDuration = endTime  - startTime;
  Serial.print(F("ROM: ")); Serial.print(samples); Serial.print(F(" cycles => ")); Serial.print(testDuration); Serial.print(F(" uS, 1 cycle => ")); Serial.print((float)testDuration / samples); Serial.println(" uS");

}

void loop() {}

На выходе имеем проигрыш PROGMEM в ~29,5%:

МК ATMega2560

RAM: 100000 cycles => 4427556 uS, 1 cycle => 44.28 uS
ROM: 100000 cycles => 5735328 uS, 1 cycle => 57.35 uS

МК ATMega328

RAM: 100000 cycles => 4427256 uS, 1 cycle => 44.27 uS
ROM: 100000 cycles => 5728664 uS, 1 cycle => 57.29 uS
 
Чем вызвана разница - не знаю, оптимизацию компилятором отключил.
b707
Offline
Зарегистрирован: 26.05.2017

думаю, что Пух имел в виду, что для константных строк первый sprintF

sadman41 пишет:


  snprintf_P(buffer, sizeof(buffer), PSTR("<html><body>\n<header>snprintf_P demo</header>\n<table>\n"));
  _stream.print(buffer);

 

можно переписать вот так:

 _stream.print(F("<html><body>\n<header>snprintf_P demo</header>\n<table>\n")));

 

sadman41
Offline
Зарегистрирован: 19.10.2016

b707 пишет:

думаю, что Пух имел в виду, что для константных строк первый sprintF

Это-то ради бога - универсального способа нет и не будет. Пример был написан для иллюстрации того, что при бездумном применении F()/PSTR() можно ещё и бессмысленный перерасход по Programm Space получить.

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

sadman41 пишет:

Совсем забыл про, наверняка волнующий всех, вопрос: а что там с быстродействием?

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

#pragma GCC optimize ("O0")

const char ramAsciiTable[] = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
const char romAsciiTable[] PROGMEM = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";

const uint32_t samples = 100000;

void setup() {
  char buffer[100];
  uint32_t startTime, endTime, testDuration;

  Serial.begin(115200);
  Serial.println(F("Need for speed: RAM vs ROM\n--------------------------\n\nwait please...\n"));

  startTime = micros();
  for (uint32_t i = 0; i < samples; i++) {
    memcpy(buffer, ramAsciiTable, sizeof(ramAsciiTable));
  }
  endTime = micros();
  testDuration = endTime  - startTime;
  Serial.print(F("RAM: ")); Serial.print(samples); Serial.print(F(" cycles => ")); Serial.print(testDuration); Serial.print(F(" uS, 1 cycle => ")); Serial.print((float)testDuration / samples); Serial.println(" uS");

  startTime = micros();
  for (uint32_t i = 0; i < samples; i++) {
    memcpy_P(buffer, romAsciiTable, sizeof(romAsciiTable));
  }
  endTime = micros();
  testDuration = endTime  - startTime;
  Serial.print(F("ROM: ")); Serial.print(samples); Serial.print(F(" cycles => ")); Serial.print(testDuration); Serial.print(F(" uS, 1 cycle => ")); Serial.print((float)testDuration / samples); Serial.println(" uS");

}

void loop() {}

На выходе имеем проигрыш PROGMEM в ~29,5%:

МК ATMega2560

RAM: 100000 cycles => 4427556 uS, 1 cycle => 44.28 uS
ROM: 100000 cycles => 5735328 uS, 1 cycle => 57.35 uS

МК ATMega328

RAM: 100000 cycles => 4427256 uS, 1 cycle => 44.27 uS
ROM: 100000 cycles => 5728664 uS, 1 cycle => 57.29 uS
 
Чем вызвана разница - не знаю, оптимизацию компилятором отключил.

Насколько понимаю, за экономию ОЗУ платим временем добывания данных из флеш командой lpm, она должна быть медленней: 3 такта вместо 2-х.

sadman41
Offline
Зарегистрирован: 19.10.2016

Я имел в виду разницу в скорости между ATMega2560 и ATMega328. Она небольшая, но есть. И это мне не особо понятно - ведь у нас не мультизадачность, всё такт в такт должно совпадать, теоретически.

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

А вот это уже не ко мне .. ничего сказать не могу. Возможно компиляция под платы разная и скорость "гуляет". Можно в "асм" глянуть в чем дело.. размер кода - одинаков? Должен отличаться только на размер таблицы прерываний и массивов Ардуино по преобразованию номера пина. Но, если Вы их не используете (а тут это так), то их и не должно быть в конечном коде. То есть, остается только размеры таблиц прерываний. Посмотрите.

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

Мега системой команд несколько отличается хотя бы из-за наличия "длинных" указателей

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Ни разу не видел чтобы они применялись компилятором для мелких программ. Но, то что компилятор может создавать разный код для них (Нано, Мега) - уже натыкался.

sadman41
Offline
Зарегистрирован: 19.10.2016

В некоторых задачах, таких как, например, применение меню в интерфейсе пользователя, совместно используются как изменяемые переменные (например - значение метрики), так и неизменяемые (например - название метрики, единица измерения). Тратить на статические данные такой ценный ресурс, как RAM, жалко, а динамические в PROGMEM не положишь. Как же сделать так, чтобы пользоваться было удобно и ненакладно?..

Попробуем усидеть на двух стульях сразу и хранить изменяемую (динамическую) часть структуры памяти в RAM, а неизменную, (статическую) в PROGMEM.

1) Закомментированный макрос STATIC_DATA_USED_PROGMEM - статические данные хранятся в RAM

Sketch uses 2,482 bytes (7%) of program storage space. Maximum is 32,256 bytes.
Global variables use 288 bytes (14%) of dynamic memory, leaving 1,760 bytes for local variables. Maximum is 2,048 bytes.

2) Раскомментированный макрос STATIC_DATA_USED_PROGMEM - статические данные хранятся в PROGMEM 

Sketch uses 2,496 bytes (7%) of program storage space. Maximum is 32,256 bytes.
Global variables use 246 bytes (12%) of dynamic memory, leaving 1,802 bytes for local variables. Maximum is 2,048 bytes.
 
#include <avr/pgmspace.h>
#include <stdlib.h>

//#define STATIC_DATA_USED_PROGMEM

#define LANGUAGE_ENGILSH
//#define LANGUAGE_RUSSIAN_TRANSLIT

#if defined(LANGUAGE_ENGILSH)
const char string_TEMPERATURE[]   PROGMEM = "Temperature";
const char string_VOLTAGE[]       PROGMEM = "Voltage";
const char string_SPEED[]         PROGMEM = "Speed";
#elif defined(LANGUAGE_RUSSIAN_TRANSLIT)
const char string_TEMPERATURE[]   PROGMEM = "Temperatura";
const char string_VOLTAGE[]       PROGMEM = "Napryajenie";
const char string_SPEED[]         PROGMEM = "Skorost'";
#else
const char string_TEMPERATURE[]   PROGMEM = "Tmp";
const char string_VOLTAGE[]       PROGMEM = "Vlt";
const char string_SPEED[]         PROGMEM = "Spd";
#endif

const char string_CELSIUS[]       PROGMEM = "C";
const char string_VOLT[]          PROGMEM = "V";
const char string_RPM[]           PROGMEM = "RPM";

typedef struct {
  uint8_t   idx;
  char      shortcut;
  PGM_P     name;
  PGM_P     unit;
  int32_t  min;
  int32_t  max;
} menuItemStaticData_t;

typedef struct {
  int32_t  value;
  uint8_t  enabled;
  const menuItemStaticData_t* menuItemStaticData;
} menuItem_t;

// Form the static part of menu
#if defined(STATIC_DATA_USED_PROGMEM)
// index, shortcut, name, min value, max value
const menuItemStaticData_t menuItemStaticData01 PROGMEM = { 0, '1', string_TEMPERATURE, string_CELSIUS, -15,  75};
const menuItemStaticData_t menuItemStaticData02 PROGMEM = { 1, '4', string_VOLTAGE,     string_VOLT,     12, 380};
const menuItemStaticData_t menuItemStaticData03 PROGMEM = { 2, '*', string_SPEED,       string_RPM,      20, 600};
#else
const menuItemStaticData_t menuItemStaticData01         = { 0, '1', string_TEMPERATURE, string_CELSIUS, -15,  75};
const menuItemStaticData_t menuItemStaticData02         = { 1, '4', string_VOLTAGE,     string_VOLT,     12, 380};
const menuItemStaticData_t menuItemStaticData03         = { 2, '*', string_SPEED,       string_RPM,      20, 600};
#endif

// Form the menu array
menuItem_t menuItems[3] = {
  {10,   true, &menuItemStaticData01 },
  {36,  false, &menuItemStaticData02 },
  {100,  true, &menuItemStaticData03 },
};

#define arraySize(_array) ( sizeof(_array) / sizeof(*(_array)) )
#define FSH_P(p) (reinterpret_cast<const __FlashStringHelper *>(p))

//*********************************************************************************
// Prints all items from menu structure
//*********************************************************************************
void printMenu(void) {
  Serial.println(F("\n\t\tDemo menu\n-------------------------------------------\n"));

  for (uint8_t i = 0; i < arraySize(menuItems); i++) {

#if defined(STATIC_DATA_USED_PROGMEM)
    PGM_P   menuItemName     = pgm_read_word(&(menuItems[i].menuItemStaticData->name));
    PGM_P   menuItemUnit     = pgm_read_word(&(menuItems[i].menuItemStaticData->unit));
    char    menuItemShortcut = pgm_read_byte(&(menuItems[i].menuItemStaticData->shortcut));
    int32_t menuItemMinValue = pgm_read_dword(&(menuItems[i].menuItemStaticData->min));
    int32_t menuItemMaxValue = pgm_read_dword(&(menuItems[i].menuItemStaticData->max));
#else
    PGM_P   menuItemName     = menuItems[i].menuItemStaticData->name;
    PGM_P   menuItemUnit     = menuItems[i].menuItemStaticData->unit;
    char    menuItemShortcut = menuItems[i].menuItemStaticData->shortcut;
    int32_t menuItemMinValue = menuItems[i].menuItemStaticData->min;
    int32_t menuItemMaxValue = menuItems[i].menuItemStaticData->max;
#endif

    // Print enable/disable sign and shortcut
    Serial.print((menuItems[i].enabled) ? F("+") : F("-") ); Serial.print(" ["); Serial.print(menuItemShortcut); Serial.print("] ");
    // Print name, value, additional 'tab' symbol if need, and unit
    Serial.print(FSH_P(menuItemName)); Serial.print(F(":\t")); Serial.print((strlen_P(menuItemName) > 8) ? "" : "\t"); Serial.print((menuItems[i].value)); Serial.print(FSH_P(menuItemUnit));
    // Print min/max limits
    Serial.print(F("\t<")); Serial.print(menuItemMinValue); Serial.print(F(" .. ")); Serial.print(menuItemMaxValue); Serial.print(F(">"));
    Serial.println();
  }
}

void setup() {
  Serial.begin(115200);
  Serial.println(F("Structs on PROGMEM"));
  uint8_t flippedItemNo = 1;
  printMenu();
  // Use dynamic part of the menu struct
  menuItems[flippedItemNo].enabled = !menuItems[flippedItemNo].enabled;
  printMenu();
}

void loop() {}

 

 

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Хороший тактический прием, но если решать стратигически задачу меню, то все приведеное выше окажется не так здоровски. Прежде всего лучше рассматривать меню , как некий автомат с состоянием - отображение конкретного екрана и написав отдельную функцию перехода в это состояния ,ну к к примеру go(/*на нужный экран*/ )  мы очень сокращаем себе головную боль , которую sadman41 решает тактически.

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

Пух, а лямбды где?  Низачот. 

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

DetSimen пишет:

Пух, а лямбды где?  Низачот. 

Почему лямды. А не динамическая перегрузка оператора << .

sadman41
Offline
Зарегистрирован: 19.10.2016

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

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Скорее структуру нафиг. Нечего инвалидов кормить.
Структура в меню это тоже костыль к которому все привыкли, хрен оторвешь.

sadman41
Offline
Зарегистрирован: 19.10.2016

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

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Так и прогресс не стоит на месте. Может еще какой-то прием вылезет и уже эта схема отправится к инвалидам. Структура тем плоха , что не позволяет организовывать нестуктурированные окна в меню. Что препятсвует дополнению новых пунктов при модернизации программы. 

sadman41
Offline
Зарегистрирован: 19.10.2016

qwone, остановись уже. Тут тема про PROGMEM, а не про алгоритмы, которые роятся у каждого из нас в голове. Нет никакой универсальной схемы построения меню (и всего другого) в рамках ограниченных ресурсов МК. Всегда выбирается решение под задачу. И эту задачу МК продолжает выполнять годами, не требуя ежедневного добавления пунктов меню.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

qwone пишет:

DetSimen пишет:

Пух, а лямбды где?  Низачот. 

Почему лямды. А не динамическая перегрузка оператора << .

Патамушта сейчас в моде лямбды, а для перегрузок не сезон :(

Да, кстати, динамической перегрузки в этом языке нету :(

qwone пишет:

Так и прогресс не стоит на месте. Может еще какой-то прием вылезет 

Срочно освойте prvalue и начинайте пихать перемещение во все "классы от Пуха" :)

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

А вот здесь вы не правы. Как раз написание меню и есть тот момент как использовать PROGMEM. Для чего может быть использован PROGMEM ? 1 - строки сообщений .2- создание различных таблиц,шрифтов и матриц изображения 3 - первичная настройка объектов 4- первичная настройка данных в EЕPROM. И все это так или иначе используется в меню. Так вот и с организации меню и использовании там PROGMEM и надо говорить. А иначе говорить не о чем. Все это давно написано в интернете и разжовано не один раз. 

sadman41
Offline
Зарегистрирован: 19.10.2016

Дак это и понятно - кто автоматы и лямбды не пользует, тот правым быть не может по определению. И, да, в интернетах разжовано всё, кроме лямбд и автоматного программирования через классы. Посему я предлагаю немедля составить петицию модераторам, чтобы они выпилили с этого форума всё, что не относится к данным двум вещам. Чтобы, стало быть, Интернет не разорвало от переизбытка информации.

 

sadman41
Offline
Зарегистрирован: 19.10.2016

ЕвгенийП пишет:

Срочно освойте prvalue и начинайте пихать перемещение во все "классы от Пуха" :)

Кстати, раз вы начали бесплатные советы давать, не могу не спросить - есть ли какие-то простые приёмы для доступа к массивам структур? Для веб-программистов вон хеш-массивы понаделали, а тут вот приходится извращаться:

dayId_t getDayIdByName(char* _dayName) {
  dayId_t dayId = NULL;
  for (uint8_t i = 0; i < arraySize(daysOfWeek); i++) {
    if (!strcmp(_dayName, daysOfWeek[i].name)) {
      dayId = daysOfWeek[i].id;
      break;
    }
  }
  return dayId;
}

Никакого волшебства нет, чтобы не плодить такие конструкции? Я, конечно, догадываюсь, что можно поменьше писать руками, размножив ЭТО через шаблоны...

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

ЕвгенийП пишет:
Да, кстати, динамической перегрузки в этом языке нету :(
Немного ошибся в терминах.
Специализация шаблона для перегрузки опрератора<< .
Специализация шаблона от сюда https://www.youtube.com/watch?v=_kJJoWDNvxM

https://ru.stackoverflow.com/questions/495141/Перегрузка-операторов-и-в-шаблонном-классе-как-дружественных-функций 
 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Пух, просто, само понятие "динамическое переопределение метода" имеет право на существование. В более продвинутых ООП языках есть динамическое создание/изменение классов причём бывает можно это сделать даже двумя способами - полиморфно и неполиморфно. Например, так в JavaScript - там правда перегрузки как таковой нет, но методы можно прицеплять и отцеплять на лету, причём как так, чтобы всем потомкам (а иногда и предкам) тоже цеплялось, так и без этого. А в С++ с динамически измененим классов напряг :(((

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

sadman41 пишет:

есть ли какие-то простые приёмы для доступа к массивам структур? 

Простите, не понял вопроса. Приведите пример как "хорошо". Можно на JS или что Вы там под "языками для web" понимаете.

sadman41
Offline
Зарегистрирован: 19.10.2016

ЕвгенийП пишет:

sadman41 пишет:

есть ли какие-то простые приёмы для доступа к массивам структур? 

Простите, не понял вопроса. Приведите пример как "хорошо". Можно на JS или что Вы там под "языками для web" понимаете.

Например, что-то похожее: 

<?php
$arr = array('fruit' => 'apple', 'veggie' => 'carrot');

print $arr['fruit'];  // apple
print $arr['veggie']; // carrot
?>

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

typedef struct { 
   char* name; 
   uint8_t id; 
} daysOfWeek_t;

daysOfWeek_t daysOfWeek[] = {
   { name_DAY_MONDAY,    66 },
   { name_DAY_TUESDAY,   30 },
   { name_DAY_WEDNESDAY, 85 },
   { name_DAY_THURSDAY,  7  },
   { name_DAY_FRIDAY,    13 },
   { name_DAY_SATURDAY,  3  },
   { name_DAY_SUNDAY,    42 }
};

 

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

подпишусь

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

sadman41 пишет:
Простите, не понял вопроса. Приведите пример как "хорошо". Можно на JS или что Вы там под "языками для web" понимаете.

Тю . это легко и не очень. всего надо освоить это http://www.solarix.ru/for_developers/cpp/stl/stl.shtml
Но скорее всего данный камень это уже не потянет.

wdrakula
wdrakula аватар
Offline
Зарегистрирован: 15.03.2016

ЕвгенийП пишет:

Пух, просто, само понятие "динамическое переопределение метода" имеет право на существование. В более продвинутых ООП языках есть динамическое создание/изменение классов


Женя! Я же не за красивое название Питон люблю.... вернее -- не только за него. ;))