Jump to content

    

Arlleex

Свой
  • Content Count

    927
  • Joined

  • Last visited

Community Reputation

0 Обычный

About Arlleex

Контакты

  • Сайт
    http://
  • ICQ
    0

Recent Profile Visitors

2676 profile views
  1. Keil, ARMCC default version 5. Ключи: --cpp --exceptions. Оптимизация -О0. Стандартный код инициализации от microlib, при сравнении значения не имеет. #define REG *(volatile u32 *)0x20000000 int main(void) { try { if(REG) throw 1; } catch(int) { return 1; } return 0; } Program Size: Code=6456 RO-data=1164 RW-data=40 ZI-data=4608. int main(void) { TRY { if(REG) THROW(1); } CATCH(1) { return 1; } ENDTRY; return 0; } Program Size: Code=888 RO-data=460 RW-data=24 ZI-data=4096. Те же условия, только оптимизация -O3. Исключения C++: Program Size: Code=6268 RO-data=1164 RW-data=40 ZI-data=4608. Исключения-велосипеды: Program Size: Code=656 RO-data=460 RW-data=24 ZI-data=4096. По скорости даже проверять не буду. Скажите, конечно, что это жалкое подобие "настоящих" исключений - например, нет RTTI. Да оно мне и не нужно - мне нужна основная функциональность - уметь гибко бросать и ловить исключения. И, ИМХО, мне это удалось Осталось изобрести что-то попроще (подобно предложенному jcxz), либо взять как есть (примерно)
  2. Спасибо! Появится снова немного свободного времени - займусь этим.
  3. Примерно понял, только есть вопросы: 1. Можете привести пример использования в Си-коде этих оберток? 2. Кто такой pjmp? По этому адресу у Вас сохраняется положение SP перед выполнением JsonInputParseExec(). 3. После вызова JsonInputParseExec() делается ADD SP, SP, #8. Почему? Код RTOS менять, конечно, не очень хотелось бы, но эксперимента ради - можно, думаю (хотя, кажется мне, этого не понадобится). Надо разузнать насчет TLS. Вот массив со списками нравится больше У меня FreeRTOS.
  4. Знаю тема эта не нова, даже где-то обсуждалась, но вскользь. Хочу поделиться некоторыми соображениями по поводу механизма исключений на чистом Си, привести свою реализацию. Топик не для начинающих и, скорее, рассчитан на подготовленного читателя-фаната, поэтому за возможно причиненные морально-нравственные страдания автор ответственности не несет Также хочу отметить, что я не претендую на безоговорочную истину всего сказанного: все, что мне довелось получить - это результат пары-тройки часов колупания над готовыми решениями (которые, как думаю уже понятно, меня не сильно радовали). Итак, как мне довелось узнать, существует много различных реализаций исключений на Си, среди которых в первых гуглорядах стоят CException и т.д. И (не соврать бы) по-моему во всех реализациях механизма исключений главным рабочим инструментом являются функции setjmp() и longjmp(). Также существует два "скелета" для этих функций: if(!setjmp(...)) <- аналог оператора try { // потенциально опасный код ... if(...) longjmp(..., N); <- аналог оператора throw } else <- аналог оператора catch(...) { // обработчик } и switch(setjmp(...)) <- аналог оператора try { case 0: { // потенциально опасный код if(...) longjmp(..., N); <- аналог оператора throw } case 1: <- аналог оператора catch(int) (но тут явно задан номер исключения №1) { // обработчик 1 break; } case 2: <- аналог оператора catch(int) (но тут явно задан номер исключения №2) { // обработчик 2 break; } ... } Ну, как видно, на if-else, либо на switch-case (разницы-то, по сути, нет (пока что). Теперь, анализируя статью Exceptions in C with Longjmp and Setjmp, останавливаемся на втором варианте, так как фильтровать и ловить код ошибки от throw() гораздо удобнее по значению, нежели скопом ловить сразу все в одном месте (как в ветке else в первом варианте). Погоняем различными тестами окончательный вариант из статьи. 1. Конструкция try-catch полная: в блоке TRY явно кидается исключение 1, в CATCH-блоке оно ловится Работает правильно. 2. Затираем выброс исключения из предыдущего примера, блок FINALLY не добавляем Работает, но если бы этот код был в функции, а эта функция вызывалась из другой функции из, в свою очередь, своего try-catch-блока, поведение считалось бы ошибочным - ведь при отсутствии явного обработчика исключение должно быть "проброшено" в более глобальный обработчик (если он есть). Это есть первый косяк. 3. Выброс исключения из примера №1 оформляем в виде отдельной функции. В результате проект не собирается (ошибка компиляции), поскольку вызов THROW(...) предполагает видимым объект ex_buf__ из начала TRY-блока. Это есть второй косяк. 4. TRY-в-TRY: один TRY-блок (полноценный) внутри другого (более глобального) TRY-блока Правильно. Но если убрать CATCH-блок внутреннего TRY, не выведется сообщение "External exception catched!", а по логике наследования исключений должно. Третий косяк. 5. В предыдущем примере попробуем явно после выдачи сообщения "Internal exception catched!" сделать THROW(1): в try-catch C++ это привело бы к выдаче исключения на более глобальный уровень и обработке его там: в нашем случае последовательно должны были вывестись сообщения "Internal exception catched!", "External exception catched!" (между "Code before try/catch" и "Code after try/catch", конечно же), однако этого не произошло: вместо этого обнаружен бесконечный цикл вывода сообщения "Internal exception catched!". Это уже четвертый косяк. Короче, печаль. Нет схожести с поведением в более высокоуровневых языках типа C++. Ладно, напишем свое... с блэкджеком и автоматами Даффа Первым делом, подразумевая все изложенные проблемы кода выше, набросаем скелет программы, при котором убьются все эти проблемы сразу одним махом. Для этого определим некую структуру - блок управления исключениями // Exception Control Block typedef struct __sECB { struct __sECB *PrevECB; // предыдущий блок jmp_buf Context; // контекст текущего выполнения }__sECB; Структура содержит два параметра: PrevECB и Context. PrevECB указывает на предыдущий объект управления исключениями для организации односвязного списка исключений: это понадобится для наследования вызовов исключений при отсутствии явного локального обработчика. Буфер Context содержит контекст текущего выполнения и используется для локального обслуживания функциями setjmp() и longjmp(). Создадим "корень" в цепочке блоков управления исключениями __sECB *__ECBList = NULL; Значение NULL будет указывать, что в цепи вызовов try-catch при любом уровне вложенности функций, из которой произошел выброс исключения, обработчиков больше нет. Наглядно это будет выглядеть так main() { TRY {func1();} CATCH(1) {...} CATCH(2) {...} ... } func1() { TRY {func2();} } func2() { TRY {func3();} } func3() { TRY { if(...) THROW(1); <- выброс исключения №1 if(...) THROW(2); <- выброс исключения №2 } CATCH(1) {...} <- локальный обработчик для THROW(1) // а для THROW(2)-то нету!!! Поэтому этот выброс должен быть обработан на более глобальном уровне: // управление передастся в func2, там нет обработчиков, потом в func1, там тоже нет, // и затем - в main() как последнюю инстанцию (в main() __ECBList будет равен NULL - нет функций "выше" данной) } Теперь самое жирное: необходимо написать весь блок TRY-THROW-CATCH в своем естественном виде do { __sECB ECB; ECB.PrevECB = __ECBList; __ECBList = &ECB; u32 Reason = setjmp(__ECBList->Context); if(Reason) __ECBList = ECB.PrevECB; switch(Reason) { case 0: while(1) { { longjmp(__ECBList->Context, 1); } break; case 1: { printf("Exception catched!\n"); } break; } if(!Reason) __ECBList = ECB.PrevECB; break; default: { if(__ECBList != NULL) longjmp(__ECBList->Context, Reason); } } }while(0); Я не зря написал слово блок. Поскольку мы хотим уметь делать вложенные try-catch, то объект текущего блока управления исключениями выделяется локально на стеке. Именно поэтому в конструкции фигурирует do{}while(0). Теперь можно "разрисовать" кто есть кто. Красный цвет - начало блока TRY. Синий - THROW. Оранжевый - CATCH. Зеленый - ENDTRY. Начинается все с выделения на стеке блока управления текущими исключениями __sECB ECB; // создаем ECB для локальных исключений ECB.PrevECB = __ECBList; // запоминаем в TRY-листе последний активный блок __ECBList = &ECB; // устанавливаем текущий блок управления в качестве "верхушки" u32 Reason = setjmp(__ECBList->Context); // ловим исключения if(Reason) __ECBList = ECB.PrevECB; // если есть исключения - возвращаем верхушку обратно на один шаг назад - потому что в CATCH-блоке может быть повторный выброс исключения // или при отсутсвии подходящего локального CATCH управление должно передаться предыдущему блоку CATCH по стеку вызовов Автомат Даффа для того, чтобы "довыполнить" кое-какие дела после любых case switch(Reason) { case 0: while(1) { { longjmp(__ECBList->Context, 1); // выброс исключения } break; case 1: // локальный отлов исключения { printf("Exception catched!\n"); // пишем, что поймали локальное исключение } break; } if(!Reason) __ECBList = ECB.PrevECB; // финт ушами Даффа - возвращаем обратно указатель на текущий блок управления, если исключение не было сформировано break; Ну и окончание блока TRY default: // сюда попадаем, если возникло исключение, которое не было обработано локально { if(__ECBList != NULL) // если сверху по стеку вызовов есть "великий и могучий" блок TRY-CATCH, то... longjmp(__ECBList->Context, Reason); // управление передать ему } } }while(0); Обращаю внимание, что очень важно сохранить синтаксис THROW - longjmp(__ECBList->Context, N) - если возможно, исключение обработается в локальном CATCH, если же нет - в следующем по иерархии (размотка стека вызовов). Проследите (кому интересно) по коду, почему так получается. Теперь можно "обернуть" весь этот мусор в макровызовы #define TRY do{__sECB ECB; ECB.PrevECB = __ECBList; __ECBList = &ECB; u32 Reason = setjmp(__ECBList->Context); if(Reason) __ECBList = ECB.PrevECB; switch(Reason){case 0: while(1){ #define CATCH(X) break; case X: #define ENDTRY break;}if(!Reason) __ECBList = ECB.PrevECB; break; default:{if(__ECBList != NULL) longjmp(__ECBList->Context, Reason);}}}while(0) #define THROW(X) longjmp(__ECBList->Context, X) Вуаля, все проблемы из реализации по ссылке в начале топика исчезли Шаблон TRY { THROW(1); <- будет вызван локальный CATCH(1) (Extern exception catched!) TRY { f(); <- будет вызван объемлющий CATCH(1) (Extern exception catched!) } ENDTRY; } CATCH(1) { printf("Extern exception catched!\n"); } ENDTRY; f() { THROW(1); } и т.д. можно как угодно вкладывать друг в друга с и без обработчиков А вот теперь для меня пока что нерешенный вопрос... Это многопоточность. Я хочу иметь реентерабельные функции, которые могут пользоваться этими макросами потокобезопасно. Имея один глобальный список __ECBList, эта безопасность не обеспечивается. А хотелось бы в зависимости от потока, выполняемого в текущий момент времени, иметь свою копию __ECBList (при этом, для сохранения единообразия написания макросов, эта копия должна иметь одно имя - __ECBList). Вот как это бы сделать - вопрос открытый. Кому интересно покопаться - буду рад предложениям
  5. Спасибо, для меня это тоже опыт. Собственно говоря, на нет и суда нет - немного пересмотрел процедуру обмена информацией о версии, дате и времени сборки проекта - получилось не так красиво, как изначально хотелось, но результат похожий.
  6. Почитать что? По какой теме? По использованию RTOS для МК? Начните с основ любой RTOS - организация многозадачности, средства синхронизации потоков и т.д. Это стандартный набор для построения любой RTOS. Ну а так, навскидку: А. Курниц. FreeRTOS - операционная система для микроконтроллеров.
  7. Тогда можно еще ж проще: сразу подставлять, что нужно const char __TIME_RUS_FORMAT__[] = {DIG_TO_CHAR(BUILD_HOURS / 10), DIG_TO_CHAR(BUILD_HOURS % 10), ':', DIG_TO_CHAR(BUILD_MINUTES / 10), DIG_TO_CHAR(BUILD_MINUTES % 10), ':', DIG_TO_CHAR(BUILD_SECONDS / 10), DIG_TO_CHAR(BUILD_SECONDS % 10)}; вместо static const uint32_t _h = BUILD_HOURS; static const uint32_t _m = BUILD_MINUTES; static const uint32_t _s = BUILD_SECONDS; const char __TIME_RUS_FORMAT__[] = {DIG_TO_CHAR(_h / 10), DIG_TO_CHAR(_h % 10), ':', DIG_TO_CHAR(_m / 10), DIG_TO_CHAR(_m % 10), ':', DIG_TO_CHAR(_s / 10), DIG_TO_CHAR(_s % 10)};
  8. Я полагал строку кода, а не строку Си. Строка кода, полученная в результате работы препроцессора, мне действительно не нужна. Именно так я понял jcxz. Чтобы отбросить все догадки, еще раз повторю, чего хотелось: 1. На этапе препроцессирования вычленить из константных строк __DATE__, __TIME__ полезное содержимое и переконвертировать их "налету" в другой формат ("19.04.2019 (... возможно какой-то текст или управляющие escape-последовательности) 14.00.05 (...)". Для этого я делаю соответствующие #defines BUILD_DAY, BUILD_MONTH, BUILD_YEAR. 2. Теперь я хотел также на этапе препроцессирования вот эти самые BUILD_DAY, BUILD_MONTH и BUILD_YEAR преобразовать в строку Си ("..." которая) с помощью директивы #. Очевидно, это не получилось. А нужно это было как раз для более читаемого формирования конечного результата: допустим, я бы хотел сформировать строку "1.0.5\r\nBuild date: 19.04.2019\r\nBuild time: 14:05:23\r\n". Для этого я хотел просто "слепить" несколько строк: #define REVISION "1.0.5" #define VERSION_STRING REVISION "\r\n" "Build date: " MAGIC_DATE(BUILD_DAY, BUILD_MONTH, BUILD_YEAR) "\r\n" " Build time: " MAGIC_TIME(BUILD_HOURS, BUILD_MINUTES, BUILD_SECONDS) "\r\n" где MAGIC_DATE и MAGIC_TIME как раз и есть те самые макросы, которые не захотели "разворачиваться" и тупо превращаться из строки кода в строку Си "...". Я ошибочно полагал, что BUILD_DAY, допустим, раз собирается на этапе build-time, то препроцессору заранее может быть известно о целочисленном значении этого параметра. Ошибку я свою понял и признаю. Конечная цель достигнута (как предложил уважаемый Integro), но не теми средствами, о которых я упоминал. Собрать строку побайтно, разумеется, мне приходило в голову, но я думал, что можно проще. P.S. Не думал, что возникнут какие-то недопонимания (возможно как с моей, так и с противоположных сторон). На первой странице как-то было очевиднее, что вопрос всеми понят правильно. Но для того это и Форум, чтобы обсуждать тонкости программерской кухни, ИМХО Всем спасибо!
  9. Суть jcxz правильную уловил. В случае const uint32_t _day = BUILD_DAY; const uint32_t _month = BUILD_MONTH; const uint32_t _year = BUILD_YEAR; const char __DATE_RUS_FORMAT__[]= {DIG_TO_CHAR(_day / 10), DIG_TO_CHAR(_day % 10), '.', DIG_TO_CHAR(_month / 10), DIG_TO_CHAR(_month % 10), '.', DIG_TO_CHAR(_year / 1000 % 10), DIG_TO_CHAR(_year / 100 % 10), DIG_TO_CHAR(_year / 10 % 10), DIG_TO_CHAR(_year % 10), '\00'}; во Flash будут храниться: _day (4 байта), _month (4 байта), _year (тоже 4 байта), а также строка __DATE_RUS_FORMAT__. Из всего этого набора действительно нужна только строка __DATE_RUS_FORMAT__ (пока что). Получается, что первые три промежуточные константы попросту зря расходуют Flash (то, что 12 байт это копейки, это не важно). А хотелось сразу скомпилировать так, чтобы в __DATE_RUS_FORMAT__ лежала строка с датой и временем сборки (например, "19.04.2019 13:17:30"). Стандартная __DATE__ меня не устраивает по причине требования единообразности формата даты/времени на всех компилируемых программах без надстроек среды в виде изменения формата констант __DATE__, __TIME__ и других. К тому же, хотелось уметь соединять строки в любой последовательности (то есть что-то типа #define MY_DATE_TIME "строка 1" "строка 2" "..."). Вот и все, собственно. P.S. Товарищи, давайте жить дружно
  10. Да вот в том-то и проблемаЯ первым делом сделал так, как привели Вы. Но потом, подумав, что все это считается в build-time, решил, почему-бы не возложить это на препроцессор (лишние константы во Flash ни к чему). И вот тут грабли и вылезли Но все равно спасибо за ответ!
  11. Keil ARMCC. Но это не так важно. Еще раз: собрать в переменную результаты разворачивания #defines мне удалось, о чем я указал ранее. Однако преобразовать на лету все это дело еще и в строку средствами препроцессора, как тут верно заметили, нельзя. Если все-таки кому-то кажется, что можно - попробуйте в любом компиляторе Если каким-то образом получится, выкладывайте, лично я буду очень признателен
  12. Нет, так не работает.
  13. FTP Analog Device

    Большое спасибо! Вещь нужная.
  14. А на плате есть МК? А входы АЦП не заведены на все напряжения? А жаль.