Инициализация периферии с помощью именованных аргументов

В статье речь пойдет о реализации способа работы c периферией микроконтроллера с помощью именованных аргументов функций Си. Способ разобран на примере реализации функции конфигурации таймера TIM4 семейства STM8L.

Вообще, существует 2 типа передачи параметров внутрь функции или программного модуля: позиционный или именной. Так например, обычные функции в Си используют позиционную передачу, т.е. мы должны знать что и в каком порядке передавать функции. Использование именованных аргументов же гарантирует, что мы присвоим нужные значения куда положено, да и освободим себя от необходимости помнить какой параметр за каким следует. Ну вообщем, на дальнейшем примере это станет понятно.

Рассмотрим таймер TIM4 мк STM8L. И для удобства, абстрагируемся от всех возможностей настройки, оставив лишь некоторый минимальный набор.

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

    CLK_PCKENR1_bit.PCKEN12 = 1;	//включение тактирования TIM4
    TIM4_PSCR = 8;	                //установка делителя таймера
    TIM4_ARR = 200;			//установка регистра перезагрузки    
    TIM4_IER_bit.UIE = 1;		//включение прерывания по обновлению
    TIM4_CR1_bit.CEN = 1;		//запуск таймера



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


#define CLK_DIS         0  
#define CLK_EN          1
#define UINT_DIS        0  
#define UINT_EN         1
#define COUNT_DIS       0  
#define COUNT_EN        1

void TIM4_init(int clk_ctrl, int prescaler, int reload_val, int uint_ctrl, int count_ctrl)
{
   	CLK_PCKENR1_bit.PCKEN12 = clk_ctrl;	//включение тактирования TIM4
	TIM4_PSCR = prescaler;	                //установка делителя таймера
	TIM4_ARR = reload_val;			//установка регистра перезагрузки
	TIM4_IER_bit.UIE = uint_ctrl;		//включение прерывания по обновлению
	TIM4_CR1_bit.CEN = count_ctrl;		//запуск таймера
}

int main( void )
{
  TIM4_init(CLK_EN, 8, 200, UINT_EN, COUNT_EN); 

  return 0;
}


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

#define CLK_DIS         0  
#define CLK_EN          1
#define UINT_DIS        0  
#define UINT_EN         1
#define COUNT_DIS       0  
#define COUNT_EN        1

typedef struct
{
  int clk_ctrl; 
  int prescaler;
  int reload_val;
  int uint_ctrl;
  int count_ctrl;  
} TIM4_init_type;

void TIM4_init(TIM4_init_type config)
{
   	CLK_PCKENR1_bit.PCKEN12 = config.clk_ctrl;	//включение тактирования TIM4
	TIM4_PSCR = config.prescaler;	                //установка делителя таймера
	TIM4_ARR = config.reload_val;			//установка регистра перезагрузки
	TIM4_IER_bit.UIE = config.uint_ctrl;		//включение прерывания по обновлению
	TIM4_CR1_bit.CEN = config.count_ctrl;		//запуск таймера
}
#define TIM4_init(...)  TIM4_init((TIM4_init_type){__VA_ARGS__}) 


int main( void )
{
  TIM4_init(
            .reload_val = 200,
            .prescaler = 8,
            .clk_ctrl = CLK_EN,
            .uint_ctrl = UINT_EN,
            .count_ctrl = COUNT_EN
            ); 

  return 0;
}


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

typedef struct
{
  int clk_ctrl; 
  int prescaler;
  int reload_val;
  int uint_ctrl;
  int count_ctrl;  
} TIM4_init_type;

void TIM4_init(TIM4_init_type config)
{
   	CLK_PCKENR1_bit.PCKEN12 = config.clk_ctrl;	//включение тактирования TIM4
	TIM4_PSCR = config.prescaler;	                //установка делителя таймера
	TIM4_ARR = config.reload_val;			//установка регистра перезагрузки
	TIM4_IER_bit.UIE = config.uint_ctrl;		//включение прерывания по обновлению
	TIM4_CR1_bit.CEN = config.count_ctrl;		//запуск таймера
}
#define TIM4_init(...)TIM4_init((TIM4_init_type){\
                                                .reload_val = 0,\
                                                .prescaler = 0,\
                                                .clk_ctrl = CLK_EN,\
                                                .uint_ctrl = UINT_DIS,\
                                                .count_ctrl = COUNT_EN,\
                                                __VA_ARGS__}) 


int main( void )
{
  TIM4_init(
            .uint_ctrl = UINT_EN,
            .reload_val = 200,
            .prescaler = 8
            ); 

  return 0;
}


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

#define CLK_DIS         0  
#define CLK_EN          1
#define UINT_DIS        0  
#define UINT_EN         1
#define COUNT_DIS       0  
#define COUNT_EN        1

typedef struct
{
  int clk_ctrl; 
  int prescaler;
  int reload_val;
  int uint_ctrl;
  int count_ctrl;  
} TIM4_config_type;

void TIM4_config(TIM4_config_type config)
{
   	CLK_PCKENR1_bit.PCKEN12 = config.clk_ctrl;	//включение тактирования TIM4
	TIM4_PSCR = config.prescaler;	                //установка делителя таймера
	TIM4_ARR = config.reload_val;			//установка регистра перезагрузки
	TIM4_IER_bit.UIE = config.uint_ctrl;		//включение прерывания по обновлению
	TIM4_CR1_bit.CEN = config.count_ctrl;		//запуск таймера
}
#define TIM4_config(...) TIM4_config((TIM4_config_type){\
                                                .reload_val = TIM4_ARR,\
                                                .prescaler = TIM4_PSCR,\
                                                .clk_ctrl = CLK_PCKENR1_bit.PCKEN12,\
                                                .uint_ctrl = TIM4_IER_bit.UIE,\
                                                .count_ctrl = TIM4_CR1_bit.CEN,\
                                                __VA_ARGS__}) 

int main( void )
{
  TIM4_config(  .reload_val = 200,  //при инициализации придется проинициализировать все нужные поля,
                .prescaler = 8,     //либо оставить их в значении после сброса по умолчанию
                .clk_ctrl = CLK_EN,
                .uint_ctrl = UINT_EN,
                .count_ctrl = COUNT_EN
            );
  
  /*
  какой-то код
  */
    TIM4_config(.count_ctrl = COUNT_DIS); //выключаем таймер

  return 0;
}

Попутно изменил название на TIM4_config, т.к. лучше отражает суть.

Подведение итогов
Такой способ конфигурации я почему-то нигде не встречал, хотя он вполне имеет право на жизнь. Ну и кратко можно прорезюмировать (мое ИМХО):
Плюсы
Работа с такими функциями проста и гибка. Очевидно, как и чем был сконфигурирован каждый параметр, не нужно заботится о правильном порядке передачи аргументов, ненужные аргументы могут опускаться. Код легче поддерживать, быстро делать наброски для проверки каких-либо идей.
Минусы
Очевидно, что за удобства приходится платить, и как обычно валютой являются скорость и объем кода. Приходится на каждую функцию держать свой тип и макрос. Также при каждом обращении к функции перезаписываются все использованные регистры, возможно для некоторой периферии осуществление записи пусть и того же самого значения не желательно. Тогда можно записывать все поля структуры значением (-1)по умолчанию, например. А в функции проверять — передали ли чего или нет изменений, и если изменений нет то регистры не трогать. Но и тут исключения будут — из функции надо убрать все поля и регистры, которые могут принимать значение 0xFF (тот же самый -1 в 8битном мк), т.к. в таком случае поведение будет неопределенно.

P.S.
Технология «именованных аргументов функции в Си» кратенько, но более подробно и понятно для начинающего Си-кодера описана в этой статье с Хабра, которая и послужила источником вдохновения. В свою очередь, в той статье честно указано, что идея подсмотрена в книжке «O'REILLY. 21st Century C, 2nd Edition».
  • +10
  • 09 февраля 2015, 21:20
  • 1essor1

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

