0
«хочу я прочитать сектор N (512 байт) из _flash_, но не весь, а лишь первые 13 байт»…

я преписываю «несчастному указателю» то использование, которое предоставляет интерфейс.
а раз интерфейс говорит «чувак, я все могу», так почему бы его мне не использовать для своих особо извратных и пошлых задач?
найдутся желающие, которые добавят еще 100500 методов, покрывающих функционал, который вы даже и не планировали (или даже считали вредным), но весь этот новый функционал будет прекрасно вписываться в ваш раздутый интерфейс.

короче, вы породили монстра, вы за него в ответе )) если он вас укусит — не обижайтесь потом.
  • avatar
  • rouming
  • 26 ноября 2012, 16:43
0
вы поймите. я выступаю лишь как тупой пользователь вашего класса. я не хочу думать о всех подводных камнях. я надеюсь, что вы за меня подумали, раз сделали это возможным. а по факту я могу лишь читать строку.
  • avatar
  • rouming
  • 26 ноября 2012, 15:47
0
именно об этом я и говорю, что ваш интерфейс предусматривает вообще все, передавай что хошь, ни о чем не думай. а low-level часть умеет читать в цикле по байту.
как только вы будете делать запись, то эффективнее будет писать блоками.
high-level об этом знать не знает, и будет дергать ваш интерфейс в цикле, по байту.

хорошо. оставим запись. хочу я прочитать сектор N (512 байт) с диска, но не весь, а лишь первые 13 байт. мне понравился ваш smartptr. хочу его использовать. как? сначала создать структуру (массив) 512 байт на стеке? (ну ведь мы же будем в нее читать, и делать всю адресную арифметику, смещаясь именно на сектор) до свидания половина моего ram. потом вычитать все 512 байт. черт, мне нужно только 13.

ну вы же предоставили интерфейс. и сказали: да, так делать можно. пользователь SmartPtr возьмет и сделает. он не будет задумываться, о том, что это все будет чертовски медленно. а виноваты будете вы. так как
1. есть интерфейс
2. нигде не сказано, что внутренние кишки работают плохо.

так зачем делать такой общий код? называть его так абстрактно?
потому что это красиво? да к черту красивость. я хочу чтоб работало.
  • avatar
  • rouming
  • 26 ноября 2012, 15:44
0
ну погодите. вы переопределили практически все операторы. назвали класс SmartPtr, ну т.е. максимально абстрактно: умный указатель на _что_угодно_. все везде templates, ну т.е. человек со стороны посмотрит и скажет «о, чудно как! а запихну-ка я свою структурку туда, наверное автор подумал обо мне, вон какой интерфейс, все есть, и моя структурка со свистом улетит в память (из памяти).». а на деле все не так, а вы говорите «Всё что должен делать этот SmartPtr — это читать байт». ну да. single responsibility. по байту на responsibility.
  • avatar
  • rouming
  • 26 ноября 2012, 15:07
0
если эта длиннющая портянка и задумывалась _только_ для чтения строк — удалите мою критику. я с позором и жидко сливаюсь.
  • avatar
  • rouming
  • 26 ноября 2012, 14:58
0
std::auto_ptr прекрасно справляется с одной единственной задачей — сборкой мусора.
он не включает в себя всю адресную арифметику, с которой справляется компилятор, не пытается внутри себя обращаться к нативным вещам, пряча их от пользователя и делая это максимально неэффективно (просто потому, что интерфейс не позволяет). задача auto_ptr вызвать деаллокатор в нужный момент. все.

данная реализация SmartPtr подходит для:
1. демонстрации возможностей С++
2. простого побайтового чтения строки из разных мест памяти.

если автор хотел именно этого достичь — работа выполнена на 5+.
для задач, где лишнее обращение к памяти слишком дорого, данная реализация и ее сопровождение (добавление нового функционала) не выдерживает никакой критики.

получается, что кода много, а задач, которые он может выполнить хорошо — одна. как же это еще назвать?
  • avatar
  • rouming
  • 26 ноября 2012, 14:45
