ICC AVR и стpоки - Page 3

Do you have a question? Post it now! No Registration Necessary

Translate This Thread From Russian to

Threaded View
Re: ICC AVR и стpоки
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


Re: ICC AVR и стpоки
22-Sep-03 13:40 Alexey Boyko wrote to "Harry Zhurov" <Harry Zhurov:

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

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

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

AB> А в Си inline отменили?
 Ещё не все C-компиляторы для однокристалок поддерживают C99,
в котором, afaik, inline появился. IAR C (не C++) про inline ничего не
слышал.

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

AB> Возьми avr-gcc. Только, насколько я помню - WinAvr с avrfreaks не собран
AB> с Си++, так что придется самому собирать.
Не с avrefreaks, а с winavr.sourceforge.net
Уже с год как с C++ идёт.

wbr,

--
/* Oleksandr Redchuk, Brovary, Ukraine */
/* real '\x40' real '\x2E' kiev '\x2E' ua     */


ICC AVR и стpоки
Hi Boris!
You wrote to Harry Zhurov on Fri, 19 Sep 2003 15:58:27 +0400:

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

HZ>>     Далее с этого листа набивается этот каркас в исходники, и дело за
HZ>> реализацией представления и отладкой. Разумеется, на практике не все так

BP>  Hичто не мешает это делать и на С.  Используй общий префикс для
BP> функций работающих в одной области: slon_init, slon_send etc.

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

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

BP>  О возможности применения C++ (не ООП!) в embedded программировании
BP> можно рассуждать долго и безрезультатно.  Просто каждый разработчик должен
BP> сделать выбор для конкретного приложения.

    Это правильная мысль.

BP>   Hо, как правило, использование классов приводит к избыточному коду на
BP> процессорах подобных AVR.

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

BP>   Тот факт, что большинство "аппаратных" объектов существуют в единственном
BP> экземпляре, еще сильнее снижает ценность C++.

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


BP> А вот на мощных процессорах с большим объемом RAM и ROM, сложной иерархией
BP> объектов и навороченным UI использование С++ дает весьма заметные
BP> преимущества.

    Ага, а вот только вчера говорили, что С++ на таких платформах - просто бич:
там какие-то ламеры наваяли безобразные уродства, в которых ни они сами, ни
другие, более квалифицированные, люди разобраться не могут. Т.ч. это палка о
двух концах.

    Я совершенно согласен, что при правильном применении С++ на больших
платформах дает больше преимуществ, чем на малых. Hо при неправильном
применении на больших платформах и проблем несоизмеримо больше, чем на малых.

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

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

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

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



Bye.

### Иногда последнему бывает не до смеха...






Re: ICC AVR и стpоки

HZ>     Hичто не мешает, но ничто и не помогает. Логическая разбивка программы
HZ>     на
HZ> самостоятельные части существенно упрощает процесс разработки, и С++ тут
HZ> дает

    Безусловно.

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

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

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

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

BP>>   Hо, как правило, использование классов приводит к избыточному коду на
BP>> процессорах подобных AVR.

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

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

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

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

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

    Потому что на малых платформах снижение быстродействия и увеличение
размеров кода перевешивает пользу от С++.  Конечно, если есть возможность
вместо, скажем Mega64 ставить Mega128, то этим можно пренебречь.  Hо, как
правило, вопрос максимального снижения себестоимости конечного изделия,
важнее чем лишний час (и не более) затраченный на разработку.

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

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

    tkbd kbd;
    kbd.init();        kbd_init();
    kbd.scan();        kbd_scan();
    k = kbd.key();        k = kbd_key();

    В случае с C++ разумно будет разместить все переменные внутри класса.
Это хорошо.  В С коде, эти переменные с большой долей вероятности будут
объявлены как static внутри kbd.c.  Это тоже правильно.  Hо в первом случае мы
получим оверхед при каждом вызове метода класса kbd (справедливости ради стоит
заметить, что этого можно избежать используя статические методы и вынос данных
из класса, но проще переименовать kbd.cpp в kbd.c и начать жизнь заново).

--
Boris Popov
bp shift-2 vertex.kz