RSS свернуть / развернуть
я бы убрал заумные слова и написал бы «передача параметров через структуру»
вот так вот начнеш читать и думаеш типа — вот же ж какой я тупой, не знал еще такой метод передачи
процел и понимаеш, что все это знал, только не знал заумного названия, т.к. сам вышел на этот вариант без быдлокниг и естественно сам придумал название методу
а оно оказывается уже за тебя придумано
-1
Никаких заумных слов здесь нет. Фактически получилось действительно реализовать поименной доступ, в данном случае практически такой же, какой существует в Verilog например. Хотя если опустить макросы, юридически да — это передача через структуру. Но если бы мне сказали про передачу параметров через структуру, то я бы ожидал увидеть в первую очередь как в функцию передается один аргумент — структура, предварительно заполненная, а не то, что представлено в посте.

Я рад, что ты сам вышел на этот вариант. Но называть разного рода справочную и техническую литературу — быдлокнигами, как-то неразумно что ли.
+3
то я бы ожидал увидеть в первую очередь как в функцию передается один аргумент — структура, предварительно заполненная, а не то, что представлено в посте.
это уже частности конкретной реализации, у тебя их несколько
но по сути ты же в макрос передаеш структуру все равно, а не параметры через запятую

Но называть разного рода справочную и техническую литературу — быдлокнигами, как-то неразумно что ли.
95% прочтенных мной или начатых книг по программированию — кал
их писали чсвшные быдлокодеры непонятно для кого и я нихера не понимал из этой мешанины заумных слов

как ни странно, но боле менее годные книги пишут пиндосы и даже в переводе они остаются лучше того гавна, что написано на русском
-2
Т.е. умные слова не понимаешь ты, а быдло они?) Интересно, однако.
+1
void TIM4_config(TIM4_config_type config)
У вас одна функция на конфиг одного таймера, для других будет все то же самое. Следовательно, логичным следующим шагом будет передавать номер таймера в функцию. Будет что-то подобное:
void TIMx_config(TIMx, TIMx_config_type)

Ничего не напоминает? Вы идете по пути создания SPL, где все это уже реализовано и отлажено. Осталось только добавить дефайны (или «именованные константы»). Именно поэтому
Такой способ конфигурации я почему-то нигде не встречал, хотя он вполне имеет право на жизнь.
Сделать аналог стандартной библиотеки — это гигантская работа, которая при наличии этой же самой SPL как-то не является необходимой. Тем более, что среды разработки, обновляясь, включают последние версии SPL, поддерживающие новые МК. Самописная библиотека всегда будет на шаг (а то и два) позади них.
P.S. за проделанную работу однозначно плюс, хоть я и не сторонник использования подобных либ.
P.P.S. два раза почти подряд определено
#define CLK_DIS         0  
#define CLK_EN          1
+3
По поводу гигантской работы и новых версий библиотек — у меня вполне конкретный набор купленных мк для хоббийных нужд, поэтому за новыми версиями библ гнаться смысла нет, да и самописное на порядок понятнее. Кроме того, я это как раз тот случай, когда написание таких абстракций доставляет не менее их использования. Ну и дополнительно стоит отметить, что по роду профессиональной деятельности я работаю с российскими мк, где никаких SPL нет)
0
Да, хобби оно такое, может превратить рутинную работу в увлекательную, да еще и пользу принести
0
я работаю с российскими мк, где никаких SPL нет
У Миландра есть, причем при беглом осмотре довольно годная.
0
Наше предприятие по сути их прямые конкуренты, поэтому их контроллеры не доводилось ковырять, да и незачем особо)
0
а какие ещё есть российские мк кроме миландра? а то как то и не слышал.
0
какие ещё есть российские мк кроме миландра
Много их — НАВИС, НИИЭТ, НИИСИ, Ангстрем, Микром, НТЦ «Модуль», ЭЛВИС, Мультиклет и т.д. Производители электроники России
+1
А кто из них МК делает, и какие МК? У ЭЛВИСа вроде DSP/SoC, Мультиклет тоже что-то вроде проца, а не МК.
0
НАВИС делает на ARM7/ARM9, НИИЭТ, Ангстрем, Микрон что-то делает ну и наверное ещё кто-то найдётся.
0
Гм, интересно. И цены вполне приемлемые для поиграться. Можно будет как-нибудь попробывать.
0
попробОвать, my ass
-1
Для этого есть ПМ
0
Обиделсо?
Не обижайсо.
Но Розенталь смотрит на тя аки на…

-1
На таких не обижаются. А вот когда научитесь поиском пользоваться — поймете даже почему.
+2
О как прищемило!
0
В НИИЭТе — 51а, 96я, с166ая, вроде что авровское, сейчас cortex m4f осваивается. Это из того, про что я в курсе.
0
Любопытно. А в продаже что-то из этого, кроме миландра, встречается?
0
Сомневаюсь, что что-то из этого идет в ширпотреб-только военка и космос.
0
Ну, НАВИС на своих МК вроде GPS/GLONASS приёмники делает и купить их можно.
0
с166ая
Это какой МК?
0
Тебе конкретную нииэтовскую номенклатуру или о ядре?
0
Тебе конкретную нииэтовскую номенклатуру
Ну да — не нашёл у них 166.
о ядре
С ядром какбы всё ясно.
0
По ссылке что ты дал, они в конце списка мк.
0
Точно, спасибо.
P.S. Как это у них столько архитектур получается передирать? Интересно много ли отличий от оригинала и ерата какая…
0
Воу воу архитектуры покупаются и лицензируются. А документация предоставляется только потребителям вроде.
0
архитектуры покупаются и лицензируются
Вобщем да — Infineon 166-е ядро лицензировал, но Atmel вроде ядро AVR никому не продавал. Так что содрали наверное.
0
Сименс 20-15 лет назад на нём делал промышленные контроллеры S7-300
http://s7detali.narod.ru/S7_315/S7_315AF03.html
0
Вы идете по пути создания SPL
таки да, забыл написал, что этот метод действительно реализован в сложных контроллерах
главное не именовать константы по 100500 букв как в спл, а то мозги выносит
0
На дворе XXI век, ООП уже четверть века используют, а микроконтроллеры почему-то всё ещё через Си-структуры-дефайны-хардкор программируют. Не понимаю.
:)
0
Надо будет — и ассемблер заюзаем! Все от задачи зависит. В большинстве случаев встраиваемое приложение не настолько сложно, чтобы использовать ООП.
+5
Я не против ассемблера.
Я просто не понимаю, почему не используется С++. Его можно и для самых простых программ использовать :)

И вообще, кусок кода лучше тысячи слов:

// Просто описание класса.
class TIM4 {
public:
  int clk_ctrl;
  int prescaler;
  int reload_val;
  int uint_ctrl;
  int count_ctrl;

  TIM4();
  Set();
};

// Конструктор, устанавливает все дефолтные параметры.
TIM4::TIM4()
 : clk_ctrl(CLK_EN)
 , prescaler(0)
 , reload_val(0)
 , uint_ctrl(UINT_DIS)
 , count_ctrl(COUNT_EN) {
}

// Записываем параметры в железку.
TIM4::Set() {
        CLK_PCKENR1_bit.PCKEN12 = clk_ctrl;      //включение тактирования TIM4
        TIM4_PSCR = prescaler;                   //установка делителя таймера
        TIM4_ARR = reload_val;                   //установка регистра перезагрузки
        TIM4_IER_bit.UIE = uint_ctrl;            //включение прерывания по обновлению
        TIM4_CR1_bit.CEN = count_ctrl;           //запуск таймера
}