0
вообще, такой «smart» подход хорошо описывает один термин — overengineering.
даже вот статья недавно попадалась
habrahabr.ru/post/99889/
вполне хорошо раскрывает тему
  • avatar
  • rouming
  • 26 ноября 2012, 00:15
0
надеюсь, мы говорим про компиляцию с оптимизацией? тогда конечно компилятор вычитает адрес функции из структуры в самом начале фнукции UsartPuts и будет прыгать по этому адресу в цикле.
и если мы продолжаем говорить о компиляции с оптимизацией, то, конечно же, никаких прологов/эпилогов не будет.

да что вы к ram прицепились? да, он будет медленнее. не в десятки раз, но медленне. но я сделал акцент на записи, которая просто так не покроется красивостью SmartPtr и он будет всасывать причмокивая. а для uart подойдет и то, что я привел. ну просто потому, что это писать 2 минуты, скорость не так важна, а выглядит в 100 раз проще. я выбираю простоту.
  • avatar
  • rouming
  • 25 ноября 2012, 23:58
0
да, для операции чтения одного байта лишний rjmp в 2 цикла окажется катастрофически неэффективным, а код окажется медленнее (внезапно!) в десятки раз. конечно, в остальных случаях, когда все-таки понадобится запись во flash блоками (с чтением и запретом прерывания), придется ох как подпрыгнуть высоко и написать еще много строк кода для SmartPtr, чтоб пользователь этого SmartPtr не догадался, что внизу-то не все так просто и гладко. зато будет красиво.

шутки шутками но время, затраченное на понимание такого кода, имеет большое значение, особенно когда его (кода) станет действительно много (да вот хотя бы реализация записи и оптимизация этой же записи). и в данном случае простота кода становится важнее потерянных двух тактов для одного случая, который кажется сомнительным (ну потому что либо ты пишешь в uart по байту, тогда какая экономия на rjmp? либо ты действительно пытаешься соптимизировать доступ к какому-то внешнему носителю, тогда какое чтение из ram?)
  • avatar
  • rouming
  • 25 ноября 2012, 23:38
0
у меня один вопрос: божечки мои, ну зачем же так сложно?

вы переписали все методы адресной арифметики, которую прекрасно выполняет сам компилятор.
ну это примерно тоже самое, если повторить все операции для «умного» integer:
class SmartInteger:
{
public:
    int operator+ (const int& i) const;
    int operator- (const int& i) const;
    // ... ну вы поняли 
};

который кроме бесконечного списка перегруженных операторов еще умеет, ну… ну не знаю… писать в поток методом operator<<. 1 новый метод, а столько лишнего кода.
а в вашем случае весь этот код написан только для generic доступа к памяти, ну т.е. для вызова *ptr для любого типа.
но ведь это совершенно не нужно. давайте по порядку.

1. у вас есть якобы generic вычитыватель из указателя и пихатель байта в uart.
template<class T>
void UsartPuts(const T str)
{
        char c;
        while(c = *str++)
        {
                UsartWriteByte( c );
        }
}

но это обман. так как достает он ровно char и его же передает UsartWriteByte. т.е. да, функция объявлена принимающий любой тип, но по факту она _требует_, чтоб operator* возвращал char. попробуете передать в UsartPuts что-то не char — будет ошибка коппиляции (не смогли скастить что-то в char). где же та дженериковость?

2. из пункта 1 в общем-то следует, что нижний уровень (уровень, где нужно что-то передать по шине, в железо, в другую память, не важно) _всегда_ будет пихать палки в эту вашу абстрактовость и дженериковость уровня выше, а значит надо будет городить все больше и больше кода, чтоб сверху все выглядило «красиво» и по «СиПлюсПлюсному». например, uart всегда принимает байт, какие-то архитектуры требует выровненный на 4 байта доступ (иначе исключение), а dma должен быть верно проинициализирован. все это тоже будет куда-то спрятано в бесконечную тряпку SmartPtr?