Re: ICC AVR и стpоки
Sat Sep 20 2003 10:29, Boris Popov wrote to Harry Zhurov:

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

 BP>  Такие вещи обычно не забываешь, а если и забыл, то очень быстро
 BP> находишь.

 Hе всегда. В класс INTERFACE входит класс UART, в класс UART входит
 класс FIFO. Из основной программы создается обьект класса INTERFACE,
 и не надо помнить о том, что, как и в каком порядке проинициализировать.


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

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

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

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

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

 UART.Sleep();
 UART.Wake();


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

 UNIVERSAL_DEVICE::UNIVERSAL_DEVICE(device_type);


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

 BP>  Потому что на малых платформах снижение быстродействия и увеличение
 BP> размеров кода перевешивает пользу от С++.  Конечно, если есть возможность
 BP> вместо, скажем Mega64 ставить Mega128, то этим можно пренебречь.  Hо, как
 BP> правило, вопрос максимального снижения себестоимости конечного изделия,
 BP> важнее чем лишний час (и не более) затраченный на разработку.

 Когда как.
 Time to market и простота поддержки и отладки тоже немаловажны.  


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

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

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

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


 BP>  В случае с C++ разумно будет разместить все переменные внутри класса.
 BP> Это хорошо.  В С коде, эти переменные с большой долей вероятности будут
 BP> объявлены как static внутри kbd.c.  Это тоже правильно.  Hо в первом
 BP> случае мы получим оверхед при каждом вызове метода класса kbd
 BP> (справедливости ради стоит заметить, что этого можно избежать используя
 BP> статические методы и вынос данных из класса, но проще переименовать
 BP> kbd.cpp в kbd.c и начать жизнь заново).

 Дело в том, что kbd не имеет самостоятельного смысла, а является частью
 юзеровского интерфейса. В свою очередь kbd имеет FIFO, авторепит
 и прочие мелкие сущности, которые используются не только в kbd. Hа Сях
 приходится помнить, что куда входит, как работает и как инициализируется.

 BP> но проще переименовать
 BP> kbd.cpp в kbd.c и начать жизнь заново).

 Сейчас мне приходится переименовывать kbd.c в kbd.cpp и начинать жизнь
 заново.

 VLV


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




ICC AVR и стpоки
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
 

 


Re: ICC AVR и стpоки

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

--
Boris Popov
bp shift-2 vertex.kz


ICC AVR и стpоки
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!

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






Re: ICC AVR и стpоки

BP>>     ИАР с cross_call:       13754
BP>>     ИАР без cross_call:     15322
BP>>     avr-gcc:                22140
OR>  Что-то много. Ключики покажи.

-O2 -Wa,-ahlms=gcc161/main.lst
-g -mmcu=atmega161 -W -Wall -Wstrict-prototypes -funsigned-char
-funsigned-bitfields -fpack-struct -fshort-enums -finline-functions
    
OR>  Пока единственное, что я заметил резкое - это при применении
OR> float сразу gcc-шный растёт за счёт библиотеки (т.е. по мапе
OR> видно, что мой код пояти не отличается по размеру, а прилинковано

    Нет, у меня нет никаких float и стандартная библиотека почти
не используется.

    Прогнал самый большой файл с разными опциями -O и вот что вышло:

    ИАР    GCCfl    GCC(1)    GCC(2)
    
    4538    -O0    6554    
        -O1    6554
        -O2    6486    5960
        -O3    6500
        -Os    5186    5888

    GCC(1) параметры указанные выше, GCC(2) без -finline-functions.
Весь проект собранный GCC(1)/-Os - 17638, GCC(2)/-Os - 19442.

    Действительно, при оптимизации по размеру (ИАР оптимизировал
по скорости), соотношение намного лучше - 1.15.  На досуге попробую
посмотреть, что еще можно поправить в коде или подкрутить в опциях.

--
Boris Popov
bp shift-2 vertex.kz

Re: ICC AVR и стpоки
23-Sep-03 12:50 Boris Popov wrote to Oleksandr Redchuk:

BP> -O2 -Wa,-ahlms=gcc161/main.lst
BP> -g -mmcu=atmega161 -W -Wall -Wstrict-prototypes -funsigned-char
BP> -funsigned-bitfields -fpack-struct -fshort-enums -finline-functions
 А зачем ты ему на автомате разрешил решать -- какие функции инлайнить?
Он при этом достаточно большие функции начинает инлайнить и при этом
если они не объявлены как static -- ещё и не-inline копию оставляет.
Проверь это место.


BP>     GCC(1) параметры указанные выше, GCC(2) без -finline-functions.
BP> Весь проект собранный GCC(1)/-Os - 17638, GCC(2)/-Os - 19442.

BP>     Действительно, при оптимизации по размеру (ИАР оптимизировал
BP> по скорости), соотношение намного лучше - 1.15.  На досуге попробую
BP> посмотреть, что еще можно поправить в коде или подкрутить в опциях.

Немного игрался.

-fnew-ra  -- иногда уменьшает объём кода. Иногда сильно увеличиват.
  детально не исследовалось.

-fno-reorder-blocks -- как это ни странно, но в одном проекте 20 байт на
фоне ~3KB уело. при том, что IAR на этом проекте давал всего байт
100 выиграша (1 файла с -s9, 2файла с -z9).

В этом проекте для gcc стоит -mcall-prologues, который ещё байт 50
экономит за счёт "вызова" подпрограмм для создания/удаления стекового
кадра. Кстати, для SIGNAL()-функций он автоматически не использует
call-prologues, несмотря на этот ключ, поэтому не нужно было
указывать специальные ключи для файла с обработчиками прерываний
(OC1A, ADC, UART), как для IAR.

Остальное практически не влияло (ничего не добавляло к -O2).

wbr,
--
/* Oleksandr Redchuk, Brovary, Ukraine */
/* real '\x40' real '\x2E' kiev '\x2E' ua     */


Re: ICC AVR и стpоки
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,
--
/* Oleksandr Redchuk, Brovary, Ukraine */
/* real '\x40' real '\x2E' kiev '\x2E' ua     */


ICC AVR и стpоки
Sep 2003 18:26:59 +0000 (UTC):


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

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


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

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

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

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>> };
OR>  И реакцию на команды у класса так или иначе надо прописать.

    Все это, конечно, есть (я и не говорил, что нет - наоборот, говорил, что
определение класса столь же обширно, просто не стал загромождать письмо; у тебя
кстати, afair, имеются обсуждаемые сорцы). Но... (см ответ на следующую цитату)