// main, да
int main(void) {
  TIM4 t; // создаём объект таймера
  t.uint_ctrl = UINT_EN; // меняем дефолтное значение на какое-то другое, обращаемся по имени
  t.Set(); // четко обозначенный момент передачи параметров железке

  return 0;
}
+2
Я просто не понимаю, почему не используется С++.
И какой смысл в таком классе? Чем он лучше варинта на Си?

P.S. Вот если бы это был шаблон, тогда смысл бы был.
+2
Шаблоны трудно читать, трудно изменять.
Макросы всегда неочевидны, ООП код читается гораздо легче.
(Вы можете сходу сказать, что прячется за строчкой «TIM4_config( .reload_val = 200,» ??))

Компилятор не умеет проверять типы внутри шаблонов.
Вариант с классами банально в 1.5 раза меньше по объему букв.
ООП код можно легко доработать напильником. Например, если известно, что надо часто менять прескалер таймера, то можно добавить метод SetPrescaler, который будет делать нужное (например в две строки: prescaler = val; Set(); )

Ну и отвечу вопросом на вопрос: и какой бы смысл был в шаблоне?
+2
Шаблоны трудно читать, трудно изменять.
Пока не освоишь конечно трудно.
Макросы всегда неочевидны
Да, макросы лучше использовать поменьше.
Компилятор не умеет проверять типы внутри шаблонов.
Разве?
Ну и отвечу вопросом на вопрос: и какой бы смысл был в шаблоне?
Быстродействие.
И к тому же, за счёт того что структура таймеров/USART/SPI/… одинаковая, то можно написать код, который будет подходить для любого периферийного блока.
0
Быстродействие.
Быстродействие — вопрос сложный. Некоторые компиляторы способны вызов виртуальной функции на объекте в куче оптимизировать до MOV EAX, 0x10 (функция вида virtual int getSomeNum(), для данного объекта возвращающая 0x10). Приведенный выше код вполне может скомпилироваться в несколько OUT прямо в теле msin, и уж врядли будет менее оптимален, чем принимающая структуру функция (хотя бы потому, что он по сути принимающей структуру функцией и является).
+2
а до этого прескейлер можно было менять в одну строку и сразу было понятно что смотреть в документации по чипу и что делает код.
А теперь придётся думать SetPrescaler задаёт значение прескейлера или прескейлер-1.
0
а до этого прескейлер можно было менять в одну строку и сразу было понятно что смотреть в документации по чипу и что делает код.
Самописные макросы имеют имена, которых в документации нет. Например, есть макрос, содержащий строку: «TIM4_CR1_bit.CEN = config.count_ctrl;»
Что можно найти в документации, увидев «вызов» «TIM4_config(.count_ctrl = COUNT_DIS);»? Всё равно придётся лезть в макрос и смотреть на нормальное имя TIM4_CR1_bit.CEN.

А теперь придётся думать SetPrescaler задаёт значение прескейлера или прескейлер-1.
Я не очень понял, откуда Вы взяли дополнительные условия, ну да ладно.
Метод SetPrescaler имеет (хотя бы) читаемое имя. На случай -1 можно придумать более говорящее имя метода. (Кстати, из макросов тоже не очевидно, есть там -1 или нет).
0
Так в этом то и фича, что по волшебном слову таймер_выключись он выключится, и мне не придется лезть в даташит, чтоб узнать какой бит в каком регистре отвечает за это действие.

По поводу прискейлера — не помню чтобы мне когда-либо приходилось задавать его значение-1. Поэтому даже таких мыслей не возникало.
Можно пример?
+2
В ООП будет таймер.выключись. ihanick утверждал, что:
а до этого прескейлер можно было менять в одну строку и сразу было понятно что смотреть в документации по чипу и что делает код.
Кажется, что ООП в этом месте ничего не ухудшает, скорее наоборот, ибо имена можно выбирать более свободно.

Про -1 в прескалере придумал ihanick, к нему и вопросы :)
0
Неудачный пример. Хотя бы
TIM4 t; t.setBaudrate(9600).setParity(ParityNone);
Или
TIM4 t; t.set(BaudRate::b9600).set(Parity::None);
А для «четкого момента» можно финализировать строку
TIM4 t; t.set(Baudrate::b9600).set(Parity::None).apply();
+2
Да, спасибо, пример действительно лучше.
Однако Текучий интерфейс — на любителя. Не говоря уже о том, что надо допиливать каждый метод так, чтобы он возвращал this.
0
Не говоря уже о том, что надо допиливать каждый метод так, чтобы он возвращал this.
Зачем this возвращать?
P.S. Логично было бы сделать методы для настройки периферии static.
0
Посмотри внимательно на пример angel5a:
TIM4 t; t.set(Baudrate::b9600).set(Parity::None).apply();


Здесь один объект t, два вызова методов set и один apply. Если метод не будет возвращать this, то тогда та же самая строка разделится вот так:
TIM4 t;
t.set(Baudrate::b9600);
t.set(Parity::None);
t.apply();


Фактически, последний вызов apply применяется не к переменной t, а к this, который вернул предыдущий set.
0
Здесь один объект t, два вызова методов set и один apply.
Не лучший способ.
Правильнее поля и методы TIM4 определять как static — тогда this не нужен. У ведь TIM4 в МК в единственном экземпляре.
0
Артём, почитай, пжл, про Текучий интерфейс. Это не моя придумка.
0
про Текучий интерфейс.
Неподходящий он для периферии.
Лучше так.
0
Чем лучше?
0
Чем лучше?
1) Удобно использовать — пишется один раз для нужного периферийного блока (таймер/UART/SPI/...) и можно использовать для работы с любым другим таким же блоком.
2) Высокая производительность получаемого кода.
0
1) Я не понял, что именно ты хотел сказать. Написать для TIM4 и использовать с TIM5? Так это и с текучим интерфейсом ничуть не сложнее.
2) Как я уже писал, хороший компилятор может даже цепочку виртуальных вызовов оптимизировать до единственного MOV. Столь же тривиальный случай, как последовательность вызовов статических инлайновых методов практически любой компилятор оптимизирует до состояния «не хуже чем на асме писать». Ну, кроме mikroElektronik'и, он ухитряется DDRB = 1 | 2 | 4 в пять команд скомпилировать.
0
Написать для TIM4 и использовать с TIM5?
Написать для TIMx и использовать для TIM1/2/3/…
Так это и с текучим интерфейсом ничуть не сложнее.
Через шаблоны будет эффективнее — все адреса известны заранее.
Столь же тривиальный случай, как последовательность вызовов статических инлайновых методов практически любой компилятор оптимизирует до состояния «не хуже чем на асме писать».
В том и плюс что «не хуже чем на асме писать», но при этом наглядно как на Си++.
0
Написать для TIMx и использовать для TIM1/2/3/…
Можно было просто написать «Да». И в чем проблема сделать точно так же с текучим интерфейсом? Ну, если не считать того, что таймеры в STM32 довольно разные.
В том и плюс что «не хуже чем на асме писать», но при этом наглядно как на Си++.
Ну так и я о том — и я писал про текучий интерфейс, если ты не заметил.
Через шаблоны будет эффективнее — все адреса известны заранее.
Да куда уж эффективнее (скомпилено под ARM):
class TIM4F {
  public:
    TIM4F &prescaler(int value) {TIM4->PSC = value; return *this;}
    TIM4F &reload_val(int value) {TIM4->ARR = value; return *this;}
};

int main(){
  TIM4F t;
  t.prescaler(16).reload_val(10);
}

