Массив переменной длины

ustas
Offline
Зарегистрирован: 12.03.2012

Есть некоторый прототип составного устройства... количество подключенных "частей" можно программно определить в setup(). Хочется написать универсальную прошивку для устройства.

"Частей" может быть от 1 до 128 (просто для определенности)

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

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

Как быть?

kisoft
kisoft аватар
Offline
Зарегистрирован: 13.11.2012

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

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

Думаю легко найдете в гугле реализацию таких великов :) Я раньше пользовался таким, поскольку был старый компилятор, а теперь STL наше всё. На большом компе, конечно.

Вот, например, тут: http://victor192007.narod.ru/files/cpp_d1.html

ustas
Offline
Зарегистрирован: 12.03.2012

Элементы нужно создать сразу после того, как стало понятно, сколько же "частей" подключено (после этого структура данных уже не меняется).

"Велики" - крутые... лихо закручено. Надо подумать (и проверить, насколько это вообще реализуемо на ардуино)...

Ну и получение значения n-ого элемента - будет не самой быстрой операцией (особенно, если n - "далеко" от начала).

kisoft
kisoft аватар
Offline
Зарегистрирован: 13.11.2012

Если элементы нужно создать всего один раз, когда известно их количество, то списки ф топку. В этом случае всё тупее и быстрее, а именно (накидал навскидку, не компилировал):

Data *gp_array = NULL;
int8_t array_cnt = 0;
...
array_cnt = <известное кол-во>;
// Здесь плохо то, что структуры не инициализируются, т.е. там мусор)
gp_array = (Data *)malloc(sizeof(Data) * array_cnt);
...
void printStruct(const Data &p_data)
{
  // Здесь печатаем одну структуру
  ...
  Serial.println(p_data.val1);
  ...
}
...
// Печатаем весь массив
for( int8_t i = 0; i < array_cnt; ++i )
{
  printStruct(gp_array[i]);
}
...
// Используем 5 структуру из массива, берем из неё val1 и присваиваем локальной переменной val
int val = gr_array[5].val1;

Из минусов, можно легко и просто перескочить за пределы массива (т.е. взять структуру с индексом большим чем array_cnt), например:

... gp_array[ array_cnt + 5 ] ...

В данном варианте все структуры расположены в памяти плоско, т.е. сначала идет одна структура, следом за ней вторая и т.п.

Это один из вариантов и оптимален он только для случая, когда создавать элементы массива нужно всего один раз, когда уже точно известно их количество. Варианты могут быть и другие.

Еще раз:

gp_array - указатель на массив структур. Т.е. gp_array[0] - это структура, а не указатель на структуру. &(gp_array[3]) - это указатель на 4 структуру в массиве (индексы с нуля).

Примеры:

gp_array[0].val1
&(gp_array[4])->val1
 

Можно использовать указатель на структуру, например, так:

... Начало из первого листинга ...
...
Data *lp_array = &(gp_array[6]); // Указатель на 7 структуру массива
lp_array++; // После этой строки указатель будет указывать на 8 структуру.
...
// Пример использования на "большом" компе:
std::cout << "lp_array\n" << "val1 = " << lp_array->val1 << ", val2 = " << lp_array->val2 << "\n";
lp_array++;
std::cout << "lp_array\n" << "val1 = " << lp_array->val1 << ", val2 = " << lp_array->val2 << "\n";
// На печать выводит типа такого:
// lp_array
// val1 = 5, val2 = 6
// lp_array
// val1 = 15, val2 = 16

 

Полный листинг из Visual Studio:

#include <iostream>
#include <stdlib.h>

typedef struct _Data
{
	int val1;
	int val2;
} Data;

Data *gp_array = nullptr;
int array_cnt = 0;

int _tmain(int argc, _TCHAR* argv[])
{
	array_cnt = 2;

	gp_array = (Data *)malloc( sizeof( Data ) * array_cnt );
	gp_array[0].val1 = 5;
	gp_array[0].val2 = 6;
	gp_array[1].val1 = 15;
	gp_array[1].val2 = 16;

	Data *lp_array = &gp_array[0];

	std::cout << "lp_array\n" << "val1 = " << lp_array->val1 << ", val2 = " << lp_array->val2 << "\n";
	lp_array++;
	std::cout << "lp_array\n" << "val1 = " << lp_array->val1 << ", val2 = " << lp_array->val2 << "\n";

	return 0;
}

Результат работы программы:

lp_array
val1 = 5, val2 = 6
lp_array
val1 = 15, val2 = 16

 

Logik
Offline
Зарегистрирован: 05.08.2014

ustas пишет:

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

И тем не мение, именно так верней. Используя динамическое распределение памяти  (malloc в setup) задействовать остальную будет проблематично.  Т.к. при разработке программы прийдется все время помнить, что возможен и плохой вариант - все 128 частей присутствуют и оставлять память на этот случай свободной. Как вариант - урезание части функционала, т.е. при 1-2 частях работает все что можна а при 128 -  только минимум, для которого хватает памяти. При этом повышается требование к качеству кода, надо нормально работать в обоих случаях. ИМХО - забудте о этом.

 

kisoft
kisoft аватар
Offline
Зарегистрирован: 13.11.2012

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

Logik
Offline
Зарегистрирован: 05.08.2014

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

А толку с этого? Если сказано максимум 128 частей, то вариант, что на них не хватает памяти никого не устроит. Потом прийдется иметь резерв свободной памяти на все 128, пусть даже распределите в malloc тока на два, а оставшиеся 126 будут просто не использоватся или использоватся для доп. функционала как я писал. А если доп. функционала не предвидится (что в общем типично), то почему бы не распределить всю память на 128 частей и забыть о проблеме? Такая специфика динамического распределения в МК в отличии от ПК, в котором смысл экономить память есть, т.к. её подберет другое приложение, кеш и т.д. А в МК подобрать "экономию" некому.

kisoft
kisoft аватар
Offline
Зарегистрирован: 13.11.2012

Вцелом логично. Однако задача сформулирована поверхностно, потому можно только философствовать.
Простой пример, для вывода отладочных сообщений может не хватить памяти, если выделить сразу все 128 блоков.

ustas
Offline
Зарегистрирован: 12.03.2012

Спасибо, обсуждение привело к переосмыслению задачи и "выделению памяти" в голове, что существенно изменило подход к написанию кода на МК :)

kisoft
kisoft аватар
Offline
Зарегистрирован: 13.11.2012

Отлично! :)
Обычно решение задачи зависит от условий.
Но динамика тоже иногда нужна. Был проект, где хорошо было выделить максимальный буфер.
Удачи