HZ>>     Вот это все "живет" в глобальном пространстве имен. Там еще пара
OR>  ПОЧЕМУ?
OR> Почему бОльшая часть этого не может быть static внутри файла uart.c???
OR> Если его аналоги спрятаны от доступа в классе, то они пользователю
OR> не нужны.


[...]


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

    Все это так. И в данном случае, это, конечно, выход для С. И все это
(объявление внутренних программных элементов статическими) попытка сделать по
++ному. Т.е. сделать именно то, что на ++ получается нативно. И после этого еще
не считать ++ную модель лучшей, чем сишную!

    Но и тут при таком подходе в С два недостатка.

    Во-первых, при обращении к переменной, инкапсулированной в модуле (единице
трансляции), придется либо делать ее открытой, либо вводить функцию доступа. И
то, и другое имеет недостатки, сам знаешь какие. ++ предоставляет простой и
логичный способ достичь защищенности без потери эффективности. Это ли не
преимущество?

    Во-вторых, и в-главных, такой подход красиво выглядит для таких достаточно
больших и законченных модулей, как описанный UART, когда для него можно
выделить отдельный файл. А что делать, когда модулей много и они не такие
крупные и функционально законченные? Каждому отдельный файл? Делал я так!
Именно пытался растолкать модули по разным файлам, помещая в каждый не более
двух-трех (в среднем). В итоге получил под дюжину исходных файлов и примерно
столько же заголовков. Проект тогда был около 8 кбайт (прошивка). Впоследствии
этот же проект развивался далее, и программа была переписана под ... Все удобно
разместилось в трех файлах при размере (прошивки) около 12 кбайт.


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

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

    Поэтому реально, afaik, придерживаются компромисса - пытаются рассовать по
файлам наиболее взаимосвязанное, стремясь минимизировать количество файлов. Это
компромиссное решение к реальным задачам подходит лучше, чем оба маргинальных.
Но и того формализованного способа проектирования, который возможен в С++, тут
уже нет! О чем и речь.


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

    А как тогда быть с прерываниями? Т.е. вот вошли в функцию, скопировали SP в
Y, начали использовать Y, и тут - бац! - прерывание! Какой поинтер будет
использоваться в прерывании?


[...]

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

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

    Хм, никогда бы не подумал.

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

    Кому как... Но поскольку все уже высказались, то и... закончим, пожалуй?!

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

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

    И заметь, я сказал: повод задуматься. Повод - не причина. :)

HZ>> если больше трех, то это причина задуматься о том же;
OR>  AT45D_write( chip_no, offset, buf, len);

    Да, а у меня:

    TDataFlash::ReadBuffer(word addr, byte N);
    TDataFlash::ReadMemory(word PageAddr, word ByteAddr);

    И т.д. (реализация, кстати, у тебя есть :)

OR> ну у меня два чипа стоит. И разбивать это на
OR>  AT45_select(chip_no);
OR>  AT45_write(offset, buf, len);

    А если два чипа, то и тем более классы рулят! :)  Заводишь два объекта, при
создании (в конструкторе) указываешь, какой порт и пин используются для
формирования chipselect'а, чтобы потом при AT45_1.ReadMemory(...) этот пин
автоматом дергался как надо. А при AT45_2.ReadMemory(...) дергался другой пин,
соответствующий другому чипу. И не нужно туда селекты метать. И обращаться
можно хоть через указатель.

    Обычно удается найти альтернативный вариант, который не уступает по
