AVR, С++ и умные указатели

AVR, С++ и умные указатели

Как известно МК AVR построены по Гарвардской архитектуре, что значит МК имеет различные шины для памяти программ — flash, eeprom и ОЗУ. Многие МК имеют различные шины для разных типов памяти, это даёт возможность, например, одновременной выборки команд и доступа в ОЗУ. Однако в большинстве современных МК при этом все различные виды памяти отображаются в единое адресное пространство. В AVR же и шины разные и отображаются они в разные адресные пространства. Это создаёт определённые неудобства при программировании на Си/Си++ поскольку эти языки подразумевают единое адресное пространство и работа, например, с константами хранящимися в памяти программ осуществляется не очень прозрачно.
В компиляторе от IAR Systems для работы с различными адресными пространствами предусмотрены специальные нестандартные ключевые слова, которые используются как модификаторы типов переменных:

//Объявили строку в памяти программ
__flash const char strInFlash[] = "Hello from flash";
// и в оперативке
char strInRam[] = "Hello from RAM";

Так как память программ часто используется для хранения строк и таблиц в примерах будем основываться именно на этом. Для примера возьмём также нехитрую функцию отправки байта в USART, инициализацию и прочее опустим для ясности:

static inline void UsartWriteByte(uint8_t value)
{
	while(!(UCSRA & (1<<UDRE)));
	UDR = value;
}

Функции для ввода строки по USART из флеша и опреративки соответственно:

void UsartPutsP(const char __flash * str)
{
	char c;
 	while(c = *str++)
	{
  		UsartWriteByte( c );
	}
}

void UsartPuts(const char * str)
{
	char c;
 	while(c = *str++)
	{
  		UsartWriteByte( c );
	}
}

Как видно, они практически идентичны, за исключением того, что в первой параметр имеет модификатор __falsh. Следует обратить внимание, что модификатор у параметра ставится именно после типа (char), что означает указатель на char, указывающий куда-то во flash. Если записать так:
void UsartPutsP(const __flash char * str)…
то это будет означать, что сам указатель хранится в памяти программ, а указывает в оперативку.
По сути в компиляторе IAR __flash char и просто char — это совершенно разные типы со своими коммандами для загрузки значения. Передать в функцию UsartPutsP обычный указатель не получится, и наоборот, UsartPuts не примет указатель на flash.
В этом простом примере у нас уже присутствует дублирование кода — две почти одинаковые функции. Необходимости двух функций можно избежать с помощью еще одного нестандартного модификатора __generic. Указатель при этом становится 3-х байтовым и содержит в себе дополнительный флаг, определяющий откуда читать данные. При каждом чтении по такому указателю, сначала проверяется этот флаг, а потом производится чтение или из flash, или из опреативки в зависимости от его значения. Соответственно цена этого удобства в потери производительности и сложностях при переносе кода на другой компилятор.

В avr-gcc дела обстоят иначе — никаких специальных ключевых слов нет, данные, которые нужно поместить в мамять программ помечаются соответствующим атрибутом и компилятор размещает их в нужной секции:

PROGMEM const char strInFlash =  "Hello from flash"; 

Внешне это определение очень похоже на аналогичное определение в IAR, однако GCC не создаёт специальных типов для данных хранящихся во flash. То есть PROGMEM — это только способ разместить данные в памяти программ, типы для данных расположенных в ОЗУ и во flash никак не различаются, и как к ним обращаться уже ответственность программиста:

void UsartPutsP(char * str)
{
char c;
	while((c = pgm_read_byte(str++)))
	{
		UsartWriteByte( c );
	}
}

Программисту надо самому помнить где, что храниться и использовать соответствующие способы чтения. Такой подход требует очень аккуратного кодирования. Функция для отправки строки из оперативки также никуда не делась и дублирование кода тоже присутствует. А если нужно еще строки из EEPROM выводить?

Шаблоны С++ нам в помощь. Поскольку В IAR-e типы переменных хранящихся в разных типах памяти отличаются, то нам и делать ничего не надо, достаточно просто сделать UsartPuts шаблонной функцией и всё будет работать:

template<class T>
void UsartPuts(const T str)
{
  	char c;
 	while(c = *str++)
	{
  		UsartWriteByte( c );
	}
}
…
__flash char strInFlash[] = "Hello from flash"; 
char strInRam[] = "Hello from RAM";
__eeprom char strInEeprom[] = "Hello from EEPROM";
…
UsartPuts(strInFlash);
UsartPuts(strInRam);
UsartPuts(strInEeprom);

Эта функция будет корректно и максимально быстро работать со строками расположенными как в ОЗУ, так и во flash и eeprom.

В avr-gcc, из-за того, что типы переменных не зависит от их места хранения, всё несколько сложнее — надо сделать чтоб зависели. В С++ есть замечательная концепция “умных указателей”, всё, что нам нужно — это реализовать такой указатель для каждого интересующего нас адресного пространства. Сначала опишем базовый класс содержащий реализацию наших указателей:

// Этот шаблон принимает два параметра:
// 1 - тип переменной на которую указывает указатель
// 2 - класс содержащий функцию чтения из целевого адресного пространства
template<class T, class Accessor>
class BasePtr
{
	// псевдоним для собственного имени класса для краткости
	typedef BasePtr Self;
public:
	BasePtr(T *address=0)
		:_address(address)
	{
	}
	// далее определяем все операции поддерживаемые этим указателем
	// преинкремент 
	inline Self& operator ++()
	{
		_address++;
		return *this;
	}
	// постинкремент
	inline Self operator ++(int)
	{
		Self tmp = *this;
		_address++;
		return tmp;
	}
	// предекремент
	inline Self& operator --()
	{
		_address--;
		return *this;
	}
	// постдекремент 
	inline Self operator --(int)
	{
		Self tmp = *this;
		_address--;
		return tmp;
	}
	
	inline Self& operator +=(int value)
	{
		_address += value;
		return *this;
	}

	inline Self& operator -=(int value)
	{
		_address -= value;
		return *this;
	}

	inline Self operator +(int value)
	{
		return Self(_address + value);
	}

	inline Self operator -(int value)
	{
		return Self(_address - value);
	}

	inline bool operator !=(const Self &other) const
	{
		return _address != other._address;
	}

