ICC AVR и стpоки

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 выпустила новые прокладки. Они превращают жидкость в гель для бритья.

Reply to
Harry Zhurov
Loading thread data ...

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

Reply to
Vladimir Vassilevsky

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); }

Reply to
Boris Popov

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.

Reply to
Dimmy Timchenko
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,

Reply to
Oleksandr Redchuk

Hello, Dimmy Timchenko !

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

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

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

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

Reply to
Dima Orlov

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!

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

Reply to
Harry Zhurov

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!

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

Reply to
Harry Zhurov
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 :-)

Reply to
Oleksandr Redchuk

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ЫЙ СОК!

Reply to
Harry Zhurov

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

Reply to
Alexey Boyko

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.

Reply to
Boris Popov

Hello Dmitry.

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

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

Alexey

Reply to
Alexey Boyko

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

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

Reply to
Dmitry Fedorov

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

Reply to
Harry Zhurov
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,

Reply to
Oleksandr Redchuk
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,

Reply to
Oleksandr Redchuk
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,

Reply to
Oleksandr Redchuk

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.

Reply to
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,

Reply to
Oleksandr Redchuk

ElectronDepot website is not affiliated with any of the manufacturers or service providers discussed here. All logos and trade names are the property of their respective owners.