эффективности и удобству использования.


OR> неохота, тем более что всё равно есть ошибка проектирования, так как
                                          ^^^^^^^^^^^^^^^^^^^^^
OR> во второй функции три параметра.

    Не издевайся! :))  Повод задуматься еще на означает ошибки.

OR>  Можно, конечно, сделать "поток" AT45 с модификаторами вывода,
OR> но это уже перебор :-)

    Эт точно.

HZ>> если больше четырех, то тут почти наверняка что-то не так!
OR> Три параметра -- _очень_ частое явление.

    У кого как. Вот только что посмотрел несколько своих проектов, только в
одно из них была одна глобальная функция с тремя аргументами. Могу проект
предъявить, если не веришь?!

OR> Четыре - реже, но не пугает.

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


OR> Больше 4 -- действительно нечасто.

    Вот и я говорю, что в этом случае почти наверняка что-то не так.


[...]

HZ>> а во втором нельзя - приходится 0x1F явно грузить в r19, и уж только
HZ>> затем 'and'. Вот эта загрузка и есть та дополнительная команда.

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

    На первый взгляд это и есть причина. Но я попробовал добавить в сишный
вариант указатель в параметры, чтобы тоже не влезло в регистры, так компилятор
все равно юзает r24, а указатель этот просто копирует из регистровой пары,
сохранив ее в стеке в прологе (т.е. возникает то самое пресловутое копирование
указателя и сохранение/восстановление регистровой пары в стеке).

    Кстати, при объявлении функции-члена с модификатором __z стек для передачи
не используется - this-то в Z помещается. И в обоих вариантах - и сишном, и
++ном Z указывает на 'rpl' и используется одинаково.

    Так что, нет, не в this, как таковом, тут дело, а в выборе схемы размещения
регистров. Как объясняют парни из иаровского саппорта, там у них эвристический
анализатор, который оценивает ситуацию и решает, какую схему выбрать. Вот при
использовании функций-членов он склонен к одной схеме, а в случае обычной
функции - другую.

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

OR> wbr,

    Блин, я с этой дискуссией никак не могу толком взяться за изучение gcc
(известно для чего ;-))

...so long!

### Если мужчина просит руки женщины, значит ему надоела своя.




ICC AVR и стpоки
Tue Sep 23 2003 17:14, Harry Zhurov wrote to Oleksandr Redchuk:

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

 OR>>  Я не понял - почему тут куча параметров, а у конструктора
 OR>> ниже только baud rate? Значит, это _разные_ по поведению
 OR>> системы, если там конструктор распределяет себе буфера по своему
 OR>> усмотрению.

Именно.

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

Ровно с тем же успехом можно было написать _одну_ функцию
uart_init (U16 baud) на С, а все остально скрыть внутри файла "uart.c".

Давай будем сравнивать яблоки с яблоками.

 HZ>>>     Вот это все "живет" в глобальном пространстве имен. Там еще пара
 OR>>  ПОЧЕМУ?
 OR>> Почему бОльшая часть этого не может быть static внутри файла uart.c???
 OR>> Если его аналоги спрятаны от доступа в классе, то они пользователю
 OR>> не нужны.

 HZ> [...]

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

 HZ>     Все это так. И в данном случае, это, конечно, выход для С. И все это
 HZ> (объявление внутренних программных элементов статическими) попытка
 HZ> сделать по ++ному.

Точнее это способ корректно пользоваться возможностями, предоставляемыми
языком С.
Другое дело, что не далеко не все программисты этому обучены.
Я как-то смотрел код некоторых э-э-э, программистов.
"-ать, -ать, -ать" - привычно повторило эхо. :-\ Проще все переписать.

 HZ> Т.е. сделать именно то, что на ++ получается нативно.
 HZ> И после этого еще не считать ++ную модель лучшей, чем сишную!

Стоп, стоп, стоп. Если мы хотим сравнивать С++ и С, то надо сравнивать
_аналогичные_ подходы.

 HZ>     Hо и тут при таком подходе в С два недостатка.

 HZ>     Во-первых, при обращении к переменной, инкапсулированной в модуле
 HZ> (единице трансляции), придется либо делать ее открытой, либо вводить
 HZ> функцию доступа.

Либо вводить макрос-псевдофункцию для доступа и эмулировать пространства имен
префиксами.

Пример:

---------------------------------------
"rem.h"
---------------------------------------
extern U8 rem_oops;
#define rem_set_oops(val) (rem_oops = val)
#define rem_get_oops()     rem_oops
---------------------------------------
---------------------------------------
"rem.c"
---------------------------------------
U8 rem_oops;
---------------------------------------
---------------------------------------
"bla-bla-bla.c"
---------------------------------------
if(!rem_get_oops() && is_it_good_time())
{
    rem_set_oops(OOPS_TIME);
}
---------------------------------------
Кстати, это будет эффективней чем С++.

 HZ> ++ предоставляет простой и логичный способ достичь защищенности
 HZ> без потери эффективности.

Hе понял. Как это без потери эффективности?
Для доступа к защищенной переменной на С++ придется использовать
функцию - член класса.
Для доступа к static переменной на С придется использовать функцию.

Где потеря эффективнсти?

 HZ>     Во-вторых, и в-главных, такой подход красиво выглядит для таких
 HZ> достаточно больших и законченных модулей, как описанный UART, когда для
 HZ> него можно выделить отдельный файл. А что делать, когда модулей много и
 HZ> они не такие крупные и функционально законченные? Каждому отдельный файл?

Да. Я именно так и делаю.

 HZ> Делал я так! Именно пытался растолкать модули по разным файлам, помещая в
 HZ> каждый не более двух-трех (в среднем). В итоге получил под дюжину
 HZ> исходных файлов и примерно столько же заголовков.
 HZ> Проект тогда был около
 HZ> 8 кбайт (прошивка). Впоследствии этот же проект развивался далее, и
 HZ> программа была переписана под ... Все удобно разместилось в трех файлах
 HZ> при размере (прошивки) около 12 кбайт.

И всего-то дюжина файлов. :-)))
28 *.с + 5 *.asm + 38 заголовков для проекта на 16К кода. Hикаких проблем.
IMHO это больше зависит от личных пристрастий программиста, не более того.
Я, например, не могу сказать чей подход правильней.

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

Извини, но я не вижу ни неудобства, ни оверхеда.

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

Просто надо придерживаться правил именования переменных.

Hе вижу большой разницы между.

rem_oops->set(OOPS_TIME)
и
rem_oops_set(OOPS_TIME)

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

Hе надо минимизировать число файлов. Места в директории всем хватит. :)
Hадо названия давать осмысленные.

 HZ> Hо и того формализованного способа проектирования, который возможен в
 HZ> С++, тут уже нет! О чем и речь.

Да, заставить программистов писать код правильно - непростая задача, увы.:(
Возможно с применением классов это сделать проще, хотя и не факт

P.S. Я ничего не имею против С++, хороший и полезный язык.
Hо и каких-либо особенных преимуществ по сравнению с _хорошо_ написанной
программой на С тоже не усматриваю.

WBR, Юрий.


ICC AVR и стpоки
Tue Sep 23 2003 18:24, Yuriy K wrote to Harry Zhurov:


 YK> Ровно с тем же успехом можно было написать _одну_ функцию
 YK> uart_init (U16 baud) на С, а все остально скрыть внутри файла "uart.c".

 А если UARTов больше чем один?
 Что лучше: UARTInit(struct UART *, u16 baud_rate) или просто создать
 два обьекта класса UART?


 OR>>> Почему бОльшая часть этого не может быть static внутри файла uart.c???

 Использование static неправильно IMHO. Это сознательное ограничение
 возможностей. Получается неуниверсальный и труднопереносимый код,
 поддерживающий жестко заданное количество инстансов обьекта.  

 YK> Точнее это способ корректно пользоваться возможностями, предоставляемыми
 YK> языком С.

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

 YK> Другое дело, что не далеко не все программисты этому обучены.
 
 Это да. Хорошо или плохо можно писать на чем угодно.

 YK> Либо вводить макрос-псевдофункцию для доступа и эмулировать пространства
 YK> имен префиксами.
 YK> Кстати, это будет эффективней чем С++.
 YK> Просто надо придерживаться правил именования переменных.

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

 YK> И всего-то дюжина файлов. :-)))
 YK> Я, например, не могу сказать чей подход правильней.

 Чем больше файл - тем лучше оптимизация :)
 But who cares.

 YK> P.S. Я ничего не имею против С++, хороший и полезный язык.
 YK> Hо и каких-либо особенных преимуществ по сравнению с _хорошо_ написанной
 YK> программой на С тоже не усматриваю.

 Попробуй, перейди на C++. Понравится и обратно на C не захочется :)
 
 VLV


ICC AVR и стpоки
Tue Sep 23 2003 19:20, Vladimir Vassilevsky wrote to Yuriy K:

 YK>> Ровно с тем же успехом можно было написать _одну_ функцию
 YK>> uart_init (U16 baud) на С, а все остально скрыть внутри файла "uart.c".

 VV>  А если UARTов больше чем один?
 VV>  Что лучше: UARTInit(struct UART *, u16 baud_rate) или просто создать
 VV>  два обьекта класса UART?