	inline bool operator ==(const Self &other) const
	{
		return _address == other._address;
	}
// Операция разъименовывания указателя. Здесь осуществляется чтение по указателю.
	inline const T operator *()const
	{
		union 
		{
			T value;
			uint8_t bytes[sizeof(T)];
		}data;

		for(unsigned i = 0; i<sizeof(T); ++i)
			data.bytes[i] = Accessor::Read(_address + i);
		return data.value;
	}
private:
	T * _address;
};

Теперь напишем специализации базового указателя для flash и EEPROM:

template<class T>
class FlashPtr :public BasePtr<T, FlashPtr<T> >
{
	public:
	FlashPtr(T *address)
	:BasePtr<T, FlashPtr<T> >(address)
	{}

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

template<class T>
class EepromPtr :public BasePtr<T, EepromPtr<T> >
{
	public:
	EepromPtr(T *address)
	:BasePtr<T, EepromPtr<T> >(address)
	{}

	static uint8_t Read(T *addr)
	{
		return eeprom_read_byte((const uint8_t*)addr);
	}
};

Теперь попробуем воспользоваться этими указателями таким-же образом как это делали в IAR-е:

char strInRam[] = "Hello from RAM";
PROGMEM char strInFlash[] = "Hello from flash"; 
EEMEM char strInEeprom[] = "Hello from EEPROM";
...
FlashPtr<char> ptrInFlash(strInFlash);
EepromPtr<char> ptrInEeprom(strInEeprom);
...
UsartPuts(ptrInFlash);
UsartPuts(ptrInEeprom);
UsartPuts(strInRam);

Генерируемый в этом примере машинный код практически идентичен, тому, что генерирует IAR. Фрагмент ассемблерного листинга, генерируемого для вызова UsartPuts(ptrInFlash):

ldi	r30, 0x54	; 84
ldi	r31, 0x00	; 0
rjmp	.+8

sbis	0x0b, 5	
rjmp	.-4
out	0x0c, r24
adiw	r30, 0x01
lpm	r24, Z+

and	r24, r24
brne	.-14

Как видно, код максимально эффективен, компилятор сумел избавиться от объекта ptrInFlash и даже не выделил для него память, разместив в регистрах. Важное замечание: чтобы развязать компилятору руки в плане оптимизации, наши умные указатели должны следовать семантике значения. То есть у них не должно быть пользовательского конструктора копирования и оператора присваивания — надо дать компилятору самому их сгенерировать.

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



UPDATE: исправил неточность
  • +11
  • 09 марта 2011, 19:51
  • neiver

Комментарии (101)

RSS свернуть / развернуть
А чего себе? В «алгоритмы и программные решения» бы перебросил.
0
Еще не успел.
0
))C++ на микроконтроллерах, это что то))что бы его эффективно использовать, надо учиться и учиться… В огромных проэктах классы бесспорно удобнейшая весч, а вот на авр незнаю, как то масштабы помельче…
0
Если выкинуть исключения, STL, stdlib то С++ получается достаточно компактным. Но тем не менее, он остаётся достаточно нишевым инструментом.
0
Нишевым инструментом С++ никогда не был — это как раз универсальный и расширяемый инструмент.
0
посоветуйте пожалуйста доступное для понимания чтиво по плюсам… вы, я вижу, явно в теме. а я вот си освоил, си-шарп освоил. а плюсы никак не лезут. пытался читать шилдта, пытался страуструпа, кого-то еще пытался — никак. на уровне того, что уже знаю вроде более-менее понятно, а как начинаются плюсовые плюшки вроде шаблонов, крышу сносит напрочь. при том, что шарповские дженерики такого не вызывали…
0
C++ часто сложен для понимания, а от шаблонного метапрограммирования так и вообще у всех по началу крышу сносит :)
Почитать можно:
Скотт Мейерс-Наиболее эффективное использование C++
Abrahams D., Gurtovoy A. — C++ Template Metaprogramming
Александреску А. Современное проектирование на C++
еще можно
Стефан К. Дьюхэрст «Скользкие места С++»

Ну и конечно надо различать подходы по написанию программ для ПК и МК. Что хорошо для «большого» ПК — для МК неподъёмная тяжесть и наоборот — что быстро и компактно для МК — для ПК — малофункционально.
0
Вот Александреску для понимания шаблонов не советую категорически. Его надо читать, когда сам уже почти гуру :). Плюсую Мейерса, очень полезная серия книг у него.
0
Читая эту статью, понимаю, как много я ещё не знаю по С++.
Есть пара вопросов, от незнания, так что просто хоть отошлите ключевыми словами:
1. inline bool operator ==(const Self &other) const
зачем в конце «const»?

2. templateclass FlashPtr :public BasePtr<T, FlashPtr>

FlashPtr определяется от BasePtr, который зависит от FlashPtr, который ещё не определён… рекурсия какая-то.

3. Зачем здесь вообще шаблоны? Почему Read не сделать абстрактной (pure virtual), а нужен Accessor?
0
1. inline bool operator ==(const Self &other) const — это оператор сравнения, он, очевидно не должен менять объект. const даёт гарантию, того, что при вызове этого метода объект не меняется, значит его можно вызывать у константного объекта.

2. Никакой рекурсии здесь нет. Класс определён после того момента, как в определении записано его имя. Это даёт возможность использовать внутри определения класса указатели на самого себя (в том числе this) и параметризовать шаблоны, в том числе и в списке базовых классов.

3. По соображениям производительности. Если всё это сделать с помощью виртуальных функций, такой указатель будет уже не равноценен настоящему встроенному указателю по ряду причин:
а — будет занимать в памяти не 2, а 4 байта, так как будет хранить в себе ещё и указатель на таблицу
виртуальных функций. Плюс сами таблицы будут висеть в памяти.
б — вызов виртуальной функции, в большинстве случаев, не может быть встроен.
Этот вызов стоит порядка 12-15 тактов процессора — это в разы больше, чем в описанном варианте.
в — при наличии виртуальных функций, класс уже не может быть просто по-байство скопирован и, соответственно передан как параметр функции в регистрах. Будет использована по-членная инициализация(если не определить свой конструктор копирования), и объект будет передаваться через стек. Это потребует, как минимум, инициализации кадра стека. Да и запихивание данных в стек и извлечение оттуда гораздо медленнее передачи в регистрах.

Статический полиморфизм (шаблоны) в данном случае гораздо выгоднее, чем динамический (виртуальные функции).
0
Спасибо.
И список литературы отличный.
0
Вот эта заметка тоже может быть полезной: «С++: зачем нужны умные указатели?»
0
  • avatar
  • Anniz
  • 13 сентября 2012, 13:55
Подскажите пожалуйста как можно сделать указатель байтовый, биты которого указывают на биты каких то портов. Мк stm8s105 8 светодиодов раскиданы по 2 порта, у порта D с 0-4 ножки(1 ножка SWIM не задействованы) и порт C с 4-7 ножки,
#define LED5 PC_ODR_ODR2
#define LED6 PC_ODR_ODR3
#define LED7 PC_ODR_ODR4
сейчас чтобы зажечь перебираю все дефайны, т.е.если зажечь нужно 1 диод,
switch (i)
{
case 0:
LED0=j;
break;
case 1:
LED1=j;
break;…
Можно ли закинуть с разных портов ножки в 1 байт или больше главное чтобы было в 1 переменной не важно массив или нет? Указатель на PF_ODR_bit.ODR0 не помогают компилятор ругается что то мол это бить
0
volatile char* led_ports[] = {PC_ODR, PC_ODR, PD_ODR};
char led_mask[] = {0x04, 0x08, 0x10};
//...
if(j) led_ports[i] |=  led_mask[i];    // turn on
else  led_ports[i] &= ~led_mask[i];    // turn off

А вообще коммент несколько не к месту. стм это отдельная история.
0
Если они вам нужны по очереди, то у вас явно имеет место быть структура. А раз так, то не надо искать приключений и сразу делать структуру. Вот тогда компилятор и перестанет их раскладывать на свое усмотрение.
0
  • avatar
  • evsi
  • 15 ноября 2012, 13:38
Других структур, кроме массива, вы не знаете?
+1
Прежде чем писать на С, неплохо бы его изучить. Типы данных, к которым относятся и структуры — самый базис языка.
+1
  • avatar
  • Vga
  • 15 ноября 2012, 14:53
Основы сперва желательно изучить. И изучаются они не на полноценных программах, а на учебных примерах.
0
Это в реале. Учиться сразу на «боевой» программе довольно опасно.
0
Опасно не только в этом плане, но и в плане результатов обучения. Хотя тут я могу и ошибаться. В общем, успехов. И еще есть смысл выкатывать программу на критику.
0
Итогом такого обучения станет привычка писать безграмотный и несопровождаемый write-only код. Так что основы лучше выучить. Структуры, объединения и адресная арифметика (на самом деле в С это очень тесно взаимосвязано) — основы основ программирования на С.
+1
P.S. Ответ есть, но я сомневаюсь, есть ли смысл отвечать, если ты не знаешь основ. Направление копания тебе уже указали — структуры (struct), описываются в любой книге (и книжке тоже) по С.
0
С этого стоило начать. Тогда вопрос, с которого началась эта ветка даже не возник бы.
0
Нужен именно массив структур?

P.S. еще с выравниванием структур стоит разобраться, а то можно наступить на неочевидные грабли.
P.P.S. мы не умничаем, а слегка подталкиваем к движению в правильном направлении.
+2
Не вижу смысла давать имена ячейкам в таких условиях. Но в принципе есть два варианта — во первых, завести константы A, B и C, содержащие соответсвующие индексы в массиве и обращаться buff_2[B], либо засунуть массив и соответствующую ему структурку в union и обращаться либо как buff_2.arr[10], либо как buff_2.str.C.
0
Думаю, тут самое место для union. Тогда при необходимости можно обращаться к одной и той же памяти как по именам, так и как, например, к массиву байтов или других елементов. Вобщем представить одну и ту же память в разном виде, так, как єто удобно в конкретном случае.
0
А Вам не страшно изучать С на реальных проектах такого масштаба?

Касательно «подсказок» в изучении С – поверьте, дело не в том, что все «умничают», наоборот, Вас хотят наставить на правильный путь. Ели показать Вам один из методов обращения к полям структуры – вы его запомните и будете везде применять. Но это частный случай, в реальности все намного сложнее (упаковка, выравнивание полей, способы разадресации, битовые поля ).
+1
Можешь глянуть фото. Это система управления зданием.
надеюсь, никогда не буду жить в таком здании, автор контроллера которого учился на этой прошивке… просто страшно.
0
С помощью такого определения можно упростить декларацию строки во флеш:

#define SPSTR(s) (__extension__({ \
static char __c[] PROGMEM = (s); \
FlashPtr<char> _c(__c); _c; }))

При перегрузке метода в классе пишем так:

#define FCHAR_PTR FlashPtr< char >

// ...

static void WriteString( FCHAR_PTR s, EnCodePage CodePage = cp866 );
static void WriteString( const char * s, EnCodePage CodePage = cp866 );

Ну и сам код будет выглядеть теперь вот так:


    CConsole::WriteString( SPSTR( "Командная оболочка, версия " ), CConsole::cp1251 );
    CConsole::WriteString( Version );
    CConsole::WriteString( "\r\n" );

    CConsole::WriteString( SPSTR( "Дата сборки проекта: " ), CConsole::cp1251 );
    CConsole::WriteString( CVersion::GetBuildDateString(), CConsole::cp1251 );

    CConsole::WriteString( SPSTR( "\r\nАвтор: Мезенцев Вячеслав (unihomelab@ya.ru)\r\n\r\n" ), CConsole::cp1251 );

    while ( true ) {

        // Выводим приглашение
        CConsole::SetTextAttr( GREEN );
        CConsole::WriteString( SPSTR( "[ATmega16]$ " ) );

Копия окошка с выводом:

Проект uSD с модулем Petit FS

Исходник проекта тут: uSD, смотреть в файле MCU.cpp. Его можно отлаживать прямо в Proteus при помощи виртуальной пары портов и терминала ZOC/Pro 5.06 (или более новой версии).

Можно также обратить внимание, что в CVersion::GetBuildDateString() также используется эта же технология. На самом деле я просто чуть подправил имеющееся определение для PSTR(), добавив буковку S — SPSRT(), что означает SmartPSTR().
0
  • avatar
  • uni
  • 25 ноября 2012, 05:20
у меня один вопрос: божечки мои, ну зачем же так сложно?

вы переписали все методы адресной арифметики, которую прекрасно выполняет сам компилятор.
ну это примерно тоже самое, если повторить все операции для «умного» 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, добавить пару методов, конструктор, деструктор, а еще включить синглтон и обработку исключений. поверьте, код сразу засияет.
0
Оригинал, как минимум, работает чисто в compile time и не использует вызов функции по указателю. Особенно это будет заметно в варианте, читающем RAM. Код из статьи выродится в LD Rx, 0xXXXX, а твой в вызов функции по указателю и окажется медленнее в десятки раз.
0
да, для операции чтения одного байта лишний rjmp в 2 цикла окажется катастрофически неэффективным, а код окажется медленнее (внезапно!) в десятки раз. конечно, в остальных случаях, когда все-таки понадобится запись во flash блоками (с чтением и запретом прерывания), придется ох как подпрыгнуть высоко и написать еще много строк кода для SmartPtr, чтоб пользователь этого SmartPtr не догадался, что внизу-то не все так просто и гладко. зато будет красиво.

шутки шутками но время, затраченное на понимание такого кода, имеет большое значение, особенно когда его (кода) станет действительно много (да вот хотя бы реализация записи и оптимизация этой же записи). и в данном случае простота кода становится важнее потерянных двух тактов для одного случая, который кажется сомнительным (ну потому что либо ты пишешь в uart по байту, тогда какая экономия на rjmp? либо ты действительно пытаешься соптимизировать доступ к какому-то внешнему носителю, тогда какое чтение из ram?)
0
Ой ли, два такта? Нужно извлечь указатель из структуры, загрузить адрес откуда читать в регистры, после этого прыгнуть на функцию (а там еще будут пролог/эпилог функции), и уже в ней прочитать байт из памяти. Я сомневаюсь, что это уложится меньше, чем в десяток команд.
0
надеюсь, мы говорим про компиляцию с оптимизацией? тогда конечно компилятор вычитает адрес функции из структуры в самом начале фнукции UsartPuts и будет прыгать по этому адресу в цикле.
и если мы продолжаем говорить о компиляции с оптимизацией, то, конечно же, никаких прологов/эпилогов не будет.

да что вы к ram прицепились? да, он будет медленнее. не в десятки раз, но медленне. но я сделал акцент на записи, которая просто так не покроется красивостью SmartPtr и он будет всасывать причмокивая. а для uart подойдет и то, что я привел. ну просто потому, что это писать 2 минуты, скорость не так важна, а выглядит в 100 раз проще. я выбираю простоту.
0
надеюсь, мы говорим про компиляцию с оптимизацией? тогда конечно компилятор вычитает адрес функции из структуры в самом начале фнукции UsartPuts и будет прыгать по этому адресу в цикле.
Да, пожалуй так.
0
вообще, такой «smart» подход хорошо описывает один термин — overengineering.
даже вот статья недавно попадалась
habrahabr.ru/post/99889/
вполне хорошо раскрывает тему
0
Я не уверен в том, что это оверинженеринг. Все зависит от задач.
Ну и на борьбе с оверинженерингом тоже не стоит зацикливаться — иначе получится то же самое, только в другую сторону.
0
Это вы оверинжиниринга не видели. Вкупе с бумажным архитектингом получается вещь невероятной разрушительной силы. Выживают только отдельные проекты, заказчик которых имеет безразмерный кошелек и невероятное терпение.
0
ИМХО, данный случай не является overengineering. Это просто обертка, созданная по мотивам std::auto_ptr. Вы же не считаете std::stging оверинженерингом? А вот если бы для создания указателей использовалась «фабрика» и т. д., то это уже действительно тянуло бы на overengineering.
0
std::auto_ptr прекрасно справляется с одной единственной задачей — сборкой мусора.
он не включает в себя всю адресную арифметику, с которой справляется компилятор, не пытается внутри себя обращаться к нативным вещам, пряча их от пользователя и делая это максимально неэффективно (просто потому, что интерфейс не позволяет). задача auto_ptr вызвать деаллокатор в нужный момент. все.

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

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

получается, что кода много, а задач, которые он может выполнить хорошо — одна. как же это еще назвать?
0
std::auto_ptr прекрасно справляется с одной единственной задачей — сборкой мусора.
Вообще-то он с этой зарачей не справляется, поскольку это не его задача.
0
ах, издержки перевода. в следующий раз буду приводить текст оригинала и сразу ссылку:
www.cplusplus.com/reference/memory/auto_ptr/
This class template provides a limited garbage collection facility for pointers, by allowing pointers to have the elements they point to automatically destroyed when the auto_ptr object is itself destroyed.
0
Это полезный побочный эффект от основной задачи — обеспечить уникальность владения указателем на объект. То есть в любой момент времени существует только один auto_ptr владеющий указателем на конкретный объект (обойти это можно, но это будет сознательный поиск приключений на собственную задницу).
0
Настоящий инженер рассмотрит все положительные и все отрицательные стороны метода. Взвесит и те, и те на конкретном примере (не в общем, а именно, на конкретном примере, ибо общих примеров в реальной практике не бывает) и тогда уже вынесет своё мнение.

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

Подход в теме этой очень интересный, да он не лишён недостатков, более того, он не завершён. Мне бы хотелось, к примеру, ещё несколько вещей:

1) Наличие оператора [];
2) Наличие оператора &;
3) Запись в EEPROM, т.е. поддержка такого рода формы записи:

#define EEPROMPTR_DECLARE(type,name,addr) \
    EepromPtr<type> name(addr);
...
EEPROMPTR_DECLARE( uint8_t, Init, ( uint8_t * ) E2END );
...
* Init = 'T';

4) Расширение возможностей шаблона для работы с шиной I2C( TWI ) или тех, где есть адресация. Я хотел бы иметь одну форму записи для работы с несколькими типами памяти.

Кое-что я доработал сам, но мне не хватает пока знаний на более глубокие модификации. Я использую этот шаблон для эмуляции __flash в GCC, чтобы писать код, который одновременно компилируется в двух компиляторах. Для этих целей этот шаблон просто находка. И я могу показать не какие-то абстрактные умствования, а конкретный работающий пример среднего уровня сложности, где я могу писать код однообразно без си-подобного вызова функций для каждого типа памяти.

Вот проект: mysvn.ru/avr/examples/uSD/ [Revision 8]

Этот проект является примером, демонстрирующим возможности известного драйвера Petit FS. Я его понемногу переделываю, т.к. хочу иметь драйвер в стиле C++, но это не мешает уже сейчас продемонстрировать как можно писать одинаковый по виду код в столь различных компиляторах. Основная проблема при такой задаче — это iar-овский подход в работе с флеш. В этой ревизии использую ключ компилятора, который сохраняет все константные строки во флеш, а при помощи шаблона я эмулирую то же самое в gcc. И это вполне нормально работает.

Без минусов конечно не обошлось. Когда что-то новое пробуешь на практике, то узнаешь что можно делать, а что нельзя и как вообще при таком подходе выглядит конечный результат. У меня была цель: сэкономить ОЗУ, разместив всё, что можно во флеш, и сделать однообразную по возможности работу со строками, массивами расположенными в ОЗУ и флеш. Я достиг некоторого баланса плюсов и минусов и меня он пока устраивает, тем более, что достичь того же результата на обычном C, вряд ли вообще возможно.

Для чего это нужно? Многие пересели с IAR'а на GCC по банальной причине — наличие множества gcc-библиотек исходных кодов и как бы не нравился iar, но функционал (совместимость) лучше (дороже). Так вот, при определённой доводке этого метода можно при небольшой переделке использовать практически любую gcc библиотеку в IAR. В этом случае методу можно поставить '+' и он перевесит минусы.

П.С. Многие мои проекты можно сразу попробовать в работе, т.к. в репозитории есть проект для Proteus и объектные файлы (с исходниками). При наличии виртуального нуль модема и терминала ZOC/Pro 5.06 (он поддерживает CP866, у меня эмуляция ANSI терминала в коде) можно пройтись по обоим исходникам (IAR, GCC) и живьём, так сказать, прочувствовать плюсы и минусы. В проектах такого уровня лучше всё смотреть живьём, тут абстракции не помогут.

Вот как выглядит такая отладка кода IAR с подключенным gcc драйвером Petit FS:

Отладка кода IAR с подключенным gcc драйвером Petit FS
0
Какой у вас знатный пост-самохвал получился, любо-дорого посмотреть. Вы уже дочитали учебник по С++? А условия задачи после получения решения отучились менять?
0
Я вижу, что тут полно комментаторов теоретиков, должно же быть хотя бы несколько практиков. Ибо это не дело заливать «водой» каждую первую или вторую статью. Если бы вы были настолько плодовиты и что касается кода, т.е. сопровождали бы каждый свой коммент чем-то практическим, то, учитывая ваши тут совместные теоретизирования, вы давно бы уже решили большинство возникающих проблем в этой области.
0
Я вижу, что тут полно комментаторов теоретиков
Угу. С вами во главе. Причем теоретических теоретиков, поскольку даже теорию вы не доучили.
Если бы вы были настолько плодовиты и что касается кода, т.е. сопровождали бы каждый свой коммент чем-то практическим
Если бы вы думали, прежде чем писать, коментировать не было бы нужды. К тому же я, в отличие от вас, не считаю хваленки в свой адрес «чем-то практическим». Как раз потому, что кода написал куда как больше, чем вы.
Ибо это не дело заливать «водой» каждую первую или вторую статью.
Вам стоит прислушаться к собственному совету.
учитывая ваши тут совместные теоретизирования, вы давно бы уже решили большинство возникающих проблем в этой области.
Простите, о каких проблемах вы говорите? Прочитать за вас учебник по плюсам? Или научить вас внимательно читать ответы?
0
Бедолага, и пожалеть вас как-то неудобно.
Я применил по делу обсуждаемый метод. То, что увидели тут вы — это сугубо ваши личные переживания. На техническом форуме так волноваться — это по-детски наивно. Написали больше — это хорошо, молодец. Была бы печенька, то наградил.

К слову, ничего против сишного кода для решения каких-то задач я не имею и более того, слепой да не увидит в этом же проекте реализацию буфера fifo. Мне понравилась реализация, я использую, хотя мог бы найти что-нить на классах или шаблонах (в другом своём проекте я использовал собственный класс для реализации почти подобной вещи).
0
слепой да не увидит в этом же проекте реализацию буфера fifo.
Странный он какой-то. Насколько я вижу, там изрядно ошибок.
0
Ну, если они и есть, то, видимо, те, которые помогают коду работать. Я испытывал этот код на ввод данных из USART и всё работало. Как же я в командном интерпретаторе что-то ввожу тогда?

Кроме того, не у меня одного: Простое FIFO.
0
То, что оно работает в определенных условиях еще не значит, что там нет ошибок. Команды Push, Pop, Front, Flush, Size должны работать нормально (ну, не считая того, что параметр макроса там то берется в скобки, то нет — даже в одной строке есть (fifo).tail и fifo.buf). Правда, при переполнении буфер молча перезапишется, да и чтение в две команды (Data = FIFO_FRONT(Buf); FIFO_POP(Buf);) сомнительного удобства решение.
А вот c остальными макросами хуже. Скажем, FIFO_IS_FULL в случае 256-байтного буфера работать не будет. FIFO_COUNT и связанные с ним тоже могут давать неправильные результаты.
Ну и ограничения. Скажем то, что буфер только для байт (да и рассчитывать на то, что unsigned char всегда байт, особенно в эмбеде, не стоит).
0
Оба компилятора спокойно превратили это дело в рабочий код. Мне большего и не нужно было. Как раз простой ввод символов из терминала и ничего лишнего, пока лень было что-то придумывать своё.

Если мне нужно будет что-то посложнее, то я напишу что-то вроде этого: MessagesQueue.cpp.
0
Ну что я могу сказать, код с ошибками, который обычно работает — худшее, что можно заложить в свой проект.
Я же возьму что-нибудь понадежней.
0
Я же возьму что-нибудь понадежней
Бла-бла-бла… и не приведу надёжного примера… ибо меня интересуют только отрицательные отзывы… или так… бла-бла-бла, т.к. я зажал для себя надёжный пример в отличие от этого… или как? :)

