Коллеги! Можете закидать меня объедками, но не могу молчать! Хочу поделиться опытом, который, с моей точки зрения, может быть полезен всем, но по странным необъяснимым причинам как-то не известен широко.
Речь пойдет о GCC - бесплатном компиляторе для разных платформ. Для "больших" систем, наверное, мои советы будут лишними, а вот для AVR, мне кажется, очень хороши. Так же менее интересен этот опыт тем, кто любит и активно использует С++, я же, как истинный ретроград, применяю Си, и хочу поделиться тем, как можно (и нужно!) сделать работы с проектами на этом языке более комфортными.
1. Атрибуты В GCC есть возможность задавать атрибуты всем элементам программы - функциям, переменным и т.п. Например, всем известен аттрибут, спрятанный в макросе PROGMEM, который заставляет компилятор помещать константы в память программ. Но есть и другие атрибуты, которые могут заметно помочь в некоторых случаях! задается атрибут при помощи специального макроса __attribute__((А)), где А - это сам атрибут, а остальное - неотъемлемая его часть (обратите внимание на двойные скобки!). Об атрибутах вообще вы можете прочесть в документации на GCC, а далее я остановлюсь на некоторых конкретных случаях их применения.
2. Секции памяти. В GCC есть несколько типов секций памяти, задаваемых при помощи атрибута section("название_секции"). Особенно полезными, с моей точки зрения, следующие (указываю конкретное название_секции): .noinit - все переменные, помещенные в эту секцию, не будут инициализированы "по умолчанию". Как известно, все статические (и глобальные) переменные в Си автоматически инициализируются нулями, есл не указано конкретное значение. Но порой бывает необходимо, чтобы после сброса (например, по WDT) переменная сохранила свое прежнее, досбросовое значение, и эта секция позволяет получить требуемое. Если поместить в этой секции массив, то посчитав CRC этого массива после сброса, можно получить весьма неплохое СЛУЧАЙНОЕ число, по-настоящему случайное, а не псевдослучайное. Чем больше массив, тем лучше случайность. Надеюсь, вы сами понимаете, почему.
.initX, где Х - номер секции от 0 до 9. Это секция для кода. Чтобы было понятно: сразу после сброса выполняется код, помещенный в секцию .init0, потом постепенно номер секции увеличивается, и функция main выполняется в секции 10. То есть пометив атрибутом с указанием определенной секции свою функцию, вы заставите ее выполниться ДО НАЧАЛА main. Только будьте осторожны: параметры стека задаются в секции 2, а до этого стек еще не определен. Я рекомендую использовать секции поближе к main, т.е. 6...9.
Зачем это надо? О! Собственно, ради этого я и затеял эту писанину! Чуть позже расскажу.
.finiX - это "зеркально" обратные секции к только что описанным: в порядке уменьшения своего номера они выполняются ПОСЛЕ ВЫХОДА ИЗ main, то есть сначала код из секции .fini9, потом меньше и меньше, и в 0-й реализуется запрет прерываний и m1: rjmp m1. Почему-то принято считать, что в МК жизни за пределами main нет, и тем более ее нет ПОСЛЕ main, но это, как видите, не так! И, хотя применений этим секциям можно найти меньше, чем инициализирующим, все-таки можно извлечь пользу и из них.
3. Потоковый ввод-вывод. Об этом я уже писал статью, если интересуетесь - почитайте: WinAVR: консольный ввод-вывод Если кратко, то смысл в том, чтобы заставить работать функции printf и scanf так, как это и было задумано в Си - для вывода и ввода. Как-то прижился подход, когда вместо этих функций используют sprintf и sscanf, т.е. работают со строками, а вот получение этих строк приходится "длать ручками". Если же реализовать потоковый ввод-вывод, а это совсем не сложно! - то можно избавиться и от этого. Поверьте, когда вы работаете с консольным выводом (например, через USART), это просто кардинально упрощает жизнь! Кстати, совсем не лишней будет эта возможность при работе с файлами на SD-карте, что тоже многие применяют в своих проектах.
Ну, а теперь о том, какая польза может быть извлечена из вышеописанных секций. Буду вести рассказ про себя, а мой опыт каждый может попробовать примерить на себя, чтобы решить, подходит ли, полезен ли он, или нет.
Я часто делаю проекты из кучи модулей. И многие из них работают с периферией. И периферию эту надо настраивать. Как принято делать? в каждом таком модуле создают функцию init_xxx(), которую затем вызывают из main. Как делаю я? эту самую функцию init_xxx() я помещаю в секцию .init7 и она вызывается сама! А в main о периферии я вообще не забочусь - все уже подготовлено само!
Какие плюсы я получаю? Во-первых, код main становится лаконичным и красивым, в нем именно то, что и должно быть: ГЛАВНАЯ работа. Надо лампочками мигать - они мигают, надо файлы читать - они читаются... а вякая ерунда вроде портов и регистров скрыта в модулях.
Как я это делаю? Кто скачивал мои исходники, наверняка видел там файл avr_helper.h - в нем я определил ряд макросов, при помощи которых все это и делается. Вот самые интересные из них:
Код:
// вспомогательный макрос конкатенации макросов #define _CONCAT_(x,y) x ## y /// макрос конкатенации основной #define CONCAT(y,x) _CONCAT_(y,x)
/// макрос для определения функции-инициализатора (вызывается автоматически в секции .inix) #define INIT(x) static void __attribute__((naked, used, section(".init" #x))) CONCAT(_init_, __COUNTER__) (void) /// то же самое для завершающих функций #define DONE(x) static void __attribute__((naked, used, section(".fini" #x))) CONCAT(_fini_, __COUNTER__) (void) /// это если лень писать - самая ранняя инициализация #define AUTOINIT() INIT(2) /// макрос для определения компактной версии функции main() #define MAIN() int __attribute__((OS_main)) main(void) /// неинициализируемые переменные #define NOINIT __attribute__((section(".noinit")))
Как видите, я использую макросы для секций памяти, ибо писать длинные строки муторно. Но - обратите внимание! - как я делаю функции инициализации-завершения: я использую встроенные макросы GCC для генерации уникального имени функции, чтобы при необходимости в листинге их найти можно было. __COUNTER__ это макрос, который автоматически увеличивается на 1 всякий раз, когда используется. Т.е. я могу написать в коде модуля несколько INIT(7) в итоге сгенерируется несколько разноименных функций, все из которых попадут в одну секцию.
Теперь в модуле, который работает с портами ввода вывода, я просто пишу нечто подобное:
Код:
INIT(7){ DDR(PORT_IO) |= _BV(PIN_IO); }
и на этом мои переживания по поводу инициализации периферии заканчиваются. Да, конечно, в заголовочном файле модуля я определяю следующие макросы
Код:
#define PORT_IO D #define PIN_IO 2
Обратите внимание: для задания порта ввода-вывода я использую не привычное PORTD, а только букву D. Знаете зачем? Чтобы так же просто и быстро перенастраивать ВСЕ СВЯЗАННЫЕ с этой буквой регистры при помощи макросов DDR(PORT_IO), PORT(PORT_IO) и PIN(PORT_IO). По-моему, это очень удобно.
Кстати, в INIT-секциях удобно считывать их EEPROM значения конфигурационных параметров устройства. А вот в секциях DONE иной раз удобно их сохранять. Правда, для этого надо обеспечить 2 условия: 1. обязательное завершение main, т.е. выход из основного цикла 2. тот самый выход должен быть связан с нажатием кнопки "выключения". Т.е. как минимум в проекте должна быть такая кнопка. Эти условия ограничивают применимость завершающих секций, но если вы делаете проект, управление питанием в котором реализуется через кнопку, заведенную на МК, это так же поможет вам сделать красивый код сохранения настроек, сведя при этом количество записей EEPROM к минимуму.
Знатоки С++ наверняка узнали в секциях .init-.fini аналоги конструкторов и деструкторов... Кстати, именно там GCC и размещает конструкторы-деструкторы статических экземпляров классов.
Ну еще пара слов о моих подходах. Главное: при инициализации портов и регистров нужно всегда использовать битовые операции ИЛИ и И для установки битов в регистрах периферии, применение прямой записи крайне нежелательно по одной причине: вы не знаете заранее, какой инициализирующий код из нескольких будет выполнен первым, а какой следующим. Но вы знаете, что после сброса все регистры обнулены (точнее - имеют дефолтное значение). Поэтому в инициализации каждого модуля вы обязаны изменять только те биты в регистрах, которые требуются этому модулю, сохраняя остальные неизменными. Это, конечно, слегка раздувает код "лишними" операциями над всякими DDRx и т.п., но это небольшое зло, как мне кажется, с лихвой компенсируется удобством программирования.
И еще одно. Задавая разные номера для макроса INIT, вы можете управлять порядком вызова инициализации. Разумеется, что код в INIT(7) будет выполнен раньше, чем INIT(8). Просто помнить номера инициализаций в нескольких модулях сложновато. Я завел себе правило: если мне надо просто проинициализировать модуль, я использую секцию 7. Если надо гарантировать превентивность инициализации - 6. Если надо гарантировать "запоздалость" - 9. Иных вариантов стараюсь избегать.
Вот и все.
Буду рад, если кому-то открыл глаза или помог стать лучшим программистом, чем ранее.
Если хотите, у могу еще рассказать о том, как можно делать трассировку кода удобно, но накладно по ресурсам... Это полезно, если МК большой, а аппаратного отладчика нет...
_________________ если рассматривать человека снизу, покажется, что мозг у него глубоко в жопе при взгляде на многих сверху ничего не меняется...
Я часто делаю проекты из кучи модулей. И многие из них работают с периферией. И периферию эту надо настраивать. Как принято делать? в каждом таком модуле создают функцию init_xxx(), которую затем вызывают из main. Как делаю я? эту самую функцию init_xxx() я помещаю в секцию .init7 и она вызывается сама! А в main о периферии я вообще не забочусь - все уже подготовлено само!
Какие плюсы я получаю? Во-первых, код main становится лаконичным и красивым, в нем именно то, что и должно быть: ГЛАВНАЯ работа. Надо лампочками мигать - они мигают, надо файлы читать - они читаются... а вякая ерунда вроде портов и регистров скрыта в модулях.
И еще одно. Задавая разные номера для макроса INIT, вы можете управлять порядком вызова инициализации. Разумеется, что код в INIT(7) будет выполнен раньше, чем INIT(8). Просто помнить номера инициализаций в нескольких модулях сложновато. Я завел себе правило: если мне надо просто проинициализировать модуль, я использую секцию 7. Если надо гарантировать превентивность инициализации - 6. Если надо гарантировать "запоздалость" - 9. Иных вариантов стараюсь избегать.
Я правильно понимаю, что вместо того чтобы сделать как-то так:
да, именно так и выходит. более того, в модулях вместо
Код:
void init_xxx(void){ // some code }
INIT(7){ init_xxx(); }
я сразу делаю
Код:
INIT(7){ // some code }
Добавлено after 40 minutes 53 seconds:
Rtmip писал(а):
Мне интересно, продолжайте, пожалуйста
Ну, раз интересно, вот еще несколько идей, как и обещал, про трассировку.
В компиляторе GCC (в той или иной мере и в любом ином) есть средства, назначение которых кажется странным. Например, есть макрос, который имеет значение в виде текстовой строки с именем текущего файла... или с именем текущей функции... И эти возможности можно использовать для организации трассировки программы. Ведь достаточно часто при отладке "через консоль" приходится выводить сведения о состоянии тех или иных переменных, так почему не автоматизировать и упростить этот процесс?
Вот что я сделал. Сначала я организовал с помощью вышеописанных возможностей модуль с названием debug.c, при подключении к проекту которого сразу инициализируется файловый вывод в USART на подходящей скорости (обычно я использую 57600 бод). В этом заголовочнике debug.h этого модуля все функции определены при помощи директив условной компиляции
То есть если проект собирается в релизной версии (макрос __DEBUG__ не определен), то все функции заменяются заглушками-пустышками, что гарантирует их исключение из прошивки, т.к. оптимизатор выбросит неиспользуемые функции из кода (если вы еще не знаете, как это сделать - скажу и об этом, хотя это должен знать каждый GCC-шник). Определить директиву __DEBUG__ можно при помощи параметра командной строки -D компилятора, т.е. так: -D__DEBUG__, при этом при компиляции любого файла этот макрос будет "виден".
Затем я определил ряд макросов для трассировки одной или нескольких переменных. Привожу пример для одной переменной типа int, по аналогии можно сделать, сколько надо
Теперь если этот макрос вставить в своем файле my_file.c в любом месте, например так (показываю номера строк):
Код:
013: void func(void){ 014: int test_var = 12; 015: log_i(test_var); 016: // какой-то код 017:}
то после компиляции и прошивки в микроконтроллер при входе в эту функцию в USART будет выведена следующая строка:
LOG> ..\my_file.c\func:0015 test_var=12
Как видите, этот текст содержит необходимую информацию, чтобы по нему определить все, что необходимо при отладке: - имя файла - имя функции - номер строки - имя выводимой переменной - значение этой переменной
Какие возможности при этом использованы? 1. Предопределенный макрос с именем файла __FILE__. Этот макрос содержит "полный путь" к файлу исходного текста модуля, как его видит компилятор, т.е. если там иерархия подпапок, они тоже будут в этом макросе 2. Конкатенация строковых констант. Запись "\nLOG> " __FILE__ слепит из двух строк одну, в моем примере получится такая "\nLOG> ..\my_file.c" 3. Предопределенный макрос __FUNCTION__, содержащий указатель на строку символов с именем функции. Обратите внимание, что это не константа типа строка, а переменная, потому для ее вывода надо указать формат %s 4. Предопределенный макрос __LINE__, равный номеру строки исходного текста, т.к. это число, для его вывода нужен формат %04d 5. Действие директивы # препроцессора, которая превращает один идентификатор правее себя в его строковый эквивалент. То есть передавая в макрос идентификатор переменной мы получим строковую константу с этим идентификатором.
Вот так, просто и незатейливо, можно организовать трассировку всего, чего угодно. Разумеется, все эти фичи можно использовать и для других целей.
Но за это придется заплатить: - повышенным расходом flash - prinft отъест у вас примерно 1,5 килобайта, плюс все "текстовые" сообщения с именами файлов, функций, номерами строк и т.п., которые благодаря PSTR тоже попадут во flash - повышенным расходом ОЗУ
Однако, когда я разбирался с библиотекой Chan-а для SD-карты без аппаратного отладчика, мне пришлось идти на эти жертвы... Потому как протеус с SD-картами далеко не всегда помогает.
_________________ если рассматривать человека снизу, покажется, что мозг у него глубоко в жопе при взгляде на многих сверху ничего не меняется...
Обязательным условием долгой и стабильной работы Li-FePO4-аккумуляторов, в том числе и производства EVE Energy, является применение специализированных BMS-микросхем. Литий-железофосфатные АКБ отличаются такими характеристиками, как высокая многократность циклов заряда-разряда, безопасность, возможность быстрой зарядки, устойчивость к буферному режиму работы и приемлемая стоимость. Но для этих АКБ очень важен контроль процесса заряда и разряда для избегания воздействия внешнего зарядного напряжения после достижения 100% заряда. Инженеры КОМПЭЛ подготовили список таких решений от разных производителей.
Так а преимущества то в чем? Хотел разгрузить main, я тебе привел пример где в main одна строка. Но дополнительно я в одном месте вижу какие функции инициализации вызываются, в каком порядке, причем этот порядок легко менять, и могу передавать в них параметры.
Компания EVE выпустила новый аккумулятор серии PLM, сочетающий в себе высокую безопасность, длительный срок службы, широкий температурный диапазон и высокую токоотдачу даже при отрицательной температуре.
Эти аккумуляторы поддерживают заряд при температуре от -40/-20°С (сниженным значением тока), безопасны (не воспламеняются и не взрываются) при механическом повреждении (протыкание и сдавливание), устойчивы к вибрации. Они могут применяться как для автотранспорта (трекеры, маячки, сигнализация), так и для промышленных устройств мониторинга, IoT-устройств.
Все преимущества я описал. Например, вы сделали модуль для работы с выводом "в консоль", т.е. USART. Какие параметры вы собрались передавать в функцию инициализации? Скорость? И собираетесь ее менять по ходу исполнения программы? Не лучше ли задать константу скорости в заголовочнике того модуля, и забыть вообще про него? Зачем вам нужен порядок инициализации, если к моменту начала main уже все инициализирвоано - хоть USART, хоть ЖКИ, хоть черт в ступе?
У меня вот есть давно сделанный модуль с названием usart_io.c с заголовочником usart_io.h. В заголовочнике прописана скорость 57600, и за последние 4 года я ни разу ее не менял. если мне надо выводить в консоль, я просто кидаю эти файлы в папку проекта, и где захочется, пишу printf - все, усилия кончились на этом. Разве что попадется МК, в котором более 1 USART - тогда приходится имена регистров подправлять, да и то из-за моей же лени - ведь можно при помощи условной компиляции предусмотреть и такой вариант...
аналогично я поступил и с ЖКИ.
_________________ если рассматривать человека снизу, покажется, что мозг у него глубоко в жопе при взгляде на многих сверху ничего не меняется...
Все преимущества я описал. Например, вы сделали модуль для работы с выводом "в консоль", т.е. USART. Какие параметры вы собрались передавать в функцию инициализации? Скорость? И собираетесь ее менять по ходу исполнения программы? Не лучше ли задать константу скорости в заголовочнике того модуля, и забыть вообще про него? Зачем вам нужен порядок инициализации, если к моменту начала main уже все инициализирвоано - хоть USART, хоть ЖКИ, хоть черт в ступе?
У меня вот есть давно сделанный модуль с названием usart_io.c с заголовочником usart_io.h. В заголовочнике прописана скорость 57600, и за последние 4 года я ни разу ее не менял. если мне надо выводить в консоль, я просто кидаю эти файлы в папку проекта, и где захочется, пишу printf - все, усилия кончились на этом. Разве что попадется МК, в котором более 1 USART - тогда приходится имена регистров подправлять, да и то из-за моей же лени - ведь можно при помощи условной компиляции предусмотреть и такой вариант...
Во-первых, порядок инициализации может иметь значение, наверно именно потому у тебя и можно менять номера секций чтобы гарантировать превентивность или "запоздалость"... Правда там даже чтобы понять в каком порядке происходит инициализация нужно взять ручку и пройтись по всем модулям Во-вторых, мой модуль USARTа принимал бы параметр скорости как минимум. По факту, т.к. я пишу на C++, он принимает намного больше, фактически все что нужно чтобы можно было обойтись без дополнительных определений в других файлах. Скорость USART тоже приходится менять, иногда периодически по ходу работы(DS18B20), бывает изначально даже не известно какая она будет. В идеале любой модуль должен переноситься между проектами без изменений, в чистом С этого добиться сложнее, но по крайней мере можно к этому стремиться.
Добавлено after 6 minutes 57 seconds:
ARV писал(а):
Вот так, просто и незатейливо, можно организовать трассировку всего, чего угодно. Разумеется, все эти фичи можно использовать и для других целей.
Это как бы повсеместно распространенный подход, обычно такой код пихают в assert. На AVR наверно так реже делают, место экономят
Во-первых, порядок инициализации может иметь значение, наверно именно потому у тебя и можно менять номера секций чтобы гарантировать превентивность или "запоздалость"...
во-первых, номера секций менять можно не у меня, а у GCC я лишь не лишаю никого этой возможности своими макросами. во-вторых, инициализация и управление по ходу пьесы - это разные вещи. инициализация задает состояние по умолчанию, для которого не требуются параметры. если хочется, по умолчанию можно настроить сразу так, чтобы работало, как хочется. а потом, если хочется, можно перенастраивать. в-третьих, если вы пишите на С++, это еще не доказательство, что вы выбрали самый лучший подход. кстати, у вас в С++ конструкторы статических экземпляров классов GCC сам помещает в секцию .init6 по-моему (могу ошибаться, т.к. не помню наизусть) - это вас не смущает? у меня то же самое, но для Си (если считать модуль эквивалентом экземпляра класса)
голову отключать я не советовал, она всегда пригодится.
Добавлено after 3 minutes 29 seconds:
Reflector писал(а):
В идеале любой модуль должен переноситься между проектами без изменений, в чистом С этого добиться сложнее, но по крайней мере можно к этому стремиться
переносимость модуля не от Си зависит, а от связи модуля с аппаратной частью. для AVR редко когда можно добиться полной абстракции модуля от аппаратуры, потому и переносимость модулей между проектами не высокая.
_________________ если рассматривать человека снизу, покажется, что мозг у него глубоко в жопе при взгляде на многих сверху ничего не меняется...
во-вторых, инициализация и управление по ходу пьесы - это разные вещи. инициализация задает состояние по умолчанию, для которого не требуются параметры. если хочется, по умолчанию можно настроить сразу так, чтобы работало, как хочется. а потом, если хочется, можно перенастраивать.
Любая нормальная инициализация может быть с параметрами или без, естественно это касается тех случаев, когда ее по крайней мере можно вызвать
Цитата:
в-третьих, если вы пишите на С++, это еще не доказательство, что вы выбрали самый лучший подход. кстати, у вас в С++ конструкторы статических экземпляров классов GCC сам помещает в секцию .init6 по-моему (могу ошибаться, т.к. не помню наизусть) - это вас не смущает? у меня то же самое, но для Си (если считать модуль эквивалентом экземпляра класса)
Я пишу для STM32, на C++, классы в большинстве своем полностью статические и шаблонные, у них всегда есть пустой конструктор, метод init и конструктор вызывающий этот init. Допустим было бы как ты хочешь, создаем глобальный экземпляр класса USART, он помещает вызов конструктора в секцию .init6, тот вызывает setBaudRate в который передается 57600. А знаешь что внутри setBaudRate? Там считывается текущая скорость одной из APB шин, на которых и висят USARTы, чтобы правильно проинитить регистр BRR. И какая на тот момент скорость APB? Ты пишешь для AVR, так что я распишу подробнее... Чтобы получить правильную частоту к этому моменту нужно определится с тем используется ли встроенный генератор или кварц, нужно ли PLL, если нужно, то дополнительно задается 3 делителя и наконец есть еще делители для каждой шины, в том числе для нужной нам APB, их тоже нужно задать. В итоге придется в модуль настройки тактирования добавить еще десяток дефайнов и настраивать все через них, причем тут важен порядок, это все должно вызываться до других модулей зависимых от скорости генератора... Естественно я так не делаю и вызываю инициализацию усарта когда уже установлена рабочая частота генератора, перед которой была задана нужная латентность флеша зависимая от этой частоты, опять же не через дефайны в модуле настройки латентности флеша или где они там должны в этом случае находиться...
Reflector писал(а):
переносимость модуля не от Си зависит, а от связи модуля с аппаратной частью. для AVR редко когда можно добиться полной абстракции модуля от аппаратуры, потому и переносимость модулей между проектами не высокая.
Смотри, берем какой-нибудь бюджетный STM32 за 2$, у которого 5 USARTов и дофига флеша. На C++ я могу проинициализировать все пять USART пятью строками, причем в качестве параметра даже пара пинов передается, т.к. они могут быть разными, или размер буферов, если используется буферизация. Правда в последнем случае придется вызвать еще одну функцию на каждый USART в обработчике соответствующего прерывания. Как это можно нормально разрулить на С, да еще и с применением твоей методики я даже не представляю
менее интересен этот опыт тем, кто любит и активно использует С++
Нет, это писалось в первом посте для всех, а мне лично ты говорил, что конструкторы в С++ ведут себя подобно твоим самовызывающимся функциям. Но у меня нет ни одного что-то делающего конструктора вызываемого до main, потому что применительно к мк такой подход плох даже для С++, где это реализовать проще.
Но, честно говоря, подобный подход к инициализации периферии кажется мне несколько перегруженным. Лично я при написании программ стараюсь по возможности исповедовать "принцип наименьшего удивления", чтобы облегчить жизнь тем, кто будет читать мой код.
_________________ Разница между теорией и практикой на практике гораздо больше, чем в теории.
Когда-то я ради прикола передавал аргументы в main(), используя похожие методы.
да, это было именно ради прикола, практической ценности не имеет.
YS писал(а):
Лично я при написании программ стараюсь по возможности исповедовать "принцип наименьшего удивления"
так и я тоже стремлюсь к этому... и при вышеописанном методе руководствовался принципом "если модуль не работает - ищи проблему в модуле". то есть если что-то не заработало - не надо ковыряться по всем файлам, выискивая, где оно инициализировалось, где стартовало, где использовалось - открывай файл модуля и думай...
в сущности, мой подход почти всегда именно так и работает. но в некоторых случаях может и усложнить жизнь - признаю. хотя чаще (с моей субъективной точки зрения) именно упрощает.
_________________ если рассматривать человека снизу, покажется, что мозг у него глубоко в жопе при взгляде на многих сверху ничего не меняется...
Заголовок сообщения: Re: Использование особенностей GCC для повышения комфорта
Добавлено: Пн июн 19, 2017 18:59:03
Друг Кота
Карма: 38
Рейтинг сообщений: 618
Зарегистрирован: Пн апр 06, 2015 11:01:53 Сообщений: 3092 Откуда: москва, уфа
Рейтинг сообщения:0
у такого повышения комфорта есть некоторая проблема - "особенности компилятора" зачастую существуют только в нем. Тот же IAR, к примеру, не обязан быть в курсе (и он таки не), что там за __attribute__ такие. Можно, конечно, сказать "не, я всегда использовал и буду использовать только один компилятор", но сие в общем не есть хороший подход.
знаете, мне кажется, бытующее мнение о том, что легко делать кроссплатформенные (хотя правильнее - кросскомпилятоные) программы на Си - это завуалированный обман.
кроссплатформенные программы представляют собой дичайшее нагромождение #ifdef и других препроцессорных штучек, и в итоге все равно без бубна не собираются на разных платформах и компиляторах. как бы разработчик не старался все сделать "просто", при смене платформы или компилятора нужно долго и внимательно читать инструкцию по сборке проекта, править конфиги и т.п. - никакой простоты и автоматичности
то есть фактически нет возможности сделать исходник под любой компилятор и платформу. так к чему самообманываься? ну не собирается IAR-ом - да и попутного ему ветра! GCC бесплатно скачивается и за 5 минут устанавливается, после чего все можно собрать и удалить GCC снова, если уж так приспичит.
_________________ если рассматривать человека снизу, покажется, что мозг у него глубоко в жопе при взгляде на многих сверху ничего не меняется...
мне кажется, бытующее мнение о том, что легко делать кроссплатформенные (хотя правильнее - кросскомпилятоные) программы на Си - это завуалированный обман
мнение о том, что их делать легко - неверно. Но возможно.
кроссплатформенные программы представляют собой дичайшее нагромождение #ifdef и других препроцессорных штучек, и в итоге все равно без бубна не собираются на разных платформах и компиляторах. как бы разработчик не старался все сделать "просто", при смене платформы или компилятора нужно долго и внимательно читать инструкцию по сборке проекта, править конфиги и т.п. - никакой простоты и автоматичности
что-то мне подсказывает, что вы их мало видели Большинство опенсорца обычно без проблем собирается на разнообразных *nix-овых платформах GCC, clang-ом и интеловским компилятором; на винде - MSVC, тут да, иногда с бубном.
Ну, я бы не стал так драматизировать. Да, конечно, просто так сменить компилятор в большинстве случаев нельзя, но если автор кода позаботился о кросс-платформенности, то перенести код в другую систему сборки можно будет с минимальными трудозатратами.
Потому, честно сказать, мой подход - использовать нестандартные возможности по минимуму и только там, где без них правда никак. Например, при смене компилятора неизбежно придется менять объявления прерываний; но это именно тот случай, когда вариантов особо нет.
Что же касается продвинутого использования препроцессора, я как раз не вижу в этом ничего особенно плохого для "боевого" кода. В этом случае и получается истинная кросс-платформенность: перенес исходники, написал в единственном заголовочнике что-то в духе "#define IAR_MODE" (а можно и не писать, многие компиляторы сами определяют уникальные для себя символы) - и все, модуль компилируется. При этом в код можно совсем не заглядывать.
Иное дело, когда модификацию кода препроцессором слишком активно используют в проектах, предназначенных для обучения. Самый жуткий пример - демонстрационные проекты для плат Discovery от ST. Я, конечно, понимаю, что так программистам удобнее - не надо делать много версий исходников, но когда стоит цель разобраться с оборудованием это, конечно, сильно мешает.
Ну и, конечно, лучше всего без нужды не использовать не только расширения, но и те элементы, для которых в стандарте не прописано четкого поведения, или с которыми по факту обращаются вольно, или которые способны создать машинно-зависимые проблемы (в частности, проблемы с выравниванием). Например, битовые поля или union'ы.
_________________ Разница между теорией и практикой на практике гораздо больше, чем в теории.
При всем уважении, YS, останусь при своем мнении. Можно пытаться решить возникающие проблемы, а можно не создавать их. И второй вариант мне ближе. Берется бесплатный компилятор и принимается решение, что никаких вариантов более быть не может. С этого момента у меня нет никаких проблем, даже с совестью по поводу ломаного IARа.
Ну а все желающие могут продолжать бороться с придуманными проблемами - кто ж запретит? Мы же не на производстве, где кто-то установил правила и требует их соблюдать! Мы - любители, мы правила сами для себя придумываем. Я для себя придумал и поделился. и всем советую проблем себе не придумывать.
_________________ если рассматривать человека снизу, покажется, что мозг у него глубоко в жопе при взгляде на многих сверху ничего не меняется...
Господи спаси, я не настаиваю на том, чтобы кого-то переубедить.
Просто для меня это имеет чисто практическое значение. У нас в лаборатории, например, живет зоопарк компиляторов по причине разнообразия используемых компонентов. Так что хочется или нет, но о кросс-платформенности думать приходится, чтобы при необходимости использовать код коллег и делиться с ними своим кодом.
Еще я люблю отлаживать аппаратно-независимые модули на x86, а потом переносить в железо. Вот, например, в текущем проекте мне пришлось писать парсер текстовых команд. Разумеется, отлаживать такое проще на ПК, с обширным тестовым выводом. Кстати, такая практика приучила меня всегда указывать тип констант явно, потому что невинно выглядящий сдвиг типа 1<<8 на x86, x86-64, ARM, MSP430, AVR и STM8 может вести себя по разному. А вот 1UL<<8 работает гораздо более предсказуемо.
Ну и GCC хорош, но не везде. Например, для него так и не сделали кодогенератор для STM8, что меня очень огорчает. Приходится использовать Cosmic, потому что желание использовать SDCC у меня отпало когда я дочитал документацию по нему примерно до половины. Ну а IAR стоит безумных денег, и лицензионная политика у него просто параноидальная.
_________________ Разница между теорией и практикой на практике гораздо больше, чем в теории.
Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 36
Вы не можете начинать темы Вы не можете отвечать на сообщения Вы не можете редактировать свои сообщения Вы не можете удалять свои сообщения Вы не можете добавлять вложения