ПредишенСледващото


Анализ на изходния код на други хора, програмисти често се намира грешки, свързани с неразбиране на квалификациите на дестинация летлив. В резултат на това недоразумение е кодът, който дава рядък, напълно непредвидими и често много разрушителни и необратими повреди. Това е особено вярно за микроконтролер системи, където на първо място, за прекъсване манипулатори са част от кода на приложението, и второ, в периферията на регистрите за контрол се появи в RAM паметта на обща цел. Грешките, свързани с неизползване (и злоупотреба) квалификант летливи труден за отстраняване на грешки поради своята непредсказуемост и повторяемост. Това се случва, че кодът на C, в които използването на летливи, където трябва да бъде задължително за употреба, работи добре, да бъде монтирано към един компилатор, и не успее (или не работи изобщо), когато отива в друга.

Повечето от примерите в тази статия, написана за компилатор GCC (MPLAB C30), тъй като, предвид архитектурата на ядрото PIC24 компилатор и се отличава най-лесно да се синтезира малки илюстрации за това, което ще бъде показано неправилната летлив. Много от тези примери ще бъдат сглобени напълно правилно в други (прости) компилатори като PICC или MicroC. Но това не означава, че когато става дума за тези които не са летливи грешки компилатора не се срещат най-малко. Просто демонстрация код за тези компилатори ще изглежда много по-голям и по-сложен.

(Летливи преведени на английски означава "нестабилна", "накъсано")

По този начин, летлив е в C - това е променлива квалификант, казвайки компилатора, че стойността на променлива може да бъде променяна по всяко време и че част от кода, който произвежда променливата над някакво действие (чете или пише), следва да бъдат оптимизирани.

От гледна точка на алгоритъма, две LSBs са разположени в променлива на. В оптимизатор може да направи тази смяна код от един оператор:

като по този начин печели няколко барове и няколко ROM клетки. Но нека си представим, че същите стъпки, ние не на някаква абстрактна променлива и над периферната регистъра:

Но в този случай, оптимизиране на замяната на "PORTB | = 3", ние не може да бъде изпълнено! Чрез контролиране на условията на контролера директно констатации, ние често важни промени сигнал последователност. Например, ние генерира SPI сигнали, както и един болт (PORTB.0) - тези данни, както и друга (PORTB.1) - часовник. В този случай, не можем да променим състоянието на тези заключения в същото време като като по този начин не е възможно да се гарантира, че контролът на синхронизация чип сигнал ще получите правилните данни. И още повече, че ние не искаме да се оптимизират кода е претърпял формиране тактови импулси в един цикъл:

Такъв код може да се тълкува от компилатора като две от реципрочната действието и на първия ред не може да се получи в резултат на обектния код. Въпреки това, на практика, ние виждаме, че това се извършва оптимизация. Това се случва, именно защото променливата, на която се показва регистърът PORTB, обявена с квалификант летлив. например:

(Можете да проверите това, като погледнете в заглавния файл за конкретния контролер снабден с компилатора). Летливи квалификант забранява код оптимизация, извършване на действия по PORTB регистър. Затова дори и взаимното действие ще остане недокоснат от оптимизатор, и ние можем да бъдем сигурни, че се образува изходната пулса.

По този начин, чрез извършване на оптимизация, компилатори са готови да ни помогнат да направим нашия код възможно най-бързо и по-компактен. Все пак, ако не се използва летлив. в някои случаи, оптимизация може да играе жестока шега с програмиста. И трябва да кажа, по-умен и по-силен от компилатора, толкова повече проблеми това могат да създават, когато се използва неправомерно летлив.

Има три основни типа грешки, свързани с квалификациите летлив.

летлив неизползване, където имате нужда от него

обикновено се прави от програмистите, които не знаят за съществуването на летлив. или виждал, но не разбирам какво е това;

използването на летливи, когато е необходимо, но не така, както искате

присъщи програмисти, които знаят колко важно е летлив в програмирането на паралелни процеси или достъп до периферните регистри, но не се вземат под внимание някои от своите нюанси;

използването на летливи, където не е необходимо (и това се случи)

Това се прави от тези, които веднъж изгорени на първите две грешки. Това не е грешка и няма да доведе до неправилно поведение на програма, но ще му беди.