Если код выполняет свой функционал правильно при заданных ограничениях и настройках, то у него нет ошибок, а неточности в оформлении можно поправить. У меня практика — критерий истины. А тут — записал байт, стёр байт. Захочешь — не ошибёшься.
0
Я бы посоветовал вариант neiver'а, но я его до конца не понял. В комментариях там кто-то находил ошибки, так что возможно и еще остались.
Я писал и для себя, но там слишком специализированно, да и на mP к тому же. Если интересно — оно в исходниках моего программатора для 2051, вроде даже в двух реализациях. Но они мне не нравятся и их надо полностью переделывать.
У меня практика — критерий истины.
Практика показывает, что плавающие ошибки (т.е. проявляющиеся только при определенном, особенно редком стечении обстоятельств) наиболее сложны в отлове и проявляются в самый неподходящий момент. Так что можешь его подождать, если тебе так угодно.
P.S. Прелестная реакция на багрепорты. Успехов.
0
Читай лучше комменты к коду, а также объяснения другого товарища, кто использовал этот код, тогда твои «багрепорты» — это неправильное использование этого кода, т.е. ты не разобрался в его применении. «Если бы, да якобы я бы использовал его так, тогда были бы ошибки...» — А зачем его использовать не так как спроектировано?
Используй по назначению и тогда у него будет только один функционал: положить байт и убрать байт.

И, к слову, когда пишут «багрепорты», то подкрепляют их конкретным демонстрационным неработающим примером, чтобы было понятно: либо человек нашёл баг, либо он не понял для чего это нужно или как это работает.

Мне вот, исходя из практического применения, даже как-то трудно придумать как сделать так, чтобы ввод у меня перестал работать, не выходя при этом за очевидные рамки.
0
Читай лучше комменты к коду, а также объяснения другого товарища, кто использовал этот код
Я внимательно изучил и комментарии (впрочем, толку от них ноль), и код, и свои выводы сделал. Слепо полагаться на мнение «другого товарища» я не собираюсь.
И, к слову, когда пишут «багрепорты», то подкрепляют их конкретным демонстрационным неработающим примером
Это при тестировании программы (типа «я нажал кнопку и оно сломалось»). При изучении кода достаточно ткнуть в ошибку.
Мне вот, исходя из практического применения, даже как-то трудно придумать как сделать так, чтобы ввод у меня перестал работать, не выходя при этом за очевидные рамки.
Немного подожди и любимая тобой практика сделает это за тебя.
0
Если ты такой умный :), то почему не нашёл ошибок в другом, уже моём коде? А? Он тоже практический и я его проверял тщательно :) А если приглядеться, то можно заметить, что они почти идентичны.

То, что ты не можешь скомпилировать элементарный «багрепорт» уже всё о тебе сказало. Ты либо не умеешь, либо витаёшь в таких высоких материях, что нам смертным куда там… по земле ползаем.
0
Ты про MessagesQueue? Я ее еще изучаю.
0
Изучай лучше и пиши конкретные и ясные «багрепорты» в повторяемом на конкретном компиляторе виде, ибо я тоже:
Слепо полагаться на мнение «другого товарища» я не собираюсь.
0
В MessagesQueue я ошибок не нашел. И насчет «почти идентичны» нельзя сказать даже с натяжкой. Разве что в том, что и то, и то — кольцевой буфер.
ибо я тоже:
Слепо полагаться на мнение «другого товарища» я не собираюсь.
А тебе никто и не предлагает. Я в ошибки ткнул, проверяй.
0
Не можешь, так и говори — не могу :) Лень, так и говори — лень. Птица высокого полёта — … можешь уже ничего не говорить. Человек дела просто берёт и делает. Словоблуд пишет комменты.

Как ты мог заметить, я никому этот код не предлагал, а лишь писал, что ничего не имеют против простых вещей на C. Ты решил найти тему для того, чтобы не в меру поумничать и не привести ничего конкретного и осязаемого. На этом всё, будет код для iar или gcc в качестве «багрепорта», будь уверен, на него я обращу внимание и даже дам тебе печеньку.
0
Словоблуд пишет комменты.
Отсюда вывод — ты словоблуд.
На этом всё, будет код для iar или gcc в качестве «багрепорта»,
Может, еще разжевать и самому исправить? Лень. Не мне этим кодом с багами пользоваться.
0
Словоблуд пишет комменты.
Надо полагать это ваша попытка самокритики по поводу постов вроде вот этого. Очень много и наукообразно, даже с блекджеком и шлюхами картинками, но по сути — ни о чем, кроме как о себе любимом вы там не пишете.
+1
Ну что я могу сказать, код с ошибками, который обычно работает — худшее, что можно заложить в свой проект.
Уговаривать его не наступать на грабли — напрасная трата времени, я уже пытался делать это несколько раз. Из чистого любопытства хотелось бы посмотреть на его реакцию, когда он на какие-нибудь из них наступит.
0
А то я этого не знаю)
0
Ну да, вобщем. С другой стороны, потроллить самовлюбленного пионера — неплохое развлечение :)
+1
Устал небось от трудов праведных, на подкрепись, тролль, тебе ещё много, ох как много работы предстоит на выбранном и ой каком не лёгком жизненном пути:

Печеньки
-1
Устал небось от трудов праведных
Нет, не устал.
тебе ещё много, ох как много работы предстоит
Троллить пионеров это не работа, а развлечение. Ваши хваленки и реакция на подколки доставляют немало лулзов, если не срываться на человеческое общение с вами и вам подобными. А то есть у меня такая слабость, вера в то, что самовлюбленные пионеры не совсем потеряны для общества и еще могут стать адекватными людьми. Вам пока успешно убеждать меня в обратном.
0
Покажете пример или как всегда постараетесь оставить последнее слово за собой?
0
Бедолага,
Сочувствую. Вижу, у вас полное самообслуживание, сами себя хвалите, сами себя жалеете. Не совсем понятно, правда, зачем остальным это читать.
Я применил по делу обсуждаемый метод.
О, снова хваленка пошла :)
То, что увидели тут вы — это сугубо ваши личные переживания.
Э нет, передергивания и изменение условий на ходу это не мои переживания, а ваши детские попытки выкрутиться из неудобного положения.
На техническом форуме так волноваться — это по-детски наивно.
Могу только повторить свое предложение вам — прислушайтесь к собственным советам, к вам они применимы в первую очередь.
0
Да, это всё нужно именно для того, чтоб можно было писать
cout << FLASH_STR("Hello");

