ReAl 0 7 ноября, 2009 Опубликовано 7 ноября, 2009 · Жалоба Выделено отдельной темой. В том же разделе, в плюсах я сам начинающий :-), если что - меня поправят. А что это? А то может мне тоже это нравится, а я и не знаю ;) ---- Почитал. Пока не понял, нравится ли... Но скорее да, чем нет:) Это такой стандартный способ сделать то, что делать нехорошо. Ну как 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 - компилятор путается, нужно вручную звать деструктор (ну а удалять ничего и не нужно) Если деструкторы по задаче классу не нужны, лучше не вызывать и не заводить даже пустые. Ну так пока всё. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
AHTOXA 15 7 ноября, 2009 Опубликовано 7 ноября, 2009 · Жалоба Спасбо, очень познавательно. Видится красивый способ запуска подзадач в 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; } } } Неперекрываемость во времени гарантирована, память для подзадач распределена статически. Красота:) Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
ReAl 0 7 ноября, 2009 Опубликовано 7 ноября, 2009 · Жалоба Видится красивый способ запуска подзадач в 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); } Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
dxp 56 7 ноября, 2009 Опубликовано 7 ноября, 2009 · Жалоба Массив для экземпляров классов можно и на стеке завести, что лучше - зависит от условий. Я, может, чего-то не понял, но на стеке (т.е. по сути локальной переменной), имхо, смыла нет. На стеке можно просто заводить локальные объекты без всяких дополнительних телодвижений. Аллокаторы актуальны когда объект должен жить между вызовами функций, использующих его. Спасбо, очень познавательно. Видится красивый способ запуска подзадач в 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 своими. Давно собирался такое соорудить, но пока жизнь по настоящему не приперла. :) Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
AHTOXA 15 7 ноября, 2009 Опубликовано 7 ноября, 2009 · Жалоба Чем это в данном конкретном случае отличается от: Наверное всё же так: 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[]. В чём-то это может быть удобнее. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
ReAl 0 7 ноября, 2009 Опубликовано 7 ноября, 2009 · Жалоба Я, может, чего-то не понял, но на стеке (т.е. по сути локальной переменной), имхо, смыла нет. На стеке можно просто заводить локальные объекты без всяких дополнительних телодвижений.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 сделать нельзя. Мне представляется, что более логичным решением для ситуации где требуется создавать объекты с не локальным и не статическим временами жизни было бы написание своего простого менеджера памяти на основе очереди указателей и набора пулов, отличающихся размерами блоков (чтобы избежать фрагментации).Да, мне нужно нелокальное (но можно статически выделить буфер для них всех). Но ради одного, максимум двух "слоёв" эдаких "оверлеев" не захотелось морочиться даже с простым менеджером. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
AHTOXA 15 7 ноября, 2009 Опубликовано 7 ноября, 2009 · Жалоба Правда, VMT задач один чёрт в ОЗУ хранится :-( arm-gcc в этом смысле получше, VMT хранит во флеше. avr-gcc тут резервирует на стеке суммарный объём, а не размер наибольшего класса. А union сделать нельзя. А вот тут - то же самое, суммарный объём. Непорядок:) Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
dxp 56 8 ноября, 2009 Опубликовано 8 ноября, 2009 · Жалоба avr-gcc тут ... резервирует на стеке суммарный объём, а не размер наибольшего класса. Это он неправильно делает. :) Ну, т.е. по сути этот прием есть ручная оптимизация. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
xvr 12 9 ноября, 2009 Опубликовано 9 ноября, 2009 · Жалоба Кстати, вместо placement delete (которого нет) можно сделать обычный delete в самом классе (пустой). Тогда можно будет писать delete my_object вместо явного вызова деструктора Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться