реклама на сайте
подробности

 
 
 
Reply to this topicStart new topic
> placement new, стандартный способ сделать то, что делать нехорошо
ReAl
сообщение Nov 7 2009, 09:36
Сообщение #1


Нечётный пользователь.
******

Группа: Свой
Сообщений: 2 033
Регистрация: 26-05-05
Из: Бровари, Україна
Пользователь №: 5 417



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

Цитата(AHTOXA @ Nov 6 2009, 22:47) *
А что это? А то может мне тоже это нравится, а я и не знаю wink.gif

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

Это такой стандартный способ сделать то, что делать нехорошо. Ну как 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 - компилятор путается, нужно вручную звать деструктор (ну а удалять ничего и не нужно)
Если деструкторы по задаче классу не нужны, лучше не вызывать и не заводить даже пустые.

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


--------------------
Ну, я пошёл… Если что – звоните…
Go to the top of the page
 
+Quote Post
AHTOXA
сообщение Nov 7 2009, 10:21
Сообщение #2


фанат дивана
******

Группа: Свой
Сообщений: 3 387
Регистрация: 9-08-07
Из: Уфа
Пользователь №: 29 684



Спасбо, очень познавательно.
Видится красивый способ запуска подзадач в 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;
         }
    }
}


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


--------------------
Если бы я знал, что такое электричество...
Go to the top of the page
 
+Quote Post
ReAl
сообщение Nov 7 2009, 10:57
Сообщение #3


Нечётный пользователь.
******

Группа: Свой
Сообщений: 2 033
Регистрация: 26-05-05
Из: Бровари, Україна
Пользователь №: 5 417



Цитата(AHTOXA @ Nov 7 2009, 12:21) *
Видится красивый способ запуска подзадач в scmRTOS:
Неперекрываемость во времени гарантирована, память для подзадач распределена статически. Красотаsmile.gif

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

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

Правда, VMT задач один чёрт в ОЗУ хранится :-(
Им бы во флеше жить, константные ж вроде, никаких поводов меняться им нет.


Цитата(ReAl @ Nov 7 2009, 11:36) *
К сожалению, нельзя сказать 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);
}


--------------------
Ну, я пошёл… Если что – звоните…
Go to the top of the page
 
+Quote Post
dxp
сообщение Nov 7 2009, 13:51
Сообщение #4


Adept
******

Группа: Свой
Сообщений: 3 469
Регистрация: 6-12-04
Из: Novosibirsk
Пользователь №: 1 343



Цитата(ReAl @ Nov 7 2009, 15:36) *
Массив для экземпляров классов можно и на стеке завести, что лучше - зависит от условий.

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

Цитата(AHTOXA @ Nov 7 2009, 16:21) *
Спасбо, очень познавательно.
Видится красивый способ запуска подзадач в 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 своими. Давно собирался такое соорудить, но пока жизнь по настоящему не приперла. smile.gif


--------------------
«Отыщи всему начало, и ты многое поймёшь» К. Прутков
Go to the top of the page
 
+Quote Post
AHTOXA
сообщение Nov 7 2009, 14:05
Сообщение #5


фанат дивана
******

Группа: Свой
Сообщений: 3 387
Регистрация: 9-08-07
Из: Уфа
Пользователь №: 29 684



Цитата(dxp @ Nov 7 2009, 18:51) *
Чем это в данном конкретном случае отличается от:


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

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


--------------------
Если бы я знал, что такое электричество...
Go to the top of the page
 
+Quote Post
ReAl
сообщение Nov 7 2009, 14:11
Сообщение #6


Нечётный пользователь.
******

Группа: Свой
Сообщений: 2 033
Регистрация: 26-05-05
Из: Бровари, Україна
Пользователь №: 5 417



Цитата(dxp @ Nov 7 2009, 15:51) *
Я, может, чего-то не понял, но на стеке (т.е. по сути локальной переменной), имхо, смыла нет. На стеке можно просто заводить локальные объекты без всяких дополнительних телодвижений.
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 сделать нельзя.

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


--------------------
Ну, я пошёл… Если что – звоните…
Go to the top of the page
 
+Quote Post
AHTOXA
сообщение Nov 7 2009, 19:20
Сообщение #7


фанат дивана
******

Группа: Свой
Сообщений: 3 387
Регистрация: 9-08-07
Из: Уфа
Пользователь №: 29 684



Цитата(ReAl @ Nov 7 2009, 15:57) *
Правда, VMT задач один чёрт в ОЗУ хранится :-(


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

Цитата(ReAl @ Nov 7 2009, 19:11) *
avr-gcc тут резервирует на стеке суммарный объём, а не размер наибольшего класса. А union сделать нельзя.


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


--------------------
Если бы я знал, что такое электричество...
Go to the top of the page
 
+Quote Post
dxp
сообщение Nov 8 2009, 07:23
Сообщение #8


Adept
******

Группа: Свой
Сообщений: 3 469
Регистрация: 6-12-04
Из: Novosibirsk
Пользователь №: 1 343



Цитата(ReAl @ Nov 7 2009, 20:11) *
avr-gcc тут
...
резервирует на стеке суммарный объём, а не размер наибольшего класса.

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


--------------------
«Отыщи всему начало, и ты многое поймёшь» К. Прутков
Go to the top of the page
 
+Quote Post
XVR
сообщение Nov 9 2009, 10:33
Сообщение #9


Гуру
******

Группа: Свой
Сообщений: 3 123
Регистрация: 7-04-07
Из: Химки
Пользователь №: 26 847



Кстати, вместо placement delete (которого нет) можно сделать обычный delete в самом классе (пустой). Тогда можно будет писать delete my_object вместо явного вызова деструктора
Go to the top of the page
 
+Quote Post

Reply to this topicStart new topic
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0

 


RSS Текстовая версия Сейчас: 19th July 2025 - 04:43
Рейтинг@Mail.ru


Страница сгенерированна за 0.01445 секунд с 7
ELECTRONIX ©2004-2016