единообразно на любой поддерживаемой платформе.
А «умный» integer, который вы привели как заведомо абсурдный вариант, тоже имеет право на жизнь, особенно если нужны вычисления с автоматическим контролем переполнения или с насыщающейся арифметикой — бывает очень полезно при обработке сигналов.

1. Всё правильно. Функция принимает не любой тип, а тот, который имеет операции постинкремента и разименовывания. И операция разименовавания в данном примере действительно должна возвращать char. Если эти требования не выполнены — ошибка компиляции. Так и должно быть.

2. Э… Это вообще что? Какое выравнивание, какое ДМА? Всё что должен делать этот SmartPtr — это читать байт по определённому адресу из определённого адресного пространства. Принцип единственной ответственности называется — single responsibility.

3. Об оптимизации чтения/записи я с самого начала задумывался. И даже замеры этой самой производительности проводил — всё с ней отлично, уж поверьте. Никто не говорит, что функция UsartWriteByte непременно должна сразу слать что-то в USART. Буферизацию никто не отменял. UsartWriteByte может складывать байты в буфер, откуда они изымаится в прерываниях USART или по ДМА — это вопрос реализации UsartWriteByte, но не SmartPtr. Опять-же single responsibility, как-бы.

4.
раздуется до размеров голубого гиганта, а потом, спустя время, сколлапсирует, а вы останетесь с черной дырой
— хороший эпитет, надо взять на вооружение :)
А по делу, компилятор с большой вероятностью не сможет заинлайнить вызов ops->read_byte, за исключением случая если все эти функции бадут объевлены static и вызовы будут происходить в одной единице трансляции, что маловероятно. А вызов функции, сохранение call-used регистров, возврат, восстановление call-used регистров… ну вы поняли.
0
если эта длиннющая портянка и задумывалась _только_ для чтения строк — удалите мою критику. я с позором и жидко сливаюсь.
0
Если вы 100 строк кода, из которых 2/3 пустые строки и фигурные скобочки, называете «длиннющая портянка»…
0
ну погодите. вы переопределили практически все операторы. назвали класс SmartPtr, ну т.е. максимально абстрактно: умный указатель на _что_угодно_. все везде templates, ну т.е. человек со стороны посмотрит и скажет «о, чудно как! а запихну-ка я свою структурку туда, наверное автор подумал обо мне, вон какой интерфейс, все есть, и моя структурка со свистом улетит в память (из памяти).». а на деле все не так, а вы говорите «Всё что должен делать этот SmartPtr — это читать байт». ну да. single responsibility. по байту на responsibility.
0
Ну, прочитанно будет не 1, sizeof(T) байт. Так, что структурку туда запихнуть можно. Не раелизованно только обращение к полям этой структуры, через этот указатель по ->, за пока не надобностью.
0
именно об этом я и говорю, что ваш интерфейс предусматривает вообще все, передавай что хошь, ни о чем не думай. а low-level часть умеет читать в цикле по байту.
как только вы будете делать запись, то эффективнее будет писать блоками.
high-level об этом знать не знает, и будет дергать ваш интерфейс в цикле, по байту.

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

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

так зачем делать такой общий код? называть его так абстрактно?
потому что это красиво? да к черту красивость. я хочу чтоб работало.
0
вы поймите. я выступаю лишь как тупой пользователь вашего класса. я не хочу думать о всех подводных камнях. я надеюсь, что вы за меня подумали, раз сделали это возможным. а по факту я могу лишь читать строку.
0
Вы опять пытаетесь этому несчастному указателю, то использоване, для которого он не предназначен. Его использование ограниченно доступом к flash и eeprom памяти на МК семейства AVR при использовании компилятора avr-gcc и только. В IAR for AVR есть замечательное, но нестандартный модификатор __flash, который определяет соответствующее адресное пространство. FlashPtr нужен для эмуляции этого модификатора в avr-gcc, чтоб можно было писать переносимый между двумя этими платформами код, чтоб прикрыть голый зад avr-gcc, который не поддерживает (не поддерживал до версии 4.7) различные адресные пространства.
0
«хочу я прочитать сектор N (512 байт) из _flash_, но не весь, а лишь первые 13 байт»…

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

короче, вы породили монстра, вы за него в ответе )) если он вас укусит — не обижайтесь потом.
0
О да! В строенные указатели предоставляют тоже крайне раздутый и монструозный интерфейс, и вы даже не представляете как люди умудряются ими пользоваться.
0
В библиотеке VE_AVR C++ Class Library сделано так:
DEV_EEPROM[addr] = value;
DEV_EEPROM[addr] >> variable;
0
Несколько неоднозначный синтаксис — смешение двух парадигм в одной сущьности. Чтение и запись должны быть единообразны. Так:
DEV_EEPROM[addr] = value;
variable = DEV_EEPROM[addr];

Или так:
DEV_EEPROM[addr] << value;
DEV_EEPROM[addr] >> variable;

Первый вариант предпочтительней. Так хотя-бы похоже на семантику массива.
А вот эта строчка у вас в коде вообще песня с припевом:
EEPROM::ptr* p = (EEPROM::ptr*) address;

Нельзя было в EEPROM::ptr положить одно единственное поле address?
0
Насчёт смешения парадигм соглашусь, надо будет добавить.

