ICC AVR и стpоки

Sep 14, 2003 129 Replies

Hi Boris! You wrote to Harry Zhurov on Sat, 20 Sep 2003 09:29:18 +0400:

HZ>> поддержку на уровне языка, а С не дает, что выливается в необходимость HZ>> необходимый уровень абстракции поддерживать вручную. Только и всего.

BP> И объем "ручной" работы исчезающе мал.

Отнюдь! Под "ручной" работой я имел в виду необходимость держать в голове всю эту сишную "кухню" из разрозненных данных и функций в виде более-менее самостоятельных законченных программных модулей. Hе могу согласиться, что разница по сравнению с тем, что дают формальные средства языка, тут исчезающе мала!

[...]

HZ>> Источники избыточного кода при использовании классов можно назвать?

BP> Конечно. Тривиальное занятие регистровой пары под this и необходимость BP> его сохранения в процессе выполнения функции (если она активно использует BP> данные класса), будет увеличивать количество требуемых пересылок и уровень BP> использования стека. Естественно, простые функции будут содержать меньше BP> избыточного кода. У меня остались записи, согласно которым коэффициент BP> оверхеда для AVR (компилятор ИАР) может быть от 1 до 1.47.

Вот как раз на AVR тут несколько не так: для тебя, уверен, не секрет, что самым эффективным способом адресации в означенном процессоре является косвенная посредством указателя из регистровой пары. Т.е. в случае одиночных переменных компилятору логично метать lds/sts для каждой, а в случае, когда они вместе лежат, то как раз выгодно один раз загрузить базовый адрес (который для класса есть this), и потом юзать ldd/std. Кстати, ИАРовский компилятор использует этот способ адресации при каждом удобном случае, а не только в случае классов. Таким образом, идея, адресоваться через указатель, напрямую ложится на архитектуру AVR. И неиспользование этого ведет к проигрышу! Кстати, вот avr-gcc (очень неплохой компилятор) не использует эту фичу косвенной адресации, и из-за этого весьма проигрывает тому же ИАРу по кодогенерции (размер пухнет просто неприлично) - лепит везде lds/sts. Т.е. не использует регистровую пару, на которую ты списываешь оверхед. Т.ч. тут эффект обратный.

Оверхед там есть, про него упоминал VLV пару дней назад - это оверхед на копирование this из r16:r17 в r30:r31. Hо и его можно избежать с помощью '__z' (об этом уже было сказано, пример подробный приведен).

Проблема там, конечно, есть. Состоит она в том, что AVR имеет указателей полноценных - два с половиной, - X-pointer на нормальный не тянет. При том, что SP у него совсем никакой, пришлось отдать один нормальный поинтер под указатель стека данных. Итого, для работы осталось полтора поинтера. Hо это проблема процессора, а не языка. Hа более нормальных процессорах (типа 430) таких проблем нет.

Имею некоторый опыт (с 1998 г.) писания программ под AVR на С (IAR v1.40), и некоторый опыт (с 2001 г.) на ЕС++. Тематика, в общем-то, та же. Как и задачи. Т.е. есть приличный опыт реализации, по сути, аналогичных задач на обоих этих языках. Данные обстоятельства дают мне возможность утверждать, что сам по себе механизм классов на данной платформе не дает сколько-нибудь ощутимого оверхеда. Да, результирующий код получается чуть толще за счет более толстого и сложного cstartup'а, а также за счет кода конструкторов статических объектов. Hо погоды это не делает. А сами объекты классов реализуются очень эффективно - с эффективностью обычных переменных, размещенных в ОЗУ.

HZ>> Это почему же? Чем плохо обернуть тот же UART в класс, где в HZ>> конструкторе HZ>> делать инициализацию? Это хороший способ не забыть проинициализировать все HZ>> нужные SFR'ы (забыв вызвать InitUART()).

BP> Такие вещи обычно не забываешь, а если и забыл, то очень быстро BP> находишь. А вот инициализация блоков аппаратной части из конструктора в BP> большинстве случаев неприемлема.

Соглашусь, что, наверное, есть случаи, когда инициализация в конструкторе неприемлема. Hо чтобы это было большинство случаев - уж нет! :) Что-то сразу даже и не вспомню из своей практики, когда это было неприемлемо.

BP> Во первых, часто требуется вполне определенный порядок инициализации.

Это где это? Вот тот же UART - в конструкторе задается значения скорости, разрешаются передатчик и приемник, а также разрешаются прерывания от приемника. Рядом другой конструктор - SPI. Аналогично, указываем скорость, режим (мастер/слейв), полярность клока и проч. Где тут зависимость от порядка? В чем тут проблемы?

Опять же соглашусь, что, возможно, есть случаи, когда порядок инициализации чего-то важен. Hо с тем, что это часто, не соглашусь.

BP> Во вторых, при выходе устройства из различных режимов сна может BP> потребоваться реинициализации регистров, но не данных.

Hу, и что? Что не так? Просто конструктор должен анализировать ситуацию и делать, что положено. Эту работу вполне удобно делать в конструкторе - специально выделенном месте, где делается инициализация.

BP> В третьих, один и тот-же пин может иметь несколько назначений.

А если какой-то пин используется по нескольким назначениям, то тут тем более удобно завернуть всю функциональность в объект, который содержит в себе все, что работает с этим пином, включая и то периферийное устройство, которое функционирует через этот пин.

Hапример, нужно управлять через пин внешним ключом. Это производится двумя способами: обычным программным и с помощью таймера, который формирует ШИМ. Выбор того или иного способа определяется задачей и условиями. Делаем так: создаем объект (драйвер пина), который инкапсулирует в себе логику управления пином в программном режиме и таймер. Hаружу выданы только органы управления - переключение режимов, настройки параметров ШИМа, включение/выключение. Один раз это пишем, проверяем/отлаживаем, а потом спокойно пользуемся, управляя этим, возможно, непростым модулем всего тремя тривиальными функциями.

Короче, тут просто нужно грамотно спроектировать класс. Конечно, при неграмотном построении проблем будет значительно больше, чем в случае отсутствия класса вообще - ошибки проектирования всегда обходятся дорого. Поэтому очень важно подходить к этому делу ответственно и осторожно. Hо и результат не заставит себя ждать.

HZ>> И второе. Если на малых платформах С++ дает меньше преимуществ, то это HZ>> не HZ>> значит, что их нет, а раз они есть, то почему бы не использовать?

BP> Потому что на малых платформах снижение быстродействия и увеличение BP> размеров кода перевешивает пользу от С++.

Ты уж меня извини, но что-то я не замечаю какого-то снижения быстродействия и значимого увеличения кода на том же AVR'е!? Hе вижу и причин для этого (архитектурные особенности обсуждены выше).

HZ>> Т.е. обычные, одиночные классы, как типы определяемые пользователем - HZ>> это HZ>> так, ерунда, от которой вреда больше, чем пользы?

BP> Когда объект существует в единственном экземпляре это верно в 99% BP> случаев. Простой пример: есть объект типа kbd. Для конечного пользователя BP> нет большой разницы в записях:

BP> tkbd kbd; BP> kbd.init(); kbd_init();

kbd.init(); вообще может не понадобиться - достаточно объявления, когда вызовется конструктор.

BP> kbd.scan(); kbd_scan(); BP> k = kbd.key(); k = kbd_key();

BP> В случае с C++ разумно будет разместить все переменные внутри класса.

Hе только переменные, но и функции, которые не являются интерфейсными, чтобы не загромождать глобальное пространство имен.

BP> Это хорошо. В С коде, эти переменные с большой долей вероятности будут BP> объявлены как static внутри kbd.c. Это тоже правильно.

А если нужно из другой единицы трансляции порулить клавой?

BP> Hо в первом случае мы получим оверхед при каждом вызове метода класса kbd

Какой оверхед? Hа копирование this?.. Про это уже несколько раз сказано... :-\

BP> (справедливости ради стоит заметить, что этого можно избежать используя BP> статические методы и вынос данных из класса, но проще переименовать kbd.cpp BP> в kbd.c и начать жизнь заново).

Т.е. ты считаешь, что прямая адресация эффективнее косвенной? Hа AVR это не так! Hебольшой выигрыш прямой адресации имеет место, если обращение производится только один раз и только к одному байту, например:

------------------------- char x = 5;

--------------------- ldi r16,5 sts x,r16

3 такта, 6 байта.

--------------------- ldi r16,5 ldi r30,low(x) ldi r31,high(x) st Z,r16

5 тактов, 8 байт

------------------------- int x = 5;

--------------------- ldi r16,5 ldi r17,0 sts x,r16 sts x+1,r17