main:
	mov	r3, #2048
	movt	r3, 32768
	movs	r1, #16
	movs	r2, #10
	strh	r1, [r3, #40]	@ movhi
	movs	r0, #0
	strh	r2, [r3, #44]	@ movhi
	bx	lr
+1
Ну, если не считать того, что таймеры в STM32 довольно разные.
Хотя не, херню сморозил.
0
И в чем проблема сделать точно так же с текучим интерфейсом?
Собственно, вот:
class TIM {
  private:
    TIM_TypeDef *tim;
  public:
    TIM(TIM_TypeDef *t) {tim = t;}
    TIM &prescaler(int value) {tim->PSC = value; return *this;}
    TIM &reload_val(int value) {tim->ARR = value; return *this;}
};

int main(){
  TIM t(TIM4);
  t.prescaler(16).reload_val(10);
  TIM t2(TIM5);
  t2.prescaler(2);
}

main:
	mov	r3, #2048
	mov	r2, #2304
	movt	r3, 32768
	movt	r2, 32768
	movs	r1, #16
	movs	r0, #10
	strh	r1, [r3, #40]	@ movhi
	movs	r1, #2
	strh	r0, [r3, #44]	@ movhi
	movs	r0, #0
	strh	r1, [r2, #40]	@ movhi
	bx	lr
+2
И еще один забавный образец — правда, на сей раз скомпилировано clang x86:
int main(){
  TIM t(TIM4);
  t.prescaler(16).reload_val(10);
  TIM *t2 = new TIM(TIM5);
  t2->prescaler(2);
  delete t2;
}

main:                                   # @main
	movl	$2147485736, %eax       # imm = 0x80000828
	movw	$16, (%rax)
	movl	$2147485740, %eax       # imm = 0x8000082C
	movw	$10, (%rax)
	movl	$2147485992, %eax       # imm = 0x80000928
	movw	$2, (%rax)
	xorl	%eax, %eax
	ret

А вот GCC честно создает t2 на куче.
+1
Да, оптимизатор справился отлично.

Но, честно говоря, мне не очень нравиться применение текучего интерфейса на МК. Основная причина – возникают сложности с обработкой ошибок, которые могут произойти в середине цепочки вызовов (их нужно как-то обрабатывать). И, как следствие, мы можем получить наполовину сформированный «объект», что тоже не всегда хорошо. Лучше выглядит «строитель», как у angel5a
t.set(Baudrate::b9600).set(Parity::None).apply();

но там нужно где-то накапливать изменения перед их применением (apply())

Сугубо ИМХО…
0
Ну, у него тоже текучий интерфейс. Полагаю, оптимизатор справитс и с запоминанием данных в полях объекта с последующим их переносом в регистры и превратит сразу в записи в регистры вперемешку с обработкой ошибок.
0
Ну, у него тоже текучий интерфейс.

Я так понял, что вариант с apply() в конце, реализует «строителя» или что-то подобное… Иначе, зачем нужен вызов и что он делает?

Полагаю, оптимизатор справитс и с запоминанием данных в полях объекта с последующим их переносом в регистры

Да. Но нужно резервировать поля в объекте для этих значений. А они, потом, после apply() мне не нужны, хотя сам объект нужен. А просто выкинуть поля из класса оптимизатор не может.
0
Я так понял, что вариант с apply() в конце, реализует «строителя» или что-то подобное…
Я GOF не читал, но полагаю, что текучий интерфейс и строитель — вещи ортогональные. Т.е. fluent — сам принцип построения цепочки команд, а применяются они сразу или накапливаются — к интерфейсу не относится.
А просто выкинуть поля из класса оптимизатор не может.
Да хрен его знает, что он может, а что нет. И, думаю, при желании что-то придумать можно.
Да и на самом деле столь крутая оптимизация обычно не нужна)
0
Ну я сам предполагал создание временного объекта, который и будет возвращен методом set, и последующие сет уже будут для него. В этом случае в объекте не нужно постоянновисящих временных полей.
В этом и скрыт мой ответ на вопрос «почему до сих пор С, а не С++». Делается ставка на оптимизатор. А к сожалению хорошие оптимизаторы либо дороги, либо не поддерживают требуемую архитектуру. Компиляторы С более доступны, и там даже плохая оптимизация может быть подкорректирована в силу того, что я поишу на С.
+1
Насколько я знаю, эмбеддед С++ компиляторов всего штуки три (gcc, iar, keil) и оптимизируют они все вполне неплохо, если не рассчитывать на оптимизацию в сложных лучаях (вроде статического связывания виртуального метода). Конечно, с теми платформами, которые ими не поддерживаются на С++ не поработаешь, но вроде на сейчас все распространенные МК С++ имеют (правда, для многих это платный IAR).
0
эмбеддед С++ компиляторов всего штуки три (gcc, iar, keil)
Tasking тоже С++.
0
Ну, если не считать того, что таймеры в STM32 довольно разные.
Есть отличия конечно, но не много. К тому же есть куча другой периферии, которая одинаковая — SPI, USART, GPIO, DMA.

Да куда уж эффективнее (скомпилено под ARM):

class TIM4F {
public:
 TIM4F &prescaler(int value) {TIM4->PSC = value; return *this;}
 TIM4F &reload_val(int value) {TIM4->ARR = value; return *this;}};
int main(){
 TIM4F t;
 t.prescaler(16).reload_val(10);
}

А если в другом проекте понадобится не TIM9, а TIM14 и что тогда код опять писать?
0
А если в другом проекте понадобится не TIM9, а TIM14 и что тогда код опять писать?
Это proof-of-concept сэмпл. Кроме того, я уже развернул его в вариант, работающий с любым таймером.
0
Да куда уж эффективнее (скомпилено под ARM):
Попробовал ваш вариант (правда под АВР переделал) — что через шаблоны, что через интерфейс код получился примерно одинаковым по размеру и по скорости выполнения.
0
А можно пример передачи пина в функцию(метод, не принципиально)? Скажем типа такого
t1=OneWare(&PinD1);
t2=OneWare(&PinD2);
Я пока вижу способ: сделать функцию шаблонной (что считаю недопустимым, т.к. ф-я имеет право быть объемной) и создав промежуточный свой интерфейс из которого вызывать требуемые объекты (что является избыточным).
Для меня куда логичнее написать
array ReadData(IStream from, IPin busy);
data1 = ReadData(serial2, rxLed);
data2 = ReadData(ethernet, encSel);
Такой подход конечно более актуален для динамических библиотек и ПК. На мк все равно пересобираем прошивку целиком и появись что новое — шаблон пересоберётся для него. Но опять же, функция может быть достаточно объемной (чтение/парсинг заголовков, проверки контрольных сумм, дочитывание данных).
0
А можно пример передачи пина в функцию(метод, не принципиально)?
Можно так:

void SetPin1(bool set)
{
if (set) PinD1::On(); else PinD1::Off();
}
typedef void (*port_set_ptr_t)(bool);

int main()
{
port_set_ptr_t port_set_ptr=&SetPin1;
port_set_ptr(true);
port_set_ptr(false);
}

Я пока вижу способ: сделать функцию шаблонной
Наверное и надо через шаблонную функцию — типы PinD1 и PinD2 разные, но размер кода при этом возрастёт. :(
0
Хорошо, убедили
TIM t(STM32::Timer4); t.set(Baudrate::b9600).set(Parity::None).apply();

При использовании статика же код будет уже процедурного вида
TIM4::set(Baudrate::b9600);
TIM4::set(Parity::None);
Но чем так, лучше чистый С (с очевидной избыточностью) в угоду переносимости
TIM4_setBaudrate(b9600);
TIM4_setParity(ParityNone);


Да и потом, TIM4 (единственный) может быть наследником абстрактного TIMx, виртуальным методом которого и являются set.
+1
При использовании статика же код будет уже процедурного вида
Что в этом коде процедурного? Обычная работа со статическими методами класса.

лучше чистый С (с очевидной избыточностью) в угоду переносимости
Переносимостью куда? На МК другого производителя? Не перенесётся — таймеры у всех разные.
0
Открою маленький секрет. Не все компиляторы C++ одинакого полезны. Не все поддерживают C++0x и пр. В то время как С89, С99 поддерживаются если не всеми, то абсолютным большенством. Если взять привычку определять все требуемые переменные в начале блока, то проблем с переносом тоже станет гораздо меньше (Да, это все ещё актуально в наш 21й век).
0
Не все поддерживают C++0x и пр
Это конечно, но большинство поддерживают.
Если взять привычку определять все требуемые переменные в начале блока, то проблем с переносом тоже станет гораздо меньше (Да, это все ещё актуально в наш 21й век).
Зачем прогибаться под таких производителей? Можно не закладывать в разработку МК для которых нет нормальных средств разработки.
0
Думаю, все понимают, что благодаря ООП программы становятся понятными, читаемыми, логичными что-ли. Сам пишу на ПК. Но во встраиваемых приложениях, какими бы простыми или сложными они ни были, очень часто важным становится время реакции на какое-либо воздействие. Лишние вызовы функций (или методов класса) — непозволительная роскошь. Да, можно открыть члены класса, использовать прямой доступ, но тогда и приходим к «Си-структуры-дефайны-хардкор». Наглядной становится только инициализация, например TIM4.init(). Но в этом случае это то же самое, что и вызов простой С-функции Init_TIM4(). Вот и получается, что использование классов во многих случаях не дает никаких преимуществ.
В защиту ООП можно сказать, что там, где не критична скорость выполнения, его использование сделает программу более читаемой и модифицируемой.
+2
Лишние вызовы функций (или методов класса) — непозволительная роскошь.
Методы можно определять как inline — тогда вызовов не будет.
+1
Если придираться, то inline не гарантирует, а лишь просит. Иными словами если компилятор захочет то он сделает обычный вызов.

Обратное тоже верно, компилятор может что функцию можно просто вставить вместо вызова.
0
Если придираться, то inline не гарантирует, а лишь просит.
Не гарантирует, но как правило инлайнит.
0
Лишние вызовы функций (или методов класса) — непозволительная роскошь.
Ну, тут есть три фактора:
1) Писать можно по разному — в том числе получая одновременно читабельность (иногда, правда, только местами) и производительность.
2) Накладные расходы даже на бездумный ООП для современных МК часто несущественны.
3) Честно сказать, на этом сайте частенько такой код, что никаким ООПом его производительность не ухудшить.
+2
Спасибо. Я так красиво формулировать не могу :)
Вообще, производительность страдает от ~5% кода (плюс-минус). Это небольшое подмножество и надо оптимизировать. Остальные 95% кода, ИМХО, вполне можно писать в стиле ООП.
0
а ты сравнивал размер бинарников? на плюсах он всяко толще в боле менее реальном проекте
если бы не ограничения памяти, то писали бы на плюсах
никто ж не спорит, что читабельность лучше, в быдлодуине так и сделано
0
а ты сравнивал размер бинарников? на плюсах он всяко толще
Размер одинаковый. С чего бы на плюсах больше получалось? Разве что если компилятор недоделаный использовать.
0
ИМХО, больший размер получается не из-за использования ООП. Как раз наоборот.
Больший размер получается из-за шаблонов, которые template.
0
Больший размер получается из-за шаблонов, которые template.
Но это не шаблоны виноваты — если код работает с разнеми типами переменных, то естественно на каждый шаблон будет генерится свой код. Если тоже самое делать на Си вручную, то размер будет также больше, только код придётся под каждый набор типов писать руками.
0
хвалить и рекомендовать цпп могут многие, а вот пользоваться — не факт. вы это отлично показали
0
Я бы сказал, что к ц это тоже относится.
0
Т.е. то, что пользоваться им умеют немногие.
0
Кхм. Аргументы в студию. Чем Вам не угодил мой код? (Поправленный мной код).
0
не поддерживается парадигма ООП. нет наследования (кто то тут уже писал что все таймеры в целом одно и тоже, логично иметь класс-предок для всех таймеров). нет полиморфизма (не кажется ли логичным иметь вариант конструктора с установкой параметров? или непонятный Set с явной передачей параметров. нет инкапсуляции (все переменные открыты, хотя логично было бы вывести Get-Set методы. в целом действительно имеем то же, что не хуже выглядит на чистом Ц.
0
Использовать все средства ООП — совершенно не обязательно. В данном случае достаточно инкапсулировать API таймера в объект. А без наследования нет и полиморфизма.
Ну и ты путаешь перегрузку методов с полиморфизмом, а сокрытие данных с инкапсуляцией.
Другое дело, что интерфейс вида «те же яйца [что и регистры], только в профиль» имеет сомнительную ценность.
+1
а что есть инкапсуляция, если не сокрытие данных? просто сокрытие можно понимать в упрощенном виде (просто закрыть доступ из вне) или в расширенном (неявное присвоение, а некий аналитический элемент между внешним миром и полями, например проверка валидности перед записью, или свойство значение которого зависит от нескольких полей). и чем перегрузка не есть полиморфизм?
Другое дело, что интерфейс вида «те же яйца [что и регистры], только в профиль» имеет сомнительную ценность.
и я о том же. я не призываю использовать все, чтоб оно было. но в данном случае имеет место пропаганда ЦПП с помощью некорректного примера, при том что можно было бы показать на много более красивое рещение той же ситуации.
0
Инкапсуляция — объединение данных и функций работы с ними в одном объекте. Сокрытие данных — закрытие доступа извне к полям объекта. Это разные вещи. Первое — основа ООП, второе — best practice (еще вопрос, притом, насколько best — в основном оно распространено именно в С++).
Полиморфизм в ООП, aka полиморфизм подтипов — возможность использовать класс-наследник как класс-предок. В С++ реализуется виртуальными функциями. Перегрузка функций/методов — тоже разновидность полиморфизма, но другого, и к ООП уже отношения не имеет (и отнюдь не во всех ООП языках наличествует, наличествуя при этом в некоторых чисто процедурных).
и я о том же
Я таки не уверен, что ты понял, о чем я. В качестве примера более удачного дизайна я приведу arduino — оно имеет простой и понятный интерфейс и абстрагирует от железа в достаточной мере, чтобы обеспечить кроссплатформенность (при условии, что не используется прямая работа с железом).
+1
согласен, не силен в терминах. скажем так, именно этими «фичами» я бы пытался показать преимущества цпп перед ц.

Я таки не уверен, что ты понял, о чем я.
о том что конкретный пример написан ради примера и ничего не дает?
0
Vga прав, вы зря ставите знак «равно» между ООП и С++. Вы можете использовать С++ при этом не использовать ни одну из парадигм ООП. Просто С++ имеет боле богатый синтаксис. Вы можете использовать, например, шаблоны, параметры по умолчанию, даже анонимный код с замыканиями… При этом не используя полиморфизм, наследование или инкапсуляцию.
0
Упс, какой ужас! Сейчас придет trengtor и ткнёт меня носом в

боле богатый
+2
Не ткну, выдохни уже. Это устаревшее просторечное выражение. Местечковый жаргонизм.
0
Ну, и на том спасибо. В очередной раз мы возвращаемся в теме «Grammar nazi». Повторю свое мание – на данном ресурсе собрались люди, которым интересны вопросы разработки/программирования электронных устройств.

Вот, даже в данном топике, речь идет о «именованных параметрах», использовании разных средств ЯП для повышения читабельности кода, ООП и, как частного случая, С++. Я очень сильно сомневаюсь (хоть в вашем профиле указанно «Программист (Pascal, Asm)», что Вам действительно понятна и интересна тема данного разговора. Вы лихо ушли от пороса про комментарии:
Мы видимо вкладываем несколько разный смысл этот термин. В его содержательную часть. Всё в этом мире индивидуально. Мы разные, нам лучше расстаться
Дык вот, если Вы ищете ресурс для обсуждения грамматики и синтаксиса русского языка – может стоить обратить внимание на специализированные ресурсы, типа gramota.ru и т. д.?
+1
Знаете, это у меня из моего студенчества (1980-1985). У нас за подобную «грамотность», коею вы грудью защищаете, можно было схлопотать полную переделку курсового (допускалось до 5 ошибок на весь проект), а то и дипломного проекта. Про лабы же и говорить нечего – просто отправляли переписывать. Прививали то, что инженер просто обязан писать грамотно.
0
Прививали то, что инженер просто обязан писать грамотно.

В этом, безусловно есть логика. Но это идеализация. Уровня «В человеке всё должно быть прекрасно: и лицо, и одежда, и душа, и мысли...»(С)

Для примера, у меня есть сотрудник, он китаец. Он очень плохо знает русский. Чуть лучше английский. Наверное, китайский он знает еще лучше, но данный его навык я оценить не могу. Могу оценит его знания С# — этот язык он знает просто отлично.

Дык вот, исходя из ваших соображений, может ли он быть профессиональным инженером? Стоит ли его увольнять с работы за « 5 ошибок на весь проект»?
0
Давайте не будем передергивать карту из рукава, приводя в пример китайцев и возводя исключение в разряд нормы. Тут вроде бы в массе своей т.н. «носители языка» тусуются. На какие-то ошибки можно закрывать глаза. Но когда, например, ошибки типа тся-ться у человека постоянно присутствуют в письменной речи – то это несколько бесит. Потому что это уровень элементарной грамотности, а не рокет сайнс. Не грех и ткнуть носом. То, что некоторые бурно обижаются – скорее их проблема восприятия себя любимого. И по-моему мы с вами достаточно продуктивно поговорили, чтобы закрыть сей диспут.
+1
Это сильно напоминает «Сэмловрийские бредни», выпуск 2. Не могу только найти рабочую ссылку на них…
0
А мне упоминание про китайца напомнило «Хроники одной лаборатории».
0
Дай ссылку.
0
Я очень сильно сомневаюсь (хоть в вашем профиле указанно «Программист (Pascal, Asm)», что Вам действительно понятна и интересна тема данного разговора.
Вы лихо ушли от пороса про комментарии:
Кстати да, действительно.
0
о том что конкретный пример написан ради примера и ничего не дает?
Да, именно что ничего не дает и ничего не демонстрирует. По сути та же запись в регистры, только переименованные.
Зато этот код стартовал любопытный топик. Единственная польза)
0
Кода точно стало меньше? читаемость увеличилась? Или мы поменяли одни названия переменных на другие? Про C++ вариант тот же вопрос.

void setup_uart_timeout() {
    CLK_PCKENR1_bit.PCKEN12 = 1;        //включение тактирования TIM4
    TIM4_PSCR = 8;                      //установка делителя таймера
    TIM4_ARR = 200;                     //установка регистра перезагрузки    
    TIM4_IER_bit.UIE = 1;               //включение прерывания по обновлению
    TIM4_CR1_bit.CEN = 1;               //запуск таймера
}

int main() {
   ...
   setup_uart_timeout();
   ...
}


много кода
int main () {
   ...
   TIM4_init(
            .uint_ctrl = UINT_EN,
            .reload_val = 200,
            .prescaler = 8
            );


Т.е. понятно что обёртки лучше чем
(* (uint32_t*)0xC0000001 ) = 200;


Здорово, когда настраиваемость увеличивается как в stm32plus (timer_master_slave.cpp, timer_interrupts.cpp) (хотя понять что происходит внутри становится очень сложно)

Но какой толк от добавления точечек и изменения названий переменных?
0
А кто обещал, что кода станет меньше?
Читаемость увеличилась. Без комментариев бывает сложно вспомнить, какой регистр и бит за что отвечает просто глядя в код. Ведь таймер это фигня, бывает периферия и посолидней. Я, например, часто переиспользую уже однажды написанный код, но в вашем варианте, мне таки придется залезть в даташит и проверить все ли нужные биты и поля я заполнил или не установил лишнего, а то вдруг в старом коде чего не так. А тут все прозрачно — у тебя есть фиксированный набор параметров и ты ничего не упустишь и не забудешь. А если по ходу дела нужно перекофигурировать несколько раз перифирию — на каждый раз процедуру писать?
+2
Читаемость одинаковая, с таким же успехом мы могли писать магию на битовых полях задать нормальные названия для полей (а не в виде абривеатур из пары букв).

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

Например такие штуки будут более полезны для повторного использования:
set_periodic_event(myhandler, 3*MHz);
run_once(myhandler, 10 * ms);


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

Предположим, Вы встречаете в коде строку
ConfigurePWM(100, 1000);

Без подглядывания в декларацию нельзя сказать, что означают параметры. С именованными – все нагляднее
ConfigurePWM(.PulseWidth = 100, .Period = 1000);

Хотя, безусловно есть и другие средства повешения читабельности (например, шаблон «строитель», как предложил выше angel5a )
+5
Без комментариев бывает сложно вспомнить...
Вот как раз об этом в соседней ветке копья ломали с адептами Самодокументирующегося Кода :)
+1
Вот он и предлагает вместо комментария заменить нечитаемый код самодокументирующимся.
+4
У меня стойкое ощущение, что мы с тобой термином «комментарий» описываем несколько разные понятия.
0
В таком слчае поясни, что именно ты подразумеваешь под комментарием. Я — пояснение к коду программы, записанное в нем же, при помощи соответствующей конструкции языка.
+1
Все верно. Это форма. Я про содержательную часть.
0
Ну так поясни, какое именно понятие ты описываешь термином «комментарий».
+1
Попробовал поискать, выкладывал ли ты где-то свой код, чтобы можно было глянуть, какие комментарии ты пишешь (и, соответственно, считаешь правильными).
Нашел только это, первый из двух аттачей. Это твой код?
0
Нет
0
Все верно. Это форма. Я про содержательную часть.
Понятнее не стало. Если ты считаешь, что мы понимаем под термином «комментарий» разное — поясни, в чем именно разница.
+1
Мы видимо вкладываем несколько разный смысл этот термин. В его содержательную часть. Всё в этом мире индивидуально.
0
Автор, благодарность тебе! Как раз думал, как реализовать подобное. Отличное решение!
0
Да на здоровье)
0
Пока накидываю программу, хочу получить ответ на следующий вопрос:
Мне требуется в теле функции работать структурами, не расписывая ее элементы. Если я не укажу какой-либо элемент, будет ли это значить, что в этом элементе будет 0 и он запишется в соответствующий элемент структуры?
0
Ты имеешь ввиду, что будет если вызвать функцию, в аргументы которой передать лишь некоторые заполненные поля структуры (как в предпоследнем/последнем примере)? Если да, то все остальные поля заполнятся теми значениями, которые идут в макросе #define
0

typedef struct task
{
   void (*func) (void);
   u08 status;
   u16 timer;
   u16 period;
} task;

extern struct task Task_Queue [];

u08 _set_task (task tmp_task); // Добавление задачи в очередь.
#define set_task(...)  _set_task((task){__VA_ARGS__})

bool _set_task (task tmp_task) // Добавление задачи в очередь.
{
   if (Tail_Task_Queue < MAX_TASKS)
   {
      Task_Queue [Tail_Task_Queue] = tmp_task;

      Tail_Task_Queue++;

      return true;
   }

   return false; // Очередь задач заполнена, критическая ошибка.

   set_task (
               .func = led_1_switch,
               .status = MULT_EXEC,
//               .timer = 0,
               .period = 500
            );
}
0
Не совсем понимаю — в функции _set_task есть 2 ветви выполнения, одна из которых возвращает тру, другая фолс. Но зачем в теле функции вызывать её еще раз? Рекурсия? Тем более из участка кода в который попасть нельзя?
Tail_Task_Queue — внешняя переменная?
и почему у _set_task вверху возвращаемый тип u08, а ниже — bool?
0
Йопт, что плохо, в комментариях нет редактирования.

//------------------------------------------------------------------------
bool _set_task (task tmp_task) // Добавление задачи в очередь.
{
   if (Tail_Task_Queue < MAX_TASKS)
   {
      Task_Queue [Tail_Task_Queue] = tmp_task;

      Tail_Task_Queue++;

      return true;
   }

   return false; // Очередь задач заполнена, критическая ошибка.
}
//------------------------------------------------------------------------

   set_task (
               .func = led_1_switch,
               .status = MULT_EXEC_FLG,
//               .timer = 0,
               .period = 500
            );
0
Вопрос был:
Если я не укажу какой-либо элемент, будет ли это значить, что в этом элементе будет 0 и он запишется в соответствующий элемент структуры?

А что мешает тебе проверить?) У меня в подобном коде в нетронутых полях нули. STM8 IAR.
0
Пока проверка дает, что записываются 0. Но мне на будущее нужно понять правила. Чтобы не влететь в будущем из-за того, что указанные элементы записались, а не указанные нет, или чтобы там не оказался мусор, как ниже указали.
0
Как вариант, добиться 100% наличия нулей можно, добавив в макрос инициализацию полей нулями.
0
Инициализировать надо все поля. Задай умолчания в макросе, как в поледнем варианте кода из топика.
0
Сначала было u08, переделал на bool. Еще везде не правил, не компилировал, в спешке сюда код выложил. В программах таких ошибок не допускаю.
0
Компилятор IAR. В дизасме видно, что на данном примере он считывает из памяти программ и загоняет в структуру.
0
Так у вас структура заявлена как изменяемая. объявите её конст для начала, тогда оптимизатору будет проще.
0
Какая именно структура?
0
Если я не укажу какой-либо элемент, будет ли это значить, что в этом элементе будет 0
Если структура — глобальная переменная, то будет 0. Если локальная — компилятор создаст её на стеке, проинициализирует указанные поля, а неуказанные нет. Будет там всякий мусор.
0
Выше код написал.
0
Вообще, жаль, что развитие стандарта С как-то затормозилось. С11 существует, по сути, только на бумаге (хотя там достаточно вкусных для эмбеддера моментов, например, поддержка атомарности обращений к памяти на уровне языка). Если С++11 дал серьезный толчок в развитии С++ (вплоть до пересмотра парадигмы) и сейчас этот стандарт поддерживается большинством компиляторов и активно используется, то о С11 мало кто знает, мало кто поддерживает. И вообще, дальнейшие планы развития стандарта туманны. Вроде и язык достаточно «живой» (вспомним ядра современных ОС) и, в тоже время, развитие остановилось, по сути, на С99.
+3
  • avatar
  • e_mc2
  • 12 февраля 2015, 21:45
Технология «именованных аргументов функции в Си» кратенько, но более подробно и понятно для начинающего Си-кодера описана в этой статье с Хабра, которая видимо и послужила источником_вдохновения. В свою очередь, в той статье честно указано, что идея подсмотрена в книжке «O'REILLY. 21st Century C, 2nd Edition»
+1
Мне одному кажется, что:
// макропереопределение функции 
#define TIM4_init(...)  TIM4_init(......

// надо бы делать все-таки капсом
#define TIM4_INIT(...)  TIM4_init(......
0
почему капсом?
0
Ну эт чтобы свежеподорвавшийся к коду новичок не кипешевался сразу, типа: а это что за х, а куда делись фигурные скобочки? В отличии от чела, загнивавшего в сей либе несколько ночей подряд. А мгновенно понял: А-а, это же МАКРО (вроде в целом ясно что делает), ну и х с ними (с этими говнокодерами своего собственного синтаксического «САХАРА» или собственного фремворка(т.е. немного другого/нового языка, в отличии от Си) на Си-коде средствами препроцессора)! Посмотрю как работает эта муть чуть позже, а сейчас надо бежать дальше по листингу и найти где же тут они дергают регистр XXXX…
0
Мне одному кажется, что:
Да.
+2
Не отрицаю, так и есть. В тексте статьи, кстати, нет никаких намеков на то, что этот метод мой и прочее) Я лишь применил его конкретно к мк, да показал местному люду.
0
Уже 5-7 лет, как C99 стал стандартом де-факто ИМЕННО у MCU-мэнов. Но почему до сих пор можно видеть в листингах все эти мутные и сбивающие с толку int или char из ветхозаветного ANSI/C89, заместо строгих и понятных uint8_t/uint16_t/int8_t… Можно было бы понять, если бы так писал какой-нибудь java и т.п. кодер с хабра, недавно прослышавший и перевозбудившийся от STM32.

Тем более, что вся фича и построена на трюке с макросом __VA_ARGS__, который появился только в C99.
0
Потому что хорошо, если каждый сотый из пишущих на С знает язык, на котором пишет.
Ну и мне попадалить довольно современные компиляторы, не знающие stdint. Например, Cosmic C STM8.
0
Что забавно, визуал студия 2008 стдинта тоже не знает. По-моему, этот хедер добавился впервые в 2010 версии. Конечно, в винде это менее актуально, но тем не менее =)
0
Microsoft в свое время забила на развитие поддержки Си в своем компиляторе — у них до сих пор в MSVC реализована поддержка только ANSI C (C89). Основные усилия они предпринимали на развитие C++, видимо посчитав, что для Windows язык Си особо уже не важен.

