Перейти к содержанию
    

SII

Свой
  • Постов

    898
  • Зарегистрирован

  • Посещение

Весь контент SII


  1. Насколько помню, в STM32H745, чтоб работать на 480 МГц, надо должным образом подавать питание на МК, из-за чего на СТМовской плате (DISCO которая) требовалось перепаять пару перемычек.
  2. А ещё лучше -- разобраться, как это делается в реальном железе, на логических элементах, триггерах и прочая. Если подходить к HDL как к языку программирования, хорошего результата не будет (даже если что-то и будет работать).
  3. STM32H7, SPI, прием с DMA

    А ещё при использовании DMA возможны проблемы из-за того, что у Cortex-M7 есть кэш -- соответственно, перед запуском передачи по DMA надо убедиться, что данные физически лежат в памяти (а не только в кэше), ну а после завершения приёма -- очистить кэш, чтоб добраться до данных в памяти.
  4. Пакеты в keil

    Или собственно ARMовский пакет, где только ядра и указаны. Но обычно лучше пакет под свой МК -- хотя б из-за возможности простого просмотра регистров периферии в процессе отладки.
  5. Ну, судя по вопросу, нужно указать компилятору, в каких секциях что размещать -- он и сгенерит соответствующий код и управляющую информацию. Затем можно, например, все объектники собрать компоновщиком в один объектник (кейловский компоновщик такое умеет -- если память не изменяет, partial linking это обзывается), который скармливать при сборке полного приложения у заказчика. Как секции указывать, зависит от компилятора, так как стандарт это не регламентирует. В ARM Clang кейловском можно, в GCC можно (насколько помню), в мелкомягком компиляторе можно -- так что и в ИАРовском наверняка сие поддерживается.
  6. Ошибка в ArmClang?

    А почему тогда в delete не выбрасывает? Действия-то принципиально одинаковые выполняются...
  7. Ошибка в ArmClang?

    Проверил union. Не работает. Заодно проверил, достаточно ли объявить как volatile только First, но не Last. Тоже недостаточно: ER_IROM1:0000202A STRH R7, [R0,#8] ER_IROM1:0000202C STR R5, [R0,#0xC] ER_IROM1:0000202E STRB R2, [R0,#0x10] ER_IROM1:00002030 STRD.W R2, R6, [R0] ER_IROM1:00002034 LDR R1, [R4,#4] ER_IROM1:00002036 LDR R3, [R4,#8] ER_IROM1:00002038 CMP R1, R0 ER_IROM1:0000203A STR R0, [R3] ER_IROM1:0000203C ADD.W R3, R8, #1 ER_IROM1:00002040 STR R0, [R4,#8] ER_IROM1:00002042 STRB.W R3, [R4,#0x40] ER_IROM1:00002046 BNE loc_20A2 Загрузка First и Last выполняется двумя отдельными командами (2034 и 2036), однако выборка First опять произошла до записи по Last. Фактически -- да. First всегда указывает на первый элемент очереди (FIFO_LNK) или содержит нуль, если очередь пуста. А вот Last указывает либо на последний элемент очереди (FIFO_LNK), либо на поле First самого заголовка (или, если угодно, на сам заголовок -- FIFO_HDR). Эм... Почему? Выборка из Last идёт по адресу 2080 (самая первая LDR), запись по Last -- в 208E, а выборка First -- в 2090. В 2092 идёт запись в Last. Похоже на то. В общем, не глюк компилятора, а оптимизация. Правда, остаётся непонятным, почему именно он выкидывал кусок кода в моём самом первом сообщении этой темы (в операции new): вроде б не должен столь своевольно обращаться с кодом, тем более, что в delete он уже не умничал.
  8. Ошибка в ArmClang?

    Эта опция в данном случае помогла. С ней такой код нагенерило: ER_IROM1:00002080 LDR R2, [R4,#8] ER_IROM1:00002082 MOVS R1, #0 ER_IROM1:00002084 STRH R7, [R0,#8] ER_IROM1:00002086 STR R6, [R0,#0xC] ER_IROM1:00002088 STRB R1, [R0,#0x10] ER_IROM1:0000208A STR R1, [R0] ER_IROM1:0000208C STR R5, [R0,#4] ER_IROM1:0000208E STR R0, [R2] ER_IROM1:00002090 LDR R2, [R4,#4] ER_IROM1:00002092 STR R0, [R4,#8] ER_IROM1:00002094 CMP R2, R0 Первая LDR -- это загрузка указателя Last, а последняя LDR -- это загрузка First; запись по адресу в Last, как видим, происходит непосредственно перед загрузкой First, поэтому всё будет корректно. Ну а без этой опции код такой: ER_IROM1:00002006 LDRD.W R3, R1, [R4,#4] ER_IROM1:0000200A MOVS R2, #0 ER_IROM1:0000200C STRB R2, [R0,#0x10] ER_IROM1:0000200E STRD.W R2, R8, [R0] ER_IROM1:00002012 STR R0, [R1] ER_IROM1:00002014 ADDS R1, R6, #1 ER_IROM1:00002016 CMP R3, R0 ER_IROM1:00002018 STRH R5, [R0,#8] ER_IROM1:0000201A STR R7, [R0,#0xC] ER_IROM1:0000201C STR R0, [R4,#8] ER_IROM1:0000201E STRB.W R1, [R4,#0x40] ER_IROM1:00002022 BNE loc_207C Переупорядочило, как видим, куда агрессивней -- и некорректно для этого случая. Ну а с volatile (и без опции) код такой: ER_IROM1:00002022 MOVS R2, #0 ER_IROM1:00002024 STR R7, [R0,#4] ER_IROM1:00002026 STRH R6, [R0,#8] ER_IROM1:00002028 STR.W R8, [R0,#0xC] ER_IROM1:0000202C STRB R2, [R0,#0x10] ER_IROM1:0000202E STR R2, [R0] ER_IROM1:00002030 LDR R1, [R4,#8] ER_IROM1:00002032 STR R0, [R1] ER_IROM1:00002034 ADDS R1, R5, #1 ER_IROM1:00002036 STR R0, [R4,#8] ER_IROM1:00002038 STRB.W R1, [R4,#0x40] ER_IROM1:0000203C LDR R1, [R4,#4] ER_IROM1:0000203E CMP R1, R0 ER_IROM1:00002040 BNE loc_209C А вот эта статейка навела меня на мысль, как сие исправить и без -fno-strict-aliasing, и без volatile: использовать объединение для поля First (одно поле -- указатель на сам заголовок, другое -- на первый элемент очереди). На досуге проверю.
  9. Ошибка в ArmClang?

    О, спасибо. Похоже, то, что нужно. Жаль, конечно, что это не стандартное средство языка, а расширение компилятора, но жрём, что дают 🙂 ADD. Правда, ссылка -- не для ARM Clang, а для старого компилятора 5-й версии, но в ARM Clang тоже есть. ADD2. Увы, не помогло. Похоже, эти вещи срабатывают лишь для операций, которые, с точки зрения компилятора, имеют побочные эффекты, и не препятствуют ему производить предзагрузку из ячейки, явного обращения на запись к которой нет. Так что только volatile.
  10. Ошибка в ArmClang?

    Он чисто сишный, вроде как, а в це++ не поддерживается. А в данном случае она inline -- для эффективности.
  11. Ошибка в ArmClang?

    Да решил я уже проблему, решил. И не жалуюсь я, а просто констатирую, что без костылей и/или потери эффективности в современной версии языка, похоже, не обойтись.
  12. Ошибка в ArmClang?

    Ну, во-первых, он не решает данную проблему, как понимаете. А во-вторых, он специфичен для конкретного компилятора, что не шибко хорошо. Ну, в моём представлении, барьер памяти влияет на работу процессора, а не компилятора (применительно к ARM -- команды DMB, DSB, ISB). А мне нужно именно на компилятор повлиять, чтоб не переносил код, куда ему вздумается. Или речь о чём-то другом?
  13. Ошибка в ArmClang?

    Стандартный? Или конкретного компилятора? В числе стандартных я не находил. Ну, выше я указывал проблему с моей функцией Append, добавляющей новый элемент в хвост очереди: inline void FIFO_HDR::Append(FIFO_LNK *Entry) { Entry->Next = nullptr; Last->Next = Entry; Last = Entry; } Когда очередь пуста, поле First заголовка очереди содержит нуль, а поле Last -- адрес поля First. Поле Next всегда является самым первым полем элемента очереди, поэтому такой "трюк" благополучно работает. Собственно, это очень распространённый способ организации очереди, который я встречал миллион раз (в частности, в Винде, но далеко не только в ней). Проблема возникла из-за того, что компилятор не способен увидеть, что вызов Append может поменять поле First заголовка очереди (что происходит, когда перед вызовом очередь пуста), он видит изменение только Last. В моём случае он выполнил предвыборку полей First и Last одной командой LDRD, затем сгенерировал вызов Append, а потом воспользовался предвыбранным, а не обновлённым значением поля First -- что дало неверный результат. Если бы была возможность атрибутировать эту функцию как имеющую побочные эффекты, запрещающие перемещение кода мимо неё, проблема бы не возникала. Конечно, это в какой-то мере ограничило бы оптимизатор, но не слишком сильно, думается. Запросто. Це++ необъятен.
  14. Ошибка в ArmClang?

    А ещё это -- загромождение текста лишними объявлениями. Мне не нравится то, что, кроме volatile, нет других атрибутов для переменных, которые могли бы указать, в частности, что данную переменную нельзя выкинуть из программы по желанию компилятора. Если бы был такой атрибут, его пришлось бы указать ровно один раз -- в объявлении самой переменной. Нет и атрибутов для функций, чтобы можно было, скажем, указать, что функция может вызывать побочные эффекты, поэтому нельзя перемещать код до и после вызова этой функции. Ну и т.д. и т.п. Да, бывают случаи, когда действительно не обойтись. Однако мне не нравится, что нужно постоянно применять костыли просто из-за того, что в языке отсутствуют средства, которые позволили бы объяснить компилятору, что можно делать, а что нельзя. Как человек выше указал, существуют же барьеры памяти -- так почему нельзя указать, что некая функция является барьером для оптимизации?
  15. Ошибка в ArmClang?

    Это уже костыль. Прямым решением был бы атрибут, объясняющий компилятору правила обращения с переменной.
  16. Ошибка в ArmClang?

    То, в чём была проблема изначально, я выложил, подсказки получил -- мне этого достаточно. Дальше в какой-то мере действительно монолог: описываю дальнейшие проблемы и указываю, как их решил. Я прекрасно знаю, что такое volatile, и использую его, когда надо. А вот что компилятор очень вольно обращается с порядком операций -- это таки да, было для меня новостью. Эффективность volatile действительно в ряде случаев убивает. Чтоб не было проблем с телепатией, слегка разжую. struct FIFO_HDR { // Адрес поля связи первого элемента в очереди или нуль. FIFO_LNK *First; // Адрес поля связи последнего элемента в очереди. Если очередь пуста, это // поле содержит адрес поля First. FIFO_LNK *Last; } Таким было моё первоначальное определение заголовка очереди, с которым компилятор позволил себе предзагрузку значения поля First, что в дальнейшем привело к неправильной работе (он сравнивал старое значение поля First, которое к тому времени изменилось). struct FIFO_HDR { FIFO_LNK* volatile First; FIFO_LNK* volatile Last; } Таким стало новое определение. Теперь компилятор никакими предзагрузками не занимается, и код работает правильно. Однако, если в программе будет нечто вроде такого: Queue.First->A = .... Queue.First->B = .... (обращения к нескольким полям структуры, на которую указывает Queue.First), компилятор уже не сможет соптимизировать этот код и загрузить указатель из First только один раз, он будет вынужден загружать First для каждого обращения к тому или иному полю, что эффективность и убивает. Так что, извините, Вы не правы, утверждая, что лишних команд не будет. Можно возразить: мол, ты можешь загрузить значение из First во временную переменную и использовать его оттуда. Могу, конечно. Но это -- раздувание текста программы и лишняя работа для программиста на, по сути, пустом месте. Правильным было бы дать программисту средства запретить компилятору слишком агрессивную оптимизацию. Например, если бы я мог снабдить мою функцию Append (которая может модифицировать поле First неочевидным для компилятора способом) неким атрибутом, запрещающим компилятору перемещать обращения к памяти до и после её вызова, проблемы бы не возникало и при этом компилятор мог бы свободно оптимизировать код до и после вызова функции. Если говорить про влияние на локальные объекты -- в общем, согласен с Вами (за исключением того, что надо бы программисту дать в руки стандартные средства явно указывать компилятору пределы возможной оптимизации, а не заставлять обходить её с помощью временных переменных и т.п. вещей, усложняющих восприятие программы). А вот если говорить про глобальные объекты -- нет, не согласен. Если идёт присваивание глобальной переменной, я считаю правильным, чтобы компилятор всегда его выполнял, даже если, как ему кажется, она нигде не используется -- на то она и глобальная. (Ну а не злоупотреблять глобальными переменными -- это уже забота программиста, да). volatile, как я уже показал выше, не является полноценным выходом, так как препятствует любой оптимизации. Вот был бы атрибут, говорящий компилятору, что эта переменная используется, и он не должен выкидывать обращения к ней, но в остальном может оптимизировать обращения к ней обычным образом, а не трактовать как volatile... В данном случае не соглашусь. Данный регистр контроллера никогда не меняется аппаратурой, т.е. его значение абсолютно устойчиво; соответственно, если программа пишет в него что-то, а затем хочет использовать записанное значение, технически совершенно корректным и наиболее эффективным было бы использовать значение, уже находящееся в одном из регистров процессора, а не загружать его каждый раз из регистра контроллера -- а вот в случае с volatile загрузка действительно обязательна. В данном конкретном случае не "любое обращение" должно бы компилироваться в обращение к регистру, а только запись в него; чтение можно было бы опустить, если уже есть копия значения, -- но объяснить это компилятору невозможно.
  17. Ошибка в ArmClang?

    Судя по всему, компилятор решил, что, раз видимого использования поля нет, то запись в него можно вообще выкинуть -- причём для этого ему нужно было проанализировать код всей программы, а не одного только файла (блоки регистров -- глобальные переменные; соответственно, обращения к их полям могут быть где угодно). Я так и не понял, разрешено ли такое стандартом. volatile исправляет ситуацию, но потенциально ведёт к потере эффективности (в данном конкретном случае -- нет, не ведёт). Вообще, похоже, разработчики стандарта разрешили крайне агрессивную оптимизацию, но не предложили никаких средств для того, чтоб её ограничивать (кроме древнего volatile, нередко убивающего эффективность, и std::atomic, которые тоже вполне могут быть абсолютно неэффективными, да и предназначены, вообще говоря, для другого). Например: IORP *P = new (std::nothrow) IORP; if ( P == nullptr ) { return false; } P->Buffer = const_cast<pvoid>(Buffer); P->Size = Size; P->CB = CB; P->Ignore_Errors = false; Tx_Queue.Append(&P->Link); ++Tx_Req_Count; if ( Tx_Queue.First == &P->Link ) { Init_Transmit(); } При включённой оптимизации компилятор одной командой LDRD грузил оба поля структуры Tx_Queue (First и Last; если очередь пуста, Last указывает на First -- типичное использование подобных очередей, например, в недрах Винды) сразу после выделения памяти, а не в Tx_Queue.Append, где осуществляется первое обращение к Tx_Queue. Позднее, в последнем if, он сравнивал старое (предварительно загруженное) значение поля First, а оно к тому времени может быть изменено в Append (только не прямо, а через указатель -- если очередь была пуста; компилятор, понятно, не видит этой возможности изменения). Никаких стандартных средств сказать компилятору, что Append должна служить "водоразделом", нет, так что приходится First и Last объявлять volatile, хотя в реальности они не меняются произвольным образом и зачастую действительно можно загрузить значение и использовать его несколько раз, что будет запрещено при использовании volatile.
  18. Ошибка в ArmClang?

    Вообще, эта проблема оказалась не единственной. В другом месте у меня простейшее присваивание (внутри функции, являющейся методом класса): HW::USB.DEV.DESCADD = EP_Desc; EP_Desc -- поле этого класса, содержит указатель на структуру типа HW::USBD_BUF_DESC (а точней, на массив таких структур -- это описатели конечных точек контроллера USB). Память под сей массив была выделена ранее и указатель на неё помещён в это поле, ну а в данном методе я переписываю указатель из поля класса в регистр контроллера USB в процессе включения последнего (HW::USB -- "переменная", на самом деле являющаяся блоком регистров контроллера USB). Так вот, это присваивание компилятором на любой оптимизации, начиная с -O1, просто выбрасывается, хотя весь остальной код функции генерируется правильно. Чтоб оно было, требуется описать поле HW::USB.DEV.DESCADD не как volatile USBD_BUF_DESC *, а как volatile USBD_BUF_DESC * volatile (т.е. волатильными являются не только описатели конечных точек, но и хранящий их адрес регистр, что не соответствует действительности). В общем, очень странное поведение...
  19. Ошибка в ArmClang?

    Да, volatile помог, хотя сгенерированный код, конечно, стал куда менее эффективным: постоянные загрузки из памяти: ER_IROM1:00005FA8 ; pvoid operator new(size_t Size, const std::nothrow_t *) ER_IROM1:00005FA8 EXPORT _ZnwjRKSt9nothrow_t ER_IROM1:00005FA8 _ZnwjRKSt9nothrow_t ; CODE XREF: UART_ATSAMx5::Receive(void *,ushort,void (*)(void const*,ushort,bool,bool,bool,bool,bool),bool)+2C↑p ER_IROM1:00005FA8 ; UART_ATSAMx5::Transmit(void const*,ushort,void (*)(void const*,ushort,bool,bool,bool,bool,bool))+2C↑p ... ER_IROM1:00005FA8 ER_IROM1:00005FA8 var_C = -0xC ER_IROM1:00005FA8 Ptr = 4 ER_IROM1:00005FA8 ER_IROM1:00005FA8 PUSH {R7,LR} ER_IROM1:00005FAA SUB SP, SP, #8 ER_IROM1:00005FAC BL malloc ER_IROM1:00005FB0 STR R0, [SP,#0x10+var_C] ER_IROM1:00005FB2 LDR R0, [SP,#0x10+var_C] ER_IROM1:00005FB4 CBZ R0, loc_5FCC ER_IROM1:00005FB6 LDR R0, [SP,#0x10+var_C] ER_IROM1:00005FB8 MOV R1, #0x20000060 ER_IROM1:00005FC0 LDR.W R0, [R0,#-4] ER_IROM1:00005FC4 LDR R2, [R1] ER_IROM1:00005FC6 ADD R0, R2 ER_IROM1:00005FC8 STR R0, [R1] ER_IROM1:00005FCA B loc_5FD2 ER_IROM1:00005FCC ; --------------------------------------------------------------------------- ER_IROM1:00005FCC ER_IROM1:00005FCC loc_5FCC ; CODE XREF: operator new(uint,std::nothrow_t const&)+C↑j ER_IROM1:00005FCC MOVS R0, #1 ; Status ER_IROM1:00005FCE BL _Z14Set_LED_Statusb ; Set_LED_Status(bool) ER_IROM1:00005FD2 ER_IROM1:00005FD2 loc_5FD2 ; CODE XREF: operator new(uint,std::nothrow_t const&)+22↑j ER_IROM1:00005FD2 LDR R0, [SP,#0x10+var_C] ER_IROM1:00005FD4 ADD SP, SP, #8 ER_IROM1:00005FD6 POP {R7,PC} ER_IROM1:00005FD6 ; End of function operator new(uint,std::nothrow_t const&) Нет, она использовалась и далее в delete, где всё было корректно (я код выше приводил), и в другом месте -- печатал её значение...
  20. Ошибка в ArmClang?

    Попробовал на всякий случай переопределить не обычную операцию new (которая, по стандарту, кидает исключение, если не может выделить память), а new(Size, std::nothrow) -- не помогло. Но зато заметил, что при включённой оптимизации компилятор сжирает if в такой конструкции: P = new type; if ( P == nullptr )... А вот в такой -- не сжирает: P = new (std::nothrow) type; if ( P == nullptr )... Но здесь к нему претензий нет: по стандарту, первый вариант никогда nullptr не возвращает, а вот второй -- именно его и возвращает, если выделить не может.
  21. Ошибка в ArmClang?

    Использую MDK ARM 5.38.0.0, версия компилятора 6.19, пишу на C++20. Для контроля за динамическим выделением/освобождением памяти переопределил операции new и delete: uint32 Alloc_Ctr = 0; [[nodiscard]] pvoid operator new(std::size_t Size) { pvoid Ptr = std::malloc(Size); if ( Ptr != nullptr ) { Alloc_Ctr = Alloc_Ctr + *(reinterpret_cast<puint32>(Ptr) - 1); } else { Set_LED_Status(true); } return Ptr; } void operator delete(pvoid Ptr) noexcept { Alloc_Ctr -= *(reinterpret_cast<puint32>(Ptr) - 1); std::free(Ptr); } Как можно видеть, они подсчитывают количество фактически выделенных или освобождённых байтов памяти, пользуясь тем, что размер выделенного блока помещается в слове, непосредственно предшествующем этому блоку. Кроме того, если new не может выделить память, он зажигает светодиод, вызывая функцию Set_LED_Status. При отключенной оптимизации (-O0) всё работает, как и следовало ожидать, замечательно. А когда включаешь -O2, работать перестаёт 🙂 Я дизассемблировал прошивку и вот что получил. Сначала операция delete: ER_IROM1:00005EA0 ; void __fastcall operator delete(pvoid Ptr) ER_IROM1:00005EA0 EXPORT _ZdlPv ER_IROM1:00005EA0 _ZdlPv ; CODE XREF: operator delete(void *,uint)↓j ER_IROM1:00005EA0 Ptr = R0 ; pvoid ER_IROM1:00005EA0 MOV R2, #0x20000060 ER_IROM1:00005EA8 LDR.W R1, [Ptr,#-4] ER_IROM1:00005EAC LDR R3, [R2] ER_IROM1:00005EAE SUBS R1, R3, R1 ER_IROM1:00005EB0 STR R1, [R2] ER_IROM1:00005EB2 B.W free ER_IROM1:00005EB2 ; End of function operator delete(void *) Как видимо, всё хорошо: в R2 загружается адрес переменной Alloc_Ctr (20000060); из слова, предшествующего освобождаемому блоку (его адрес -- в R0, дизассемблер влепил ему имя Ptr по названию параметра функции), извлекается размер блока, он вычитается из счётчика, счётчик записывается на место, а потом вызывается обычная функция free, которая и освобождает память. В общем, поведение абсолютно соответствует тому, что написано на C++. А теперь смотрим на new: ER_IROM1:00005ED4 ; pvoid operator new(size_t Size) ER_IROM1:00005ED4 EXPORT _Znwj ER_IROM1:00005ED4 _Znwj ; CODE XREF: UART_ATSAMx5::Receive(void *,ushort,void (*)(void const*,ushort,bool,bool,bool,bool,bool),bool)+2A↑p ER_IROM1:00005ED4 ; UART_ATSAMx5::Transmit(void const*,ushort,void (*)(void const*,ushort,bool,bool,bool,bool,bool))+26↑p ... ER_IROM1:00005ED4 PUSH {R4,LR} ER_IROM1:00005ED6 BL malloc ER_IROM1:00005EDA MOV R4, R0 ER_IROM1:00005EDC CBZ R0, loc_5EE2 ER_IROM1:00005EDE MOV R0, R4 ER_IROM1:00005EE0 POP {R4,PC} ER_IROM1:00005EE2 ; --------------------------------------------------------------------------- ER_IROM1:00005EE2 ER_IROM1:00005EE2 loc_5EE2 ; CODE XREF: operator new(uint)+8↑j ER_IROM1:00005EE2 MOVS R0, #1 ; Status ER_IROM1:00005EE4 BL _Z14Set_LED_Statusb ; Set_LED_Status(bool) ER_IROM1:00005EE8 MOV R0, R4 ER_IROM1:00005EEA POP {R4,PC} ER_IROM1:00005EEA ; End of function operator new(uint) Как видим, сначала вызывается malloc, затем производится условный переход по значению полученного указателя (CBZ -- переход, если нуль). Ветка для нулевого значения совершенно корректна: вызывается функция зажигания светодиода и возвращается нулевой указатель. А вот ветка для ненулевого указателя некорректна: компилятор полностью выкинул увеличение счётчика выделенной памяти, как будто строки Alloc_Ctr = Alloc_Ctr + *(reinterpret_cast<puint32>(Ptr) - 1); внутри if попросту не было, и просто возвращает полученный указатель! Есть у кого-нибудь какие-нибудь мысли по этому поводу? Пы.Сы. Вот потому я и люблю ассемблер: что ты написал, то и будет 🙂
  22. Ну, Active означает, что ТИ (именно ТИ) готова их поставлять. Понятно, что по 10 штук она не продаёт, но если партия в ннадцать тысяч -- то пожалуйста.
  23. У нас сняты, а вот TI, насколько помню, продолжает выпускать половину, если не 2/3, классических ТТЛ/ТТЛШ микросхем, разработанных ею ещё в 60-70-х годах... Ну и, кроме того, если не понимать, как всё это работает на уровне логики, и учиться ваять сразу на HDL, результаты будут... так себе.
  24. Сие ключевое слово указывает компилятору, что значение переменной может измениться в любой момент неочевидным для компилятора образом. Например: int a; ... if ( a == 0 ) { ... } ... if ( a != 0 ) { ... } Если переменная a объявлена, как написано, т.е. без volatile, компилятор может загрузить её значение в регистр, а затем использовать для обоих проверок. Если же указать volatile, для каждой из проверок значение будет в обязательном порядке загружаться из памяти. Соответственно, если после первого, но перед вторым if значение изменилось (обработчиком прерывания или другим потоком), то без volatile это изменение может быть замечено, а может остаться незамеченным (в зависимости от того, какой код будет сгенерирован -- а это зависит в т.ч. от уровня оптимизации), а с volatile будет гарантированно замечено.
  25. Сравнить же всё равно интересно: насколько велик прирост скорости у разных компиляторов и как много места им для этого понадобилось. Да и не всегда "готов отдать всё": скажем, прошивка перестанет влезать во флэш определённого размера, нужно искать что-нибудь побольше -- или несколько пожертвовать скоростью.
×
×
  • Создать...