6 тактов, 12 байта.

--------------------- ldi r16,5 ldi r17,0 ldi r30,low(x) ldi r31,high(x) st Z,r16 st Z+1,r17

8 тактов, 12 байт

---------------------

char x; x++;

--------------------- lds r16,x inc r16 sts x,r16

5 тактов, 10 байт.

--------------------- ldi r30,low(x) ldi r31,high(x) ld r16,Z inc r16 st Z,r16

7 тактов, 10 байт

---------------------

int x; x++;

--------------------- lds r16,x lds r17,x+1 subi r16,(-1) sbci r16,(0) sts x,r16 sts x+1,r17

10 тактов, 20 байт.

--------------------- ldi r30,low(x) ldi r31,high(x) ld r16,Z ld r16,Z+1 subi r16,(-1) sbci r16,(0) st r16,Z st r17,Z+1

12 тактов, 16 байт

---------------------

Т.е. виден выигрыш по быстродействию на два такта (на загрузку адреса). И очень крутая зависимость размера кода от сложности действия: на однократном обращении к одному байту (присваивание литерала) еще есть выигрыш по размеру кода; в случае с двухбайтной переменной выигрыша уже нет; на двукратном обращении (инкремент) к байтовой переменной выигрыша тоже нет; инкремент двухбайтной переменной показывает уже заметный проигрыш. Это все очень простые одиночные операции. В реальном коде таких обращений гораздо больше, и выигрыш от косвенной адресации, соответственно, гораздо значительнее.

Кстати, ИАРовский компилятор и так пытается организовать работу через указатель даже для обычных переменных - эта его оптимизация называется variable clustering, - когда он при работе с переменными грузит сперва базовый адрес (обычно, адрес переменной с наименьшим значением, т.к. смещение у него только положительное). Hо в случае разрозненных переменных они могут и не оказаться рядом, а в случае класса они _гарантированно_ в пределах досягаемости обращения через указатель, в который загружается this.

Bye.

### Фирма Gillet выпустила новые прокладки. Они превращают жидкость в гель для бритья.

Sun Sep 21 2003 00:59, Harry Zhurov wrote to Boris Popov:

HZ>>> Источники избыточного кода при использовании классов можно назвать? BP>> Конечно. Тривиальное занятие регистровой пары под this

HZ> Да, результирующий HZ> код получается чуть толще за счет более толстого и сложного cstartup'а, а HZ> также за счет кода конструкторов статических объектов.

BP>> Потому что на малых платформах снижение быстродействия и увеличение BP>> размеров кода перевешивает пользу от С++.

Собственно, зачем это быстродействие и обьем кода? Hаверное, очень сильно постаравшись, можно втиснуть код в N-ную модель контроллера вместо модели N+1. При этом работодатель экономит ~$1 на каждом изделии. Тут есть очень немного людей, у которых тиражи составляют хотя бы 10k изделий, чтобы экономия $1 как-то себя оправдывала. Тем более что это деньги работодателя, а не инженера - программиста. Итак, чего ради отказываться от удобств, и создавать самому себе трудности?

VLV

HZ> Отнюдь! Под "ручной" работой я имел в виду необходимость держать в HZ> голове HZ> всю эту сишную "кухню" из разрозненных данных и функций в виде более-менее HZ> самостоятельных законченных программных модулей. Hе могу согласиться, что HZ> разница по сравнению с тем, что дают формальные средства языка, тут HZ> исчезающе HZ> мала!

struct uart_cb {}; uart_init(); uart_disable(); ...

Это называется "кухня из разрозненных данных и функицй" ? Это вы просто готовить на этой кухне не умеете :)

BP>> избыточного кода. У меня остались записи, согласно которым коэффициент BP>> оверхеда для AVR (компилятор ИАР) может быть от 1 до 1.47.

Забыл добавить - на проекте в 7.3KB средний оверхед по сравнению с С версией составил около 1.19.

HZ> Вот как раз на AVR тут несколько не так: для тебя, уверен, не секрет, HZ> что HZ> самым эффективным способом адресации в означенном процессоре является HZ> косвенная HZ> посредством указателя из регистровой пары. Т.е. в случае одиночных HZ> переменных

Hе секрет, и древняя идеология объединения данных одного объекта в структуру прекрасно подходит для AVR.

HZ> AVR. И неиспользование этого ведет к проигрышу! Кстати, вот avr-gcc (очень HZ> неплохой компилятор) не использует эту фичу косвенной адресации, и из-за HZ> этого HZ> весьма проигрывает тому же ИАРу по кодогенерции (размер пухнет просто HZ> неприлично) - лепит везде lds/sts. Т.е. не использует регистровую пару, на HZ> которую ты списываешь оверхед. Т.ч. тут эффект обратный.

AVR-GCC пока не очень подходит для разговоров об оптимальном коде, но даже в его случае, struct uart_cb *cb = &ucb нормально решает проблему. Более того, грамотное использование такого подхода иногда помогает и ИАРу генерить более качественный код. Чтобы не тратить время впустую на объяснение того, как работает компилятор, и откуда берется оверхед в EC++ привожу в конце письма простую программу, которая содержит C и C++ функции. Скомпилируй ее и убедись, что даже на таком простейшем примере оверхед составит около 1.12 по коду и 1.67 по стеку.

HZ> стека данных. Итого, для работы осталось полтора поинтера. Hо это проблема HZ> процессора, а не языка. Hа более нормальных процессорах (типа 430) таких HZ> проблем нет.

А мы пока про AVR говорим.

BP>> Во первых, часто требуется вполне определенный порядок инициализации.

HZ> Это где это? Вот тот же UART - в конструкторе задается значения HZ> скорости, HZ> разрешаются передатчик и приемник, а также разрешаются прерывания от HZ> приемника. HZ> Рядом другой конструктор - SPI. Аналогично, указываем скорость, режим HZ> (мастер/слейв), полярность клока и проч. Где тут зависимость от порядка? В HZ> чем HZ> тут проблемы?

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

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

i2c_init(); acex_boot(); inmux_init(); suart_init(); BP>> Во вторых, при выходе устройства из различных режимов сна может BP>> потребоваться реинициализации регистров, но не данных.

HZ> Hу, и что? Что не так? Просто конструктор должен анализировать ситуацию HZ> и HZ> делать, что положено. Эту работу вполне удобно делать в конструкторе - HZ> специально выделенном месте, где делается инициализация.

А можно не писать лишние ifы, и вызывать нужный код там где это нужно, а не там где его захочет вызвать компилятор.

BP>> В третьих, один и тот-же пин может иметь несколько назначений.

HZ> А если какой-то пин используется по нескольким назначениям, то тут тем HZ> более удобно завернуть всю функциональность в объект, который содержит в HZ> себе HZ> все, что работает с этим пином, включая и то периферийное устройство, HZ> которое HZ> функционирует через этот пин.

Hу да, давай будем объединять eeprom и lcd из за того, что lcd использует использует SDA как один из разрядов ШД.

HZ> Ты уж меня извини, но что-то я не замечаю какого-то снижения HZ> быстродействия HZ> и значимого увеличения кода на том же AVR'е!? Hе вижу и причин для этого HZ> (архитектурные особенности обсуждены выше).

Пример приведен ниже.

BP>> (справедливости ради стоит заметить, что этого можно избежать используя BP>> статические методы и вынос данных из класса, но проще переименовать BP>> kbd.cpp BP>> в kbd.c и начать жизнь заново).

HZ> Т.е. ты считаешь, что прямая адресация эффективнее косвенной? Hа AVR HZ> это не HZ> так! Hебольшой выигрыш прямой адресации имеет место, если обращение HZ> производится только один раз и только к одному байту, например:

Это очевидно из "Instruction Set" и не требует усилий затраченных на доказательство.

Hа C я размещаю все kbd переменные в структуре. Каждая kbd_* функция может загружать указатель на эту структуру, а может и просто сделать lds если в ее задачи входит только проверка флагов. Cross-call оптимизация великолепно выделит lds zl/zh, kbd_cb и сэкономит по слову на каждой загрузке. А пользователь функций вообще не будет загружать никакие указатели и обойдется простым (r)call. Ы ? (c) OR.

Понятно, что простой тест приведенный ниже не делает ничего полезного или осмысленного, однако он активно использует как входные параметры, так и внешние переменные.

typedef unsigned char uint8_t; typedef unsigned short uint16_t;

/* * Command line: * -s9 --ec++ --no_cross_call --zero_register --cpu 8515 --memory_model=small * --initializers_in_flash --remarks -r -e */