Главным локомотивом внедрения стандарта C99 в жизнь все эти годы был GCC.
0
Основные усилия они предпринимали на развитие C++
Я бы не сказал. По крайней мере MFC – это худшее, что я видел в своей жизни. До появления C# Микрософт разрывалась между VB, C++ и даже пилила свою версию Java. И все это получалось, честно сказать, хреново.

Сейчас, они сконцентрировались на C#, и это ИМХО, дало свои плоды.
0
По-моему, он про компилятор. А компилятор С++ у MS действительно хорош — и я уже слышал, что один и тот же код он куда лучше компилирует, если у файлика расширение .cpp, а не .c.
0
По-моему, он про компилятор.
Да, извиняюсь вспылил, был неправ. Просто вспомнил о MFC :)
А компилятор С++ у MS действительно хорош
Не знаю… А чем он хорош? ИМХО: по поддержке новых стандартов – он «середнячок», по качеству оптимизации кода – он проигрывал intel c++ compiler (не знаю как сейчас, сугубо из моего опыта лет эдак 6 назад), кросскомпиляцией он тоже никогда не славился…
0
Ну, с компилятором от производителя проца сложно тягаться (интересно, правда, как дела обстояли на амдовых процах...), но в целом по оптимизации он один из лучших, и на сейчас — основной компилятор под виндой. Даже опенсорс обычно в нем под винду собирается (ну или по крайней мере, так было несколько лет назад).
0
Если рассматривать связку с экосистемой Windows – безусловно, он выигрывает. Но как абстрактный С++ компилятор, ИМХО, он «середнячок».
0
Ну, оптимизацию на его уровне из ПКных компиляторов имеют только ICC, GCC и, с недавних пор, Clang. По сравнению с кучей других компилеров, вроде Watcom, Borland, etc — он сильно выигрывал по оптимизации.
Плюс IDE, достойные конкуренты которой только сравнительно недавно появились.
0
Только что этот язык C# дает эмбеддерам (linuxer'ам и MCU'шникам)?

