{imgicourl}{zamok}
<< предыдущая заметкаследующая заметка >>
04 февраля 2020
Подскажите решение по C++ ?

Часто встречаю на Ардуино библиотеки, которые требуют инициализации в коде с заранее заданнными пинами, например:

#include <LampDriver.h>

#define PIN1 4
#define PIN2 16

LampDriver MyLAMP1(PIN1);
LampDriver MyLAMP2(PIN2);

void loop {
  MyLAMP1.blink();
  MyLAMP2.blink();
  sleep(100);
}

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

Соответственно, я подозреваю, что в C++ имеется на этот случай какой-то особый синтаксис. Например, вот мне подсказывали, new. Но как это правильно записать? LampDriver *MyLAMP1 = new LampDriver(4); ? И далее MyLAMP1.blink(); будет работать? У меня всякие ошибки сыпятся.

<< предыдущая заметка следующая заметка >>
пожаловаться на эту публикацию администрации портала
Страницы, которые привлекли мое внимание за последние дни, рекомендую:
архив ссылок
Оставить комментарий
Mac Safari Chrome
 Washington
5
0
Гена
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
LampDriver *MyLAMP1 = new LampDriver(4);
MyLAMP1 -> blink();

....

в конце

delete MyLAMP1;

Ну, правда, я надеюсь, что это игрушечный пример кода, а не весь код. Объект *MyLamp не стоит создавать и уничтожать на каждое мигание лампы... наверное это не требуется объяснять, но если вопрос про new, то я не уверен...

Наверное что-то такое вам надо:

// создать указатель на пока несуществующий объект
LampDriver * MyLAMP1;

// запомнить, что это - первый проход цикла,
// в конце мы его навсегда изменим
bool first_pass = true;

void loop {

// на первом проходе создать объект
if (first_pass==true){
MyLAMP1 = new LampDriver(4);
first_pass = false;
}

// мигать до посинения. Использовать стрелку,
// а не точку, ибо MyLAMP1 - указатель, такие тут
// традиции
MyLAMP1 -> blink();
sleep(100);
}

// удалить обьект, когда он уже не нужен - сам не удалится, будет засорять память
delete MyLAMP1;

P.S. Что-то я не нашел как сделать, чтоб код выглядел как код: отступы убираются, фонт разной ширины. Есть тут в коментах code block?
Linux Safari Chrome
 Москва
0
0
LLeo
А почему после этой процедуры MyLAMP1.blink(); превратилось в MyLAMP1 -> blink(); ? Это меня пугает.

Нет, код абсолютно условный, механика его другая (он свои особые текстовые конфиги читает после старта) да и не лампы там.
Mac Safari Chrome
 Washington
0
0
Гена
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
Я добавил комментарии: потому что теперь это не объект, а указатель на объект, для него специальный синтаксис. Не пугайтесь. :)

В c++ переменные бывают в трех ипостасях: просто переменные (или l-value), указатели, и ссылки. Это чтобы жизнь медом не казалась.

L-value - это то, к чему вы привыкли, то, что у вас в примере как

LampDriver MyLAMP1(PIN1);

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

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

Чтобы доступиться к объекту через указатель, есть специальный синтаксис - стрелка ->

В принципе можно сначала доступиться к самому объекту (dereference) и потом с точкой:

(*MyLAMP1).blink();

Но это не нужно.
Windows Firefox
 Москва
5
0
vshvetsov
Уважаемый сэр! Позволю себе покритиковать ваш комментарий. Он может несколько запутать новичков в C. Вы так написали, как будто l-value - синоним "обычного" объекта, в отличие от указателя. Но ведь различие между left-value и right-value лежит совсем в другой плоскости. И указатели на объекты, и сами объекты могут выступать как left и right-value. Просто:
int i; // имя i может выступать как l и rvalue
j = i; i = 1; // норм
int func() {тело};
i = func(); // можно
func() = 1; // нельзя.
Именно поэтому func() - не left, но right-value.
И ровно то же справедливо для указателей:
int* i;
i = new int[1];
int* func_alloc() {};
i = func_alloc(); // норм
func_alloc() = new int [1]; // нельзя, так как не lvalue, хотя по типу подходит.
Mac Safari Chrome
 Washington
0
0
Гена
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
Все вроде так, но непонятно, ИМХО. Просто для "обычных переменных" я не знаю хорошего названия. "Имя объекта на стеке"? Предложите ваш вариант. В этом примере мне показалось, что l-value - это наиболее точное и относительно понятное название, используется-то оно именно как l-value здесь.
Windows Firefox
 Москва
6
0
vshvetsov
Хочу высказаться также по триаде:
объект - указатель - ссылка
Изначально в C (не ++) ссылок не было. Были только объекты и указатели. Любой начинающий сталкивался с указателями, как только ему требовалось впервые изменить в теле функции значение переданного функции параметра:
void func1(int i) { i = 1; }
void func2(int* i) { *i = 1; }
Далее:
int i = 0;
func1(i); // всё ещё i==0
func2(i); // вот теперь i==1
Этот код демонстрирует то свойство C, из-за которого он называется "высокоуровневым ассемблером". В тексте языка явно указано, что происходит на уровне ассемблера. В func1 в ячейку на стеке, отведённую под параметры функции, кладут целое значение, i - имя этой ячейки. Туда пишется 1. В func2() в ячейку кладётся указатель i, то есть адрес некоторой другой ячейки, где и лежит значение. В теле явно стоит операция разадресации - *i - "обратимся к той другой ячейке, адрес которой лежит i". Именно в ту ячейку засовываем 1.
Спрашивается, так зачем ещё потребовались ссылки? Ответ таков. В мире существует, не исчезает и даже порой ширится класс "программистов-волшебников", то есть любителей "программирования с волшебными словами". Программисты-волшебники рассуждают так: запись i = 1; хорошая и понятная. "Присвой i единицу". Запись *i = 1; - противная, компьютерная, не для людей. "Положи 1 в ячейку, адрес которой лежит в i". Фу. Пусть лучше в теле func2 тоже будет написано по-человечески: i=1. Однако работает пусть всё, как раньше - func1 получает на стек значение, а func2 - указатель. И оператор присваивания, с виду теперь одинаковый, пусть работает по разному. В func1 - обычное присваивание, в func2 - с разадресацией указателя. А чтобы теперь понять, как это работает, мы введём в язык волшебный значок "&". Теперь всё просто:
void func1(int i) { i = 1; }
void func1(int &i){ i = 1; }
int i = 0;
func1(i); // волшебного значка нет, i не изменилось
func2(i); // волшебный значок! i изменилось.
Смотрите, операторы в теле функций одинаковые, а работает по-разному, и всё из-за волшебного значка! При этом можно даже не разбираться, как это работает, даже не знать про какие-то указатели. Как-то само присваивается, из-за чудесного значка.
Программисты C долго сопротивлялись, но программисты-волшебники победили, и ссылки появились в C++.
Linux Safari Chrome
 Москва
2
0
LLeo
Вау!!! Спасибо, впервые за много-много лет я наконец кажется понял...

Давайте проверим, правильно ли, понял вас.

То есть, когда я передаю в функцию &i - это просто означает, что я передал не копию i, а тот же самый указатель как *i, но при этом просто велел компилятору далее по коду воспринимать переменную i в краткой записи, не загрязняя код постоянными звёздочками, напоминающими, что я работаю с указателем, хотя это и так было всем (и мне, и компилятору) ясно при описании функции?
Windows Firefox
 Москва
0
0
Abstraction
В общем да, с точки зрения "поведения" ссылка и указатель это одно и то же (за вычетом того, что а) переменной-указателю можно присвоить другое значение и б) у переменной-указателя есть корректное значение nullptr, а у ссылки такого нет).
Linux Safari Chrome
 Москва
1
0
Андрей Рогожников
Собственно то, что ссылка не может иметь под собой nullptr, и делает ее алиасом, то есть полноценной заменой исходному объекту.
Mac Safari Chrome
 Washington
0
0
Гена
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
По-моему не совсем правильно. Тут надо стараться помнить, что и как в памяти. Все еще дополнительно запутывает двойной смысл амперсанта &.

Возьмем пример 1, объект на стеке:

MyClass obj;

obj - сам объект,
&obj - это указатель на объект,
*obj - в этом контексте не имеет смысла (но наверное сработает с безумными результатами)

Пример 2, объект в куче:

MyClass * ptr = new MyClass();

ptr - указатель,
*ptr - сам объект,
&ptr - указатель на указатель на объект.

Теперь

MyClass & ref = *ptr;

ref - ссылка на тот же самый объект

Теперь можно пользоваться ref как самим объектом.

Для того, чтобы можно было передавать объект в функцию, не копируя сам объект, функция должна об этом знать:

func1(MyClass x){} примет и obj, и ref, и все равно скопирует.

func2(MyClass & x) примет и obj, и ref, и копировать не будет.
Linux Safari Chrome
 Санкт-Петербург
1
1
читал
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
да, бдь, стало сильно понятней )) вам не стоит преподавать С++ ))
Mac Safari Chrome
 Washington
0
0
Гена
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
Да я и не преподаю, зря ругаетесь. Впрочем, как говорит народная мудрость, не говорите мне, что делать, и я не скажу вам, куда вам пойти.
Windows Firefox
 Москва
0
0
vshvetsov
Да, абсолютно точно. Ссылка - это другой синтаксис для указателя. Каким из 2-х синтаксисов пользоваться - дело вкуса. Впрочем, есть много случаев, когда нужно пользоваться именно указателем, а не ссылкой. Там выше в комментах уже писали: указатель можно перенаправить на другой объект, ссылку нет. То есть ссылка - это замаскированный константный указатель. Кроме того, указателю можно присвоить значение 0 (==null), что означат, что этот указатель не указывает ни на какой объект. Ссылке такого значения присвоить нельзя, она всегда ссылается на существующий объект.
void func(some_type* param, some_type* additional)
{
// что-нибудь делаем с param
if (additional) {
// раз передали не ноль, делаем что-то с additional
}
}
Со ссылками так не получится.
Windows Firefox
 Москва