3. как только вы задумаетесь об оптимизации записи/чтения, вы все равно придете к вызовам, работаяющим с блоками данных:
int read(void *data, int offset, int size);
int write(const void *data, int offset, int size);
ну потому что писать по байту это… это… подходит только разве что для uart, да и то для синхронного режима. ни файловая система, ни блочное устройство (вставьте любое устройство, которое работает с памятью _быстро_) _никогда_ ничего не будет делать по байтам, интам, короче любым типам, пробегая по ним циклом. потому что это — неэффективно. да вот давайте возьмем для примера запись (которую вы предусмотрительно не стали реализовывать, а оставили это как домашнее заданее для читателей). рассмотрим запись во flash, которая делается блоками по 128 байт (для atmega16), а если вы пишите меньше блока, то предварительно должны вычитать часть. а теперь ответьте мне, пожалуйста, честно: ну неужели вы ради «красивости» конструкции *ptr = byte; будете писать циклом по байту?

4. как только вы приходите к вызовам, которые не являются операторами в терминах С++ (я про вызовы из пункта 3), весь ваш SmartPtr становится не нужен. совсем, так как вы уже не сможете просто написать *ptr++. нет, конечно, вы можете продолжать городить абстракцию над абстрацией, но рано или поздно код раздуется до размеров голубого гиганта, а потом, спустя время, сколлапсирует, а вы останетесь с черной дырой.

почему бы не сделать максимально просто?

struct mem_ops
{
    uint8_t (*read_byte)(const uint8_t *addr);
    /* some other ops, whatever you want */
};

void init_eeprom_ops(struct mem_ops *ops)
{
    ops->read_byte = eeprom_read_byte;
}

static uint8_t __pgm_read_byte(const uint8_t *addr)
{
   return pgm_read_byte(addr);
}

void init_pgm_ops(struct mem_ops *ops)
{
   ops->read_byte = __pgm_read_byte;
}

static uint8_t __ram_read_byte(const uint8_t *addr)
{
  return *addr;
}

void init_ram_ops(struct mem_ops *ops)
{
  ops->read_byte = __ram_read_byte;
}

void UsartPuts(struct mem_ops *ops, uint8_t *addr)
{
  char c;
  while(c = ops->read_byte(addr++))
     UsartWriteByte(C);
}

да, такой подход имеет, ряд недостатков:
* это не C++
* код предельно скучен, а в статье красочно не опишешь
* не используются классные модные чтучки, типа template, наследование и проч.
* код просто работает

и да, если читая эти ~30 строк кода, вы зеваете, то можно struct заменить на class, добавить пару методов, конструктор, деструктор, а еще включить синглтон и обработку исключений. поверьте, код сразу засияет.
  • avatar
  • rouming
  • 25 ноября 2012, 22:31
0
такой избыточной конструкцией вы сказали:
1. p не может быть изменен как указатель
2. данные не могут быть модифицированы через p.
ничего о реальной константности данных, на которые указыает p, не сказано
  • avatar
  • rouming
  • 03 ноября 2012, 21:40
0
хорошо. усложниим. добавим _везде_ const
const int const * const p = &i
  • avatar
  • rouming
  • 03 ноября 2012, 21:37
0
вас не смущает такой код?
int i = 0;
const int *p = &i;
int ii = *p; /* ii == 0 */
i = 666;
ii = *p; /* ii == 666 */
константа изменилась.
  • avatar
  • rouming
  • 03 ноября 2012, 21:20
0
вы переменную не можете изменить. const.
переменная может быть изменена волшебными силами извне (общая память с устройством, например). volatile.
каждое обращение на _чтение_ переменной — лезем в память, читаем. никаких оптимизаций. volatile.
  • avatar
  • rouming
  • 03 ноября 2012, 21:13
0
это значит, что
1. переменная будет видна только в той единицы трансляции (файле), где объявлена. за это отвечает модификатор static.
2. если инициализации у переменной отсутствует, то компилятор присвоит ей 0.
static int v; /* v == 0 */
за это тоже отвечает модификатор static.
3. переменная будет существовать все время жизни вашей программы. опять static.
4. каждое обращение к переменной — это всегда доступ к памяти этой переменной, т.е. не будет никакого кеша в регистрах или оптимизации. за это отвечает volatile.