Про C++ (в противовес C99) я писал в том смысле, что они же (Microsoft) допиливали его постоянно до некоего соответствия текущему новому стандарту — C++98, C++03(2003), сейчас C++11.
0
Только что этот язык C# дает эмбеддерам (linuxer'ам и MCU'шникам)?

Ну, можно попытаться высосать из пальца .NET micro framework, но это плохая идея :)

Ничего C# не дает эмбеддерам. Но и реализация С++ от MS тоже практически ничего не дает эмбеддерам.
0
Ну, можно попытаться высосать из пальца .NET micro framework, но это плохая идея :)
Есть еще Mono)
0
Есть еще Mono
Mono хорош. И, в свете последних событий, будет активно развиваться. Но, все же, это не уровень встраиваемых систем.
0
Ну, это к вопросу линукса. На такой ВС, как распи, его спокойно можно запускать и еще вопрос, уступит ли оно плюсам по производительности.
0
Т.е. MS рассуждала так: ты хочешь писать в стиле Си, но со всеми доп.плюшками и сахарком C99 — пиши в C++, тем более что плюшек там еще больше и это даже без всякого ООП.

Так до недавнего времени думала MS (со своим пониманием универсального низкоуровнего инструмента(C++) якобы для всех и для всего, под всем и все — скорее всего подразумевалась Windows, и у них видимо было и свое личное понимание «портируемости ПО» на разные CPU и платформы) — и облажалась, поскольку прозевала назревающий бум в эмбеддерном ПО и то, как технология и сообщество GCC проявили способность портироваться практически на какую угодно платформу. А теперь уже поздно совать stdint и прочие хидеры из стандарта C99 в свой компилятор — мертвому припарка.
0
Ну как раз то, что оно поддерживает только свою платформу — это понятно. Когда какие-то продукты M$ начинают поддерживать не-майкрософтовские платформы (при наличии у M$ аналога), это значит, что соответствующая платформа M$ проиграла и они стремятся хоть как-то зализать убытки.
0
С++ они не позиционировали как низкоуровневый инструмент (по крайней мере раньше). Весь DDK (Driver Development Kit) был на чистом С.
На уровне приложений к ОС — да, С++. Но как я уже говорил, любая стороння GUI библиотека/фреймворк (VCL, Qt, WxWindows) была лучше, чем их нативный MFC.