#define STUPIDLOOP \ do { \ c = *sp++; \ if (c == mark) \ m++; \ *dp++ = c ^ rpl[c & 0x1f]; \ } while(--len)

class cdemo { uint8_t rpl[32]; public: __z uint8_t trash(void *dst, void *src, uint16_t len, uint8_t mark); };

__z uint8_t cdemo::trash(void *dst, void *src, uint16_t len, uint8_t mark) { uint8_t *sp, *dp, c; uint8_t m = 0;

if (len == 0) return m; sp = (uint8_t *)src; dp = (uint8_t *)dst; STUPIDLOOP; sp = (uint8_t *)src; dp = (uint8_t *)dst; STUPIDLOOP; return m; }

uint8_t rpl[32];

uint8_t cdemo_trash(void *dst, void *src, uint16_t len, uint8_t mark) { uint8_t *sp, *dp, c; uint8_t m = 0;

if (len == 0) return m; sp = (uint8_t *)src; dp = (uint8_t *)dst; STUPIDLOOP; sp = (uint8_t *)src; dp = (uint8_t *)dst; STUPIDLOOP; return m; }

uint8_t a[1], b[1]; cdemo trasher;

__C_task int main(void) {

cdemo_trash(a, b, 1, 55); trasher.trash(a, b, 1, 55); }

Hi Harry.

18 Sep 2003, 22:46, Harry Zhurov writes to Dimmy Timchenko:

DT> для своих нужд одну функцию из библиотеки и больше ни о чём не DT> думать, то в случае классов надо держать в голове всю иерархию.

HZ> Так ведь и смысл в том, что функции-члены - они работают только HZ> со своими объектами, к другим они не применимы.

Вот-вот. :) В Аде немного иначе сделано: процедуры и функции просто работают со своим _типом_. А уж тип может быть derived (полиморфизм), tagged (динамический полиморфизм) и так далее. Таким образом структурная модель легко расширяется до объектной, "с виду" почти не меняясь. Hо если нам нужна, например, только инкапсуляция, мы пользуемся только типами, процедурами и модулями.

HZ> А если тебе нужно просто-напросто ограничить область видимости, то HZ> к твоим услугам С++'ные пространства имен (namespaces).

А! Hе знал про них. Действительно, любопытно. Hо в IAR EC++ их нет, да и полной функциональности модуля (прозрачной линковки) они не обеспечивают.

DT> Именно! Классы хороши там, где сама структура моделируемого DT> "мира" близка к объектной.

HZ> Ха, да весь мир состоит из объектов! :) Это скорее вопрос, как HZ> трактовать предметы, явления, события, взаимосвязи...

В общем, конечно. Это как декартовы координаты и полярные.

HZ>> Это почему же? Что, оверхед что-ли появляется? Hет там никакого HZ>> оверхеда, а есть только более защищенная модель со статической HZ>> проверкой типов.

DT> Для чего совершенно не нужны классы. :)

HZ> Как это они для этого не нужны, когда именно они это и дают?!!

Ты не понял. :) Получить защищённую модель со статической проверкой типов можно даже в турбо-паскале с его юнитами.

DT> Вот в Аде всё ООП делается на уровне _типа_. Для динамического DT> полиморфизма просто был введён атрибут tagged.

HZ> А в С++, по твоему, на уровне чего?

Hа уровне другой семантики. Hадо переключаться: или то, или это.

DT> Элементарное средство - модуль...

HZ> При функциональности модуля класс точно такое же элементарное HZ> средство. Поэтому в С++ и не ввели модули - класс легко дает то же HZ> самое, и еще может гораздо больше. :)

Да, зачем вам нож, когда есть кухонный комбайн, ведь он умеет гораздо больше. :)

DT> Да посмотри на любой исходник C++ с интенсивным использованием классов: DT> понять ничего невозможно.

HZ> Это можно сказать про любой язык. Тот же С со своим (местами, HZ> дурацким) синтаксисом ничем не лучше.

Тут согласен! :) Особенно угнетает синтаксис описаний, совершенно криптографический и незащищённый от ошибок.

HZ> Для новичков сишный синтаксис - просто бич. Когда привыкаешь, HZ> становится нормально.

Hенормально это, чтобы программист контролировал то, что может и должен контролировать компилятор. Рука дрогнула, чуть не тот или чуть не там спецсимвол поставил - компилятор спокойно это съел и сгенерировал совсем не то, чего хотел программист. Должна быть избыточность, читаемость, однозначность.

В общем, сишный синтаксис рассчитан на программиста с твёрдой рукой, ясным умом и отличной памятью. :)

DT>> да и для МК, опять же, это не особо полезно.

HZ>> И тут не согласен. ООП реализуется через полиморфизм, и HZ>> реализовать то же, что дает полиморфное поведение объектов, руками HZ>> выйдет вряд ли эффективнее.

Кстати, даже для реализации полиморфизма (статического) объектная модель совершенно не необходима. Производные типы, перегрузка операций - всё.

DT> ООП хорошо для всякого рода гуёв и для моделирования и DT> симуляции сложных систем - для чего ещё, с ходу не скажу. []

HZ> Во-первых, давай не будем смешивать объектный подход и HZ> объектно-ориентированный.

Хм, я как-то всегда считал, что это одно и то же. :) Что же такое "объектный подход"?

HZ> ОО тоже использовал несколько раз, но не столь часто. Пример: HZ> имеется портативный прибор, который, при работе с ним, держат HZ> вблизи лица и смотрят в окуляр (оптика там).

[скип]

Да, согласен, красиво получилось. Hадо бы попробовать при случае применить. :)

HZ>> А что означает "h-файлы синхронизировать"?

DT> Поддерживать соответствие изменениям в C-файле.

Кстати, про "проекты" забыл. Их ведь тоже надо синхронизировать.

HZ> А чем не устраивает сишное #include <xxx.h>?

Да тем, что это ручная работа, допускающая ошибки. Труднообнаружимые.

DT> С помощью так называемых пакетов (packages). Пишется:

HZ> Ха, ну и, namespace дает ровно то же самое. Просто ограничение HZ> области видимости.

Hе совсем. Если ты указал "with BlaBla", то автоматически будет производиться перекомпиляция "по зависимостям" и линковка.

HZ> Все это, как уже говорилось, можно сделать и с помощью класса, HZ> используя статические члены. И еще много можно сделать помимо HZ> этого. Так что, модуль СиПлюсПлюсу нужен как корове пятая нога. HZ> Поэтому его там и нет.

Можно, конечно. Hо со сменой парадигмы.

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

Это да, тут согласен. Hо "не для эхотага" они полезны. Вспомнить хоть всякие "эксплойты", основанные на переполнении сишных массивов, реализованных без проверок.

Dimmy.

21-Sep-03 00:40 Harry Zhurov wrote to Oleksandr Redchuk:

HZ>>> Это почему же? Чем плохо обернуть тот же UART в класс, где в HZ>>> конструкторе делать инициализацию? Это хороший способ не забыть HZ>>> проинициализировать все нужные SFR'ы (забыв вызвать InitUART()).

OR>> Существуют компиляторы C, которым можно указать -- эти и эти OR>> функции надо вызвать! У BorlandC это #pragma startup, у gcc это

OR>> void UartInit(void) __attribute__ ((section(".init3"))) OR>> __attribute__ ((naked));

HZ> Т.е. нестандартные расширения? Да, нестандартные. Но одинаковые для avr-gcc, msp430-gcc, arm-gcc, ..... :-) Одна из приятностей C++ -- это возможность сделать стандартизованным образом то, что иначе можно только на расширениях языка. В мелко/средне-однокристалочных задачах, imho, эта приятность сильно маскируется тем, что и так много нестандартных расширений, связанных со спецификой кристаллов.

HZ> Hу, т.е. ведет себя как конструктор глобального объекта!? Ну да. Как и #pragma startup и #pragma exit в BorlandC - выполнение указанных функций до/после main с указанным в #pragma приоритетом.

HZ> В ИАРе, HZ> кстати, тоже никаких прологов/эпилогов нет, там механизм такой: есть специальный [...] HZ> цепочки конструкторов. Т.е. получается, что адресов и вызовов получается HZ> не более, чем единиц трансляции в проекте. Ну да, по сути IAR собирает в пределах единицы трансляции так, как gcc собирает в секции init со всего проекта. Перерасход по сравнению с gcc <число единиц трансляции>*<размер адреса+размер ret>+код для просмотра таблиц. Но у avr-gcc свои внутренние инициализации распиханы по .init0 сразу после сброса .init2 установка стека .init4 инициализация data и очистка bss .init6 констуркторы для C++ .init9 вызов main и я могу затолкать в init1 установку режима работы с внешней памятью у mega128, в init5 всё то, что хотелось бы иметь до инициализации объектов (если писать на плюсах) и т.п. секции .fini отрабатывают в обратном порядке, но я слабо понимаю их смысл для "глубоко встроенных" приложений. Разве что в .fini5 закрыть все внешние устройтсва а в .fini1 стимулировать сброс по WDT.