1
0
vshvetsov
Впрочем, погодите. То, что я только что написал, правильно само по себе, но, может быть, неправильно отвечает на ваш вопрос: "когда я передаю в функцию &i ...". Давайте уточним.
Пусть функция определена с параметром типа указатель:
void func1(int* i);
Тогда при вызове ей нужно передавать именно указатель, то есть адрес объекта:
int k = 0;
func1(&k);
Здесь & - это не "волшебный значок", о котором шла речь, здесь это оператор "взятия адреса", взаимно обратный с оператором * "обращения к объекту по адресу":
int i; // i - объект
int* pi = &i; // &i - адрес объекта i
int j = *pi; // *pi - объект, адрес которого в pi, т.е. сам i.
Всё это - из обычного C, где никаких ссылок не было.
Теперь пусть функция определена так:
void func2(int& i);
Вот здесь & - волшебный значок. Вызов выглядит теперь так:
int i = 0;
func2(i); // передаём объект i, а не его адрес &i
Но это только с виду. На самом деле транслятор передаст именно указатель, и во всех операциях в теле func2 будет оперировать с переменной i через этот указатель, но теперь это скрыто от программиста.
Mac Safari Chrome
 Washington
0
0
Гена
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
По-моему в последнем вашем примере важно объяснить, что в случае, когда функция берет ссылку, передаваемый объект НЕ КОПИРУЕТСЯ. Это важно объяснять, потому что можно не понять, что передаваемые данные могут измениться, что невозможно в случае, когда функция берет объект.

В этом, собственно, и цель обычно, и тем не менее это по-моему не всегда легко усвоить.
Mac Safari Chrome
 Washington
0
0
Гена
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
Мне всегда казалось, что термин "объект" в триаду не очень подходит, потому что имя объекта на стеке и сам объект в куче как-то мне кажутся достаточно разными сущностями. Потому я как-то и привык использовать для этого l-value. Впрочем, не знаю, как этому формально учат и как это официально называется, в книжках. На самом деле "объект"?
Windows Firefox
 Москва
1
0
vshvetsov
имя объекта на стеке и сам объект в куче как-то мне кажутся достаточно разными сущностями - неясное и, думается, неправильное противопоставление. Если сам объект создан вами в куче:

TMyObjectType* px = new TMyObjectType;

то на стеке у вас, вероятно, не "имя объекта", а имя указателя на созданный объект - "px". Имени самого объекта тогда просто нет. Однако "сам объект" может и не быть в куче:
TMyObjectType x;
TMyObjectType* px = &x;
TMyObjectType& rx = x;
Здесь и объект, и указатель на него, и ссылка на него - все в стеке.
Стек (где локальные переменные), сегмент данных (где глобальные переменные) и куча (динамически отводимая память) - разные места, где можно создавать объекты.
Триада "Объект-указатель-ссылка" в настоящей дискуссии - способы доступа к объекту: непосредственно по значению, по адресу ячейки, тоже по адресу, но скрытно (ссылка).
Lvalue - любое выражение, которое можно употребить в левой части оператора присваивания, то есть всё, чему можно осмысленно присвоить значение, Rvalue - любое выражение, которое можно употребить в правой части оператора присваивания, то есть всё, у чего есть значение, которое можно взять.
Mac Safari Chrome
 Washington
0
0
Гена
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
Да это все понятно. Я по сути спрашиваю, как принято "это" называть. Если "объект", то и ладно.

Мне не очень нравится использовать "объект", потому что ... как-то интуитивно, по историческим причинам... а! Вот, наверное, почему:

MyClass a;
MyClass * b = new My Class();

Объекты тут a и (*b). Меня чисто эстетически смущает, что невозможно иметь объект на куче без * - только ссылку. Поэтому меня и тянет называть "а" каким-то другим словом. Но это наверное субъективно. В общем, если все говорят "объект", то и я буду.
Windows Safari Chrome
 Москва
1
0
ew-gny
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
Не то чтобы я был настоящим сварщиком, но разве такая вот хрень (в примере где про си, а не про сиплюсплюс) не вызовет сигментейшн фолт?

void func2(int *i) {*i = 1;}
int i = 0;
func2(i);


?

должно же быть func2(&i)
Windows Firefox
 Москва
0
0
Abstraction
Такая хрень никаких проблем времени выполнения не вызовет, потому что не скомпилируется.
Mac Safari Chrome
 Киев
0
0
lim
Может скомпилиться, и может вызвать. Я пример привел ниже.
Windows Safari Chrome
 Москва
0
0
ew-gny
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
В c++ не скомпилируется, а в c - скомпилируется и вызовет segfault.
Windows Firefox
 Москва