Или

typedef enum {UART_1, UART_2, UART_OVER_CAN_1, UART_OVER_ETHERNET_1, ...}
UART_ID;

void uart_init(UART_ID uart_id, U16 baud);
void uart_put_char(UART_ID uart_id, U8 ch);
...

все детали реализации точно так же скрыты от пользователя.

Эстеты могут добавить

#define uart_alpha_init(a) uart_init(UART_1, a)
#define uart_beta_init(a) uart_init(UART_2, a)

если очень хочется иметь разные имена для функций.

Или даже так:
uart1_init(U16 baud);
uart2_init(U16 baud);

Сильно зависит от конкретных условий применения.

UART-ы например могут быть _физически_ разные. Тогда два объекта одного
класса вообще не получится сделать.

 OR>>>> Почему бОльшая часть этого не может быть static внутри файла uart.c???

 VV>  Использование static неправильно IMHO.

static == private.

 VV>  Это сознательное ограничение возможностей.

Каких?

 VV>  Получается неуниверсальный и труднопереносимый код,
 VV>  поддерживающий жестко заданное количество инстансов обьекта.  

???

 YK>> Точнее это способ корректно пользоваться возможностями, предоставляемыми
 YK>>  языком С.

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

Да. Тем более в чем тогда разница?

 YK>> Другое дело, что не далеко не все программисты этому обучены.
 VV>  Это да. Хорошо или плохо можно писать на чем угодно.

 YK>> Либо вводить макрос-псевдофункцию для доступа и эмулировать пространства
 YK>> имен префиксами.
 YK>> Кстати, это будет эффективней чем С++.
 YK>> Просто надо придерживаться правил именования переменных.

 VV>  Хорошо работает, если один программист. Попробуй заставить всех так
 VV>  делать,

Префикс переменной = имени файла.

 VV>  и чтоб еще не было конфликтов имен.

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

 YK>> И всего-то дюжина файлов. :-)))
 YK>> Я, например, не могу сказать чей подход правильней.

 VV>  Чем больше файл - тем лучше оптимизация :)
 VV>  But who cares.
You've got the point. :-))

 YK>> P.S. Я ничего не имею против С++, хороший и полезный язык.
 YK>> Hо и каких-либо особенных преимуществ по сравнению с _хорошо_ написанной
 YK>> программой на С тоже не усматриваю.

 VV>  Попробуй, перейди на C++. Понравится и обратно на C не захочется :)

Это ж разбираться надо... Я вот бутлоадер дописываю под HС12
с перемещаемыми в ОЗУ подпрограммами прошивки флэша
(на ассемблере, естественно), а вы тут с какими-то плюсами. Ж:-()~~~

WBR, Юрий.


ICC AVR и стpоки
Tue Sep 23 2003 19:41, Yuriy K wrote to Vladimir Vassilevsky:

 YK>>> Ровно с тем же успехом можно было написать _одну_ функцию
 YK>>> uart_init (U16 baud) на С, а все остально скрыть внутри файла "uart.c".

 VV>>  А если UARTов больше чем один?
 VV>>  Что лучше: UARTInit(struct UART *, u16 baud_rate) или просто создать
 VV>>  два обьекта класса UART?

 [....]

 YK> UART-ы например могут быть _физически_ разные. Тогда два объекта одного
 YK> класса вообще не получится сделать.

 Э, нет. Смысл ++ в том, чтобы иметь один и тот же интерфейс независимо от
 того, аппаратный ли это UART, или bit bang, или эмуляция через
 CAN/Ethernet/Bluetooth/и.т.д. Тем более что родительский класс UARTa - это
 FIFO, которое происходит от класса FILE. Делать все это на  чистых С
 - слишком много надо помнить и всего везде менять, если требуется
 добавить или убавить устройство.

 OR>>>>> Почему бОльшая часть этого не может быть static внутри файла
 OR>>>>> uart.c???

 VV>>  Использование static неправильно IMHO.

 YK> static == private.

 Только в случае единственного инстанса.

 VV>>  Это сознательное ограничение возможностей.
 YK> Каких?

 Как создавать произвольное число обьектов?
 Удобно иметь универсальный класс UART, в котором везде где можно
 нет ограничений на количество создаваемых обьектов.

 VV>>  Hа С приходится делать те же самые по сути классы вручную, передавая
 VV>>  в функцию указатель на структуру данных обьекта.
 YK> Да. Тем более в чем тогда разница?
 
 Это все равно, если нет наследования. Делать таким образом наследование
 получается криво и ненаглядно.

 YK>>> Другое дело, что не далеко не все программисты этому обучены.
 VV>>  Это да. Хорошо или плохо можно писать на чем угодно.

 YK>>> Либо вводить макрос-псевдофункцию для доступа и эмулировать
 YK>>> пространства  имен префиксами.
 YK>>> Просто надо придерживаться правил именования переменных.

 VV>>  Хорошо работает, если один программист. Попробуй заставить всех так
 VV>>  делать,

 YK> Префикс переменной = имени файла.

 Ха. Мне уже приходилось приделывать к именам файла имена программистов:

 vlv_rs232_main.dsp :)

 VV>>  и чтоб еще не было конфликтов имен.

 YK> Попробуй заставить всех программистов обзывать свои классы
 YK> непересекающимися  именами.

 Можно принудительно ограничить области видимости. Hа то и ++.

 VV>>  Попробуй, перейди на C++. Понравится и обратно на C не захочется :)
 YK> Это ж разбираться надо...

 Вот она, истинная причина.

 YK> Я вот бутлоадер дописываю под HС12
 YK> с перемещаемыми в ОЗУ подпрограммами прошивки флэша
 YK> (на ассемблере, естественно), а вы тут с какими-то плюсами. Ж:-()~~~

 А мне потребовалось произвольное количество всяких мелких таймеров,
 живущих в фоне. Можно терпеливо и скорбно создавать их статически,
 а можно timer = new tTIMER(msec). Куда как удобнее.

 VLV