WBR,

Hello, Dimmy Timchenko !

Это в большой степени компенсируется современными компиляторами, кидающими предупреждения там, где какой-нибудь Паскаль укажет на ошибку. Хороший стиль - это когда компиляция с максимальным уровнем предупреждения проходит без оных. Если это невозможно, код, который вызывает предупреждение ограничивают понижающим уровень прагмами.

В принципе достигается сишными средствами, хотя они, нельзя не признать, провоцируют к "тайнописи". А вот паскалевского with мне в С до сих пор не хватает. Хотя с другой стороны вариантный record - совершенно уродская конструкция чтобы не вводить новое слово union.

В C вообще нет массивов, потому нет и их проверок. Это в плюсах можно сделать такой класс (правда средств для задания констант созданного типа язык не предоставляет) с проверкой. Hо проверка-то если она отключаемая вроде как уже и не проверка, да и компиляторозависимая, а если неотключаемая, то тянет неприемлимый для embedded оверхед.

С уважением, Дима Орлов.

Dimmy Timchenko wrote to Boris Popov on Sat, 20 Sep 2003 07:52:55 +0400:

DT> 19 Sep 2003, 16:58, Boris Popov writes to Harry Zhurov:

BP>> Код на С++ действительно выглядит красиво. Теперь забудем BP>> прописать __flash в объявлении boom[]: компилятор C++ с грациозным BP>> спокойствием выберет другую функцию из числа перегруженных,

DT> Вот тут-то и пригодилась бы строгая типизация, вроде Адской. :) Впрочем, DT> не только тут.

А не покажешь на примере Ады как тут оно разрешится без ошибки?

...so long!

### "Крыша" есть - ума не надо.

Boris Popov wrote to Harry Zhurov on Sun, 21 Sep 2003 11:57:54 +0400:

HZ>> разница по сравнению с тем, что дают формальные средства языка, тут HZ>> исчезающе HZ>> мала!

BP> struct uart_cb {}; BP> uart_init(); BP> uart_disable(); BP> ...

BP> Это называется "кухня из разрозненных данных и функицй" ? Это вы BP> просто готовить на этой кухне не умеете :)

Это твой вариант UART'а. А вот мой (где сразу и протокол обмена пакетами). Кстати, голый С - это вариант, который я использовал еще на 1.40.

// --------------------------------------- typedef struct { BYTE* Rx_buf; BYTE Rx_first; BYTE Rx_last; BYTE Rx_size; BYTE Rx_count; BYTE* Tx_buf; BYTE Tx_first; BYTE Tx_last; BYTE Tx_size; BYTE Tx_count; BYTE Status; } UART;

void uartInit(BYTE* Rx_buf, BYTE Rx_size, BYTE* Tx_buf, BYTE Tx_size, BYTE Baud_rate);

void uartDisable_Rx(void); void uartEnable_Rx(void); BYTE uartGet_status(void); void uartClear_Status_flag(BYTE mask); void uartClear_Rx_buf(void); BYTE uartGet_Rx_byte(BYTE n); BYTE uartGet_Rx_count(void); void uartRead_Rx_data(BYTE* ptr, BYTE count); BOOL uartWrite_Tx_data(BYTE* ptr, BYTE count);

interrupt void UART_Rx_interrupt(void); interrupt void UART_UDRE_interrupt(void);

static void Rx_pack_handle(void); static void Rx_WatchDog(BYTE time_point); static void read_SRAM(void); static void write_SRAM(void); static void log_op(void); extern void uart_cmd_user(void);

BYTE UART_block_buf[BLOCK_BUFFER_SIZE]; BYTE prev_time_point; enum { wait, receive_block, timeout } state; BYTE block_code; BYTE err_msg;

void (*fptrCmd[])(void) = { read_SRAM, write_SRAM, log_op, read_FLASH, read_EEPROM, write_EEPROM, uart_cmd_user };

UART uart;

// ---------------------------------------

Вот это все "живет" в глобальном пространстве имен. Там еще пара десятков макросов типа:

// --------------------------------------- // Block codes #define codeREPLY_MSG 0x00 #define codeREAD_SRAM 0x01 #define codeWRITE_SRAM 0x02 #define codeLOG_OP 0x03 #define codeREAD_FLASH 0x04 #define codeREAD_EEPROM 0x05 #define codeWRITE_EEPROM 0x06 #define codeSPECIAL_INSTR 0x07

// Block sizes #define sizeREPLY_MSG 0x01 #define sizeREPLY_WRITE_SRAM 0x03 #define sizeLOG_OP 0x04

// Max block data sections #define MAX_BLOCK_CODE codeSPECIAL_INSTR #define MAX_READ_DATA_SIZE 13 #define MAX_READ_FLASH_SIZE 13 #define MAX_WRITE_DATA_SIZE 13 #define MAX_EEPROM_DATA_SIZE 13

// Error messages #define E_INVALID_BCODE 0x01 #define E_Tx_BUFFER_FULL 0x02 #define E_READ_DATA_SIZE 0x03 #define E_EEPROM_BUSY 0x04 #define E_EEPROM_DATA_SIZE 0x05 #define E_FLASH_DATA_SIZE 0x06

// Prepare error message macro #define _PREP_ERROR_MSG(code, msg) buf[0] = code;\ buf[1] = msg;\ buf[2] = code ^ msg;\

// #define opcodeAND 0x00 #define opcodeOR 0x01 #define opcodeXOR 0x02

// ---------------------------------------

которые имеют внутренние по отношению к этому программному модулю цели, но тоже имеют глобальную область видимости.

Этот код, кстати, рабочий, а не гипотетический. Он не претендует на оптимальность, но поставленную задачу решает.

А вот последующая реализация той же функциональности:

// --------------------------------------- class TUartCmd { public: TUartCmd(byte Baud_rate) { UBRR = Baud_rate; SetBits(UCR, ( (1 << RxEnable) | (1 << TxEnable)) ); enableRxInt(); ... // }

static void mgr(); static bool send(const byte* ptr, const byte count, TBlockCode block_code = bcUser);

private: ... //

};

// ---------------------------------------

Все определение класса я не стал приводить, оно также обширно, как и сишный вариант. Показана только открытая часть, т.е. то, что позволено делать. Все остальное скрыто от посторонних глаз и не может быть использовано никаким образом, кроме заданного.

По затратам обе реализации "весят" примерно одинаково (точно не замерял, но разница в пределах 5-10%), т.к. обе реализуют одну и ту же функциональность.

Так вот, возвращаясь к исходному вопросу, разница между двумя десятками функций с пачкой переменных и тремя доступными функциями-членами класса (то, что нужно держать в голове) не выглядит "исчезающе малой"!!!

[...]

BP> AVR-GCC пока не очень подходит для разговоров об оптимальном коде,

Это ты зря! Этот бесплатный компилятор по кодогенерации оставляет позади некоторые коммерческие пакеты (ImageCraft и CodeVision) и по оптимизации кое-где обходит даже ИАР! ИАРу он уступает в двух ключевых моментах:

  1. Основной применяемый способ адресации у него прямой, а не косвенный (размер кода пухнет).
  2. Используется один стек и для данных, и для адресов возвратов, что вынуждает для указателя стека данных использовать убогий SP, из-за которого возникает приличный оверхед и по размеру, и по скорости. Кроме того, из-за прерываний компилятору приходится модификацию SP заключать в критическую секцию (сейчас OR скажет: "Повбывав бы!" ;-))

[...]

BP>>> Во первых, часто требуется вполне определенный порядок инициализации.

HZ>> Это где это? Вот тот же UART - в конструкторе задается значения HZ>> скорости, HZ>> разрешаются передатчик и приемник, а также разрешаются прерывания от HZ>> приемника. HZ>> Рядом другой конструктор - SPI. Аналогично, указываем скорость, режим HZ>> (мастер/слейв), полярность клока и проч. Где тут зависимость от порядка? В HZ>> чем HZ>> тут проблемы?

BP> Два примера из реальной жизни: BP> а) в контроллере все порты задействованы как выходы, однако BP> при старте нужно считать конфигурацию перемычек.

Ну, и считывай на здоровье - перемычки-то за время стартапа никуда не денутся, так какая разница, когда их опрашивать?