Помислете например за PIC24:

Ако оптимизация е забранено, кодът ще работи. Но е необходимо да се даде възможност за оптимизиране на начина, по който програмата ще започне да се задържите в (функцията за изчакване). Какво става? Състав, разпространение функция за изчакване (), не знае за това, че променливата Counter може да се промени по всяко време, в случай на прекъсване. Той вижда само това, което имаме е нулиране и веднага след това да се сравни с параметъра Time. С други думи, компилаторът предполага, че променливата време винаги е в сравнение с нула, а в списъка за изчакване () функция, когато оптимизация е разрешена, ще изглежда така:

(Забележка: Функцията за единична байт аргумент компилатор предава чрез w0 регистър)

Това, което виждаме в този код: Counter променлива се направи нула, а след това функцията Time, тя преминава през W0 на регистър, в сравнение с нула, вече не се обръща внимание на истинската стойност на променливата Coutner, които редовно се увеличава всеки път прекъсва таймер. С други думи, ние бяхме в постоянна цикъл. Както вече споменахме, факт е, че компилаторът не означава, че функцията ще бъде прекъснат от част от кода, който ще работи на променливите, които участват в работните функции. Тук идват на помощ и класиралият летлив.

Сега, когато компилаторът на предаването Генериране на код, който ще всеки път, достъп променливата Counter:

Ако програмата работи с RTOS, т.е. функцията може да бъде прекъснато в някакъв момент, а след това отново я изпрати до мястото, където е била прекъсната. Няма значение дали кооперация планировчика от операционната система (т.е. програмист решава за себе си, където се изпълняват функциите, за да бъде прекъснато) или изместване (тук програмистът не решава нищо, а неговата функция може да се прекъсне напълно по всяко време по-висок). Важното е, че компилаторът не знае, че някои чуждестранни код може да бъде изпълнена в средата на функция. Ето една програмка от истинска програма:

Програмата работи добре, се събират от компилатора HT-PICC18, но когато прехвърли същия код на PIC24 (MCC30 компилатор) на е преустановило дейността си, т.е. Той не отговори на един бутон. Проблемът е, че оптимизатор MCC30, за разлика от оптимизатор HT-PICC18, се вземат под внимание, че по време на изпълнение превключите променлива Бътън вече се съхраняват в един от регистрите на общо предназначение (w0) (в PIC18 само една батерия, така че, когато се работи с него такова поведение е по-малко вероятно):

Често, за да се създадат малки закъснения са такива функции:

Въпреки това, някои компилатори виждат тези функции безполезен код и не го включват в резултат обектния код. Ако това забавяне се използва за намаляване на скоростта на софтуер i2c (или SPI) за съставител, например, HT-PICC, програмата вече няма работа, или по-скоро, че ще работи толкова бързо, че контролният чип не е в прехвърлянето на ядрото AVR с компилатор WINAVR можем да обработим сигнали се дължи на факта, че всички повиквания функционални Забавяне ще бъдат премахнати.

За да избегнете това, използвайте квалификациите летлив.

Също така често срещана грешка е да се използва конвенционален (енергонезависима) указател към летливи -variable. Отколкото може да ни навреди? Вземем примера (от истинска програма), в която е била използвана указател към регистър вход / изход порт. от SPI програма изтича две еднакви чипове, които са свързани към различни терминали на микроконтролера, порта, който е свързан с активен чип, е избран чрез указател:

Имаше същото положение, както в предишния пример: под един компилатор (MCC18) код работи, но под друго (MCC30) - спря. Причината е неправилно декларирани в индекса. Disassembler SPI_Send () функция изглежда така:

Обръщаме внимание на факта, че компилаторът е "изхвърлен" настройка на малко 1 ( "* Порт | = 2"), като се има предвид че не е необходимо, тъй като Почти веднага след нея, това малко се връща отново, т.е. той прави нечувствителни оптимизация функции, които включват променлива Port, но той направи така, защото програмистът не обясни на компилатора, че регистърът не е лесно. За да поправите грешката трябваше да декларират една променлива Порт като указател към летливи променлива:

Сега компилаторът знае, че оптимизацията на променливата, посочи от пристанището, не е възможно да се произвеждат. И това се отразява новата обява:

В противен случай, ние получаваме всички същите грешки, както в предишния пример.

се хареса на мулти-байт променлива;

четене / модифициране / запис чрез батерията.

Вземем примера на работа с променлива, която заема повече от една памет клетка (16-битов контролер е int32, int64, поплавък, двойно; 8-битов - и дори int16). Често съм цитира този пример тук отново (за HT-PICC18):

Имайте предвид, че ADCValue променлива има измерение на 2 байта (за съхраняване на резултата от 10-битов ADC). Колко опасен е този код? Помислете списък на сравненията (да речем първата):

Да приемем стойност ADC входно напрежение, така че в резултат на превръщането е 255 (0x0FF). Накрая стойността на ADCValue, съответно, също = 0x0FF. С тази стойност започва да изпълни код сравнение на цени за стойност 100 (0x064). Първо, да сравните горните байтове на променливата и константа (0x00 до 0x00), а след това - младши (0x64 и 0xff). Резултатът изглежда е очевиден. Въпреки това, тук се крие проблемът. Въпреки, че в резултат на АД преобразуване и равна на 0xFF, то се влияе от няколко фактора: стабилност захранващо напрежение (или препратка напрежение) вход стабилността на измереното ниво на напрежение от близостта на праг за смяна на LSB, прослушване, шум и т.н. Затова резултат АД реализация. трептене е една и две единици LSB. Т.е. ADC резултат може да скача между стойностите на 0xFF и 0x100. И ако има прекъсване може да се случи между изпълнението на следните сравнения:

стойност ADCValue = 0x0FF;

Сравнение на висока байт: 0x00 и 0x00;

там е била прекъсната ADIF, в което стойността на ADCValue Обновяване 0x100;

сравнява ниско байт: 0x64 и 0x00 вече!

защото програмата си мисли, че е имало сравнение 0x000 <0x064, то она вызывает функцию Alarm.

И летлив квалификант не се запазва. Там ще бъдат спасени само деактивиране прекъсва за времето на сравненията.

Така че може би, летливи не се нуждае? Прекъсването, тя все още е забранено? Да, прекъсвания са забранени, но летливи. Все още е необходимо. Защо? Помислете за почти един и същ код за съставител C30:

Това е мястото, където летливите вещества и полезни! Обърнете внимание на линията пред вечния цикъл на - присвояване на стойност на променлива ADCValue Темп. И в същото време виждате оферта:

Както можете да видите Вътре в цикъла има друг избор, за да променлива ADCValue, но вместо това сравнението се прави с W1 на регистър, който е бил копиран променлива ADCValue още преди цикъла. Ето защо, нито ще се промени ADCValue, нашата програма няма да забележат. Така летливи в този случай задължително трябва, просто не забравяйте, че този квалификационен не ни гарантира атомност на операциите по обявената променлива.

И двата записа са идентични, но те не правят много променливи р летливи инстанция променливи. И двете записи означават "променлива р е указател към летливи Чар". Последиците от това определение са очевидни: когато стойността на показалеца, за да се прекъсне или паралелна задача, програмата може да не забележи, защото Тя ще работи с показалеца върху RON.

Правилното определение е, както следва:

Ако имаме нужда от показалеца на летлив летлив -variable, той се обявява, както следва:

Тук ясни препоръки, не мога да дам. В повечето случаи, когато пишете програми в C. аз се опитвам да се придържат към следните правила:

глобални променливи, използвани в капаните и в програмата (или прекъсва различни приоритети), е необходимо да се декларират като нестабилна;

Глобалните променливи, които се обработват от две или повече задачи на работното място в рамките на многозадачна операционна система, е необходимо да се декларират като нестабилна;

указатели към периферните регистри, както и променливи декларирани като летлив. Трябва да се декларират като указатели към нестабилна;

всичко, което не е обхванато от първите три условия и не е свързан към периферията, се препоръчва да се напише, абстрахиране от желязото. Тогава става ясно, че циклите на формата "за (I = 0; аз<100; i++) <>; "Не носи каквато и алгоритмично натоварване и може да бъде отстранен и ако те трябва да се запази, променливите трябва да се декларират като летлив ..

във всички останали случаи, летлив е ненужно.

Още няколко бележки:

Свързани статии

Подкрепете проекта - споделете линка, благодаря!