0
0
vshvetsov
Конечно, я опис`ался.
Mac Safari Chrome
 Киев
0
0
lim
А нет ли ошибки в первом примере в func2?
Если она принимает int*, а на вход ей передавать int, то компилятор будет ругаться, как мне кажется. Может быть должно было быть func2(&i) ?
https://onlinegdb.com/HkLL8oOzU
вот тут пишет ошибка, и при выполнении segfault
А с исправлением все ок:
https://onlinegdb.com/BJKoUs_G8
Windows Firefox
 Москва
0
0
Abstraction
Да, не вполне точно выразился в ветке выше. Такая хрень означает, в терминах стандарта C++, что "invocation is ill-formed" - другими словами, что стандарт в этом случае не накладывает на компилятор вообще никаких обязательств кроме обязательства выдать диагностическое сообщение: формально компилятор может прекратить компиляцию (что все нормальные люди и делают), может сгенерировать код как если бы это был язык C (что сделал ваш onlinegdb, потому что в качестве языка вы указали C), может сгенерировать код, проигрывающий фрагмент песни "Never gonna give you up", может сгенерировать код, форматирующий все доступные дисковые разделы...
Windows Firefox
 Москва
0
0
vshvetsov
Да,конечно. Описка.
Mac Safari
 Москва
1
0
dimonbbb
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
If (! MyLAMP1)
MyLAMP1 = new

Инициализировать статическую переменную null-ом
Windows Firefox
 Москва
1
0
vshvetsov
Флаг first_pass - лишний. Лучше при объявлении переменной инициализировать нулём, а в цикле проверить:
if (!MyLAMP) MyLAMP new ...
А, смотрю, уже написали. Это со всех точек зрения правильно, корифеи C учат:
- всегда сразу инициализируй объявляемые переменные,
- избегай проблем синхронизаици состояния разных переменных. Т.е. не флаг показывает, создан ли объект, а объект всегда либо создан и есть, либо указатель ноль. Особо это полезно, когда объект создаётся и удаляется неоднократно. При удалнии надо занулять указатель. И поэтому самое правильное - всегда использовать "спрятанные" указатели - объекты типа смарт-пойнтеров.
Mac Safari Chrome
 Washington
0
0
Гена
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
Мне так больше нравится. Я последние лет 5-6 пишу на питоне, а питоновская философия, как известно, explicit is better than implicit

Впрочем, про инициализацию вы правы конечно, но наверное не надо запутывать человека сразу. Если повезет, может ему больше и не придется на плюсах ничего писать. Он же до сих пор как-то протянул без этого, вон сколько всего написал! :)
Windows Firefox
 Тольятти
0
1
oncle t
Слава Богу, никому уже давно не приходится ничего писать ни на каких сях, это исключительно вопрос выбора и извращённого вкуса.
Linux Safari Chrome
 Санкт-Петербург
1
0
LLeo
Я пишу код под микрочипы. Посмотрел бы я на вас, как бы вы его писали на Java или Питоне.

Я знал людей, которые писали под esp8266 на JS. Они потом очень удивлялись, что код тормозит и чип виснет и ничего не помогает. А у меня они по полгода работают без перезагрузки.
Windows Firefox
 Тольятти
0
0
oncle t
Я про раст, конечно же. А то, может, уже и го допилили.
Linux Safari Chrome
 Санкт-Петербург
1
1
LLeo
Удачки. Похвастайтесь нам потом лично напрограммированными микрочипами на расте, вместе посмеемся.
Linux Firefox
 Германия
1
0
Igel
у меня товарищ под STM32 и ARM пишет.
можно компилять код под всё, умеет llvm.
http://llvm.org/doxygen/Triple_8h_source.html
судя по тому, что можно и avr, значит и под ардуину вполне возможно код собрать.
Windows Safari Chrome
 Москва
0
0
Кондратий
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
Вообще, Раст потихоньку проникает в мк. https://rust-embedded.github.io/book/

Но я бы пока поостерегся им пользоваться.
Linux Safari Chrome
 Санкт-Петербург
1
1
LLeo
Проникает, но раньше, я полагаю, в микроконтроллеры проникнет Линукс и 1024кб ОЗУ :)
Windows Safari Chrome
 Домодедово
0
0
id
ESP32 уже здесь - стоит не то чтоб сильно дороже чем ESP8266, но совсем другой зверь.

А для тех, кому и этого мало, есть полный спектр всяких *pi :)
Лично я пользую OrangePi во всяких вариантах, начиная с Zero.
Linux Firefox
 Boulder
1
0
Михаил (#1684620)
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
Вы какую-то фигню гоните про Rust. Он точно так же компилируется в машинный код, как и С++, но имеет более строгую систему типов и работы с памятью, а также лучшие проверки на этапе компиляции, что всё вместе позволяет компилятору лучше оптимизировать код и отлавливать косяки в процессе компиляции.
Windows Safari Chrome
 Москва
0
1
Кондратий
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
А еще, а еще я видел и трогал руками Бейсик и Паскаль для AVR. Только они платные (бесплатно ограничивается размер кода). Но все это тлен: gcc надежная, проверенная миллионами и вылизанная штука, а всеми этими паскалями-растами пользуется пяток калекэнтузиастов, там наверняка багов видимо-невидимо.
Mac Safari Chrome
 Киев
0
0
lim
Вот кстати про JS. Есть такая штука - NodeMCU - это такой nodeJS для всяких ESP. Так вот там аналогичную задачу уже решили - во всяком случае можно зайти на девайс по UART и написав несколько строк на жабаскрипте назначить на пин функцию (или драйвер).
Я не рекомендую этим пользоваться, так как согласен что оно нестабильное. Но во всяком случае можно заглянуть в сорцы и посмотреть как это сделано там.
Кстати, написано оно на чистом C. так что C++ для этого будет лишним.
Windows Firefox
 Москва
2
0
DrGluck
Ну ок, я вот пишут под amd64 на голом С. Вот прямо сейчас, в эту минуту. И этой моей программой пользуется довольно много народу (по меркам авиасимуляторов). Вот мы все извращенцы, да?
Что касается Java, то на микроконтроллерах с ней как-то не сложилось. У нас там всё про такты и 8K памяти (иногда даже меньше килобайта).
Windows Firefox
 Тольятти
0
0
oncle t
Судя по тому, что вы всё сравниваете с джавой, я бы предположил, что вам за сорок и выбора у вас не было, а теперь уже лень новое изучать.
Если я ошибаюсь, то расскажите, пожалуйста, почему вы джаву сюда приплели, исследовательский интерес.
Windows Firefox
 Вологда
1
0
blinkfrog
Это не вопрос вкуса.
Все эти ваши новомодные интерпретируемые языки и виртуальные среды исполнения, как раз, одна из причин, почему современные компы в тысячу раз быстрее, чем 20 лет назад, а ОС и программы грузятся и работают примерно так же, как и тогда, а также почему сейчас система с SSD работает, как еще несколько лет назад с HDD.
Основной программный продукт, над которым наша компания работает, использует C# и C++. Обычное дело, когда один и тот же код, будучи перенесенный с C++ в C#, работает в два (иногда и больше) раза медленнее (и наоборот). Да, если попотеть, можно сделать код на C# побыстрее (и все же медленнее, чем на C++), но тогда приходится отказываться от многого из того, что в C# преподносится как преимущества (например, safe-код без указателей, GC, большая часть стандартных классов). Так что это все глупости - про вопрос выбора.
Mac Safari Chrome
 Washington
0
0
Гена
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
Ну вы же наверняка сами знаете, каков ответ: языки программирования - это инструменты для достижения цели. Большинству нужно достичь конкретной цели наиболее дешевым способом, и практика показывает, что дешевле использовать максимально удобный и простой язык. Да, производительность теряется.
Windows Safari Chrome
 Москва
0
0
ew-gny
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
Все эти ваши новомодные интерпретируемые языки и виртуальные среды исполнения, как раз, одна из причин, почему современные компы в тысячу раз быстрее, чем 20 лет назад
Думаю, здесь стоит поставить точку.
Windows Safari Chrome
 Донецк
0
0
dms
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
Разве что в серверных узлах, а в потеребительском сегменте прогресс двигают игори, у которых 90% кода — обычное цэ-пэ-пэ, без тонн синтаксического сахара в ущерб производительности.
Windows Safari Chrome
 Израиль
2
0
б
> никому уже давно не приходится ничего писать ни на каких сях

Категоричненько. Смешно.
Windows Safari Chrome
 Москва
1
0
Кондратий
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
Судя по описанию задачи, создавать объекты стоит в setup(), а не в loop(), чтобы не городить огород.
Mac Safari Chrome
 Washington
0
0
Гена
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
Судя по описанию задачи, решать нужно поставленную задачу. :) В условии сказано, что пины появляются в loop. Дальше наверное автор разберется как улучшить дизайн, вопрос же был не об этом, а о том как разделить декларацию и инициализацию по сути. Ну и вторичный вопрос был про пополнение списка устройств на ходу.
Linux Ubuntu Firefox
 Санкт-Петербург
1
0
LLeo
Гена, а можно тогда еще вопрос?

Ситуация: мне в ESP8266 в рамках созданного языка нужно чтобы в процессе работы пользователь в своих скриптах имел возможность назначать ПЕРЕМЕННЫЕ, и система с ними работала, например:

set VERSION 4.01
set СтрочкаПриветствия 
Добрый день!


Количество переменных, длина их имен и значений - всё это заранее неизвестно. Удаление переменных не предполагается (хотя по окончании использования особенно длинных значений я рекомендую пользователю присваивать переменной пустое значение).

На данный момент я использую:

а) Тип String (и имена и значения, даже цифровые, - текстовые строки)
б) Заранее ограниченное количество 50 переменных

Выглядит эта структура в глобальной области так:

#define NSET 50

struct JeniferSettes // шаблон базы переменных
  
String n="";
  
String v="";
};

JeniferSettes Settes[NSET];


Каждый элемент этой структуры (проверял) мне обходится в 24 байта HEAP. ОЗУ у меня дорогое, его мало.
То есть, если у моего пользователя реально переменных 7, а не 50, то я теряю целый килобайт ни за что. А если кому-то не хватит 50 переменных и мне придется расширить максимальное число до 100, то еще один килобайт потерян.

Мне это не нравится, и я вижу для себя два пути совершенствования:

Первый путь - избавиться от структуры и от непонятного String, по-старинке выделить себе malloc область в байтах и самостоятельно размещать в ней переменные сплошным байтовым потоком, перемежая нулями [имя1][0][значение1][0][имя2][0][значение2][0]. По этой базе потом несложно пробегать и искать нужные имена. При заведении новой переменной - наращивать объем цельной области через malloc. При изменении значения переменной на две буквы - двигать всю структуру на две буквы. И так далее.

Но что-то мне подсказывает, что это неправильный метод, потому что в какой-то момент в свободной памяти у malloc может не найтись места для добавления новых 50 байт к непрерывным 10000, потому что там впереди что-то уже в памяти сидит. И свободной кучи 10050 байт просто нету.

В то же время как (по моим предположениям, я точно не знаю, как это устроено) struct { String n; String v; } может не иметь таких проблем, поскольку оперирует указателями, а сами строки размещает где есть место, благо каждая по отдельности не 10кб длиной.

В этом случае конечно эффективнее делать структуру нулевой длины и увеличивать ее по мере необходимости завести новое значение. Это возможно? Это будет экономичнее? Каким синтаксисом лучше воспользоваться, как накидать такой пример?
Mac Safari Chrome
 Washington
0
0
Гена
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
Ну сходу я бы использовал тот же самый подход, что вы выберете для драйверов, разницы вроде нет: драйвер это или переменная:

// create new variable
JeniferSettes * var1 = new JeniferSettes;

// modify variable
var1 -> n = "var1 name";
var1 -> v = "var1 value";

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

P.S. Code block пока не очень работает - попробуйте код выше, он неверно отображается.
Linux Firefox
 Mt Laurel
0
0
Михаил (#1684620)
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
> пользователь в своих скриптах

Вроде бы для этого вашего ESP8266 уже есть человеческая Lua. Можно было бы вместо велосипедостроения написать к ней библиотеку или модуль с вашими штуками, а дальше пусть пользователь в своих скриптах что угодно делает.
Linux Safari Chrome
 Санкт-Петербург
0
0
LLeo
Предсказуемый коммент, и очень долго объяснять. Наверно поэтому я до сих пор не публиковал свою систему и сомневаюсь, что это надо делать. Я свои задачи прекрасно решил, а остальные пусть мудохаются с местной Lua, известной своей глючностью.
Linux Ubuntu Firefox
 Санкт-Петербург
1
0
LLeo
> Есть тут в коментах code block?

Вы правы, я сейчас сделал. Пишем:

[code]
for(i=0;i<10;i++) {
   fopen(i,0);
   if(k < 0) exit;
}
[/code]

(я вставил невидимые символы чтобы верхний пример не сработал)

И получаем:

for(i=0;i<10;i++) {
   
fopen(i,0);
   if(
0) exit;
}
Mac Safari Chrome
 Washington
0
0
Гена
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
Пока не совсем работает. Вот этот код

// create new variable
JeniferSettes * var1 = new JeniferSettes;

// modify variable
var1 -> n = "var1 name";
var1 -> v = "var1 value";

отображается так:

// create new variable
JeniferSettes var1 = new JeniferSettes;

// modify variable
var1 -<font color=gray>> = </font>"var1 name";
var1 -<font color=gray>> = </font>"var1 value";
Linux Firefox
 Mt Laurel
0
0
Михаил (#1684620)
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
У Лео какое-то исключительно кривое выделение цитат. Оно зачем-то ищет символ «больше» где попало вместо начала строки и прерывает выделение по любому чиху (в частности, перед символом «"») вместо конца строки. А теперь ещё оказывается, что и внутрь другого форматирования пытается лезть.
Linux Safari Chrome
 Санкт-Петербург
0
0
LLeo
Ну тогда хрен с ним, не буду городить хаки чтобы привинтить стандартную highlight_string() к особенностям кода комментариев. Пусть или так останется или уберу опцию вообще. Нет возможности это сделать нормально и для языка С к тому же.
Linux Safari Chrome
 Зеленоград
0
0
kryksyh
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
Если параметры работы действительно нельзя выяснить на этапе инициализации в setup (а может быть можно?), то тогда не остается ничего кроме new/delete в loop. Главное предусмотреть блоки инициализации и деинициализации, чтобы не создавать объекты на каждой итерации.

Еще можно посмотреть в сторону placement new, это позволит создавать объекты на заранее выделенной памяти, таким образом можно будет гарантированно избежать проблем с фрагментацией памяти.

Но лучше все таки найти способ создавать всё что нужно в setup.
Linux Safari Chrome
 Зеленоград
0
0
kryksyh
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
Что касается синтаксиса, то что то вроде такого:
#include <vector>
std::vector <LampDriver*> lamps;

void loop(){

// Добавляем новый объект
lamps.push_back(new LampDriver(PIN1));
...
//используем объект
lamps[0]->Toggle();
...
}
Windows Firefox
 Москва
0
0
Abstraction
Это как-то ой, особенно если нам важно корректное уничтожение объекта LampDriver при завершении программы. Я бы сказал, что если у нас есть стандартные контейнеры, то имеет смысл использовать либо std::vector<LampDriver>, либо std::list<LampDriver> если требования для std::vector не выполнены (я полагаю что доступ к std::list<LampDriver>::emplace_back(...) есть).
Linux Safari Chrome
 Москва
0
0
LLeo
Уничтожение объектов не требует задача. Пришел пользователь, включил систему, дал задачи, на каких пинах у него сегодня какое оборудование под драйверами, и работает до выключения системы. Примерно так, сильно упрощая.
Mac Safari Chrome
 Washington
0
0
Гена
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
Просто помните, что объект, созданный при помощи new будет в памяти болтаться вечно, пока вы его не удалите при помощи delete. Эта память будет занята, даже если этот объект уже давно не нужен, не используется, и все указатели и ссылки на него уже не существуют.

Если это не проблема, то ок. Но в целом считается хорошей практикой, чтобы все объекты были удалены в конце, а лучше - сразу как они перестали быть нужны.
Linux Safari Chrome
 Москва
0
0
LLeo
Да, конечно.
Ситуации, когда объект больше не нужен, возникнуть не может.

По крайней мере я пока себе не представляю железку, у которой в процессе работы переподключаются к одним и тем же контактам разные устройства, то лампы, то шаговые двигатели, то термометры или mp3-плееры... Теоретически можно придумать такое устройство на электронных коммутаторах при острой недостаче пинов... Но очень не хочется :)
Linux Safari Chrome
 Киев
1
0
Vitaliy
> Но в целом считается хорошей практикой, чтобы все объекты были удалены в конце, а лучше - сразу как они перестали быть нужны.

В целом - да, но что касается именно Arduino и прочих embedded устройств, то там с удалением объекта нужно быть очень и очень аккуратным. В них нет дефрагментатора памяти и при активном использовании new/delete вполне реально получить ситуацию, когда вроде и полно свободного места, но нет достаточно длинного непрерывного куска памяти, чтобы разместить новый объект.

Когда мне впервые пришлось дописывать один проект на Atmel, меня сильно изумило, что мой предшественник везде использует только статические объекты, далеко не сразу дошло, почему.
Linux Safari Chrome
 Санкт-Петербург
0
0
LLeo
Это не Atmel и не STM. Это гораздо более сложный чип 8266.
Linux Firefox
 Латвия
1
0
Heisenberg2
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
esp8266 от stm32 или avr32 отличается в первую очередь встроенным вайфаем и отсутствием нормальной документации. Каких-то принципиальных различий в архитектуре нет. Кроме того это софтварная функция.
Linux Safari Chrome
 Санкт-Петербург
0
0
LLeo
Речь шла про менеджмент памяти. Наличие WiFi с полным функционалом по обслуживанию точки доступа, клиента WiFi и хостинга внутреннего веб-сайта - как бы подразумевает очень высокий уровень организации, для которого правильное распределение памяти уже как-то само собой, ибо без него не выжил бы проект такого уровня.
Linux Firefox
 Латвия
0
0
Heisenberg2
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
Я не уверен, что я правильно понимаю, что Вы имеете в виду под "очень высоким уровнем организации".

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

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

Чисто практически, я бы не стал закладываться на наличие дефрагментации памяти на ESP. Есть много способов избежать ее необходимости. Например (не самый лучший, но один из самых простых) полная очистка и пересканирование конфигов при любом их изменении, если, конечно, это приемлемо с точки зрения алгоритмов работы приложения.
Mac Safari Chrome
 Washington
0
0
Гена
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
Ну мы как-то свалили все в кучу: и проблемы языка, и проблемы компилятора, и ограничения хардвера. Я все же говорю про язык си++. То, о чем вы говорите, это наверное сочетание недочетов компилятора и хардвера. С практической стороны вы наверное правы, не знаю деталей этого хардвера.
Mac Safari
 Домодедово
2
0
id
std
В embedded
На 2х КИЛОбайтах памяти
Ага.
Linux Safari Chrome
 Москва
1
0
LLeo
А мне чуется тут самое разумное зерно. Хотя пока понятия не имею, что такое std::vector но чую, что оно.
Windows Firefox
 Москва
1
0
vshvetsov
Практически уверен, что ничего страшного не будет в смысле необходимой памяти. Включение std вовсе не означает, что в вашу программу будет втянута куча всего лишнего. C++ в этом смысле очень smart!
std::vector - это "шаблон" класса, в предложенном коде будет "сгенерирован" на его основе конкретный класс std::vector<LampDriver*> (это так называют класс, созданный по шаблону). Этот класс можно охарактеризовать, как динамический массив для хранения указателей на LampDriver (не самих LampDriver). Это - предельно простой класс (именно, когда массив указателей, а не самих объектов). Вам из него нужны только базовые функции, поэтому подлинкован будет самый минимум. Причём, за счёт умения C++ вставлять простые функции inline (вставлять операторы прямо в тело вашей функции, без вызова библиотечных функций), будет втянуто ещё меньше, чем я написал.
Windows Firefox
 Австралия
0
0
iugssf
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
std не весь плох для embedded, std::vector достаточно маленький, если не собирать в debug версии, и с ним вполне можно обращаться так, чтобы не фрагментировать память
Linux Safari Chrome
 Москва
1
0
LLeo
Нет, в коде в сетапе ничего вписать не получится - моя задача не конечная программа, а некий фреймворк, упрощенный язык программирования. Пользователь описывает свое оборудование и алгоритм в текстовых файлах специальными скриптами, задача кода - лишь исполнять инструкции по этим скриптам.
Linux Safari Chrome
 Тверь
0
0
juunitaki
https://forum.arduino.cc/index.php?topic=60531.0

вот тут человек взял и поправил библиотеку, чтобы можно было менять пин налету.

То есть он делает так:

OneWire w1(10);

а потом так:

w1.setpin(11);

О, там 2011 год. Смотрим OneWire 2.3.5 (https://www.arduinolibraries.info/libraries/one-wire):

public:
OneWire() { }
OneWire(uint8_t pin) { begin(pin); }
void begin(uint8_t pin);

Можно создать объект без инициализации пина:

OneWire w1;

А потом сделать

w1.begin(11);
Linux Safari Chrome
 Москва
1
0
LLeo
Библиотек сотни, все не перепишешь. Хочу найти универсальный принцип работы с ними.
Windows Safari Chrome
 Москва
0
0
Кондратий
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
Если эти текстовые файлы не будут меняться во время исполнения цикла, то я не очень понимаю, зачем создавать объекты внутри цикла...
Linux Safari Chrome
 Санкт-Петербург
1
0
LLeo
Меняться скорее всего не будут, но поступят в любой момент работы и в любом порядке.
Linux Safari Chrome
 Санкт-Петербург
0
0
Онаним
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
А спецификация языка конфигов уже опубликована или её можно менять? Чтобы, значит, сделать их удобнее для интерпретатора (какого-нибудь форта, который даже для avr8 имеется).
Linux Safari Chrome
 Санкт-Петербург
0
0
LLeo
Да там три оператора, а остальное функции устройств.
Windows Firefox
 Москва
1
0
Abstraction
LampDriver MyLAMP1(PIN1);
Этот код создаёт глобальную переменную MyLAMP1 и для её создания (в какой-то момент перед началом выполнения main()) вызывает конструктор LampDriver::LampDriver(int). Объект MyLAMP1 при этом - поскольку его количество (1) и его размер (sizeof(LampDriver)) известны в момент компиляции, - размещается в памяти, выделенной при запуске программы и выделение этой памяти на совести компилятора.
Если требуется создать N объектов, где N на момент компиляции неизвестно, то вместо компилятора выделением памяти придётся заняться программисту.

Если не использовать стандартную библиотеку (если использовать - см. std::vector<LampDriver>), то самый простой способ - создать пару глобальных переменных:
LampDriver* g_lamps = nullptr; //указатель на начало массива ламп либо nullptr если ламп нет и/или они недоступны
size_t g_lampsCount = 0; //число ламп в массиве

Далее, в конце main() (или в любом ином месте, после которого использования ламп не ожидается) пишем:
delete[] g_lamps; //освобождаем память
g_lamps = nullptr;
g_lampsCount = 0; //поддерживаем инвариант "описание глобальных переменных врать не должно никогда"

Далее, в точке, в которой выяснилось, что там у нас с лампами, пишем что-то вроде:
if(lampsTotal == 0) return; //ламп нет, остальное делать нельзя - new Type[0] писать запрещено
if(g_lamps != nullptr) exit(1); //WTF, так быть не должно
g_lamps = new LampDriver[lampsTotal]; //выделяем память под все объекты ламп
g_lampsCount = lampsTotal;
for(size_t i = 0; i < g_lampsCount; ++i){
  g_lamps[i] = LampDriver(lampPins[i]);
}
Внимание: в третьей строке lampsTotal раз будет вызван конструктор LampDriver::LampDriver(void) для создания "пустых" объектов; в шестой строке они поштучно замещаются новыми объектами. Если такового конструктора или оператора присваивания для типа не существует, то это засада, хорошего решения задача не имеет, см. ниже.
Также, следует уточнить, какое ожидается поведение среды при невозможности выделения памяти. Поведение по умолчанию - возбуждение исключения std::bad_alloc, но в случае Arduino возможно старое поведение, когда g_lamps просто присваивается nullptr; во втором случае четвёртую строку следует заменить на
if(g_lamps != nullptr) g_lampsCount = lampsTotal;
else g_lampsCount = 0;

Окончательно, всякий раз когда нужно что-то сделать со всеми лампами, пишем
for(size_t i = 0; i < g_lampsCount; ++i){
//что там надо сделать с g_lamps[i]
}

Если тип LampDriver позволяет создать новые объекты только конструктором с параметрами, то выделение памяти под массив объектов этого типа, к сожалению, невозможно. Можно либо руками реализовывать связный список (это сравнительно громоздкое решение, я предпочту не писать его без нужды), либо сделать следующий сомнительный трюк. Вместо выделения памяти под массив объектов в строке 3, выделить кусок памяти размером lampsTotal*sizeof(LampDriver)
g_lamps = reinterpret_cast<LampDriver*>(new unsigned char[lampsTotal * sizeof(LampDriver)]);
И затем вместо присваивания в строке 6 (которое писать в этом случае нельзя) пишем принудительную инициализацию "сырой" памяти новым объектом:
new(&g_lamps[i]) LampDriver(lampPins[i]);
Фокус хакерский и в общем случае приводит к неопределённому поведению из-за требований выравнивания (alignment), однако имеет неплохие шансы сработать.
Linux Safari Chrome
 Москва
0
0
LLeo
Спасибо!
Очень сложно, пытаюсь разобраться.
А что такое LampTotal? По условиям задачи количество ламп заранее неизвестно. Пользователь может послать команду инициализировать две лампы на пинах 3 и 5, затем ещё одну. Или ещё триста. Или вообще ни одной лампы, потому что у него не лампы, а моторы или 300 вентиляторов на тех же пинах, но уже с драйвером вентиляторов. А вот удаление не предусматривается.
Mac Safari Chrome
 Washington
0
0
Гена
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
Из любопытства, а что непонятно или не подходит в моем самом первом коменте?
Linux Safari Chrome
 Москва
1
0
LLeo
Ваш самый первый коммент прекрасен. Это первое, что я сделаю, когда вернусь в город к компьютерам. По крайней мере, так я смогу указать нужный пин, не прошивая его в коде.

Но дальше передо мной встанет вопрос, как реализовать массив из неограниченного числа ламп (от 0 и далее), выделяя память лишь при необходимости и обозначив каждую именем String или char. И вот когда я буду пытаться рисовать такую структуру, динамически выделяя память всякий раз при добавлении элементов, тут мне пригодится все остальное, что мне советовали.
Mac Safari Chrome
 Washington
0
0
Гена
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
Если есть возможность использовать стандартную библиотеку темплейтов (STL - standard template library), то проще всего использовать std::vector<LampDriver*>, как уже сказали выше. Это по сути массив произвольного типа и динамически меняемого размера, в вашем случае это будет массив указателей на объекты драйверов.

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

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

Ну и наконец, если уж хочется очень все правильно делать, можно динамически руками выделять память под каждый новый адрес с помощью malloc. Но по-моему это перебор для вашей задачи.
Linux Safari Chrome
 Москва
2
0
LLeo
Насчёт памяти - не уверен, что там все плохо. Я работаю не с классической Ардуино/AVR, а с чипом ESP8266. Памяти там мало (около 32кб доступного мне ОЗУ), но вообще это сильный чип, и работает он под управлением внутренней системы RTOS над сложными задачами - TCP/IP стек, точка доступа раздает WIFI, веб-сайт раздает контент, и так далее, и все это параллельно выполняется с моими задачами, мой код там как бы в фоне выполняется с большим приоритетом. То есть, задачи чипа и его RTOS - не "зажечь лампочку", мягко говоря. И у него очень высокая надёжность, они к меня месяцами не перегружаются. Поэтому я предполагаю, что там все очень неплохо со внутренним менеджером памяти.

Скажем так: чем больше мне удастся сэкономить памяти, ухитрились не инициализировать те структуры и узлы, что в этот раз не понадобятся, - тем длиннее сэмплы mp3 я смогу воспроизводить в динамике :)

Код свой я кстати скоро наверно выложу в доступ. Там получилось интересная система разработки на внутреннем языке. Бинарник один - тестовые скрипты разные, любая задача решается просто, это удобно.
Windows Safari Chrome
 Домодедово
1
0
id
С памятью всегда все плохо, пока ты не имеешь уверенности что ось свопнет ради тебя пару-другую гигабайт. :)

Везде, где тебе надо заботиться о памяти - о ней надо заботиться.
Windows Safari Chrome
 Самара
0
0
975
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
Леонид, почему бы не перейти на esp32?
Linux Safari Chrome
 Санкт-Петербург
0
0
Онаним
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
Способность не перезагружаться годами — ещё не признак надёжности. К примеру, циски тоже могут непрерывно крутиться хоть 10 лет, однако это не мешает периодически отваливаться функциям, в реализации которых течёт память. Через несколько минут они, правда, восстанавливаются, но с потерей состояния. Этакая перезагрузка без остановки.
Windows Safari Chrome
 Домодедово
1
0
id
> Но дальше передо мной встанет вопрос, как реализовать массив из неограниченного числа ламп (от 0 и далее), выделяя память лишь при необходимости и обозначив каждую именем String или char.

Классический динамический однонаправленный список из школьного учебника?

Объект - пусть даже просто структура, если нравится, имеющая следующие поля: имя (твой String), указатель на "рабочий объект" (лампа или что там), указатель на следующий элемент списка (NULL для последнего элемента).

Надо добавить элемент в список - new новый элемент списка, перенастроить указатели, new новый "рабочий объект".

А дальше делаешь метод, который обходит список и ищет по имени нужную запись - ты ведь это хочешь в итоге? - и ты получаешь что-то вроде devlist.findByName("My red lamp")->blink();
Linux Safari Chrome
 Санкт-Петербург
0
0
LLeo
Видимо так и буду делать.
Windows Firefox
 Москва
0
0
Abstraction
Я предположил, что есть только два состояния - "лампы не инициализированы" и "мы уже знаем, сколько ламп надо".
Если это не так, то, боюсь, вам придётся разбираться с тем, как работает динамическая память.

new T[count] - это выражение, которое выделяет (в динамической памяти, также называемой "кучей" (heap)) столько места, чтобы туда влезло count объектов типа T подряд, инициализирует все эти объекты конструктором без параметров T::T() (если он определён, иначе выражение некорректно) и возвращает указатель (адрес) первого объекта (если count равно 0, то результат вычисления выражения - то ли не определён, то ли генерирует исключение, не помню точно).
Что при этом важно понимать: с точки зрения кучи, место сразу за последним из объектов "свободно" - она может при выделении найти кусок, сразу за которым идут чьи-то ещё данные, либо положить туда чужие данные впоследствии. Поэтому единожды выделенный блок памяти в общем случае невозможно "расширить" без перемещения.
Вариант перемещения (очень неэффективный в том смысле, что при добавлении N объектов произойдёт порядка N^2 операций):
void addLamp(int pin){
  LampDriver* resizedLamps = new LampDriver[g_lampsCount + 1];
  using std::swap;
  for(size_t i = 0; i < g_lampsCount; ++i){
  swap(resizedLamps[i], g_lamps[i]);
  }
  resizedLamps[g_lampsCount] = LampDriver(pin);
  ++g_lampsCount;
  delete[] g_lamps;
  g_lamps = resizedLamps;
}

Словами: мы выделяем в куче новый кусок, размера на 1 объект больше (при этом создаётся g_lampsCount+1 новых объектов с помощью конструктора без параметров); затем мы меняем местами новосозданные объекты с существовавшими (обмен вместо присваивания потому что а) это _может_ быть быстрее и/или гарантировать отсутствие исключений; б) я не исключаю, что копирование объекта LampDriver с заданным PIN и уничтожение одной из копий _может_ привести к проблемам); затем заменяем последний объект нового массива новосозданным объектом LampDriver, затем освобождаем старую память и пишем в g_lamps адрес первого элемента нового массива.
Код довольно легко обобщить на случай переданного массива пинов:
void addLamps(const int* pins, size_t pinsCount){
if(pinsCount == 0) return;
LampDriver* resizedLamps = new LampDriver[g_lampsCount + pinsCount];
using std::swap;
for(size_t i = 0; i < g_lampsCount; ++i){
swap(resizedLamps[i], g_lamps[i]);
}
for(size_t i = 0; i < pinsCount; ++i){
resizedLamps[g_lampsCount + i] = LampDriver(pins[i]);
}
g_lampsCount += pinsCount;
delete[] g_lamps;
g_lamps = resizedLamps;
}
В обоих случаях опущена проверка на переполнение при увеличении g_lampsCount.
Если std::swap<LampDriver> недоступно (что странно), можно написать свою функцию в том же пространстве имён что и addLamp(s):
inline void swap(LampDriver& a, LampDriver& b){
LampDriver tmp(b);
b = a;
a = tmp;
}
Легко видеть что это универсальный вариант; если особенности типа LampDriver позволяют более эффективный обмен, функцию можно улучшить. LampDriver& - это _ссылка_ на объект типа LampDriver, такой синтаксис вызова позволяет передать объекты в функцию так, что они могут быть модифицированы внутри неё (я упрощаю, но для данного замечания этого описания достаточно).
Linux Safari Chrome
 Москва
1
0
LLeo
Спасибо, буду осмыслять.
Нет, списка пинов тоже нет ни в какой момент. Команды подключить к тем или иным пинам то или иное оборудование поступают поочередно с неизвестными интервалом. Будем для простоты считать, что их присылают в разных sms :)
Windows Firefox
 Москва
1
0
Abstraction
Ну тогда при больших количествах этот вариант плох (на самом деле он всё равно при больших количествах плох, потому что мы выделяем память одним куском, в какой-то момент непрерывного куска такого размера у кучи может и не оказаться).
Вариант "лучше" называется "связный список" (linked list), но вот его реализация - и в части заполнения, и в части использования, - заметно хитрее. Общая идея: мы создаём новый тип "узла" (node)
struct LampDriverNode{
  LampDriver driver;
LampDriverNode* next;
LampDriverNode(int pin) : driver(pin), next(nullptr) {}
}
который включает один объект и указатель на "следующий" узел.

Глобально мы храним указатель на "первый" узел:
LampDriverNode* g_firstLamp;

В конце работы удаляем все узлы последовательно:
if(g_frstLamp != nullptr){
LampDriverNode* p;
do{
p = g_firstLamp->next;
delete g_firstLamp;
g_firstLamp = p;
} while(p != nullptr);
}
(Да, это выглядит очень странно. Попробуйте нарисовать объекты LampDriverNode как прямоугольники, в которых из точки с названием next исходит стрелочка, указывающая либо на другой прямоугольник, либо на ∅, возможно так будет понятнее.)

Добавление лампы упрощается:
void addLamp(int pin){
//проще добавлять в начало списка
LampDriverNode* lamp = new LampDriverNode(pin);
lamp->next = g_firstLamp;
g_firstLamp = lamp;
}

Обход ламп тогда выглядит так:
//...
for(LampDriverNode* p = g_firstLamp; p != nullptr; p = p->next){
p->driver.blink();
}
Windows Safari Chrome
 Домодедово
1
0
id
Потенциальные грабли, какие тут возможно могут выплыть - это ситуация, когда в качестве LampDriver требуется в одном списке держать объекты настолько разных классов, что у них даже общего предка нет...

"моргалка лампочкой", "вращалка шагового двигателя"... да и тот же поток - "посылалка данных по последовательному порту"... Так ли легко они встанут в общий список?
Windows Firefox
 Москва
1
0
Abstraction
(пожимает плечами)
Тогда нет и одного общего действия для всех элементов списка. Если действия обобщаются (типа "отдать записанную текстовой строкой команду, если она осмысленна для данного устройства"), то можно (и нужно) написать структуру-обёртку с единым интерфейсом. Если не обобщаются - то набор таких объектов как единая сущность изначально лишён смысла. В любом случае, ИМХО, мы при этом уходим за пределы того, что можно более-менее понятно рассказать в формате древовидных комментариев без подсветки синтаксиса.
Linux Ubuntu Firefox
 Москва
1
0
Adamos
Если я правильно понял и Леонид пытается создать библиотеку, позволяющую перевести текстовый скрипт в реальные действия с железками - ему все равно придется все драйвера с их особенностями инкапсулировать в классы с интерфейсом, позволяющим вызвать метод по его текстовому имени, а заодно проверяющим, доступно ли такое действие вообще. Большая часть кода таких оберток будет общей для всех классов, вот вам и базовый класс для этого списка.
Другое дело, что, не зная плюсов, городить на них подобные системы... смело, что тут скажешь...
Mac Safari Chrome
 Washington
1
0
Гена
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
Это - типичный случай, когда "любая проблема решается введением еще одного уровня абстракции". :) Кажется об этом уже сказали - Ллео сделает базовый класс для всех.
Windows Firefox
 Москва
2
0
vshvetsov
Мне кажется, в предложенном решении плохо то, что создаётся динамический массив самих объектов LampDriver, а лучше бы создавать динамический массив указателей на эти объекты, при том, что каждый объект создаётся индивидуальным оператором new, так что при resize массива никакой необходимости вот этих swap-ов не возникнет. (Это то, что в старом добром борланде называлось "непрямые контейнеры"). Сам массив:
LampDriver** Lamps = new [10] (LampDriver*);
int size = 0; // столько объектов уже вставили
int capacity = 10; // на столько отведено места
(чтобы без лишней фрагментации, лучше не перезанимать память на каждый новый объект, а расширять массив сразу на 10 или другую подходящую константу, исходя из примерных ожиданий)
Добавление нового объекта тогда просто:
if (size == capacity) { // пора расширять массив
capacity += 10;
LampDriver** tmp = new (LampDriver*) [capacity];
// копировать указатели - это вам не сами объекты, это просто и быстро
memcpy(tmp, Lamps, size*sizeof(LampDriver*));
delete [] Lamps; // старые долой
Lamps = tmp;
}
// далее само добавление нового объекта в массив.
LampDriver[size++] = new LampDriver[pin];
Динамический массив готов. Думаю, такое решение проще, чем переход к связным спискам. Ну, если нужно аккуратно удалять объекты, то нужно чуть доделать - инициализировать отводимую память нулями, написать функцию удаления (со сдвигом остающегося хвоста на позицию влево), написать "деструктор". Кажется, Леонид пишет, что удалять их не нужно до конца работы программы, а тогда можно не возиться.
Windows Firefox
 Москва
0
0
Abstraction
Не очевидно. Не исключено что LampDriver сам хранит только пару адресов, а в этом случае мы просто добавили лишнюю косвенную адресацию (если LampDriver - это эвфемизм для чего-то, что способно хранить объекты разных типов разных размеров, то так оно и есть). Кроме того, понятно что всё это можно улучшать (a.k.a. "сейчас мы напишем свой STL"), но задача-то в том, чтобы это осталось вполне понятным автору блога.
Linux Safari Chrome
 Самара
1
0
Redmi
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
Я советую почитать документацию по языку.
Linux Firefox
 Израиль
1
0
Mason
1) Использовать С++ для Ардуино - это как нассать себе в штаны на морозе для сугреву. Сначала быстро и приятно согреваешься, но потом и сам страдаешь, и окружающим неудобно.

2) То, о чем ты спрашиваешь, называется "динамическое выделение памяти". В общем случае в С для этого используются malloc()/free(). Не исключено, что тебе надо что-то более специализированное. В любом случае это вопрос не языка, а системы и имеющихся ресурсов.

3) Раз уж ты выше спросил про ссылки (которые появились в С++ как альтернатива указателю), то там правило одно - никогда не используй ссылки, только указатели. Ссылка - это указатель, который выглядит как локальная переменная. Ты смотришь на код, думаешь, что изменяешь локальную переменную, а где-то на другом конце проекта все летит к херам.
Linux Ubuntu Firefox
 Москва
1
2
Adamos
Обратное правило: никогда не используй указатели, только ссылки - позволяет перенести боль и ужас с этапа выполнения на этап программирования. Особенно новичкам.
Windows Safari Chrome
 Домодедово
1
0
id
Смотря откуда кто пришел в C++

Тем, кто пришел из ассемблера, указатель стократ понятнее.
Linux Ubuntu Firefox
 Москва
1
0
Adamos
Когда архитектура перестает влезать в голову и приходится применять паттерны, просто чтобы понимать, кто на ком стоял, "понятность" указателей уже не так важна, как трудноуловимые утечки из-за использования сырых указателей вместо ссылок, проблемы которых выявляются куда проще.
Новичок может накосячить и так, и эдак, но зачем привыкать к плохому?
Windows Firefox
 Москва
0
0
vshvetsov
Поскольку я полностью согласен с Mason (и даже целый пост выше накатал) то интересно узнать аргументы. Почему проблемы ссылок выявляются легче?
Да, сразу уточню: разумеется, программист никогда не должен объявлять локальных переменных типа указатель, а только "обёрнутые" указатели, то есть простенькие классы с конструктором и деструктором, наподобие auto_ptr, которые не приводят к утяжелению программы и гарантируют от мусорных указателей и замусоривания памяти.
С учётом этого замечания, чем синтаксис ссылок лучше?
Linux Ubuntu Firefox
 Москва
0
0
Adamos
Аналогично отличию высокоуровневых языков от низкоуровневых, ссылки отличаются от указателей тем, что ограничивают программиста в возможностях выстрелить себе в ногу. Причем не в момент выстрела, как умные указатели, а на этапе планирования операции ;)
Умные указатели позволяют иногда (только на полшишечки!) сделать reinterpret_cast, например. В прикладном коде, а не библиотечном.
Linux Firefox
 Boulder
1
0
Михаил (#1684620)
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
> Ты смотришь на код, думаешь, что изменяешь локальную переменную, а где-то на другом конце проекта все летит к херам.

В C++ есть унаследованный из C очень полезный модификатор const, причём даже стандартная библиотека приучает к его использованию. Он помогает от летания к херам даже при работе со ссылками гораздо лучше, чем указатели без него.
Linux Ubuntu Firefox
 Москва
0
0
Adamos
Если я правильно понимаю задачу, нужно:
1. Инициализировать драйвер конкретным пином и присвоить ему строковое имя.
2. По команде с этим именем найти и дернуть этот драйвер.

std::vector< std::pair< LampDriver*, char* > >
Пополняем его в п. 1
Перебираем в п. 2.
А в п. 3 перебираем и удаляем объекты, освобождая память, буде таковое действие понадобится.

По-моему, так. И ничего особенно страшного даже в условиях дефицита памяти компилятор тут не создаст.
Windows Safari Chrome
 Домодедово
0
0
id
Да может я и неправ... компиляторы ж давно на месте не стоят.
Сделать два варианта: с "ручным" односвязным списком, и с std::vector, собрать - да и посмотреть наглядно. Сколько кода тратится там и там при прочих равных, и сколько памяти жрет на скажем 20 элементов списка.
Linux Ubuntu Firefox
 Москва
0
0
Adamos
Ручной список и вектор будут особенно различаться в момент, когда предварительно выделенной под вектор памяти не хватит. Тут от юз-кейса больше зависит, что уместнее будет.

А еще, если этот кусок кода кому-то надо будет часто читать или править - наглядность STL-кода на плюсах (относительно ручного спагетти) сэкономит немало нервных клеток и убережет от банальных ошибок.
Linux Safari Chrome
 Санкт-Петербург
0
0
Svami Dhyan Nataraj
Попробую добавить свои 5 копеек в эту историю...

Тут надо понимать, как устроен вызов LampDriver MyLAMP1(PIN1);

Если это С++ (а в этом по хорошему надо убедиться) то оно работает примерно так: резервируется место под переменную типа LampDriver и после этого вызывается специальный метод класса LampDriver называемый конструктор, и этому конструктору передаются значения указанные в скобках. И этот конструктор вновь-создаваемую переменную инициализирует.

В приведенном примере PIN1 заранее определено, и поэтому зашито в #define. Но для конструктора это не важно, ему это придет как конкретное значение.

Соответсвено тебе надо перенести вызов конструктора не на этап объявления переменной, а на этап, когда ты будешь знать какой пин у тебя где.

И тут в комментариях как раз хорошо показали что это можно сделать через указатели. В global scope объявить указатель но объект, чтобы его все части программы нормально видели, и когда будет понятно как этот объект инициализировать, тогда и создавать новый объект, вот тут уже через вызов new.

Можно еще как-то по-другому более по Си-плюс-плюсному. Но общий смысл в том чтобы не совмещать объявление переменной и создания объекта как это сделано в примере...
Windows Safari Chrome
 Москва
1
0
Toxa
LampDriver* ErrorSignalLamp = NULL;

int ErrorSignalLampPin = LoadErrorSignalLampPinFromConfig();

if (ErrorSignalLampPin >= 0) ErrorSignalLamp = new LampDriver(ErrorSignalLampPin);

...

while(1) {
if (ErrorSignalLamp != NULL) ErrorSignalLamp->blink();

...
}

if (ErrorSignalLamp != NULL) delete ErrorSignalLamp;
Windows Safari Chrome
 Amsterdam
0
0
oppol
В Ардуине все выводы изначально назначены на ввод.
Если надо подключать лампы, то сначала назначить их все на вывод, а потом уже разбираться сколько и куда там ламп.
Linux Safari Chrome
 Санкт-Петербург
1
0
LLeo
Представьте, что это не лампы, а квантовые канделябры. Их двайвер при инициализации требует не пин, а ключ кармического портала.

Я не работаю с Ардуино в смысле AVR, у меня нет никаких ламп, имя драйвера я выдумал чтобы сберечь ваше время и не грузить ненужными подробностями.
Windows Safari Chrome
 Amsterdam
0
0
oppol
О да, время вы сберегли, хорошо придумали !
Windows Firefox
 Москва
0
0
DrGluck
Выход тут один - создавать объекты динамически и удалять их когда требуется. Выше уже несколько примеров привели.

Проблемы тут может быть разные. Например, в микроконтроллерах с маленьким количеством RAM обычно не очень приветствуется динамический менеджмент памяти, и может даже не быть операторов new и delete. Вот в МК от AVR (mega, xmega) этих операторов нет по-умолчанию, хотя можно добавить различные костыли на эту тему.
И это довольно забавно, потому что там есть C++11 со всеми своими плюшками, лямбды там, пользовательские литералы, enum-классы. А вот new нету, живите как хотите.

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

Ну а так выше правильно описали - создаём объект какого-то типа, в конструктор которого передаём параметры. Далее объект что-то делает. Поскольку у нас тут не многопоточное приложение, то наверное надо будет периодически вызывать функцию process() у каждого объекта. Это если у объектов есть машина состояний.
Windows Safari Chrome
 Домодедово
0
0
id
Мне кажется вы не очень поняли задачу )
"Нет никакой ложки лампы", есть необходимость в универсальном списке для совсем разных устройств, пополняемом асинхронно в процессе работы.
Windows Safari Chrome
 Amsterdam
1
0
oppol
Да-да, упоминание Ардуино, с жёстко прошитой схемотехникой, для построения универсального списка С++ очень помогло ;)
Windows Safari Chrome
 Домодедово
0
0
id
А упоминание скажем x86 с куда более жестко прошитой схемотехникой (попробуйте подключить светодиод напрямую к какой-нибудь ноге PCIe и поморгать им) - помогло бы больше? :)
Windows Safari Chrome
 Amsterdam
0
0
oppol
Давайте не будем придуриваться и исходить из того, что написано.
PCI шина кстати двунаправленная, в отличие от начальной конфигурации Ардуины.
Windows Safari Chrome
 Домодедово
0
0
id
Я просто не вполне понял ваш аргумент насчет "жестко прошитой схемотехники". Выводы ардуины легко перенастраиваются программно, в отличие от выводов основных шин современных x86 машин, так что о чем именно был ваш аргумент - ни разу не ясно.

Если вы хотели сказать "выводы надо настраивать", то разумеется надо. В топикстарте приведен пример использования класса LampDriver, конструктор которого и делает всю работу по настройке режима работы конкретного пина.

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

Ардуино тут, последний Макбук или S/360 - вообще никакого отношения к сути вопроса не имеет. Кроме разве что того, что памяти вообще говоря не то чтоб очень много, и желательно придумать экономичный подход.

Больше того, предположу, даже упор в конкретный стандарт C++ не имеет особого отношения к делу. Ибо Леонид (готов поспорить) компилирует свой код в gcc, и из этого и надо исходить: нужно что-то, что успешно скомпилируется в gcc и даст более-менее экономичный результат.
Windows Safari Chrome
 Amsterdam
0
0
oppol
Выводы Ардуины легко настраиваются один раз, при задании начальной конфигурации. И Ардуина это микроконтроллер, а не ЦП.
Mac Safari
 Домодедово
0
0
id
Выводы ардуины настраиваются столько раз, сколько это нужно. Больше того, куча протоколов (тот же 1wire например) работают, непрерывно на лету меняя режимы работы порта.
И ардуино это готовая электронно-вычислительная машина. Как и прочие упомянутые мной выше. К чему вдруг вы вспомнили про ЦП, не ясно ни разу.
Windows Safari Chrome
 Amsterdam
0
0
oppol
Код в студию, чтобы 1wire на одном пине был.
Linux Safari Chrome
 Санкт-Петербург
1
0
LLeo
А что вас удивляет? Вы полагали, что 1wire должен занимать не менее 4 пинов, вопреки своему названию?

Собственно, вчерашнее добавление во фреймворк 1wire с датчиком температуры DS18B20 для одного проекта и стало последней каплей, когда я решил спросить совета, что вообще делать с подобными библиотеками.



Windows Safari Chrome
 Amsterdam
0
0
oppol
Пардон, нашёл код.
Ну, представляю скорость обмена, да...
Windows Safari Chrome
 Домодедово
0
0
id
У 1wire только одна скорость обмена - штатная.

А на одном пине можно сделать чуть более интересную штуку, чем просто переключать его между "вход" и "выход-ноль": можно разряжать емкость линии после ноля, переключаясь в режим "вход" через "выход-один" на буквально один цикл. И внезапно без дополнительной аппаратуры 1wire начинает устойчиво работать на гораздо более длинных линиях.

Видимо, вы просто не представляете себе, как устроены порты AVR. Переключение направления возможно осуществлять ровно с той же скоростью, с какой возможно изменять состояние линий порта. Это вообще формально равнозначные операции.
Linux Safari Chrome
 Санкт-Петербург
2
0
LLeo
Не везде нужна скорость обмена. Вы вот свои комментарии набираете в сотню раз медленнее, чем любой 1wire, но это же не мешает ни думать, ни формулировать, ни общаться.
Linux Firefox
 Латвия
0
0
Heisenberg2
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
Всё равно под каждый конкретный камень код надо компилить. А у конкретного камня конкретное заранее известное число ног часть из которых еще и занята.

Это всё же не x86 где программы в скомпиленном бинарнике привычное дело. Можно и для ардуины такое организовать, но зачем?
Linux Safari Chrome
 Санкт-Петербург
1
0
LLeo
Ардуино я упомянул потому, что это немного особый С++, он отличается от классического и имеет свои особенности. Вопрос был про синтаксис этого C++, а не о том, как побыстрее включить абстрактную несуществующую лампу.
Windows Safari Chrome
 Москва
0
0
Кондратий
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
Ардуино использует avrgcc. Как видно из названия, он только про AVR, да и к Ардуино, по большому счету, отношения не имеет (в Atmel Studio тоже он, например). А вот другие "модули", типа STMduino и ESP, используют иные компиляторы. И эти компиляторы вполне могут быть полностью C++ корректными, ничто не мешает. Или просто С. Или Раст, уж коль на то пошло.
Linux Firefox
 Латвия
1
0
Heisenberg2
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
Дело не в с++, дело в ардуине.

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

Т.е. в данном случае это константа.

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

Никто не мешает заменить константу на переменную и присваивать ей любые значения на ходу. Тут уже надо по месту смотреть.

Но держать номер пина в переменной это моветон ибо противоестественно - набор доступных для той или иной функции пинов в железке всегда определен заранее схемой устройства.
Linux Safari Chrome
 Санкт-Петербург
0
0
Онаним
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
Воткнул на ходу что-то в USB — схема и изменилась.
Windows Safari Chrome
 Москва
1
0
Кондратий
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
С new все понятно, но вообще, как известно, использовать его в МК не рекомендуется. В любом случае, надо прописать какую-то штуку, чтобы проверять съеденную память и не давать пользователю съесть больше, чем можно.

Но, если я правильно понимаю описание задачи, пользователь может подключить разные устройства, но список их конечен, так ведь? Возможно, имеет смысл сразу создать по одному объекту на каждое, а внутри объектов сделать массивы с каким-то разумным количеством пинов (или что там у них отличается). Ну и дальше использовать этот один объект для управления множеством одинаковых устройств. Да, это скушает больше памяти, чем на самом деле нужно, но зато позволит контролировать расход этой самой памяти, что какбэ гуд практис для микрочипов.
Mac Safari
 Домодедово
0
0
id
Мысль в целом неплоха, имхо! Но у объектов может быть целая большая куча внутренних переменных, а далеко не только «номер пина»...
Linux Firefox
 Латвия
0
0
Heisenberg2
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
Я не до конца понимаю задачу, но, наверное, я бы сделал
Массив доступных пинов и массив ссылок на объекты (возможно, с типом этих объектов) с общим индексом.
Linux Ubuntu Firefox
 Киев
2
0
ichthuss
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
Комментаторы говорят много дельного, но не нашел в комментах важный момент: для new / delete необходимо динамическое выделение памяти. Очень часто для мелких embedded-устройств оно попросту не реализовано - в частности, ЕМНИП, для avr-ардуин. На такие случаи в C++ предусмотрен редко используемый синтаксис placement new, когда оператору new указывается предварительно (статически) выделенное место под размещение переменной.
Windows Firefox
 Москва
0
0
DrGluck
Это да, у нас иногда джуниоры пугаются. C++11 есть, а new нету.
Mac Safari Chrome
 Киев
0
0
lim
Только что проверил, для esp32 new работает. 99.9% что и для esp8266 тоже будет.
Windows Safari Chrome
 Москва
1
0
Кондратий
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
Есть new в avrgcc, есть. Использую его в своей библиотеке, но только не в цикле, а при создании объекта. Но мне без него никак - я не знаю, сколько именно LED-драйверов подключит пользователь, а надо сделать массив со всеми лампочками.
Linux Ubuntu Firefox
 Киев
0
0
ichthuss1
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
Значит, документация врет:

"Can I use C++ on the AVR?

Basically yes, C++ is supported (assuming your compiler has been configured and compiled to support it, of course). (...)

However, there's currently no support for libstdc++, the standard support library needed for a complete C++ implementation. This imposes a number of restrictions on the C++ programs that can be compiled. Among them are:

(...)
The operators new and delete are not implemented, attempting to use them will cause the linker to complain about undefined external references. (This could perhaps be fixed.)"
(...)

https://www.nongnu.org/avr-libc/user-manual/FAQ.html
Windows Safari Chrome
 Москва
0
0
Кондратий
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
Значит, врет, потому что вот это у меня работает с 2016-го года:

void DMdriver::init(DMLEDTABLE *table)
{
pixel = new uint8_t[byteNum]; //init the table of actual pixels
LATbit = digitalPinToBitMask(LATpin); // calculate LAT addresses
LATport = digitalPinToPort(LATpin);
LATreg = portModeRegister(LATport);
LATout = portOutputRegister(LATport);
DDRB |= _BV(PB3); // MOSI output
DDRB |= _BV(PB5); // SCK output
DDRB |= _BV(PB2); // SS output
*LATreg |= LATbit; // LAT output

PORTB &= ~_BV(PB5); // set SCK low

SPSR = _BV(SPI2X); // double SPI speed
SPCR = _BV(SPE) | _BV(MSTR); // enable SPI, master mode, SCK low rising edge

ledTable = table;

clearAll();
sendAll(); // found that this is useful to have a clear start

}
Windows Safari Chrome
 Домодедово
0
0
id
Вот как раз классический пример, чем отличаются "ардуинщики" от "Труъ эмбеддеров".

Те, которые труъ, солидно обсуждают нереализованность чего-то в своих труъ инструментах и скребут костыли по сусекам.

А ардуинщики просто юзают свою ардуиновую среду, в avr-gcc которой "из коробки" реализовано все что надо, и пожимают плечами, глядя на гордые страдания труъ.
Windows Safari Chrome
 Москва
0
0
Кондратий
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
Atmel Studio, видимо, знает что-то такое, чего не знают "труъ эмбеддеры":




Судя по всему, Атмел тоже тупо вставил new, обернув в него malloc. Но это не отменяет того, что он есть и там, и там, и работает.

Лезть проверять, правда ли в Ардуино реализован вектор для base64, мне лень (вектор при 2кб памяти - это такое ме, что ой). Но мужики не знают и городят всякое: https://github.com/maniacbug/StandardCplusplus/blob/master/R[...]
Linux Firefox
 Щелково
0
0
Heisenberg2
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
У тру эмбедеров вообще вопросов таких не возникает.

Можно и на ардуине, но зачем? Кроме экономии пары баксов никакого смысла. Библиотеки уже все написаны и выучены наизусть под 3-4 линейки контроллеров. Среда разработки никакая. Железо всяко хуже кастомного.

Сколько раз я брался за ардуину и подобные платформы столько раз плевался в первую очередь от библиотек. Под ответственное применение ревью кода всё равно делать, а это как бы не больше по трудозатратам чем написать самому.
Mac Safari Chrome
 Киев
4
0
lim
Проверил - во всяком случае на esp32 всякие new работают, должны и на esp8266.

Я б решал задачу так:

#include <LampDriver.h>
#include <MotorDriver.h>
#include <QuantumСandelabrumDriver.h>

LampDriver *lamps[17];
MotorDriver *motors[17];
QuantumСandelabrumDriver *quantumСandelabrums[17];

void setPinForLamp(uint8_t pin) {
if (!lamps[pin]) { // статические массивы в Си инициализируются нулями, так что можно так проверять занят ли пин уже лампой
lamps[pin] = new LampDriver(pin);
} else {
//already set
}
}

void blinkLampOnPin(uint8_t pin) {
if (!lamps[pin]) {
lamps[pin]->blink();
}
}

// аналогично для остальных квантовых канделябров

У esp8266 всего-то 13 GPIO, и то не на все из них можно что-то повесить. Последний из них будет GPIO16. Для каждого типа устройств нужно значит создать статический массив, где индексом будет номер пина, а содержимым соответствующий драйвер.
Городить ради этого динамические массивы, как предлагают любители C++ (тот же std::vector) - это из пушки по воробьям. Лучше старый-добрый сишный статический массив (расплатой за который будет лишние (17-k)*4 байт памяти, где k - количество подключенных устройств)
Windows Safari Chrome
 Россия
0
0
Anton
да, мне тоже непонято зачем там огород городить с динамическим выделением памяти ради контейнера емкостью в полтора десятка значений.
Linux Safari Chrome
 Самара
0
0
Redmi
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
Красиво, чёрт побери. Снимаю шляпу.
Linux Ubuntu Firefox
 Москва
0
0
Adamos
Не факт, что буфер вектора будет меньше тех 17 значений, так что расплата может быть и отрицательной.
Леонид просил решение на С++, собственно. А на "крестах" как-то и не пишут оптимальный код под конкретное устройство.
Windows Safari Chrome
 Домодедово
0
0
id
Хм... а для 30 возможных типов устройств - заводить массив 30х17?

И вопрос универсального интерфейса остается ведь открытым? Да, можно обойти все массивы с поиском конкретного устройства по имени, найти его, получить его тип, а дальше switch по возможным типам - соответствующие варианты действий. Но не монструозно ли выйдет в итоге? 30 разных типов устройств, по 5 хотя бы действий у каждого...


Может, действительно для каждого нового типа устройств писать единообразную "обертку", а массив сделать одним?
Mac Safari Chrome
 Киев
1
0
lim
Я рассчитываю на то, что драйвера устройств разношерстные, и типов устройств относительно немного - меньше десятка. Если устройств станет больше, то да - можно завести структуру тип-ссылка, и хранить в одном массиве. Можно еще больше упороться в ООП и написать обертку для каждого класса драйвера, чтоб был единый интерфейс, и ссылки на эти объекты уже пихать в массив. Но целесообразно ли это с точки зрения а) трудовых затрат программиста б) вычислительных затрат модуля (нужен будет больше стек для всех этих вызовов)?
В общем цель была показать идею. Деталей задачи я не знаю, потому решения про 30 устройств нужно будет обсуждать отдельно.
Mac Safari
 Домодедово
0
0
id
В случае обертки предельные трудовые затраты программиста - затраты на добавление 51го драйвера к 50 имеющимся - будут наименьшими. Потому что ничего, кроме этой самой обертки, трогать не придется.
Linux Firefox
 Латвия
1
0
Heisenberg2
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
Массив указателей. В массиве 2 поля - указатель и тип устройства.

Вообще задачу можно решить миллионом способов, гораздо сложнее угадать что понравится Леониду.
Mac Safari Chrome
 Киев
0
0
lim
Опередил вас на 9 минут :)
Windows Safari Chrome
 Домодедово
1
0
id
А еще можно сделать грязный хак, который в прошлом веке вызвал бы бурный восторг публики, а в этом считается за дикую крамолу...

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

Что мы при этом получаем? Исполняемый код будет в одном экземпляре - но он будет так и так нужен, здесь ничего не выигрываем и не проигрываем.

А вот данные будут в 17ти экземплярах на каждый требуемый вид драйвера, и тут мы дико транжирим память... казалось бы.

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

Итого, нам потребуется памяти на 17 экземпляров того класса, в котором больше всего данных.

Но нам так и так ее может потребоваться столько: ситуация "на все пины назначен как раз этот драйвер" не выпадает за постановку задачи.

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

P.S. Поясню.
Допустим, первый экземпляр класса MotorDriver имеет восемь внутренних переменных типа uint32_t - итого, ему нужно 32 байта под переменные. И допустим, что область его данных находится по адресу 0x01234500
А экземпляру класса LampDriver нужно всего две переменных типа uint8_t - то есть, два байта.

Итого, нам нужно 34 байта? А нет, поскольку на первый пин не могут быть одновременно назначены MotorDriver и LampDriver. Так что мы говорим, что область данных первого экземпляра класса LampDriver начинается... тоже с адреса 0x01234500

И первого экземпляра класса MissleDriver - тоже там же. И всех прочих первых экземпляров.

Классический грязный хак родом из древних ассемблерных времен... но... суровые времена - суровые меры?
Mac Safari Chrome
 Киев
1
0
lim
Тут может быть момент в том, что на одном пине может быть несколько функций :) Но этот вариант не рассматривается и в моем примере. Так что идея имеет право на существование.
В общем, ждем комментария от Лео - насколько подходит ему это решение, и если нет - то какие доп условия.
Linux Ubuntu Firefox
 Санкт-Петербург
0
0
LLeo
Слушайте, ну что вы так все привязались к пинам и числу 17?

А если драйвер вообще не имеет отношения к пинам? Если это, скажем, драйвер ответа чипа по TCP/IP порту, по особому протоколу KEY-TCP, например? И он требует при инициализации указать номер порта и завести себе структуру на 4кб ОЗУ, где будет хранить присланные в этот порт извне инициализационные данные (ключи шифрования), изменять их время от времени по таймеру каким-то своим способом, и отдавать результаты обратно по внешнему запросу? И ты заранее не знаешь, сколько тебе надо поддерживать таких портов, может один порт 6300, а может десятка полтора: 6300, 6301, 6302...
Linux Ubuntu Firefox
 Москва
2
0
Adamos
Здесь в комментариях просто столкнулись два подхода: оптимизация до байта и абстракция до полного скрытия реализации за человекопонятным интерфейсом. Делая второй вариант, желательно отложить первый хотя бы до тех пор, пока код заработает и выяснится, сколько именно ресурсов он жрет и почему.
Mac Safari
 Домодедово
0
0
id
Тогда нет иного пути, кроме как... встать на путь и идти :)

Либо на путь односвязного списка, либо на путь std::vector

А там - куда кривая выведет :)
Windows Safari Chrome
 Москва
0
0
Кондратий
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
А есть вообще vector на ESP? На AVR его нет вообще-то.
Windows Safari Chrome
 Домодедово
0
0
id
> На AVR его нет вообще-то.

Ну, учитывая, что в ардуиновской библиотеке для работы с base64 есть и std:vector, и new в полный рост... предположу, что все там нормально, и с AVR, и с прочими.
Linux Firefox
 Щелково
0
0
Heisenberg2
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
Массив или список указателей на объекты чем не устраивает?

Точнее, основная структура - список, а для пинов, например, отдельно завести массив со ссылками на нужные объекты чтобы не гонять по списку перебором лишний раз.
Windows Safari Chrome
 Самара
3
0
975
Этот человек не загрузил свой юзерпик, и я подобрал ему этот. Человек, пишущий такое, должен именно так выглядеть, верно?
Задача: "Часто встречаю на Ардуино библиотеки, которые требуют инициализации в коде с заранее заданнными пинами. Всё бы ничего, но в моей задаче нельзя заранее знать, сколько будет ламп (и будут ли вообще), и к каким пинам они окажутся подключены". Через три дня: "Слушайте, ну что вы так все привязались к пинам?"
Windows Safari Chrome
 Домодедово
0
0
id
"Он сам не знает, чего хочет" (с)

Но это совершенно нормальная ситуация, на самом деле: вбросить что-то, чтобы мы тут мозговой штурм устроили, и из наших мыслей собирать что-то свое - ранее, возможно, вообще не предполагавшееся :)
Mac Safari Chrome
 Киев
0
0
lim
Справедливости ради, кроме пинов и ламп там проскакивали еще квантовые канделябры, которые при инициализации требуют указание ключа кармического портала.
Если все настолько неизвестно, то таки да - нужен массив из ссылок на объекты-обертки вокруг драйверов, которые будут инкапсулировать как сами драйвера, так и то что им нужно для инициализации.
Linux Safari Chrome
 Германия
0
0
nero_schwarz
спросил помощи у https://alex-avr2.livejournal.com/, он точно знает, как сделать, надеюсь, зайдёт и откомментит:)
Linux Safari Chrome
 Санкт-Петербург
0
0
LLeo
Право же, мне выше всё уже объяснили, незачем беспокоить посторонних и незнакомых людей. Особенно с ником AVR, когда речь вовсе не по AVR, а про ESP (название Ардуино здесь обманчиво, это не то Ардуино, что AVR).
Windows Safari Chrome
 Германия
0
0
nero_schwarz
дык, он на чём только не программировал. ESP в том числе:
https://alex-avr2.livejournal.com/210759.html

Ну, может, он ничего нового не скажет, а может даст толковый совет, который ещё не был озвучен;)
Он делает качественный продукт и в теме, почему бы и нет.
Linux Ubuntu Firefox
 Санкт-Петербург
1
0
LLeo
Возможно, но мне не нравится сама идея отвлекать посторонних людей, которые мой дневник не читают и вообще не слышали, кто я такой. У меня своих френдов-читателей хватает, ничуть не менее профессиональных. К ним и был вопрос.

всего комментариев: 194

<< предыдущая заметка следующая заметка >>