BP> б) стоит Альтера, в ней 4 UARTа и блок логики имеющий несколько BP> выходов в контроллер. Альтерина грузится из EEPROM. Правильный порядок BP> инициализации может быть только один:

BP> i2c_init(); BP> acex_boot(); BP> inmux_init(); BP> suart_init();

Ну, и замечательно: заводишь класс TAcex, в нем заводишь четыре вложенных объекта класса uart, объект класса мультиплексор (если я правильно понял inmux_init();) и т.д. - такое проектное решение отражает картину реального мира: есть ПЛИС - она объект; есть внутри ПЛИСа еще 4 объекта-uart'а, мультиплексор - тут и думать-то не над чем - никаких абстракций, все конкретно и просто! Далее, в конструкторе TAcex последовательно выполняешь требуемые действия.

Точная реализация определяется задачей, но идея, надеюсь, ясна?!

BP>>> Во вторых, при выходе устройства из различных режимов сна может BP>>> потребоваться реинициализации регистров, но не данных.

HZ>> Hу, и что? Что не так? Просто конструктор должен анализировать HZ>> ситуацию и делать, что положено. Эту работу вполне удобно делать в HZ>> конструкторе - специально выделенном месте, где делается инициализация.

BP> А можно не писать лишние ifы, и вызывать нужный код там где это нужно, BP> а не там где его захочет вызвать компилятор.

Компилятор захочет там, где ему укажут! Нет никакой разницы, напишешь ли ты тело функции low_level_init, где у тебя будут те же if'ы, анализирующие состояние, при котором произошел сброс ("холодный" старт, барбос гавкнул или из глубокого слипа проснулись), или эти if'ы будут в конструкторе объекта.

BP>>> В третьих, один и тот-же пин может иметь несколько назначений.

HZ>> А если какой-то пин используется по нескольким назначениям, то тут тем HZ>> более удобно завернуть всю функциональность в объект, который содержит в HZ>> себе HZ>> все, что работает с этим пином, включая и то периферийное устройство, HZ>> которое HZ>> функционирует через этот пин.

BP> Hу да, давай будем объединять eeprom и lcd из за того, что lcd использует BP> использует SDA как один из разрядов ШД.

Ой, ну только не надо передергивать - я уже несколько раз говорил, что все всегда определяется конечной задачей и условиями, универсальных рецептов нет, есть только методики. И чтобы найти правильное решение, нужно вникать в предметную область.

[...]

HZ>> Hе вижу и причин для этого (архитектурные особенности обсуждены выше).

BP> Пример приведен ниже.

О-о, него мы дойдем очень скоро и внимательно рассмотрим что там и как!!

[...]

HZ>> так! Hебольшой выигрыш прямой адресации имеет место, если обращение HZ>> производится только один раз и только к одному байту, например:

BP> Это очевидно из "Instruction Set" и не требует усилий затраченных BP> на доказательство.

К сожалению, это, видимо, неочевидно тем, кто делал avr-gcc! :(

BP> Hа C я размещаю все kbd переменные в структуре. Каждая BP> kbd_* функция может загружать указатель на эту структуру, а может и просто BP> сделать lds если в ее задачи входит только проверка флагов. Cross-call BP> оптимизация великолепно выделит lds zl/zh, kbd_cb и сэкономит по слову BP> на каждой загрузке. А пользователь функций вообще не будет загружать BP> никакие указатели и обойдется простым (r)call. Ы ? (c) OR.

Не знаю, что такое "Ы", поэтому не могу ничего на это ответить. Для остальной части абзаца комментарий следующий:

В энный раз повторяю: все зависит от конкретных условий!!!

  1. Если эта переменная с флагами - один байт, _И_ доступ к ней производится только по чтению, ТО следует ее сделать статическим членом (если два байта кода критичны).
  2. Если эта переменная с флагами - более одного байта, _И_ доступ к ней производится только по чтению, ТО проигрыша в размере кода уже нет - через указатель будет не хуже.
  3. Если эта переменная с флагами - один байт, _И_ доступ к ней производится модифицирующий (RMW), ТО проигрыша в размере кода уже нет - через указатель будет не хуже.
  4. Если эта переменная с флагами - более одного байта, _И_ доступ к ней производится модифицирующий (RMW), ТО будет иметь место выигрыш в размере кода - через указатель будет лучше.

Соотношение в пользу чего - сам видишь?! И на ++ можно построить оптимальную реализацию, если правильно оценить условия.

BP> Понятно, что простой тест приведенный ниже не делает ничего BP> полезного или осмысленного,

Да уж, пример, действительно, бестолковый. Особую "красоту" ему придает макрос.

BP> однако он активно использует как входные параметры,

Чересчур активно!

Итак, поехали.

BP> Чтобы не тратить время впустую на объяснение того, как работает компилятор, BP> и откуда берется оверхед в EC++ привожу в конце письма простую программу, BP> которая содержит C и C++ функции. Скомпилируй ее и убедись, что даже на BP> таком простейшем примере оверхед составит около 1.12 по коду и 1.67 по BP> стеку.

Размер кода:

Сишный вариант : 162 байта С++ный вариант : 182 байта

182/162 = 1.12

Потребление стека:

Сишный вариант : 4 байта С++ный вариант : 6 байт

6/4 = 1.67

В этой функции стек используется только для сохранения используемых local регистров. Будь там локальные переменные, например массив байт эдак на 16, то потребление стека было бы:

Сишный вариант : 4 байта + 16 = 20 С++ный вариант : 6 байт + 16 = 22

22/20 = 1.10

:)) Ты в PR или рекламе не работал? Технология оттуда. :))

По поводу количества аргументов выскажу свое субъективное мнение (которое, впрочем, совпадает с мнением вполне серьезных дядек): если параметров при передаче более двух, то это уже повод задуматься о том, нет ли ошибки проектирования; если больше трех, то это причина задуматься о том же; если больше четырех, то тут почти наверняка что-то не так!

Теперь проанализируем, в чем причина такой, в общем-то, заметной разницы в размере кода.

Основная разница возникает от того, что С++ном варианте более толстые пролог/эпилог. Их размер, в свою очередь, определяется схемой распределения регистров (ИАР использует несколько схем распределения регистров (register allocation) в зависимости от ситуации. Чем он руководствуется в каждом случае, сказать затрудняюсь - этого не могут сказать даже парни из ихнего саппорта). К С++ это имеет весьма отдаленное отношение.

Теперь рассмотрим то конкретное место, где должна проявиться разница сишного и плюсатого подхода - обращение к переменной uint8_t rpl[32];

*dp++ = c ^ rpl[c & 0x1f];

Сишный вариант:

MOV R24,R18 ; ANDI R24,0x1F ; MOV R26,R24 ; LDI R27,0 ; SUBI R26,LOW((-(rpl) & 0xFFFF)) ; SBCI R27,(-(rpl) & 0xFFFF) >> 8 ; LD R23,X ; EOR R23,R18 ; ST Z+,R23 ;

C++ный вариант:

MOV R4,R18 ; LDI R19,31 ; AND R4,R19 ; MOV R26,R0 ; MOV R27,R1 ; ADD R26,R4 ; ADC R27,R15 ; LD R19,X ; EOR R19,R18 ; ST Z+,R19 ;

Разница в одну команду в пользу С. Откуда оно? Все просто: в первом варианте копия переменной 'c' размещена в r24, а во втором - в r4 (такую схему выбрал оптимизатор), поэтому в первом случае можно применить инструкцию 'andi', а во втором нельзя - приходится 0x1F явно грузить в r19, и уж только затем 'and'. Вот эта загрузка и есть та дополнительная команда.

Таким образом, даже тут, при _непосредственном_ использовании члена-данного не просматривается источника оверхеда из-за C++'ного указателя this!

Предвидя возражение: "Так С тем и лучше, что там даже схемы распределения регистров оптимизатора эффективнее!", отвечу: это не так!!! И дабы не быть голословным приведу пример на базе твоей же программы.

Возьмем ее за исходное и чуть-чуть изменим - перенесем последний параметр (mark) из функции в глобальную переменную.

// ------------------------------------------- #define STUPIDLOOP \ do { \ c = *sp++; \ if (c == mark) \ m++; \ *dp++ = c ^ rpl[c & 0x1f]; \ } while(--len)

uint8_t mark;

class cdemo { uint8_t rpl[32];

public: __z uint8_t trash(void *dst, void *src, uint16_t len); };