кстати, даже можно писать так:
static const volatile int i;
и модификаторы const и volatile никак не противоречат друг другу, хотя, казалось бы, одновременно «изменчивый» и «постоянный» не могут ужиться вместе.
  • avatar
  • rouming
  • 03 ноября 2012, 21:02
0
фишка в том, что конструкция __asm__ volatile ("" ::: «memory»); не содержит никаких инструкций, поэтому компилятору переставлять/оптимизировать нечего. в статье, что вы привели, речь идет о cli/sti. а с ними все хитро получается. так как реально барьер это лишь фраза «эй, сбрось все в память до и перечитай после», поэтому этот трюк работает для выставления флагов, находящихся в памяти. cli/sti к памяти никакого отношения не имеют, поэтому с точки зрения абстрактной машины эти инструкции не создают _никаких_ побочных эффектов. а вообще, эта ссылка в очередной раз подтверждает, что все еще хуже, чем есть, а фраза
«Unfortunately, at the moment, in avr-gcc (nor in the C standard), there is no mechanism to enforce complete match of written and executed code ordering» — рвет шаблоны и заставляет всех лизать кегли.

но еще раз повторюсь, все эти игры с volatile опасны, общих решений нет, а каждая платформа и компилятор предлагают какие-то свои, не всегда верно работающие решения.

кстати, у gcc c версии 4.4 есть __sync_syncrhonize, что должно быть эквивалентно __asm__ volatile ("" ::: «memory»);. хотя, я не смотрел, какой получается в итоге код.
  • avatar
  • rouming
  • 02 ноября 2012, 10:51
0
зачем так сложно?
*(volatile int*)&G_flags |= A_FLAG;


кстати, компилятор avr-gcc и для ваших трех строчек и для этой одной сделает одинаковый код:

foo:
	movw r30,r24
	ld r24,Z
	ldd r25,Z+1
	or r22,r24
	or r23,r25
	std Z+1,r23
	st Z,r22
	ret
 


т.е. вычитали из памяти в регистр, применили маску, снова записали регистры в память.
  • avatar
  • rouming
  • 01 ноября 2012, 23:02
0
используйте непосредственный каст к volatile.

/* здесь и сейчас получаю state, так как потом уже будет поздно */
int state = *(volatile int*)ptr;

switch(state) {
...
}
  • avatar
  • rouming
  • 01 ноября 2012, 22:33
0
ну ваши флаги с volatile будут изменяться из прерывания точно так же, как и без него.
на какой архитектуре вы работаете? какой компилятор? если AVR и avr-gcc и хотите чего-то ждать (крутиться в цикле), то сделайте например так:

#define barrier() asm volatile ("" ::: "memory")

struct data
{
        int ready;
        int one;
        int two;
        int three;
};

struct data my_data;

ISR(XXX_vect)
{
        my_data.one = 0;
        my_data.two = 1;
        my_data.three = 2;
        
        /* флаг ready должен быть выставлен после обновления всей структуры. сейчас avr-gcc не переставляет
           инструкции и я _не_ использую вложенных прерывания, но кто знает, что будет завтра? */
        barrier();

        my_data.ready = 1;
}

int main(void)
{
        while (my_data.ready)
                /* ждем выставления ready из прерывания */
                barrier();

        return 0;
}


Кстати говоря, в avr-glibc даже объявлен барьер, но он не компилируется, так как пропущены "" до :::.

/usr/avr/include/avr/cpufunc.h:#define _MemoryBarrier() __asm__ __volatile__(:::«memory»)
  • avatar
  • rouming
  • 01 ноября 2012, 22:29
0
да. я слишком глобально подхожу к проблеме. но просто цена _такой_ ошибки оказывается слишком велика.
  • avatar
  • rouming
  • 01 ноября 2012, 21:59