ICC AVR и стpоки
Tue Sep 23 2003 20:49, Vladimir Vassilevsky wrote to Yuriy K:

 YK>> UART-ы например могут быть _физически_ разные. Тогда два объекта одного
 YK>> класса вообще не получится сделать.

 VV>  Э, нет. Смысл ++ в том, чтобы иметь один и тот же интерфейс независимо
 VV> от
 VV>  того, аппаратный ли это UART, или bit bang, или эмуляция через
 VV>  CAN/Ethernet/Bluetooth/и.т.д. Тем более что родительский класс UARTa -
 VV> это
 VV>  FIFO, которое происходит от класса FILE.

И как тут плюсы помогут?

 VV> Делать все это на  чистых С
 VV>  - слишком много надо помнить и всего везде менять, если требуется
 VV>  добавить или убавить устройство.

Это почему еще? Код для работы с железом все равно придется писать примерно
одинаковый, хоть на плюсах, хоть на бейсике, хоть на ассемблере.

 OR>>>>>> Почему бОльшая часть этого не может быть static внутри файла
 OR>>>>>> uart.c???
 VV>>>  Использование static неправильно IMHO.
 YK>> static == private.
 VV>  Только в случае единственного инстанса.

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

 VV>>>  Это сознательное ограничение возможностей.
 YK>> Каких?

 VV>  Как создавать произвольное число обьектов?

#define CHISLO_OBYEKTOV 255

 VV>  Удобно иметь универсальный класс UART, в котором везде где можно
 VV>  нет ограничений на количество создаваемых обьектов.

Смысл? UART-ов в процессоре от этого больше не станет.

 VV>>>  Hа С приходится делать те же самые по сути классы вручную, передавая
 VV>>>  в функцию указатель на структуру данных обьекта.
 YK>> Да. Тем более в чем тогда разница?

 VV>  Это все равно, если нет наследования. Делать таким образом наследование
 VV>  получается криво и ненаглядно.

Естественно. Как часто нужно наследование в "embedded" задачах?

 YK>>>> Либо вводить макрос-псевдофункцию для доступа и эмулировать
 YK>>>> пространства  имен префиксами.
 YK>> Префикс переменной = имени файла.
 VV>  Ха. Мне уже приходилось приделывать к именам файла имена программистов:
 VV>  vlv_rs232_main.dsp :)

Это потому что имена файлов были неправильные. :-Р

 VV>>>  и чтоб еще не было конфликтов имен.
 YK>> Попробуй заставить всех программистов обзывать свои классы
 YK>> непересекающимися  именами.
 VV>  Можно принудительно ограничить области видимости. Hа то и ++.

Области видимости _чего_?

 VV>>>  Попробуй, перейди на C++. Понравится и обратно на C не захочется :)
 YK>> Это ж разбираться надо...
 VV>  Вот она, истинная причина.

Дык о то ж. :-) Выяснять есть ли С++ под HС12, узнавать сколько стоит,
выбивать деньги, а потом еще разбираться, как оно работает и обучать
местных программистов. Кто должен быть счастлив? :-Р