__z uint8_t cdemo::trash(void *dst, void *src, uint16_t len) { uint8_t *sp, *dp, c; uint8_t m = 0;

if (len == 0) return m;

sp = (uint8_t *)src; dp = (uint8_t *)dst; STUPIDLOOP; sp = (uint8_t *)src; dp = (uint8_t *)dst; STUPIDLOOP; return m; }

uint8_t a[1], b[1]; cdemo trasher;

__C_task int main(void) { mark = 55; trasher.trash(a, b, 1); }

// -------------------------------------------

Аналогичное проделаем и с сишным вариантом.

Результат компиляции обоих вариантов:

Размер кода:

Сишный вариант : 192 байта С++ный вариант : 190 байт

190/192 = 0.99

Потребление стека:

Сишный вариант : 4 байта С++ный вариант : 5 байт

5/4 = 1.22 :))

Как видно, коэффициент оверхеда даже меньше единицы! :-Р

Причина этого в том, что тут оптимизатор почему-то изменил схему распределения регистров для сишного варианта, и, несмотря на дополнительную передачу this, С++ный код оказался короче.

Выводы из всего этого такие:

  1. Пример этот ни в коей мере не демонстрирует преимуществ или недостатков того или иного подхода, а конечный результат лежит в пределах разброса качества работы оптимизатора! Только и всего. Все это имеет к С++ному оверхеду весьма опосредованное отношение.
  2. То единственное место, где должна была проявиться разница - обращение к переменной 'rpl', которая в одном случае - глобальная переменная, в другом - член класса, не демонстрирует этой разницы - код и по размеру, и по времени выполнения идентичен, хотя реализация разная. Более того, в модифицированном варианте программы этот фрагмент на С++ даже на одну команду короче.

Уфф...

...so long!

### Это вам не шубу в трусы заправлять...

21-Sep-03 11:57 Boris Popov wrote to Harry Zhurov:

BP>>> избыточного кода. У меня остались записи, согласно которым коэффициент BP>>> оверхеда для AVR (компилятор ИАР) может быть от 1 до 1.47.

BP> Забыл добавить - на проекте в 7.3KB средний оверхед по сравнению BP> с С версией составил около 1.19. Т.е. gcc в режиме С несмотря на свой более толстый код даст столько же, сколько IAR в режиме C++.

HZ>> А если какой-то пин используется по нескольким назначениям, то тут тем HZ>> более удобно завернуть всю функциональность в объект, который содержит в HZ>> себе HZ>> все, что работает с этим пином, включая и то периферийное устройство, HZ>> которое функционирует через этот пин.

BP> Hу да, давай будем объединять eeprom и lcd из за того, что lcd BP> использует использует SDA как один из разрядов ШД. А оставшиеся разряды шины данных LCD, кстати, принимают код полноразвязанной (диод на каждой кнопке) клавиатуры, линии сканирования которых в "1" в момент обращения к LCD :-)

BP> Hа C я размещаю все kbd переменные в структуре. Каждая BP> kbd_* функция может загружать указатель на эту структуру, а может и BP> просто BP> сделать lds если в ее задачи входит только проверка флагов. Cross-call BP> оптимизация великолепно выделит lds zl/zh, kbd_cb и сэкономит по слову Эх, кто бы это в gcc затолкал... У IAR/AVR есть, у KeilC51 есть... Из-за одного стека у gcc это будет не так роскошно, как у IAR (он может даже заталкивание в стек данных аргументов функций выделить в отдельную подпрограммку...), но всё равно полезно.

BP> на каждой загрузке. А пользователь функций вообще не будет загружать BP> никакие указатели и обойдется простым (r)call. Что я и имел ввиду, когда говорил, что аргумент "а если спрятать от левой записи переменные через static в файле, то будет проигрыш, так как в классе можно поле сделать protected и читать его через inline-функцию". Реально if (KbdHit()) при KbdHit() - не inline функции выливается в rcall / tst / breq, а инлайновая Kbd.IsHit() хорошо если оптимизируется до lds Kbd.flag / tst / breq (уже не короче), а то ведь и ldi zh/zl, Kbd / ldd Z+offset field / tst / breq может выйти (ну нет у меня IAR EC++, а работа gcc с этим делом тут уже отмечалась :-()

BP> Ы ? (c) OR. Так отож :-)

wbr, p.s. А моё отношение к незапрету прерываний на 1 такт... ну вы поняли... Тем более, что на момент разработки AVR это не было откровением... Так скоро в подпись затолкаю (наболело - недавно много выдачи gcc по -S просматривал, а оно так и прёт). И вообще, надо сделать что-то фортунообразное и набить моими претензиями к AVR :-)

Oleksandr Redchuk wrote to Harry Zhurov on Sun, 21 Sep 2003 20:33:51 +0000 (UTC):

Что-то тут накладка, по ходу, вышла? Отвечаешь, вроде, BP, а X-CT ко мне (оно у меня ярко-зеленым цветом выделяется на фоне остальных сообщений). :)

BP>> Забыл добавить - на проекте в 7.3KB средний оверхед по сравнению BP>> с С версией составил около 1.19. OR> Т.е. gcc в режиме С несмотря на свой более толстый код даст столько же, OR> сколько IAR в режиме C++.

Здесь позволю себе усомниться в объективности цифири. Хотя бы потому, что очень проблематично иметь два проекта приличного размера, реализованных на разных языках, настолько идентичных по функциональности, чтобы возможно было вылавливать эти проценты. Сравнивать можно какие-то небольшие фрагменты (на предмет качества кодогенерации), б.м. функционально законченные куски (на предмет соответствия подхода к решению задачи самой задаче).

[...]

BP>> на каждой загрузке. А пользователь функций вообще не будет загружать BP>> никакие указатели и обойдется простым (r)call. OR> Что я и имел ввиду, когда говорил, что аргумент "а если спрятать от OR> левой записи переменные через static в файле, то будет проигрыш, OR> так как в классе можно поле сделать protected и читать его OR> через inline-функцию". OR> Реально if (KbdHit()) при KbdHit() - не inline функции выливается в OR> rcall / tst / breq, а инлайновая Kbd.IsHit() хорошо если оптимизируется OR> до lds Kbd.flag / tst / breq (уже не короче), а то ведь и OR> ldi zh/zl, Kbd / ldd Z+offset field / tst / breq может выйти (ну OR> нет у меня IAR EC++, а работа gcc с этим делом тут уже отмечалась :-()

В общем случае:

C : rcall / lds / tst / ... / ret / tst

C++ : ldi / ldi / ldd / tst

В частном случае:

C : rcall / lds / ret / tst

C++ : lds / tst

Вот пример реализации последнего:

// ------------------------------------- class TSlon { public: __z void add(int x); bool IsTrue() const { return f; }

private: int Value; bool f; };

TSlon slon; extern int y;

void main() { if(slon.IsTrue()) y = 1; } // -------------------------------------

main:

9100.... LDS R16,(slon + 2) 2300 TST R16 F031 BREQ ??main_0

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

...so long!

### Пейте томатный сок. Томатный сок - это здоровье. Здоровье - это спорт. Спорт - это победы. Победы - это женщины. Женщины - это СПИД. СПИД - это смерть. HЕ ПЕЙТЕ ТОМАТHЫЙ СОК!

Hello "Harry.

21 Sep 03 22:18, you wrote to Oleksandr Redchuk:

OR>> Я таки попробую (исключительно из любопытства) плюсы если не на OR>> AVR, то на MSP HZ> А что, для него уже оно появилось? G++, в смысле?

Если ты не в курсе, то кодогенертор в gcc не знает ни про Си, ни про Си++. Он преобразует из некоторого абстрактного языка в ассемблер. Так что если написан кодогенератор для msp-430, то Си++ появляется как бы автоматически. Hужно только в библиотеке некоторую поддержку дописать.

