Перейти к содержанию
    

placement new

Выделено отдельной темой.

В том же разделе, в плюсах я сам начинающий :-), если что - меня поправят.

 

А что это? А то может мне тоже это нравится, а я и не знаю ;)

 

----

Почитал. Пока не понял, нравится ли... Но скорее да, чем нет:)

Это такой стандартный способ сделать то, что делать нехорошо. Ну как goto - нехорошо, а break/continue/switch+case - хорошо (правда, на switch+case делается такой Duff's device, что вся структурность в гробу переворачивается, но зато без goto).

 

Конкретнее, placement new - это способ сделать

#include "foo.h"

    unsigned char buf[sizeof(foo)];            // выделяем место под экземпляр
    foo *pfoo = reinterpret_cast<foo*>(buf);    // заводим указатель#define
    pfoo->foo(0x88);                // ( . ) конструиреум
    pfoo->action();                    // пользуемся
    pfoo->~foo();                    // удаляем

Тут сделать можно всё, кроме строки, помеченной ( . ) - ну нельзя так.

Зато можно так

#include "foo.h"

    unsigned char buf[sizeof(foo)];            // выделяем место под экземпляр
    foo *pfoo = new(buf) foo(0x88);            // конструиреум и приводим указатель
    pfoo->action();                    // пользуемся
    pfoo->~foo();                    // удаляем

и эта штука и называется placement new. Она не проверяет, достаточно ли места в предоставленном буфере и возвращает указатель на начало буфера, приведенный к конструируемому типу. Короче, сплошное "фу". Но такая лапочка.

Как было написано в какой-то статье по этому делу, "если вы не знаете, что такое alignment, то и placement new лучше не пользуйтесь".

 

Фактически это глобальная перегрузка оператора new своим аллокатором, тип "аллокатора" void* и он ничего не делает.

В библиотеке должна быть готовая реализация перегруженного оператора, но в avr-gcc её нет. И не надо, inline она смотрится куда лучше.

#include <stdlib.h> // size_t
// placement new support - сответствующий стандарту прототип
inline void* operator new(size_t size, void* ptr) throw()
{
    (void)size;
    return ptr;
}

 

Ну а теперь - почему мне это нравится.

Пусть у нас есть несколько классов, которые используются строго по очереди.

Если экзмепляры заводить статически - абсолютно зря жрётся место в ОЗУ.

Если динамически - надо прицепть new/delete (пусть хоть и отмапленные на malloc/free) - а это лишний код и возможные проблемы с фрагментацией памяти (хотя если new/delete в программе всё равно нужны, то почему бы и нет).

Но можно так (NOINLINE для удобства разбора функции kwa() ):

class foo {
public:
    NOINLINE foo(uint8_t mask) : _mask(mask) { PORTB &= ~_mask; DDRB |= _mask; }
    NOINLINE ~foo() { PORTB |= _mask; DDRB &= ~_mask; }
    NOINLINE void action() { PORTB ^= _mask; }
private:
    uint8_t _mask;
};

class moo {
public:
    NOINLINE moo(uint8_t mask, uint8_t period)
        : _mask(mask), _period(period), _cnt(period)
        {  PORTB &= ~_mask; DDRC |= _mask; }
    
    NOINLINE ~moo() { DDRC &= ~_mask; }
    
    NOINLINE void action() { 
        if( --_cnt == 0) {
            _cnt = _period;
            PORTC ^= _mask;
        }
    }
private:
    uint8_t _mask;
    uint8_t _period;
    uint8_t _cnt;
};

const int bufsize = sizeof(foo) > sizeof(moo) ? sizeof(foo) : sizeof(moo);

uint8_t buf[bufsize];

void kwa()
{
    uint8_t i;
    
    foo *pfoo = new(buf) foo(0x11);
    i = 8;
    do {        pfoo->action();        } while(--i);
    pfoo->~foo();

    moo *pmoo = new(buf) moo(0x11,3);
    i = 8;
    do {        pmoo->action();        } while(--i);
    pmoo->~moo();
}

Код kwa()

.global    _Z3kwav
    .type    _Z3kwav, @function
_Z3kwav:
    push r17
; operator new(size_t,void*) заинлайнился в ничто
    ldi r24,lo8(buf)
    ldi r25,hi8(buf)
    ldi r22,lo8(17)
    rcall _ZN3fooC1Eh; конструктор foo
    ldi r17,lo8(8)
.L20:
    ldi r24,lo8(buf)
    ldi r25,hi8(buf)
    rcall _ZN3foo6actionEv; попользовались 
    subi r17,lo8(-(-1))
    brne .L20
    ldi r24,lo8(buf)
    ldi r25,hi8(buf)
    rcall _ZN3fooD1Ev; деструктор foo
    
; new опять пропал
    ldi r24,lo8(buf)
    ldi r25,hi8(buf)
    ldi r22,lo8(17)
    ldi r20,lo8(3)
    rcall _ZN3mooC1Ehh; конструктор moo
    ldi r17,lo8(8)
.L21:
    ldi r24,lo8(buf)
    ldi r25,hi8(buf)
    rcall _ZN3moo6actionEv; попользовались 
    subi r17,lo8(-(-1))
    brne .L21
    ldi r24,lo8(buf)
    ldi r25,hi8(buf)
    rcall _ZN3mooD1Ev; деструктор moo

    pop r17
    ret

Массив для экземпляров классов можно и на стеке завести, что лучше - зависит от условий. Даже alloca() может быть лучше malloc/free

К сожалению, нельзя сказать delete(buf) pfoo - компилятор путается, нужно вручную звать деструктор (ну а удалять ничего и не нужно)

Если деструкторы по задаче классу не нужны, лучше не вызывать и не заводить даже пустые.

 

Ну так пока всё.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Спасбо, очень познавательно.

Видится красивый способ запуска подзадач в scmRTOS:

 

char thread_buf[MAX_THREAD_SIZE];

template <>
OS_PROCESS void TProc1::Exec()
{
    char ch;
    for(;;)
    {
        Tasks.pop(ch); // достаём задачу из очереди
        switch(ch)
       {
       case 'A':
            thread_A_t * thread = new(thread_buf) thread_A_t;
            thread->work();
            thread->~thread_A_t();
             break;
        case 'B':
             thread_B_t * thread = new(thread_buf) thread_B_t;
             thread->work();
             thread->~thread_B_t();
              break;
         default:
             thread_err_t * thread = new(thread_buf) thread_err_t;
             thread->work();
             thread->~thread_err_t();
              break;
         }
    }
}

 

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Видится красивый способ запуска подзадач в scmRTOS:

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

Да-да-да!!!

Собственно, я сейчас в одной работе мучаю на mega168 на плюсах нечто prothothread-подобное (там довольно много ОЗУ для всяких буферов нужно, вытеснялка не лезет нивкакую со своими потребностями в стеке). Тихий ужас с непривычки :-)

 