Сейчас они делают акцент на управляемом коде. Что, в принципе, имеет смысл, ибо обеспечивает отвязку от аппаратной платформы …

А «эмбеддерское» ПО их никогда особо не интересовало – они всегда ориентировались на десктопы и корпоративных клиентов.
0
Упс, промахнулся веткой, ответ адресовался well-man2000
0
была лучше, чем их нативный MFC.
Ну, они и сами отнюдь не один фреймворк запилили.
А «эмбеддерское» ПО их никогда особо не интересовало
У них был (а может и сейчас есть) eVC (embedded Visual Studio) — для WinCE и встраиваемых систем на его основе. Подерживал ARM и, вероятно, MIPS с SH3.
0
Ну, они и сами отнюдь не один фреймворк запилили.

Я не являюсь хейтером МС (скорее наоборот). Они много чего сделали. Но вот фрейворки у них не блистали удобством для программиста.
Например, на рынке разработки GUI приложений для своей ОС они (в свое время) вчистую проигрывали тому же Borland, играя «на своей территории». Правда появляется другой вопрос – как, то же Borland умудрился «просрать все полимеры»…

У них был (а может и сейчас есть) eVC (embedded Visual Studio) — для WinCE
Да был (потом eVC они интегрировали все в MSVC, потом Compact framework, сейчас windows phone). Тогда была конкуренция на рынке КПК с Palm. Но МС тогда не цеплялся за этот рынок, поэтому, имея изначальное преимущество, достаточно быстро без особой борьбы этот рынок отдала другим. Видать, тогда они недооценили рынок мобильных платформ.
Для МС всегда основным был рынок корпоративных клиентов. В других областях они «отмечались», но особо не боролись за рынок. Они и сейчас так делают. Есть .NET micro framework (я даже с ним баловался на at91sam7x512) – прикольно, но неюзабельно для реальной разработки реальных устройств.
0
Но вот фрейворки у них не блистали удобством для программиста.
Угу. Поэтому для дотнета они поступили мудро — переманили Хейльсберга)
0
А «эмбеддерское» ПО их никогда особо не интересовало – они всегда ориентировались на десктопы и корпоративных клиентов.
MS вместе с Intel уже обосрались недавно(упустили огромные прибыли, имея тем не менее огромные материальные/интеллектуальные/технологические ресурсы) на волне сотов.телефонов и затем смартфонов.