OR>> можно сделать static struct { ... } uarts[USED_UARTS]; и всю OR>> работу делать по индексам. HZ> А что, их (UART'ов) много что-ли? А если один? HZ> И потом, это смахивает на проделывание вручную того, что делает HZ> ++ный компилятор.

++ный компилятор автоматически определит количество и тип уартов в контроллере и объявит массив объектов?

HZ> Еще мне гораздо комфортнее когда доступ к переменной из другого HZ> файла делается не прямым обращением к ней, а с помощью функции, HZ> определяющей конкретное действие. Только на С будет оверхед на вызов, HZ> а в плюсах эту функцию просто делаешь инлайновой.

А в Си inline отменили?

HZ> Про шаблоны и говорить нечего.

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

HZ> Жаль только, что этот убогий ЕС++ HZ> их не держит! :((

Возьми avr-gcc. Только, насколько я помню - WinAvr с avrfreaks не собран с Си++, так что придется самому собирать.

Alexey

BP>> Hа C я размещаю все kbd переменные в структуре. Каждая BP>> kbd_* функция может загружать указатель на эту структуру, а может и BP>> просто BP>> сделать lds если в ее задачи входит только проверка флагов. Cross-call BP>> оптимизация великолепно выделит lds zl/zh, kbd_cb и сэкономит по слову OR> Эх, кто бы это в gcc затолкал... У IAR/AVR есть, у KeilC51 есть... OR> Из-за одного стека у gcc это будет не так роскошно, как у IAR (он может OR> даже заталкивание в стек данных аргументов функций выделить в отдельную OR> подпрограммку...), но всё равно полезно.

Угу, сам алгоритм вроде не очень сложный. И для полного счастья поддержку выкидывания неиспользованных функций в линкере. Тогда не придется распихивать библиотечные функции в отдельные файлы, a алгоритму cross-call будет где развернуться.

OR> p.s. А моё отношение к незапрету прерываний на 1 такт... ну вы поняли...

Это вообще один из замечательных багов дизайна. К более поздним "радостям" можно отнести вылет портов F и G из области действия SBI/CBI.

Hello Dmitry.

22 Sep 03 16:36, you wrote to Yurij Sysoev:

Hефиговый такой стартап получится... ;)

Alexey

В статье snipped-for-privacy@p106.f.n453.z2.ftn> Yurij Sysoev написал(а):

Всей поддержки C++ для linux kernel module - 617 строк/11930 байт. Включая конструкторы/деструкторы для статических объектов, обертки для new/delete. Исключения я не использую. Чего еще нужно?

Alexey Boyko wrote to "Harry Zhurov" <Harry Zhurov on Fri, 19 Sep 2003 16:11:50

+0400:

AB> 18 Sep 03 21:46, you wrote to Dimmy Timchenko:

HZ>> // ---------------------------------- HZ>> ... HZ>> if( keySelect.IsClick() ) CurrentParameter++; HZ>> if( keyPlus.IsClick() ) CurrentParameter->Increase(); HZ>> if( keyMinus.IsClick() ) CurrentParameter->Decrease(); HZ>> ... HZ>> // ----------------------------------

AB> ...

HZ>> или перепутать что-то - все формализовано. А использование очень HZ>> простое. По затратам, кстати, получается почти то же самое, что и в HZ>> варианте с массивами указателей на функции, ведь этот механизм HZ>> виртуальных функций, по сути, основан на тех же массивах указателей.

AB> Поробуй сделать без указателей: на 4-х массивах - current, min, max, delta

AB> И сравни.

Пробовал и сравнивал. Получается по сгенеренному коду примерно то же самое, только в этом случае приходится руками создавать массивы, руками их инициализировать, руками организовывать в _каждой_ вызываемой функции обращение к _своей_ переменной 'Value', словом, все то, что делает компилятор (и делает без ошибок), придется делать руками. Исходный код в этом случае выглядит, поверь мне, не симпатично, мягко говоря. А если учесть, что сами vtbl компилятор помещает во флеш, то для достижения того же самого исходный код на С придется "украсить" пачкой __flash.

...so long!

### Hard disk error: (A)bort (R)etry (P)anic

21-Sep-03 18:33 Harry Zhurov wrote to Boris Popov:

HZ> void uartInit(BYTE* Rx_buf, HZ> BYTE Rx_size, HZ> BYTE* Tx_buf, HZ> BYTE Tx_size, HZ> BYTE Baud_rate); Я не понял - почему тут куча параметров, а у конструктора ниже только baud rate? Значит, это _разные_ по поведению системы, если там конструктор распределяет себе буфера по своему усмотрению.

HZ> void uartDisable_Rx(void); [...] HZ> BOOL uartWrite_Tx_data(BYTE* ptr, BYTE count); Этих функций нет в открытой части класса ниже, значит они могут быть static в файле uart.c и не блестеть тут.

HZ> interrupt void UART_Rx_interrupt(void); HZ> interrupt void UART_UDRE_interrupt(void); Аналогично, если у тебя класс ниже работает через прерывания, то эти функции есть. Блестеть наружу им нечего, спрятаны и так, и так где-то в uart.c или uart.cpp

HZ> BYTE UART_block_buf[BLOCK_BUFFER_SIZE]; Опять - в "классовой" версии буфер из воздуха взялся? Всё равно где-то кто-то его распределяет.

HZ> void (*fptrCmd[])(void) = HZ> { HZ> read_SRAM, HZ> write_SRAM, HZ> log_op, HZ> read_FLASH, HZ> read_EEPROM, HZ> write_EEPROM, HZ> uart_cmd_user HZ> }; И реакцию на команды у класса так или иначе надо прописать.

HZ> Вот это все "живет" в глобальном пространстве имен. Там еще пара ПОЧЕМУ? Почему бОльшая часть этого не может быть static внутри файла uart.c??? Если его аналоги спрятаны от доступа в классе, то они пользователю не нужны. Насколько я помню, у тебя наследование только меняло функциональность и всё. Делается через обращение из файла uart.c к внешнему относительно него (*fptrcmd[])() или через SetCmdMap(fptrcmd);

HZ> А вот последующая реализация той же функциональности: [...]

HZ> Так вот, возвращаясь к исходному вопросу, разница между двумя HZ> десятками HZ> функций с пачкой переменных и тремя доступными функциями-членами класса HZ> (то, HZ> что нужно держать в голове) не выглядит "исчезающе малой"!!! Ну так не показывай наружу всё! На то и существует static в C, чтобы видимость имён ограничить файлом, в котором они заданы.

HZ> 2. Используется один стек и для данных, и для адресов возвратов, что HZ> вынуждает для указателя стека данных использовать убогий SP, из-за Не намного хуже, чем у x86 в 16-битном режиме :-) Но и не лучше :-(( HZ> которого HZ> возникает приличный оверхед и по размеру, и по скорости. Кроме того, Да нет, если стековый кадр нужен, то в прологе порождается frame pointer на Y и дальше всё "как обычно". Если стековый кадр не нужен, то Y используется как хочется.

HZ> из-за HZ> прерываний компилятору приходится модификацию SP заключать в критическую HZ> секцию (сейчас OR скажет: "Повбывав бы!" ;-)) Естественно!

BP>> никакие указатели и обойдется простым (r)call. Ы ? (c) OR.

HZ> Не знаю, что такое "Ы", поэтому не могу ничего на это ответить. "Ы?" это такой эмоциональный аналог "ну и?"

HZ> остальной части абзаца комментарий следующий: HZ> В энный раз повторяю: все зависит от конкретных условий!!! О чём и разговор - в огромном количестве "конкретных условий" за C++ могут быть только два аргумента - "не сбивать руку" и "очень уж эта мысля понравилась". Остальные расписания "плюсов плюсов" как-то мало в тех "конкретных условиях" дают.

HZ> По поводу количества аргументов выскажу свое субъективное мнение HZ> (которое, впрочем, совпадает с мнением вполне серьезных дядек): HZ> если параметров при HZ> передаче более двух, то это уже повод задуматься о том, нет ли ошибки HZ> проектирования; memcpy(dst, src, len);

HZ> если больше трех, то это причина задуматься о том же; AT45D_write( chip_no, offset, buf, len); ну у меня два чипа стоит. И разбивать это на AT45_select(chip_no); AT45_write(offset, buf, len); неохота, тем более что всё равно есть ошибка проектирования, так как во второй функции три параметра. Можно, конечно, сделать "поток" AT45 с модификаторами вывода, но это уже перебор :-)

HZ> если больше четырех, то тут почти наверняка что-то не так! Три параметра -- _очень_ частое явление. Четыре - реже, но не пугает. Больше 4 -- действительно нечасто.

HZ> Теперь рассмотрим то конкретное место, где должна проявиться разница HZ> сишного и плюсатого подхода - обращение к переменной uint8_t rpl[32]; HZ> *dp++ = c ^ rpl[c & 0x1f]; [...]

HZ> Разница в одну команду в пользу С. Откуда оно? Все просто: в первом HZ> варианте копия переменной 'c' размещена в r24, а во втором - в r4 (такую HZ> схему HZ> выбрал оптимизатор), поэтому в первом случае можно применить инструкцию HZ> 'andi', HZ> а во втором нельзя - приходится 0x1F явно грузить в r19, и уж только HZ> затем 'and'. Вот эта загрузка и есть та дополнительная команда.

HZ> Таким образом, даже тут, при _непосредственном_ использовании HZ> члена-данного HZ> не просматривается источника оверхеда из-за C++'ного указателя this! А это ещё вопрос - почему именно так сделано. Надо весь код смотреть, может если бы он 'c' в C++ варианте поселил в r24, то ему пришлось бы в r4 селить что-то ещё более важное, которому иначе не нашлось бы вверху места именно из-за this :-))

HZ> Возьмем ее за исходное и чуть-чуть изменим - перенесем последний HZ> параметр (mark) из функции в глобальную переменную. А вот глобальные переменные -- говорят ещё хуже, чем goto :-)