Так там, в силу необходимости переключений/ожиданий на верхнем уровне задачи, очень удобно для разных дел с ожиданиями "вызывать" подзадачи, реализующие нужный функционал и ждать их завершения. Выходит много мелких "задач", каждая из которых жрёт место в ОЗУ под свои приватные-"локальные" переменные. Ну вот вспомнил про placement new, полегчало.

 

Правда, VMT задач один чёрт в ОЗУ хранится :-(

Им бы во флеше жить, константные ж вроде, никаких поводов меняться им нет.

 

 

К сожалению, нельзя сказать delete(buf) pfoo - компилятор путается, нужно вручную звать деструктор (ну а удалять ничего и не нужно)
"историческая справка"

Синтаксиса для placement delete нет, я так понимаю, что для delete(buf) pfoo; просто само собой при разборе выйдет, что делается попытка удалить сам буфер buf - по разруливанию прототипов подходит delete(unsigned char*), а pfoo после закрывающей скобки вообще syntax error. Именно поэтому не выйдет просто через delete вызвать деструктор и деаллокатор.

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

class my_allocator; //ну вот как-то там выделяет
extern my_allocator ma;

// эти две должны пользоваться функциями my_allocator для выделения-освобождения
void* operator new(size_t size, my_allocator& a) throw();
void operator delete(void*, my_allocator& a) throw();

void kwa()
{
    foo *pfoo = new(ma) foo(0x11);
    pfoo->action();
    pfoo->~foo();
    operator delete(pfoo, ma);
}

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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

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

 

Спасбо, очень познавательно.

Видится красивый способ запуска подзадач в scmRTOS:

 

 

Чем это в данном конкретном случае отличается от:

 

char thread_buf[MAX_THREAD_SIZE];

template <>
OS_PROCESS void TProc1::Exec()
{
    char ch;
    for(;;)
    {
        Tasks.pop(ch); // достаём задачу из очереди
        switch(ch)
       {
       case 'A':
            thread_A_t * thread;
            thread->work();
             break;
        case 'B':
             thread_B_t * thread;
             thread->work();
              break;
         default:
             thread_err_t * thread;
             thread->work();
              break;
         }
    }
}

 

?

 

Объект локальный, время жизни тоже локальное. Зачем аллокация?

 

Мне представляется, что более логичным решением для ситуации где требуется создавать объекты с не локальным и не статическим временами жизни было бы написание своего простого менеджера памяти на основе очереди указателей и набора пулов, отличающихся размерами блоков (чтобы избежать фрагментации). Размеры задавать на этапе компиляции статически. Код будет простым, эффективным, сам по себе подход стандартный - просто перегрузить операторы new/delete своими. Давно собирался такое соорудить, но пока жизнь по настоящему не приперла. :)

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Чем это в данном конкретном случае отличается от:

 

Наверное всё же так:

template <>
OS_PROCESS void TProc1::Exec()
{
     char ch;
     for(;;)
     {
         Tasks.pop(ch); // достаём задачу из очереди
         switch(ch)
        {
        case 'A':
             thread_A_t thread;
             thread.work();
            break;
...
         }
     }
}

?

Тоже вариант, сейчас так и делаю:). Но тут все объекты отъедают стек процесса. А в случае с placement new у них свой статически распределённый буфер thread_buf[]. В чём-то это может быть удобнее.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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

void kwa_kwa()
{
    uint8_t i;
    {    
        foo f(0x11);
        i = 8;
        do {
            f.action();
        } while(--i);
    }
    {    
        moo m(0x22,3);
        i = 8;
        do {
            m.action();
        } while(--i);
    }
}

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

 

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Правда, VMT задач один чёрт в ОЗУ хранится :-(

 

arm-gcc в этом смысле получше, VMT хранит во флеше.

 

avr-gcc тут резервирует на стеке суммарный объём, а не размер наибольшего класса. А union сделать нельзя.

 

А вот тут - то же самое, суммарный объём. Непорядок:)

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

avr-gcc тут

...

резервирует на стеке суммарный объём, а не размер наибольшего класса.

Это он неправильно делает. :) Ну, т.е. по сути этот прием есть ручная оптимизация.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Кстати, вместо placement delete (которого нет) можно сделать обычный delete в самом классе (пустой). Тогда можно будет писать delete my_object вместо явного вызова деструктора

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

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

Гость
К сожалению, ваш контент содержит запрещённые слова. Пожалуйста, отредактируйте контент, чтобы удалить выделенные ниже слова.
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...