Комментарии 20
Я на всякий случай уточню, это EC++?
Ну и заодно: что использовалось в качестве стандартной библиотеки? Как бы, libstdc++ в embedded жирновато будет.
P.S.: Хотя, возможно, вообще собирали без оной. В коде STL не видно.
P.S.: Хотя, возможно, вообще собирали без оной. В коде STL не видно.
Судя из определения на википедии http://en.wikipedia.org/wiki/Embedded_C++, то это полноценный С++. Я использую компилятор IAR Embedded Workbench for STMicroelectronics STM8 IDE, в нем можно использовать множественное наследование, абстрактные классы, ну и шаблоны как видно из поста тоже можно.
Судя из определения на википедии http://en.wikipedia.org/wiki/Embedded_C++, то это полноценный С++. Я использую компилятор IAR Embedded Workbench for STMicroelectronics STM8 IDE, в нем можно использовать множественное наследование, абстрактные классы, ну и шаблоны как видно из поста тоже можно.
Транслит гугловый в коде вижу я
в каком месте?
Ну мне лично в глаза больше всего бросилось вот это :)
Кстати, а раз уж на плюсах пишете, то почему не использовать RAII для работы с прерываниями и прочими критическими ресурсами? Кода становится меньше и он становится проще.
Значительно скорость выполнения драйвера можно увеличить с помощью использования шаблона С++. Шаблон С++ позволяет обращаться на прямую к памяти, тем самым заменить указатели, разыменование которых занимает время.
Кстати, а раз уж на плюсах пишете, то почему не использовать RAII для работы с прерываниями и прочими критическими ресурсами? Кода становится меньше и он становится проще.
Отличная статья! Прерывания вот только расстраивают — приходится объявлять объект-драйвер под конкретную периферию, а потом еще этот объект привязывать к прерыванию объявлением отдельной функции обработчика прерывания. Не удалось придумать способ, как обойтись только созданием объекта?
Спасибо за отзыв.
Я долго думал, как это сделать, но красивого решения так и не нашел. Можно создать внутри класса только статическое прерывания, но тогда внутри прерывания должны быть либо уже объявленные переменные или статические, нельзя использовать переменные самого класса, так как самого объекта еще не существует.
Вот если бы создавать прерывание динамически во время создания объекта, но наверно так сделать не возможно.
Я долго думал, как это сделать, но красивого решения так и не нашел. Можно создать внутри класса только статическое прерывания, но тогда внутри прерывания должны быть либо уже объявленные переменные или статические, нельзя использовать переменные самого класса, так как самого объекта еще не существует.
Вот если бы создавать прерывание динамически во время создания объекта, но наверно так сделать не возможно.
А как с выделением памяти у Вас? Вот это тоже краеугольный камень С++ на микроконтроллерах. Динамически создавать объекты компилятор не запрещает, но это занятие не благодарное. А размещать в статике можно либо обычным глобальным объявлением, как это сделано в Вашей статье, но это тоже опасный метод (во первых объект становится глобально доступным, а во вторых память под него будет выделяться так как вздумается компилятору (вот тут это обсуждалось)). Поэтому люди рекомендуют (как мне рассказывают, сам я еще не до конца разобрался, поэтому и пишу), нужно выделять память статическими пулами. Вы такими вещами не пользуетесь? Если пользуетесь, то как?
Поможет placement new.
Как-то так (код компилится)
Вывод:
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <iostream>
using namespace std;
class X
{
public:
X(const char *const name)
{
strncpy(this->name, name, sizeof(this->name));
}
void printName() const
{
cout << "My name is " << name << endl;
}
private:
char name[10];
};
void printHex(const void *const memory, const size_t memorySize)
{
const ios_base::fmtflags savedFmtflags = cout.flags();
for (size_t i = 0; i < memorySize; ++i)
{
const unsigned int byteValue = reinterpret_cast<const uint8_t *const>(memory)[i];
cout << hex << setw(2) << setfill('0') << byteValue << ' ';
}
cout << endl;
cout.flags(savedFmtflags);
}
int main()
{
static uint8_t xMemoryPoolBytes[sizeof(X)];
static X *const xMemoryPool = reinterpret_cast<X*>(xMemoryPoolBytes);
printHex(xMemoryPoolBytes, sizeof(xMemoryPoolBytes));
// Понадобился объект класса X
const X *const instance = new (xMemoryPool) X("Ivan");
instance->printName();
// Больше не нужен
instance->~X();
operator delete (xMemoryPool);
printHex(xMemoryPoolBytes, sizeof(xMemoryPoolBytes));
return EXIT_SUCCESS;
}
Вывод:
00 00 00 00 00 00 00 00 00 00
My name is Ivan
49 76 61 6e 00 00 00 00 00 00
ООО! Спасибо Вам большое! Если я правильно понимаю как это работает, то это то что нужно! Я только начинаю переходить от простого Си на C++, поэтому мне не все так очевидно, буду признателен, если Вы растолкуете, правилно ли я понял:
И если я буду активно создавать и удалять объекты разной длинны в таком стеке, то не будет проблем с фрагментацией памяти в нем?
int main()
{
static uint8_t xMemoryPoolBytes[sizeof(X)]; /**< Это и есть статический стек для объектов? Здесь он объевлен ровно на один объект, а как быть, есть я хочу много объектов? С объявлением пула на несколько понятно, а как создавать нескоолько объектов в этом пуле? */
static X *const xMemoryPool = reinterpret_cast<X*>(xMemoryPoolBytes); /**< Тут Вы приводите область памяти к типу Х? reinterpret_cast это стандартная функция? */
printHex(xMemoryPoolBytes, sizeof(xMemoryPoolBytes)); /**< Это я так понимаю Вы сделали для наглядности того, что происходит в стеке */
/** далее все ясно */
const X *const instance = new (xMemoryPool) X("Ivan");
instance->printName();
instance->~X();
operator delete (xMemoryPool);
printHex(xMemoryPoolBytes, sizeof(xMemoryPoolBytes));
return EXIT_SUCCESS;
}
И если я буду активно создавать и удалять объекты разной длинны в таком стеке, то не будет проблем с фрагментацией памяти в нем?
Это и есть статический стек для объектов? Здесь он объевлен ровно на один объект, а как быть, есть я хочу много объектов? С объявлением пула на несколько понятно, а как создавать нескоолько объектов в этом пуле?Вы всё правильно поняли. Если хотите создавать несколько объектов, вам нужно указывать соответствующие адреса памяти в пуле, то бишь делать смещение на i*sizeof(X) байт, где i — номер объекта.
Тут Вы приводите область памяти к типу Х? reinterpret_cast это стандартная функция?Типа того. В C++, помимо стандартного сишного приведения типов (X*) xMemoryPoolBytes, есть ещё:
- const_cast для снятия константности с объекта, если очень нужно; типы проверяются во время компиляции (ошибка компиляции при несоответствии)
- static_cast — для приведения совместимых типов: например, для приведения B к A, если B наследуется от A; типы проверяются во время компиляции
- dynamic_cast — для приведения A* к B*, если B наследуется от A; возвращает NULL, если объект не является экземпляром B*; проверка наследуемости одного типа от другога — при компиляции, проверка того, что по указателю именно тип B — во время выполнения
- reinterpret_cast — аналог сишного приведения типов, использовать имеет смысл только для несовместимых типов, как в моём примере — для массива байтов и объекта
Я мог и не приводить пул к X*,new не требует этого, но просто так будет удобнее в случае работы с множеством объектов:
X *instance1 = new (xMemoryPool[0]) X("Ivan");
X *instance2 = new (xMemoryPool[1]) X("Nikolai");
...
А без приведения к X* пришлось бы писать уродливое new (xMemoryPoolBytes + sizeof(X)).Это я так понимаю Вы сделали для наглядности того, что происходит в стекеИменно — чтобы было видно, как поменялись байты в статическом пуле после того, как вы создали в нём объект и использовали его. Отчётливо видно, что «Ivan» записалось в пул.
И да, я — балбес: вот это
operator delete (xMemoryPool);
не нужно, написал на автомате. Не нужно динамически удалять память пула, мы же её выделили статически :D хлопнул себя по лбу. Нужно только вручную вызвать деструктор, раз мы не освобождаем память.Вы всё правильно поняли. Если хотите создавать несколько объектов, вам нужно указывать соответствующие адреса памяти в пуле, то бишь делать смещение на i*sizeof(X) байт, где i — номер объекта.
Стандарт требует, чтобы память, которую будете передавать в placement new была корректно выровнена: stackoverflow.com/a/11782277
Это может породить суровые баги: компилятор считает, что i*sizeof(X), которую передаёте в new, уже выровнена, поэтому он не будет вставлять дальнейших проверок на выравнивание, которые он бы вставил для SSE инструкций. Вот пример такой баги.
Спасибо, не знал, мотаю на ус. Стало быть,
нужно делать так?
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <iomanip>
#include <iostream>
using namespace std;
void printHex(const void *const memory, const size_t memorySize)
{
const ios_base::fmtflags savedFmtflags = cout.flags();
for (size_t i = 0; i < memorySize; ++i)
{
const unsigned int byteValue = reinterpret_cast<const uint8_t *const>(memory)[i];
cout << hex << setw(2) << setfill('0') << byteValue << ' ';
}
cout << endl;
cout.flags(savedFmtflags);
}
class X
{
public:
X(const uint8_t n)
{
snprintf(name, sizeof(name), "Ivan%d", n);
}
void printName() const
{
cout << "My name is " << name << endl;
}
private:
char name[10];
};
struct XBlock
{
public:
X *create(const uint8_t n)
{
return new (memory) X(n);
}
static void destroy(X *const instance)
{
instance->~X();
}
private:
uint8_t memory[sizeof(X)];
};
enum { MAX_X_INSTANCES = 4 };
static XBlock xPool[MAX_X_INSTANCES];
int main()
{
printHex(xPool, sizeof(xPool));
X *xInstances[MAX_X_INSTANCES];
for (size_t i = 0; i < MAX_X_INSTANCES; ++i)
{
xInstances[i] = xPool[i].create(i);
xInstances[i]->printName();
}
// ...
for (size_t i = 0; i < MAX_X_INSTANCES; ++i)
XBlock::destroy(xInstances[i]);
printHex(xPool, sizeof(xPool));
return EXIT_SUCCESS;
}
Да, совсем забыл о фрагментации. Если вам нужно выделять память переменной длины, то вам придётся использовать динамическую память. Описанный мной пул предназначен для объектов определённого фиксированного размера. Нужны другие объекты — делаете ещё один специальный пул. А с динамической длиной выделяемой памяти фрагментация неизбежна.
Впрочем, если захотите хорошенько заморочиться, можете сделать свой менеджер памяти с периодическим уплотнением блоков, но тогда вам придётся пожертвовать или половиной всей доступной памяти (для переноса туда блоков), или временем на написание и работу дефрагментатора памяти без таких требований (но тогда он будет тормозить), и в любом случае нужно будет как-то обновлять старые указатели после уплотнения.
Есть книга Jeff Alger «C++ For Real Programmers», там описано множество интересных вещей, включая уплотнение памяти, которые возможны в C++. Но эта книга не для новичков, сломаете себе мозг.
Впрочем, если захотите хорошенько заморочиться, можете сделать свой менеджер памяти с периодическим уплотнением блоков, но тогда вам придётся пожертвовать или половиной всей доступной памяти (для переноса туда блоков), или временем на написание и работу дефрагментатора памяти без таких требований (но тогда он будет тормозить), и в любом случае нужно будет как-то обновлять старые указатели после уплотнения.
Есть книга Jeff Alger «C++ For Real Programmers», там описано множество интересных вещей, включая уплотнение памяти, которые возможны в C++. Но эта книга не для новичков, сломаете себе мозг.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Драйвера на С++ для STM8L051F3