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

 
 
> 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
 
Start new topic
Ответов
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
AHTOXA
сообщение Nov 7 2009, 19:20
Сообщение #4


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

Группа: Свой
Сообщений: 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



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

 


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


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