Сейчас уже формируется еще одна такая волна потребления — интернет вещей (умные дома/чайники/лампочки и т.п.), которая будет в десятки раз больше смартфонно-телефонной, т.к. огромное кол-во чипов(уже ценой за $1 или даже меньше) надо куда-то девать производителям.

Видимо поэтому у Intel и MS с недавних пор периодически происходят какие-то конвульсии на эту тему — какие-то ардуиноподобные платки с процами Intel или NET framework. Правда пока безуспешные.
0
MS вместе с Intel уже обосрались недавно
Спорный момент. То, что недооценили мобильный рынок – я с вами соглашусь.
Касательно «обосрались» — вы видели ценники в этерпрайз секторе? Например IMB, с их оборудованием, AIX, DB2 и тд…
0
Знаменитое «видение будущего» глазами БГ заключалось в том, чтобы заранее что-то унюхать из области, связанной с ПО и компьютерными технологиями, и вовремя это окучить, возможно даже используя за деньги чужой готовый труд. Что хапнуть и сколько — не имело особого значения. Так 2 красноглазика процессорных кодов купили у одного говнокодера(который тем не менее был выше классом как кодер, чем они, на тот момент) ОС, написанную на asm, и на долгие годы стали монополистами на рынке OC для IBM PC. Какое там корпоративное и энтерпрайз ПО, когда все ПО умещается на одной дискетке, но уже намного позже MS, не имея никакого успешного опыта в этом деле, купила у Sybase готовую СУБД и назвала ее MS SQL Server. Потом они на абсолютно ровном месте наваяли IE, наняв кого надо и купив/достав нужные лицензии и готовые коды Mosaic и Javascript. Та же ситуация и с Windows, и с NT(т.е. уже с серверной ОС). Еще у этой вроде бы изначально софтверной фирмы есть XBOX и т.д. и т.п. — продолжать можно долго.

Так что — на этот раз не унюхали, как обычно — т.е. обосрались :D
0
Да, история с DR-DOS была весьма интересная. Они еще долго трепыхались, делали всякий софт, ЕМНИС, даже DR-DOS 3.30 была – весьма неплохо себя показывала.
0
Но как я уже говорил, любая стороння GUI библиотека/фреймворк (VCL, Qt, WxWindows) была лучше, чем их нативный MFC.
Все это вышеперечисленное тоже скоро улетит в небытье, т.к. уже несколько лет, как всем уже понятно, что самый лучший/безграничный в возможностях и легко программируемый даже «дебилами» GUI (и вообще, любое Application с плюшками/графикой/видео и прочими картинками и мерцаниями/миганиями) — это HTML(на этот момент — это HTML5) + Javascript(под любыми видами соусов либ и фреймворков) в некоем окне, которое некогда давно принадлежало отдельной конкретной программе — браузеру.

Мало того, старый трюк MS с использованием частей одного и того же кода в GUI оболочке для рабочего стола ОС и браузере для интернета — Explorer бесплатен, т.к. он встроен в ОС и есть часть ее! Многие (google, файрфокс и прочие некогда пострадавшие от IE) уже пытаются реализовать даже больше и жаднее — уже вся ОС на основе движка браузера.
0
Все это вышеперечисленное тоже скоро улетит в небытье.
Ну и ок. Даже сейчас подобные фрейворки имеют свой язык декларативного описания интерфейса (QML, XAML, HTML5).
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.