Задач таких нет. Вот (если) буду писать GUI для управления трактором, тогда
и займусь. Может быть.

 VV>  А мне потребовалось произвольное количество всяких мелких таймеров,
 VV>  живущих в фоне. Можно терпеливо и скорбно создавать их статически,
 VV>  а можно timer = new tTIMER(msec). Куда как удобнее.

Главное - чтобы куча не переполнилась.

А можно #define TIMER_MAX 10 и точно знать, сколько памяти использовано.

P.S. остается тот же.

WBR, Юрий.


ICC AVR и стpоки
Tue Sep 23 2003 21:46, Yuriy K wrote to Vladimir Vassilevsky:

 

 YK> Это почему еще? Код для работы с железом все равно придется писать
 YK> примерно одинаковый, хоть на плюсах, хоть на бейсике, хоть на ассемблере.

 Код для работы с железом - это ~5% задачи. Все остальное может быть
 вполне реюзабельным, универсальным и переносимым.
 Тот же UART на 90% состоит из FIFO и прочих аппаратно независимых
 интерфейсных частей, которые удобно оформить в универсальный класс.

 YK>>> static == private.
 VV>>  Только в случае единственного инстанса.
 YK> Согласен. Только ведь количество инстансов для аппаратных ресурсов
 YK> все равно определяется железом.
 VV>>>>  Это сознательное ограничение возможностей.
 YK>>> Каких?

 При очередной переделке проекта решили добавить еще один UART.
 (не суть важно, как он реализован в хардвере)
 Весь интерфейс переписывать?


 VV>>>>  Hа С приходится делать те же самые по сути классы вручную, передавая
 VV>>>>  в функцию указатель на структуру данных обьекта.
 YK>>> Да. Тем более в чем тогда разница?

 VV>>  Это все равно, если нет наследования. Делать таким образом наследование
 VV>>  получается криво и ненаглядно.

 YK> Естественно. Как часто нужно наследование в "embedded" задачах?

 Это весьма удобно, если несколько разных сущностей могут использоваться
 в одних и тех же целях. Hапример, ввод/вывод клавиатуры/LCD,
 взаимозаменяемый с вводом/выводом UARTa.
 Применяются одни и те же методы.
 


 VV>>>>  Попробуй, перейди на C++. Понравится и обратно на C не захочется :)
 YK>>> Это ж разбираться надо...
 VV>>  Вот она, истинная причина.

 YK> Дык о то ж. :-) Выяснять есть ли С++ под HС12, узнавать сколько стоит,
 YK> выбивать деньги, а потом еще разбираться, как оно работает и обучать
 YK> местных программистов. Кто должен быть счастлив? :-Р

 YK> Задач таких нет. Вот (если) буду писать GUI для управления трактором,
 YK> тогда и займусь. Может быть.

 Да даже менюшный ввод/вывод на двухстрочник удобнее писать на ++.

 VLV


ICC AVR и стpоки
Wed Sep 24 2003 18:35, Vladimir Vassilevsky wrote to Yuriy K:

 YK>> Это почему еще? Код для работы с железом все равно придется писать
 YK>> примерно одинаковый, хоть на плюсах, хоть на бейсике, хоть на
 YK>> ассемблере.

 VV>  Код для работы с железом - это ~5% задачи.

ОК. По крайней мере здесь пришли к согласию.

 VV>  Все остальное может быть
 VV>  вполне реюзабельным, универсальным и переносимым.
 VV>  Тот же UART на 90% состоит из FIFO и прочих аппаратно независимых
 VV>  интерфейсных частей, которые удобно оформить в универсальный класс.

Или в универсальный модуль. Пока UARTы не появляются и пропадают во время
работы разницы никакой.

 YK>>>> static == private.
 VV>>>  Только в случае единственного инстанса.
 YK>> Согласен. Только ведь количество инстансов для аппаратных ресурсов
 YK>> все равно определяется железом.
 VV>>>>>  Это сознательное ограничение возможностей.
 YK>>>> Каких?

 VV>  При очередной переделке проекта решили добавить еще один UART.
 VV>  (не суть важно, как он реализован в хардвере)
 VV>  Весь интерфейс переписывать?

Зачем? Добавить еще один тип в UARD_ID и написать работу с этим новым типом.
Переписывать существующий код не надо.

Hа С++ придется делать то же самое.

 YK>> Естественно. Как часто нужно наследование в "embedded" задачах?

 VV>  Это весьма удобно, если несколько разных сущностей могут использоваться
 VV>  в одних и тех же целях. Hапример, ввод/вывод клавиатуры/LCD,
 VV>  взаимозаменяемый с вводом/выводом UARTa.
 VV>  Применяются одни и те же методы.

Может быть. Пока не сталкивался.

WBR, Юрий.


Site Timeline