Если бы класс EEPROM::ptr создавался, а не представлял собою простую типизацию значения адреса для компилятора, то компилятор генерил бы код соответствующий, память RAM для хранения полей класса выделял бы. А так происходит следующая вещь (для ATmega328P):
EEPROM::ptr x = (0x40)->operator [] (addr);
x->operator = (value);
x->operator >> (variable);
где
x==addr
0
Память для объекта компилятор выделяет в любом случае независимо от того есть ли в классе поля или нет, размер объекта без полей может быть 1 байт, а может быть 2 или 4 байта для выравнивания. Этот указатель на объект EEPROM::ptr указывает в RAM, я понимаю, что вы его используете только для хранения адреса в EEPROM и не собираетесь его разименовывать. Но сам факт, что есть указатель куда-то неведомо куда в RAM, с которым происходят какие-то неочевидные манипуляции, он к чему-то кастится, присваивается, выглядит как хождение по лезвию ножа. К тому-же у вас есть предположение, что ++p, увеличит этот указатель на 1. Это вы закладываетесь на повеление конкретного компилятора, даже конкретной его версии. Сейчас это работает. В следующей версии компилятора сделают размер объекта без полей равным 2 байта и ++p бедет увеличивать адрес на 2.
Правильно будет добавить в EEPROM::ptr поле uint16_t address;. Из EEPROM::operator [] возвращать его по значению, не по ссылке, не надо бояться, что объект будет создан где-то в RAM, на стеке. Если он используется только как значение, то есть от него не берётся адрес, то он прекрасно разместится в регистрах, ровно в двух, как и указатель.
0
Я не стал бы настолько заморачиваться, если бы была разница в машинном коде между
uint16_t addr;
char value;
write_to_EEPROM(addr, value);
и
((0x40)->operator [] (addr))->operator = (value);

=)

AVR-GCC — это машинно-зависимый компилятор, многих трюков, доступных в GCC, в нём нет вообще. И мой код — это код строго для AVR8, задачу сделать его переносимым ещё и на AVR32 я перед собой не ставил. =)

Строго говоря там не
((EEPROM::ptr) addr)
, а
((EEPROM::ptr&) *((EEPROM::ptr*) addr)))
. Спрашивается, зачем нужно генерить код там, где его не должно быть? =)
0
Я просто хочу сказать, что закладываться на implementation depended behavior — это не очень хорошо, особенно если этого можно не делать. Код был-бы проще и понятнее, а результат тот-же.
0
Вообще-то я проверял, что именно генерит компилятор в том или ином случае. Поведение же ++pointer зависит от разрядности процессора. В данном случае это 8 бит.
0
Вообще-то поведение же ++pointer для пустого класса зависит от настроения разработчиков компилятора. Гарантируется лишь то, что он увеличится на величину большую нуля. Для avr-gcc, в используемой версии, это — 1. Это и есть implementation depended behavior.
Я посмотрел еще ваш класс FIFO. У него нет проверки на заполнение буфера. А занчит если данные в него будут писаться всреднем чуть быстрее, чем читаться, то периодически будут теряться куски размером _size.
0
Вообще=то это указатель не на «пустой» класс, а на место в памяти. Если он по каким-то причинам «вдруг» начнёт увеличиваться больше, чем на единицу, то во-первых это легко увидеть, просмотрев содержимое EEPROM, а во-вторых такое поведение легко исправить, написав соответствующий оператор.

Так и есть, это же FIFO =)
0
На заметку. Когда нужно использовать стандартные строковые функции, там может понадобится адрес начала строки во флеш памяти. Например, у меня все строки находятся во флеш и нужно узнать имеется ли в имени файла конкретное расширение. Драйвер файловой системы: PetitFS. Имя файла находится в ОЗУ, а указатель на строку с расширением имеет тип этого шаблона. Задача: как вызвать строковую функцию strstr_P(), чтобы всё работало?

Для этого в шаблон добавляем такую конструкцию:

inline T * operator & () {
            
        return ( T * ) & _address;

    }

А в коде пишем так:

if ( strstr_P( pCurrentPanel->FileInfo.fname, * ( PGM_P * ) & SPSTR( ".TXT" ) ) != NULL ) {

    CPLC::SetActiveWindow( HWND_VIEWER );
}

Хотелось бы узнать насколько это правильно я сделал и нет ли метода полегче, чтобы звездочки не маячили в коде. Определение SPSTR() выглядит так (тут в куче мусора уже не найти):

#define SPSTR(s) (__extension__({ \
    static char __c[] PROGMEM = (s); \
    FlashPtr<char> _c(__c); _c; }))
0
  • avatar
  • uni
  • 14 декабря 2012, 17:22
Я перемудрил немного, т.к. в другом месте проекта мне нужен был адрес указателя _address. Если вернуть как было изначально, то всё записывается обычным приведением:

inline T * operator & () {
        
    return ( T * ) _address;

}

и

// Если объект - файл.
} else {


    #ifdef __GNUC__

        if ( strstr_P( pCurrentPanel->FileInfo.fname, ( PGM_P ) & SPSTR( ".TXT" ) ) != NULL ) {

    #elif defined( __ICCAVR__ )

        if ( strstr_P( pCurrentPanel->FileInfo.fname, SPSTR( ".TXT" ) ) != NULL ) {


    #endif

            CPLC::SetActiveWindow( HWND_VIEWER );
        }

}
0
Не, не, не… Перегружать оператор взятия адреса в этом случае идеологически неверно, так как встроенный указатель на flash в IAR и этот шаблон будут себя вести по разному. IAR указатель на flash не приводится к обычному указателю на RAM неявным образом, но его можно привести явно, а оператор взятия адреса возвращает, естественно, адрес переменной, в которой хранится указатель. То есть указатель на указатель — тип T**.
Вы здесь пытаетесь вернуть из оператора взятия адреса, адрес по которому лежит поле _address — это совсем не то, что нужно. Чтоб этот шаблон продолжал вести себя как встроенный указатель, нужно добавить оператор приведения типа:
explicit operator const T*()const
{
    return _address;
}

Прошу обратить внимание на слово explicit. Оно нужно чтоб запертить неявное приведение.
0
Да, да. Они и ведут себя нехорошо. Мне как раз надо, чтобы запись была одинакова. Никак не мог понять как это правильно сделать, так как функции со строками нужны, которые с флеш работают.
0
Не добавляется :(. Что я делаю не так?

SmartPtr.h(113): error: only declarations of constructors can be 'explicit'
0
А какая версия avr-gcc? Проверил на 4.6.2 — работает.
0
avr-gcc (WinAVR 20100110) 4.3.3

Я устарел видимо.
0
Вот блин, этож из нового C++11 стандарта фишка.
0
С одной стороны желательно, чтобы шаблон работал с функциями strxxx_P(), а с другой стороны, если мне для этого нужно переходить на новую версию, то можно сразу на 4.7.0, но там, я так понимаю, шаблон уже не нужен. Так что делать тогда? Оставлю пока как есть.
0
Ну и второе, эта запись конфликтует с operator !=

MMC.cpp(392): error: ambiguous overload for 'operator!=' in 'buff != 0'
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.