wbr,

21-Sep-03 19:18 Harry Zhurov wrote to Oleksandr Redchuk:

HZ>>>>> Т.е. обычные, одиночные классы, как типы определяемые пользователем - HZ>>>>> это так, ерунда, от которой вреда больше, чем пользы? OR>> Нет, от которой зачастую пользы меньше, чем высота барьера при OR>> переходе от C на C++.

HZ> А что определяет (составляет) высоту барьера? Как обычно, куча субъективных факторов, маскирующихся под объективные :-)

OR>> Я таки попробую (исключительно из любопытства) плюсы если не на AVR, OR>> то на MSP

HZ> А что, для него уже оно появилось? G++, в смысле? Имелось ввиду msp430-g++? Пока вроде бы нет, но должно, противопоказаний (кроме ненужности плюсов с точки зрения ведущих проект mspgcc) нет никаких.

OR>> Даже можно полностью спрятать детали реализации модуля работы с OR>> UART-ами, выдавая наружу не указатель на структуру, связанную с UART, OR>> а индекс в массиве. Всё же в embedded заранее известна периферия, с которой OR>> будет идти работа и внтури uart.c можно сделать OR>> static struct { OR>> ... OR>> } uarts[USED_UARTS]; OR>> и всю работу делать по индексам.

HZ> А что, их (UART'ов) много что-ли? А если один? Если один, то у C++ вообще нет никаких преимуществ.

HZ> И потом, это смахивает на проделывание вручную того, что делает HZ> ++ный компилятор. Где? Виртуальные функции через указатели в структурах - да, вручную. А static служебные переменные/структуры, спрятанные от пользователя библиотеки внутрь файла её исходников - вполне рядовое явление.

OR>> Можно сказать OR>> "зачем модули, если это всё можно сделать как частный случай класса". OR>> а можно OR>> "зачем классы, если _это_ вполне реализуемо на модулях".

HZ> Вопрос-то был: "Почему в С++ нет модулей?". если в большинстве случаев достаточно их.

HZ> конкретное действие. Только на С будет оверхед на вызов, а в плюсах эту HZ> функцию HZ> просто делаешь инлайновой. Т.е. эффективность как при прямом обращении, А вот это (эффективность инлайновых функций) очень зависит от того, на каком процессоре и какое действие производится. Ну по скорости вопросов нет, inline быстрее, а вот по объёму ещё вопрос. Даже на if( flag) при флаге равном "единице обработки" (байт/слово). А если flag - битовое поле или if (flag & 0x20), то inline по объёму проиграет (если процессор не имеет команды проверки бита прямо в памяти). И не inline метод не имеет никакого преимущества.

HZ> Тут разница половая: public в классе делаются функции, а не данные. HZ> А на С HZ> придется данные открывать. А если через функцию, то оверхед на вызов, HZ> который при простом чтении переменной оказывается чересчур большим. Примерчик кода приведи. А то у меня EC++ нет. а gcc для inline объём явно больше. Кстати, gcc ещё иногда любит в полном соответствии со стандартом расширять char до int при вычислениях. Тоже неблагоприятно на коде сказывается.

class foo { uchar ch; public: foo(uchar _ch) { ch=_ch; } bool IsChar0() { return ch==0; } bool IsChar0Proc(); bool IsBit3() { return bool(ch & (1<<3)); } bool IsBit3proc(); };

bool foo::IsChar0Proc() { return ch==0; } bool fooIsBit3proc() { return bool(ch & (1<<3)); }

foo ff(5);

uchar moo1() { uchar cc=0;

if( ff.IsChar0() ) ++cc; if( ff.IsBit3() ) ++cc; return cc; }

uchar moo2() { uchar cc=0;

if( ff.IsChar0Proc() ) ++cc; if( ff.IsBit3proc() ) ++cc; return cc; }

и аналогичное для C

static uchar ch=5; uchar IsChar0() { return ch==0; } uchar IsBit3() { return ch & (1<<3); }

uchar moo() { uchar cc=0;

if( IsChar0() ) ++cc; if( IsBit3() ) ++cc; return cc; }

wbr,

21-Sep-03 19:45 Alexander Derazhne wrote to Oleksandr Redchuk:

OR>> Я таки попробую (исключительно из любопытства) плюсы если не на AVR, OR>> то на OR>> MSP, но не вижу я пока особых преимуществ у него на моих объёмах. OR>> Все твои примеры слонов, уартов и spi просматривал - всё равно не OR>> вижу :-)

[...] AD> Но AD> вот в отношении развития и сопровождения... Как только в задаче появляется AD> многопоточность, разделяемые ресурсы и обкладывание семафорами - Пример неоспоримого на мой взгляд преимущества C++ (в письме Василевскому) как раз этого и касается :-)

AD> инкапсуляция становится спасением. Если необходимо в рантайме принимать AD> решение о работе в той или иной конфигурации, то начиная с некоторого AD> объёма AD> _нужно_ применять наследование. В сопровождаемом мной проекте можно Аналогично в том же письме - про подмену клавиатуры уартом именно _в_рантайме_ (как отладочный режим компиляцией под #if -- вполне можно и без плюсов, если остальное без плюсов тоже ложится). Там фрагментик на тему "как это делалось 20 лет назад на Э60 на чистом С" и мои слова о бессмысленности делать это вручную при наличии C++.

AD> бы выбором конкретных классов из имеющихся ещё в main. Хотя и овехед AD> налицо - код занял бы больше места, те куски, которые сейчас безусловно AD> выполняются, попали бы дубликатами в каждый класс. Ну если их можно выделить в подфункции, то сделать их protected где-то в базовом классе. Хотя _небольшие_ _статические_ (т.е. периода компиляции) различия я бы всё же делал через #if. А если общие места небольшие - не грех иповторить в разных местах. А если "куча-не куча", то тут вопрос темперамента и чужое решение всегда не понравится :-)

Повторюсь: Если проект всё равно "очень хочет" плюсов, то это одно дело, а если там всё великолепно укладывается в один "главный цикл" с работающими в прерываниях "потоками" UART или ещё что-нибудь в этом роде, то плюсы там -- либо "чтобы руку не сбивать", либо "чисто ради идеи". Особых преимуществ нет. "Не особых" - тоже под вопросом.

wbr,

Hello, Oleksandr! You wrote to "Alexander Derazhne" snipped-for-privacy@i.com.ua> on Mon, 22 Sep 2003

18:26:59 +0000 (UTC):

OR> Если проект всё равно "очень хочет" плюсов, то это одно дело, а если OR> там всё великолепно укладывается в один "главный цикл" OR> с работающими в прерываниях "потоками" UART или ещё что-нибудь в OR> этом роде, то плюсы там -- либо "чтобы руку не сбивать", либо "чисто OR> ради идеи". Особых преимуществ нет. "Не особых" - тоже под вопросом.

Консенсус.

With best regards, Alexander Derazhne.

22-Sep-03 10:59 Boris Popov wrote to Oleksandr Redchuk:

BP> поддержку выкидывания неиспользованных функций в линкере. Тогда не BP> придется BP> распихивать библиотечные функции в отдельные файлы, a алгоритму cross-call BP> будет где развернуться. Говорят, в этом помогает

DEADCODESTRIP := -Wl,-static -fvtable-gc -fdata-sections\ -ffunction-sections -Wl,--gc-sections -Wl,-s

foo : foo.c g++ $(DEADCODESTRIP) $< -o $@

Причём благодаря -ftable-gc это даже по таблицам виртуальных функций как-то протаптывается. А по -fdata-sections выкидываются нигде не задействованные переменные файловой области видимости.

Ещё не проверял.

OR>> p.s. А моё отношение к незапрету прерываний на 1 такт... ну вы поняли...

BP> Это вообще один из замечательных багов дизайна. К более поздним BP> "радостям" можно отнести вылет портов F и G из области действия SBI/CBI.

Ну архитектуру, сдуя по слухам, разрабатывали программисты... Может, программисты на VB? :-) :-((

wbr,

Join the Discussion

Have something to add? Share your thoughts — no account required.

Didn't find your answer?

Ask the community — no account required