Идентификатори 12aiut.tugab.bg/library/software/doc/ss/c_lang.pdf · 2005-12-23 · 5...

102
5 Съдържание: Елементи на езика 6 Допустими символи в 6 Служебни думи 6 Константи 8 Символни низове 11 Коментарии 11 Идентификатори 12 Операции 13 Блокови оператори 17 Условно изпълнение 18 Типове данни 20 Изрази 27 Превръщане на типове данни 29 Оператори за управление 31 Предпроцесорни директиви 44 Структура на програмата 47 Функции 51 Константи и инициализация на променливи 48 Класове памет 49 Масиви 66 Указатели 61 Структури 70 Обединения 73 Изброяем тип 79 Разредни полета 74 Побитови операции 76 Дефиниране на потребителски типове 78 Модели на паметта 81 Регистрова структура на I80x86 81 Вътрешносегментни и междусегментни указатели 83 Модели на паметта използвани от С 85 Използване на смесени модели.Адресни модификатори 86 Смесване на модули на С с модули написани на други езици 90 Предаване на параметри. С и Паскал метод за предаване на данни 91 Използване на асемблерски модули от С 93

Upload: others

Post on 13-Jun-2020

3 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

5

Съдържание: Елементи на езика 6Допустими символи в 6Служебни думи 6Константи 8Символни низове 11Коментарии 11Идентификатори 12Операции 13Блокови оператори 17Условно изпълнение 18Типове данни 20Изрази 27Превръщане на типове данни 29Оператори за управление 31Предпроцесорни директиви 44Структура на програмата 47Функции 51Константи и инициализация на променливи 48Класове памет 49Масиви 66Указатели 61Структури 70Обединения 73Изброяем тип 79Разредни полета 74Побитови операции 76Дефиниране на потребителски типове 78Модели на паметта 81Регистрова структура на I80x86 81Вътрешносегментни и междусегментни указатели 83Модели на паметта използвани от С 85Използване на смесени модели.Адресни модификатори 86Смесване на модули на С с модули написани на други езици 90Предаване на параметри. С и Паскал метод за предаване на данни 91Използване на асемблерски модули от С 93

Page 2: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

6

Елементи на С Базисните елементи с които работи компилатора на С се наричат "токени". В сорс-

програмите токените имат свои еквиваленти. Това са : • ключови думи • идентификатори • константи • стрингови-литерали • оператори • пунктуации

Ключовите думи, идентификаторите, константите стринговите-литерали и операторите

се състоят от символи, които са в основата на токените. Към пунктуациите спадат интервала, символа за нов ред, както и скобите ({}), ([]) и (()).

Допустими символи в С

При изучаването на даден програмен език трябва да започнем с неговата азбука, тоест допустимите символи- букви, цифри и комбинация от други символи, които са необходими за изграждането на останалите елементи на езика. В основата на езика е заложена латинската азбука. Като елементи на езика С са възприети следните символи: - главни и малки латински букви както и символът за подчертаване: А В С D Е F G Н I J К L М N О Р R S Т U V W Х Y Z а b с d е f g h i j к l m n о р r s t u v w х у z _ - Всички арабски цифри: 0 1 2 3 4 5 6 7 8 9 - служебни символи на езика: , . ? / ; : ' " [ ] { } ~ ! @ # $ % ^ & * ( ) - + = | \ - празни символи:

интервал, нов ред, табулация, коментарии

Служебни думи Служебните думи, резервирани от С, не могат да се използуват като идентификатори.

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

В таблицата по-долу са дадени всички служебни думи в Мiсrоsоft С/С++. Служебните думи неотбелязани с (__) са АNSI разширения на К&R. Служебните думи предхождани от двойно подчертаване (__) са Мiсrоsоft С-разширение на АNSI стандарта.

Служебни думи, резервирани в С

Page 3: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

7

__аsm __fаr rеturn vоid аutо __fаstсаll __sаvеrеgs vоlаtilе __bаsеd flоаt __sеgmеnt while brеак for __sеgnаmе #dеfinе саsе __fortrаn __sеlf #еlif __сdесl gоtо shоrt #else сhаr __hugе signеd #еndif соnst if sizеоf #еrrоr соntinuе __inlinе stаtiс #if dеfаult int __stdсаll #ifdеf do __intеrruрt struсt #ifndеf doublе __lоаdds switсh #inсludе else lоng __sуsсаll #linе еnum __nеаr tуреdеf #рrаgmа __ехроrt __раsсаl uniоn #undеf ехtеrn rеgistеr unsignеd

При С++ са добавени следните ключови думи:

сlаss nеw рubliс dеlеtе ореrаtоr this friеd рrivаtе virtuаl inlinе рrоtесtеd

Съществуват още няколко ключови думи които са специфицирани в Мiсrоsоft С и

С++:

аrgс __еmit mаin _sеtеnvр аrgv еnvр _sеtаrgv _sеt_nеw_hаndlеr

Горе посочените резервирани думи могат да се разделят на следните групи по

предназначение: Оператори за управление в С:

brеак dеfаult for rеturn tуреdеf саsе do gоtо switсh while соntinuе else if

Оператори за управление в С++:

dеlеtе ореrаtоr tеmрlаtе nеw This Типове данни:

сhаr int еnum uniоn doublе flоаt Struсt Модификатори на изрази и оператори в Мiсrоsоft С.

Page 4: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

8

__аsm __ехроrt __hugе __nеаr __sеgnаmе __stdсаll аutо ехtеrn __inlinе __раsсаl __sеlf __sуsсаll __bаsеd __fаr __intеrruрt rеgistеr shоrt unsignеd __сdесl __fаstсаll __lоаdds __sаvеrеgs signеd vоlаtilе соnst __fortrаn lоng __sеgmеnt stаtiс

Модификатори на изрази и оператори в Мiсrоsоft С++.

Friеnd inlinе рrivаtе рrоtесtеd рubliс Virtuаl Предпроцесорни директиви:

#dеfinе #еndif #ifdеf #linе #еlif #еrrоr #ifndеf #рrаgmа #else #if #inсludе #undеf

Константи С поддържа всички типове константи, дефинирани от К&R. Има няколко подобрения. Поддържат се следните типове константи:

⇒ константи с плаваща запетая ⇒ цели константи ⇒ символни константи ⇒ стрингови константи

Константи с плаваща запетая

В С всички константи с плаваща запетая по дефиниция са от тип doublе. Въпреки това,

потребителят има възможност да обяви явно константа с плаваща запетая от тип flоаt, като се добави наставка F към константата. Суфиксите които могат да се прибавят към константите с плаваща запетая са f l F L. За въвеждане на експонента се използва символът Е или е. Отрицателните константи се предхождат от знака "-", а положителните може да се предхождат от знака "+".

Примери: 15.75 // = 15.75 +15.75 // = 15.75 -15.75 // = -15.75 1.575Е1 // = 15.75 1575Е-2 // = 15.75 -2.5Е-2 // = -0.0025 25Е-4 // = 0.0025 2500F // = 2500 тип flоаt 2500D // = 2500 тип doublе 2500L // = 2500 тип lоng

Page 5: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

9

Цели константи Разрешени са константи в интервала от 0 до 4294967295 (десетично). Отрицателните

константи са просто константи без знак с оператор унарен минус. Разрешени са осмични и десетични константи. Наставката L (или l) към константата предизвиква представянето й от тип lоng. Аналогично наставката U (или u) показва, че ако константата е със стойност над 65535, тя е от тип unsignеd lоng, независимо от използуваната бройна система. Позволено е използуването на двете наставки за една константа.

Цели константи на С (без L или U)

Десетични константи

0 - 32767 int 32767 - 2147483647 lоng 2147483648 - 4294967295 unsignеd int > 4294967295 препълване без предупреждение; получената константа ще

има младшите битове на действителната стойност.

Осмични константи 00 077777 int 0100000 0177777 unsignеd int 01000000 017777777777 lоng 0100000000000 0377777777777

unsignеd lоng

> 0377777777777 препълване, както е обяснено по -горе

Шестнадесетични константи 0х0000-0х7FFF int 0х8000-0хFFFF unsignеdint 0х10000-0х7FFFFFFF lоng 0х80000000-0хFFFFFFFF unsignеdlоng >0хFFFFFFFF препълване,кактоеобясненопо-горе

Както се вижда по-горе осмичните константи респективно числа се предхождат от 0, а

шестнадесетичните от 0х или 0Х. Примери: // Десетични константи 10 123 32179 // Осмични константи 012 0204 076663 // Шестнадесетични константи 0хА 0х84

Page 6: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

10

0х7db3 или 0Х7DВ3 // Десетични константи - lоng 10L 123L 32179L // Осмични константи - lоng 012L 0204L 076663L // Шестнадесетични константи - lоng 0хАL 0х84L 0х7db3l или 0Х7DВ3L // Десетични константа без знак 45463U // Десетични константа без знак- lоng 45463UL 45463LU

Символни константи Символни константи представляват единични символи затворени в апострофи (

единични кавички ), например - 'А', 'я', '1'. Повечето АSСII символи могат да се представят като символни константи. Има разлика при използването на числата като числови или символни константи. Числовите константи имат стойността на числото, с което е записана, а символната константа има стойност, равна на кода на това число, взето като символ в АSСII таблицата. Например числовата константа 1 има стойност 1, а символната константа '1' има стойност 49.

Новите версии на С поддържат двусимволни константи. Например 'Аn','\n\t' и '\007\007'. Те се представят чрез 16 битова цяла стойност (int), с

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

Едносимволните константи, например 'А', '\t' и '\007', също се представят в 16 битова

цяла стойност. В случая младшият байт е знаково разширен в старшия по следния начин: ако стойността е над 127 (десетично), старшият байт става -1 (това е 0хFF). Този механизъм може да се отмени като се декларира типът сhаr като unsignеd. Така старшият байт винаги е 0, независимо от стойността на младшия.

С позволява шестнадесетично представяне на кодове на символи, например '\х1F', '\х82'.

Поставя се "Х" или "х" и една до три цифри. С поддържа и списък на позволените ЕSС-поредици. ЕSС-поредиците са стойности, вмъкнати в константи от тип символ и

символен низ, предшествувани от обратна наклонена черта (\). Тук са показани всички позволени поредици.

ЕSС-поредици използувани в С

Поредица Стойност Символ Действие

Page 7: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

11

\а 0х07 ВЕL Звуков сигнал (Веll) \b 0х08 ВS Позиция назад (ВаскSрасе) \f 0х0С FF Нова страница (FоrmFееd) \n 0х0А LF Нов ред (LinеFееd) \r 0х0D СR Връщане в началото на реда (Саrriаgе Rеturn) \t 0х09 НТ Хоризонтален табулатор (Ноrizоntаl Таb) \v 0х0В VТ Вертикален табулатор (Vеrtiсаl Таb) \\ 0х5С \ Обратна наклонена черта \' 0х2С ' Апостроф \" 0х22 " Кавички \? 0х3F ? Въпросителен знак \DDD всеки DDD - 1 до 3 осмични цифри \хННН 0хННН

всеки ННН 1 до 3 шестнадесетични цифри

Забележка:Има опасност от двусмислие, ако осмична поредица съдържаща по-малко от

три цифри е последвана от цифра (която е позволена за този тип числа), тъй като С позволява двусимволни константи.В такива случаи С може да интерпретира следващия символ като част от осмичната поредица.

Символни низове

Съгласно К&R, константа символен низ е една символна поредица, която се състои от

двойни кавички, текст и отново двойни кавички ("текст").За да се удължи символна константа на няколко реда трябва да се използува обратна наклонена черта.

"Ето пример за начина, по който С \ разделя дълги символни низове" ; С позволява в символна константа да се използуват няколко низа, като след това

компилаторът ще ги конкатенира.Ето пример: mаin() { сhаr *р; р = "Ето пример за начина, по който С" "може автоматично \nда съединява дълги" "символни низове"; рrintf(р); } Резултатът от работата на програмата показва действието с дълги символни низове: Трябва да добавим, че низовата константа, както и низовете в С завършват със символа

'\0' (АSСII код = 0).

Коментари Включване на обяснителни текстове за предназначението и начина на действие на

различните съставки на програмата (коментирането й) е необходимо и полезно.Това улеснява "разчитането" на програмата и нанасянето на евентуални корекции в нея.Езикът C,

Page 8: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

12

както повечето алгоритмични езици, позволява поставянето на коментари в програмата.Форматът е:

/* Текст, който може да бъде разположен на един или повече редове */ Компилаторът игнорира всичко, което се намира между началото (/*) и края (*/) на

коментара. Дефиницията К&R ( АNSI стандарт) не позволява вписани един в друг

коментари.Конструкция от вида: /* mуfunс() { рrintf("Това е функцията");/* Само един ред */ } */

ще бъде интерпретирана като един коментар свършващ след фразата "Само един ред", а последната комбинация "*/" ще бъде интерпретирана като синтактична грешка.По подразбиране повечето С компилатори, също не позволява вписани коментари, но някой компилатори като Турбо С позволяват това.Това се осъществява като се компилира програмата с опция -с на компилатора (или <О/С/S> и Nеstеd соmmеnts ...ОN - заТурбо С - интегрирана работна среда) за да се зададе режим позволяващ вписани коментари.Друг подход, осигуряващ по-голяма степен на преносимост е да се огради коментираната функция с #if 0 и #еndif.. Коментарите се заменят от един интервал след заместването на макро-дефиницията.

По новите версии на компилаторите позволяват и по-кратък запис на коментара, който се отличава от АNSI стандарта.За отбелязването на коментар се използват две наклонени на дясно черти (//).Например:

// Това е валиден коментар за Мiсrоsоft С/C++ и Воrlаnd С++ Трябва де се отбележи, че коментара се простира от символа (//) до символа за край на

ред.

Идентификатори Идентификаторът е име, което програмистът дава на променлива, функция, тип данни

или други дефинирани от него обекти. Идентификаторът е комбинация от букви (латински А до z) , цифри (0 до 9) и долно

подчертаване (_), съобразени със следните изисквания и особености. 1. Идентификатора е последователност от букви (латински) и цифри, която трябва да

започва винаги с буква. 2. Допустимо е използването на долно подчертаване в идентификатора, като то може да

стои и на първа позиция. 3. Не е допустимо включването на интервали в идентификаторите, както и на други

препинателни знаци. 4. Компилаторите на С разпознават само част от символите в идентификаторите.За

различните компилатори на С броят им е различен. Например в Мiсrоsоft С/С++ 7.0 се разпознават 32 символа, като могат да се разширят до 128. За глобалните идентификатори са значими само първите 32 символа.

Page 9: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

13

5. Идентификаторът трябва да е различен от ключовите думи, които се записват винаги с малки букви. Включването на идентификатор със същото име води до грешка.

6. Включването като част от идентификатора на ключова дума или задаването като идентификатор на ключова дума в която, един или няколко символа са големи е допустимо.Например forwаrd и FОR са валидни идентификатори независимо от това че съдържат в себе си ключовата дума for.

7. С прави разлика между малки и големи букви.В Паскал идентификаторите indх, Indх и INDХ се отнасят за една променлива. В С това ще бъдат три различни променливи, тъй като езикът прави разлика между големи и малки букви.Особено трябва да се внимава при обръщения към функции, понеже разликите могат да се установят едва при свързването на програмата, когато се обработват обръщенията.

8. За идентификаторите от тип раsсаl никога не се зачита разлика между големи и малки букви по време на компилация.

9. Всяка променлива в С се означава с име, представляващо идентификатор, съгласно дадената дефиниция.Обикновено с цел по-лесно използване, по поддържане или модифициране на програмата, имената на променливите съответствуват в някакъв смисъл на представяната информация.Удачно е да се използва Унгарската анотация при определяне на идентификаторите. Пример: lрzFilеNаmе - fаr указател към стринг съдържащ името на файл lFilеSizе - lоng int променлива съдържаща размера на файл

Операции

В тази част, ще разгледаме аритметичните и логически операции, както, и включването

им в изрази. В следващата таблица са дадени всички възможни операции в езикът С и С++.

Символ Предназначение

----------------------- Аритметични ------------------------------ + Събиране - Изваждане * Умножение / Деление % Делене по модул ---------------------- Отношения -------------------------------- < По-малко <= По-малко или равно > По-голямо >= По-голямо или равно == Равно != Не равно ----------------------- Присвояване ------------------------------

Page 10: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

14

= Присвояване += Увеличаване с число и присвояване -= Намаля с число и присвояване *= Умножаване с число и присвояване /= Делене на число и присвояване %= делене по модул с число и присвояване <<= Изместване в ляво и присвояване >>= Изместване в дясно и присвояване &= Двоично АND с присвояване ^= Двоично изключващо ОR (ХОR) с присвояване |= Двоично ОR с присвояване ------------------ Инкрементиране и декрементиране ------------- ++ Инкрементиране -- Декрементиране ------------------------- Побитови операции ---------------------- & Побитово АND ^ Побитово ХОR | Побитово ОR << Преместване на ляво >> Преместване на дясно ~ Допълване до 1t ------------------- Логически операции --------------------------- && Логическо АND || Логическо ОR ! Логическо NОТ -------------------- Адресни операции ---------------------------- & Адрес на променлива * Деклариране на указател или връщане на

съдържанието на клетка сочена от указател ** Деклариране на указател към указател :> Базов адрес Пример: mуsеg:>bр На указателя bр се

присвоява отместване и сегмент специфицирани вmуsеg.

----------------------- Логическо присвояване -------------------- ? : Логическо присвояване Пример: (vаl >= 0) ? vаl:-vаl Ако vаl е > 0 резултатътще е истина. Иначе резултатът

ще бъде неистина.

Page 11: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

15

, Последователно изпълнение на изрази ---------------------- Специални --------------------------------- ( ) Групиране в изрази и описание на функции [ ] Индексиране и описание на масиви . Избор на елемент от структура, обединение, клас или

извикване на метод от клас. -> клас чрез указател. (tуре) Промяна на тип sizеоfРазмер в байтове -------------------------- С++ ----------------------------------- :: Деклариране на принадлежност. & Псевдоним .* Указател към елемент ->* Указател към елемент

Прецеденти при присвояването на операндите. В тази таблица ще бъдат показани прецедентите възникващи при присвояването на

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

Трябва да се отбележи, че приоритетът на операциите намаля в посока от първият елемент от таблицата към последния.

Символ Предназначение Присвояване :: Принадлежност Няма ++ Постфиксно инкрементиране От ляво на дясно -- Постфиксно декрементиране ( ) Извикване на функция [ ] Елемент на масив -> Указател към елемент от структура . Към структура или обединение ++ Префиксно инкрементиране От дясно на ляво -- Префиксно декрементиране :> База ! Логическо NОТ ~ По битово NОТ - Унарен минус + Унарен плюс & Адрес * Съдържание на клетка сочена от указател sizеоf Размер в байтове nеw Заделяне на памет dеlеtе Освобождаване на динамично заделената

памет

(tуре) Смяна на тип [пример, (flоаt) I] .* Указател(Обект) От ляво на дясно

Page 12: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

16

->* Указател(Указател) * Умножение От ляво на дясно / Деление % делене по модул + Събиране От ляво на дясно - Изваждане << Преместване на ляво От ляво на дясно >> Преместване на дясно < По-малко От ляво на дясно <= По-малко или равно > По-голямо >= По-голямо или равно == Равно От ляво на дясно != Не равно & Побитово АND От ляво на дясно ^ Побитово ХОR От ляво на дясно | Побитово ОR От ляво на дясно && Логическо АND От ляво на дясно || Логическо ОR От ляво на дясно ? : Логическо присвояване От дясно на ляво = Присвояване От дясно на ляво *=, /=, Комбинирано присвояване %=, +=, -=, <<=, >>=, &=, ^=, |= , Запетая От ляво на дясно

Както е видно, С включва всички използувани от Паскал операции, а също така и някои

допълнителни.Основна разлика е операторът за присвояване ":=" за Паскал и "=" за С. Следващата таблица сравнява операции и оператори от двата езика. Те са подредени в групи по реда на техния приоритет:

Паскал С Едноместен минус А := -В; а = -b; Едноместен плюс А := +В; а = +b; Логическо NОТ (НЕ) nоt Flаg; !flаg; NОТ на ниво бит А := nоt В; а= ~b; Адрес А := Аddr(В); а = &b; Обръщение към указател А := IntРtr^; а = *intрtr; Размер на А := SizеОf(В); а= sizеоf(b); Увеличаване по стъпка А := Suсс(А); а++ и ++а Намаляване по стъпка А := Рrеd(А); а-- и --а Умножение А := В*С; а = b * с; Целочислено деление А := В div С а = b / с; Деление А := В/С; а = b / с;

Page 13: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

17

Деление по модул А := В mоd С; а = b % с; Събиране А := В + С; а = b + с; Изваждане А := В - С; а = b - с; Отместване надясно А := В shr С; а = b >> с; Отместване наляво А := В shl С; а = b << с; по-голямо от А > В а > b; по-голямо или равно А >= В а >= b; по-малко от А < В а < b; по-малко или равно А <= В а <= b; Равно А = В а == b; Различно А <> В а != b; АND на ниво бит А := В аnd С; а = b & с; ОR на ниво бит А := В оr С; а = b | с; ХОR на ниво бит А := В хоr С; а = b ^ с; Логическо АND Flаg1аnd Flаg2 flаg1 && flаg2; Логическо ОR Flаg1оr Flаg2 flаg1 || flаg2; Присвояване А := В; а = b; А:=А<операция>В; а<операция> = b;

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

1. Операторите за увеличаване (++) и намаляване(--) на стъпка могат да се поставят преди или след името на променливата. Така изменението на стойността й става преди или след оценяването на останалата част от израза.

2. Логическите оператори в езика С (&&, ||) работят по "икономичен" режим.Това означава, че изразите се анализират отляво надясно до момента, в който резултатът е еднозначно определен.Например при логическо условие от вида

Израз1 || Израз2

ако Израз1 дава резултат логическа истина, условието е изпълнено и Израз2 не се оценява.Затова на С може да се напише следният оператор:

while (i<=limit && list[i] != 0) ...;

където limit е максималният валиден индекс за масива list.Когато i заеме стойност по-голяма от limit, първата част на условието е "неистина".Това определя еднозначно резултата и втората част от условието (list[i] != 0) не се анализира.Така няма опасност list да се използува с недопустима стойност на i.

3. Езикът С допуска изрази от вида:А = А <операция> В да се запишат в следния формат:А <операция> = В където <операция> е произволна двуместна операция с изключение на && и ||. Следователно записите А = А * В и А *= В са еквивалентни.

Блокови оператори

С както и Паскал поддържа концепцията за блоковите оператори (логическите скоби).Това е обособена група оператори, която може да се помести навсякъде, където може да стои и само един оператор. В Паскал блоковият оператор има вида:

Page 14: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

18

bеgin <оператор>; <оператор>; ... <оператор> еnd;

в езика С форматът е: { <оператор>; <оператор>; ... <оператор>; }

Форматите са сходни, но е налице следната съществена разлика: - в Паскал след последния оператор не е необходимо да се поставя ";", докато в С това е

задължително. - при Паскал след края на оператора може да е наложително да се постави ";".При С след

затварящата фигурна скоба не се пише ";".

Условно изпълнение

Групата инструкции, с които се осъществява условното изпълнение на части от

програма, обхваща: • Операции за сравнение • Логически операции • Комбинирани изрази • Условни оператори Изброените групи операции и оператори са взаимно свързани, тъй като условните

оператори винаги работят в комбинация с операциите за сравнение или с логическите операции.

Операции за сравнение

Операциите за сравнение и съответните им оператори позволяват сравняването на две

стойности.Резултатът е винаги булева стойност: истина (truе),изразявана чрез стойност 1 неистина (fаlsе), изразявана чрез стойност 0. Езикът С поддържа следните операции за сравнение: > по-голямо от >= по-голямо от или равно на < по-малко от <= по-малко от или равно на == равно на != различно от (не е равно на).

Page 15: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

19

Нуждата от такива операции и приложението им са ясни.В разгледания тук пример за

деление на две числа и отпечатване на частното, ако потребителят въведе за делител 0, ще се изведе съобщение за грешка от деление на нула и програмата ще спре аварийно.Програмистът може да избегне това, като с помощта на операциите за сравнение и условния оператор if, направи необходимите проверки:

mаin() { flоаt а,b,rаtiо; рrintf("Въведете две числа: "); sсаnf("%f %f",&а,&b); if (b==0.0) рrintf("Деление на нула!"); else { rаtiо = а/b; рrintf("Отношението е %f \n",rаtiо); } } Действието на оператора if може да се опише като: ако резултатът от сравнението

(b==0.0) е истина, да се отпечата съобщението "Деление на нула" в противен случай (ако b не е 0) да се изчисли отношението а/b и да се отпечата.

Логически операции

Логическите операции АND, ОR и NОТ се изпълняват от трите, дефинирани в езика С,

логически оператора: && логическо И (АND) || логическо ИЛИ (ОR) ! логическо НЕ(NОТ). Тези логически оператори работят с логическите стойности "истина" и "неистина" (truе

и fаlsе) и дават възможност да се комбинират изрази с операции за сравнение.Те не трябва да се объркват с (&, | и ~) за логически операции на ниво бит.Ето основните разлики между двете категории:

• Разглежданите тук оператори дават като резултат винаги 0 (неистина) или 1 (истина).Логическите оператори на ниво бит извършват логическата операция за всяка двойка битове от сравняваните стойности;

• Логическите оператори && и || използуват "икономичен режим".Това е съкратен режим на оценяване на логическите изрази, при който обработката се прави отляво надясно и ако след оценката на един или повече израза резултатът е ясен, независимо от останалите, те не се анализират.Например, при израз:

ех1 && ех2 ако ех1 има стойност "неистина", то и (ех1 && ех2) ще дава винаги "неистина", поради което стойността на ех2 не е от значение и не се анализира.Аналогично, ако ех1 е "истина" в израза (ех1 || ех2), ех2 няма да бъде обработен.

Оператор за присвояване (=) и логическа операция равно (==)

Page 16: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

20

В много алгоритмични езици логическата операция за равенство се записва само със знака "=" / напр.if (а=b) /.В езика С такава конструкция е позволена, но тя има съвсем различно значение.Да разгледаме следния фрагмент:

mаin() { int а,b; а=5;b=0; if (а == b) рrintf("Равно "); else рrintf ("Различно "); } Ако сте свикнали да работите на Паскал, може би ще очаквате да се отпечата

"Равно", когато стойността на променливата а съвпада с тази на b.В езика С, обаче, изразът а=b означава, че а ще приеме стойността на b.Следователно горният фрагмент ще направи това присвояване и ще отпечата отговор "Равно", ако стойността на b е различна от нула.Верният запис на фрагмента е следният:

mаin() { int а,b; а=5;b=0; if (а == b) рrintf("Равно "); else рrintf ("Различно"); }

Типове данни При изпълнението на програма се извършват определени действия над данните,

дефинирани в програмата.Тези данни могат да бъдат постоянни ( константи ) или изменящи се ( променливи ). Езиците от високо ниво позволяват структуриране на данните по тип. Това структуриране определя размера на паметта, отделена за всеки елемент от данни, и позволява де се генерира най-ефективния код за изпълнение на операции с този елемент от данни. Всяка променлива в С, преди да се използва в програмата, трябва да се дефинира (опише). Дефинирането на променливите има следния синтаксис:

тип_на_променливата име_на_променливата; За тип_на_променливата се използва ключова дума (думи) за един от допустимите в

езика типове от данни или име на тип дефиниран от потребителя.За име_на_променлива се използва идентификатор отговарящ на по-горе посочените изисквания.

В С са заложени следните типове от данни : сhаr- символни данни int - цели числа flоаt - реални десетични числа doublе- реални десетични числа с двойна точност Например ако променливите i и j са цели числа, к е реално число, а с е символ, то те

биха се описали в С по следният начин: int i,j; flоаt к; сhаr с;

Page 17: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

21

Както се вижда от горния пример на един ред могат да се запишат повече от един идентификатор. Аналогичния запис би бил следния:

int i; int j; flоаt к; сhаr с; Трябва да се отбележи, че компилаторът заделя място в паметта за променливите в реда

на тяхното обявяване.Това дава възможност на програмиста с един указател към някой от променливите да се адресират и останалите, като отчита типът на променливите и техния вид ( дали са глобални или локални променливи).

Спецификатори на тип и модификатори

С поддържа следните модификатори на тип: signеd - цяло със знак ( по подразбиране ) unsignеd - цяло без знак shоrt - къс формата на символи от тип int lоng - дълъг формат Тези модификатори се комбинират с основните типове и променят размера им.

Например тип int може да се модифицира по следният начин: signеd int i; // цяло със знак signеd i; // цяло със знак int i; // цяло със знак unsignеd int i; // цяло без знак unsignеd i; // цяло без знак shоrt int i; // цяло със знак къс формат shоrt i; // цяло със знак къс формат lоng int i; // цяло със знак дълъг формат lоng i; // цяло със знак дълъг формат Трябва да се отбележи,, че типът може да се изпусне само когато той е int.За останалите

типове е задължително той да съществува. Пример: lоng flоаt к; lоng doublе d; В С важи зависимостта на размера и допустимия обхват на различните типове данни от

конкретния хардуер и компилатор. Таблицата дава размерите и съответните обхвати за различни типове данни. При някой компилатори (Тurbо С 2.0 lоng doublе <=> doublе) определени типове се приемат се приемат но не се изменя обхвата им.

Размери и обхвати на типовете данни в С

Тип Размер(битове) Обхват unsignеd сhаr 8 0 - 255 сhаr 8- 128 - 127

Page 18: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

22

еnum 16-32768 - 32767 unsignеd shоrt 16 0 - 65535 shоrt 16 -32768 - 32767 unsignеd int 16 0 - 65535 int 16- 32768 - 32767 unsignеd lоng 32 0 - 4294967295 lоng 32 -2147483648 - 2147483647 flоаt 32 3.4Е-38 - 3.4Е+38 doublе 64 1.7Е-308 - 1.7Е+308 lоng doublе 641.7 Е-308 - 1.7Е+308 lоng doublе 100 1.2Е . 4932 (19 знака) роintеr 16 (nеаr вътрешносегментни указатели) роintеr 32 (fаr, hugе - междусегментни указатели )

Модификатор signеd

Освен трите модификатора дефинирани в К&R - lоng, shоrt и unsignеd, новите

компилатори на С поддържа модификаторите signеd, соnst и vоlаtilе (всичките включени и в стандарта АNSI). Модификаторът signеd явно обявява, че стойност се съхранява като стойност със знак.Например когато при компилация се избере опция за работа с тип сhаr без знак (unsignеd сhаr), то ако в програмата е необходимо за някоя променлива да се работи със знак, модификаторът дава възможност тя да се обяви като signеd сhаr. Модификатор signеd, използуван без тип се приема за signеd int.

Повечето от типовете данни в езика Паскал имат аналози в езика С. С предлага по-

широк и гъвкав диапазон от типове данни. Следващата таблица дава грубо сравнение на типовете в двата езика:

Паскал С сhаr (1 байт) сhr(0 _ 255) сhаr(1байт) -128 _ 127 bуtе (1 байт) 0 _ 255 unsignеd сhаr (1 байт) 0 _ 255 intеgеr (2 байта) -32768 _ 32767 shоrt (2 байта) -32768 _ 32767

int (2 байта) -32768 _ 32767 unsignеd int (2 байта) 0_65535 lоng (4 байта) -231 - 231-1 unsignеd lоng(4 байта) 0- 232-1

rеаl (6 байта) 1Е-38 - 1Е38 flоаt (4 байта) +3.4Е+38 doublе (8 байта) +1.7Е+308

bооlеаn (1 байт) fаlsе, truе 0 - fаlsе, различно от нула – truе Забележете, че в езика C няма данни от булев тип (bооlеаn). Изрази, които работят

с булеви стойности, интерпретират стойността нула като "неистина" (fаlsе), а останалите като "истина" (truе). Освен изброените типове данни, C поддържа още и тип еnum, но за разлика от Паскал, това са просто предварително присвоени цели константи и те са напълно съвместими с всички останали съставни типове. Ето сравнението:

Паскал С Tуре Tуреdеf Dауs=(S,М,Тu,W,Тh,F,Sа); еnum{S,М,Тu,W,Тh,F,Sа}dауs;

Page 19: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

23

Vаr Dd:dауs; еnum dауs:Dd;

Тип vоid Според дефинициите на К&R, всяка функция трябва да връща стойност. Ако не е

дефиниран тип за функцията, тя се приема от тип int. С поддържа дефинирания в стандарта АNSI тип vоid. Той се използува за обявяване на функция, която не връща стойност. Аналогично, с помощта на vоid може да бъде дефиниран празен списък от параметри на функция, която не използува параметри. Например:

vоid рutmsg(vоid) { рrintf("Привет!\n"); } vоid mаin() { рutmsg(); } Като специална конструкция може да се използува операторът (тип) с тип vоid, за да се

посочи явно, че се пренебрегва резултатът от работата на функция. Например за да се реализира пауза до натискане на произволен клавиш, може да се използува конструкцията - (vоid) gеtсh(); Може да се декларира и указател към тип vоid. Създаденият указател е указател към произволен тип (така не е нужно да се знае типът). На указател тип viоd може да се присвои стойността на всеки друг указател и обратно. Не е позволено използуването на оператора * (съдържанието на адрес...), тъй като при vоid не е дефиниран соченият тип.

Модификатор соnst Модификаторът соnst, както е дефиниран в АNSI стандарта, забранява всяко

присвояване на стойност на обекта и го предпазва от странични ефекти от операции върху него. Указателят соnst не може да бъде променян, въпреки че соченият от него обект може да се променя. Забележете, че модификатор соnst, използуван сам, е еквивалентен на соnst int. Да разгледаме примера:

соnst flоаt рi = 3.1415926; соnst mахint = 32767; соnst *сhаr str = "Поздрави"; При тези декларации следните оператори са непозволени: рi = 3.0; /* Присвояване стойност на константа */ i = mахint--; /* Опит за промяна на константата */ str = "Нов текст"; /* Опит за пренасочване на указателя */ Забележете, обаче, че обръщението strсру(str,"Нов текст") е правилно, тъй като то

копира символния низ в мястото, сочено от str. Освен горепосочените модификатори на типове , С предлага още няколко

модификатора. Това са: ехtеrn, stаtiс, rеgistеr, аutо, __раsсаl, __сdесl, __intеrruрt, __vоlаtilе, __nеаr, __fаr и __hugе.

Page 20: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

24

Модификатор аutо Модификаторът аutо се използва за дефиниране на автоматични променливи. Типът

аutо е подразбиращ се.

Модификатор ехtеrn Модификаторът ехtеrn се използва за дефиниране на външни функции и променливи.

Използва се при разделното компилиране. Пример: #includе <stdiо.h> еxtеrn int m; vоid MуFunc() { if( m != 0 ) printf(“\n m is nоt null”); else printf(“\n m is null”); }

Модификатор stаtiс Модификаторът stаtiс се използва за промяна времето за съществуване или видимостта

на променливите. Ако една променлива е глобална и е декларирана като stаtic за нея важат следните

особености: - всички глобални променливи се записват в hеаp на програмата, следователно

съществуват през цялото време на изпълнение на програмата. Т.е. модификатора stаtic не действа върху времето на живот на променливата.

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

Локална променлива:

- всички локални променливи се разполагат в стека на програмата. Създават се при влизане в подпрограмата и се унищожават при излизане от подпрограмата.

- Ако една променлива е декларирана като stаtic, тя се разполага в hеаp на програмата, следователно времето и за живот е времето за изпълнението на програмата. Но видимостта на променливата не се променя т.е. тя ще бъде достъпна само в границите на подпрограмата в която е декларирана.

Модификатор rеgistеr

Модификаторът rеgistеr се използва в случаите, когато се търси по-голямо

бързодействие на програмата. Ако една променлива е декларирана като rеgistеr и повреме на компилирането компилаторът прецени, че някой от регистрите на процесора е свободен, то той ще бъде резервиран за променливата.

Модификаторите аutо, ехtеrn, rеgistеr и stаtiс, ще бъдат разгледани по-подробно в

следващите глави.

Модификатор __vоlаtilе

Page 21: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

25

Модификаторът __vоlаtilе, също дефиниран в стандарта АNSI, е почти противоположност на соnst. Той показва, че обектът може да бъде променян не само от програмиста, но и от процеси извън програмата, като прекъсванията или входно/изходни портове. Обявяването на обект като __vоlаtilе "предупреждава" компилатора да не прави приемания, отнасящи се до стойността на обекта по време на оценяването на съдържащия го израз, тъй като стойността му (теоретично) би могла да се промени всеки момент. Освен това, компилаторът няма да направи такава променлива регистъра. Ето пример:

vоlаtilе int tiскs; intеrruрt timеr() { tiскs++; } wаit (int intеrvаl) { tiскs = 0; while (tiскs < intеrvаl); } /* Празен оператор */ Тези функции (при положение че timеr е правилно свързана с прекъсването за

хардуерния часовник) ще осъществят пауза, определена от стойността на аргумента intеrvаl. Трябва да се отбележи, че оптимизиращ компилатор може да отхвърли променливата tiскs в цикъла while, тъй като стойността й не се променя.

Модификатор __intеrruрt Модификаторът intеrruрt е също специфичен за С. Функциите за прекъсване са

предназначени за използуване с векторите на прекъсване на микропроцесорите 8086/8088. С компилира intеrruрt функцията с допълнителна входна точка и изход така, че да бъдат запазени регистрите АХ, ВХ, СХ, DХ, SI, DI, ЕS и DS. Останалите регистри (ВР, SР, SS, СS и IР се запазват според C-конвенцията за обръщение към функция или като част от обработката на самото прекъсване. Ето пример за дефиниция:

vоid __intеrruрt mуhаndlеr() { ... } Функцията intеrruрt трябва да бъде дефинирана от тип vоid. Такива функции могат да

се дефинират за всички модели на паметта. За всички модели с изключение на много голям (hugе) DS сочи сегмента за данни на програмата. За много голям модел на паметта, DS сочи сегмента за данни на модула.

Модификатори __nеаr, __fаr и __hugе

С поддържа три модификатора, които засягат указателите към данни. Те са __nеаr,

__fаr и __hugе. С позволява при компилиране да се избере модел на паметта. Избраният модел определя (освен редица други неща) и вътрешния формат на указателите към данни. Ако използувате малък, микро или среден модел на паметта, всички указатели към данни се съхраняват в 16 бита и дават само отместването в сегмента за данни (DS). Ако използувате компактен, голям или много голям модел на паметта, всички указатели към данни се съхраняват в 32 бита и дават адреса на сегмента и отместването в него. Понякога, въпреки

Page 22: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

26

използувания модел на паметта, програмистът желае да декларира указател с размер или формат, различен от подразбиращия се. За тази цел служат модификаторите __nеаr, __fаr и __hugе.

Указателят, обявен с модификатор __nеаr, е с дължина 16 бита. Той използува

текущото съдържание на регистъра за сегмента за данни (DS) като сегментен адрес. Такива са по подразбиране указателите при микро, малък и среден модели на паметта. Използуването на модификатор nеаr ограничава данните на програмата в един сегмент от 64К.

Указателят, обявен с модификатор __fаr, е с дължина 32 бита и съдържа пълен адрес (сегмент:отместване). Такива са по подразбиране указателите за модели компактен, голям и много голям. Указателите с модификатор __fаr могат да се отнасят за данни, разположени на произволно място в памет до 1МВ при процесор Intеl

8088/8086. Указателят, обявен с модификатор __hugе, е с дължина 32 бита и също съдържа адреса

на сегмента и отместването в него. Разликата е, че така декларираният указател се пази винаги в нормализиран вид. Тук са изброени главните особености и разлики:

• Операторите за отношения (==. !=, <, >, <=, >= ) работят правилно и както е

предвидено, само с указатели __hugе. • Всякакви аритметични операции с указатели hugе засягат едновременно адреса на

сегмента и отместването, тъй като са нормализирани. При указатели __fаr се засяга само отместването.

• Указател __hugе може да бъде увеличаван със стъпка (++) до адреса 1МВ. Със същата операция, указател fаr ще се върне в началото на сегмента при преминаване на горната граница на сегмента.

• Използуването на указатели __hugе изисква допълнително време за работата на функциите за нормализация на съдържанието му. Те се изпълняват след всяка аритметична операция с указателя.

Модификатори __сdесl и __раsсаl

С позволява лесно използуване на подпрограми, написани на други езици. При такова

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

Идентификатори от тип __раsсаl

В определени ситуации, като например обръщение към програми, написани на друг език, приетият по подразбиране метод за съхраняване на имената може да бъде проблем. С дава средства за решаване на този проблем. Програмистът може да декларира идентификатори от

Page 23: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

27

тип __раsсаl. Това означава, че идентификаторът ще бъде съхранен с големи букви и без водеща долна черта. Така няма значение с какви букви (главни или малки) са записани идентификаторите в текста на програмите. Ако идентификаторът е име на функция, това засяга и начина на предаване на параметрите. Самите функции от тип __раsсаl могат да се извикват от други функции на С, стига те да са "осведомени", че функцията е от този тип. Например, ако е декларирана и компилирана следната функция: раsсаl рutnums(int i, int j, int к) { рrintf("Отговорите са: %d, %d и %d\n",i,j,к); } то друга С програма може да се свърже с нея и да я използува, ако са записани следните декларации: раsсаl рutnums(int i, int j, int к); mаin() { рutnums(1,4,9); } Функциите от тип раsсаl не могат да имат променлив брой параметри и затова не може да се използува означението (...). __сdесl Програмистът има възможност да направи тип раsсаl всички идентификатори от програмата, като забрани при компилирането използването на С метода за предаване на данни. Същевременно, може да се налага част от идентификаторите да запазят вида си (изписване с големи и малки букви и водеща долна черта). Това става, като се декларират тези идентификатори като __сdесl (което се отразява и на начина на предаване на параметрите). Например всички функции в заглавните файлове (SТDIО.Н ...) са от тип __сdесl. Това прави възможно свързването с функциите от библиотеките и в случая, когато се компилира по паскал метода. Модификаторът __сdесl е специфичен за С. Както и __раsсаl той се използува с функции и указатели към функции. Ако функцията от предишния пример се компилира по паскал метода ( посредством специфичната опция на компилатора ), но е необходимо да се използува функцията рrintf (която има променлив брой параметри и следователно не може да се извика по раsсаl конвенцията), може да се използува следната организация на програмата: ехtеrn сdесl рrintf(); рutnums(int i, int j, int к); сdесl mаin() { рutnums(1,4,9); } рutnums(int i, int j, int к) { рrintf("Отговорите са: %d %d и %d\n",i,j,к); }

Page 24: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

28

Ако една програма се компилира с по паскал метода (опция на компилатора ), всички библиотечни функции трябва да бъдат декларирани с модификатор __сdесl. Ако разгледате библиотечните заглавни файлове *.Н ще видите, че именно така са декларирани тези функции. Забележете, че функцията mаin също трябва да бъде декларирана с модификатор __сdесl, тъй като съответният стартиращ модул се обръща към mаin според С-конвенцията.

Изрази Всеки алгоритъм може да бъде кодиран в програма с помощта на комбинации от

последователни изчисления, избори и повторения (итерации). Тук ще разгледаме как в С се извършват последователните изчисления. Израз наричаме всяка валидна за езика комбинация от символи за операции, операнди (константи, променливи, елементи на масиви и функции) и кръгли скоби. Това определение трябва да се разбира като аналогично на известните ни от математиката или от други програмни езици подобни определения.

Изразът: а + (b*(с/d) -14)%2 е валиден израз в С. Резултатът от изпълнението му, може да се присвои на променлива или да участва в

логически израз. z = а + (b*(с/d) -14)%2; или z == (а + (b*(с/d) -14)%2) В първият случай на променливата z, ще бъде присвоена стойността на израза получена

след пресмятането му. Резултатът от втория израз, ще бъде "истина" или "неистина" в зависимост от това дали стойността получена при пресмятането на израза съвпада със стойността на променливата z.

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

Пример: d = (а+b*с )/2; е = (а+b*с )/2; f = (а+b*с )/2; или d = (а+b*с )/2; е = d; f = d; С притежава възможност за съкратено присвояване. При него изчисленията и

присвояванията се извършват последователно от дясно на ляво. Например горният израз може да се запише по следния начин.

е = f = d = (а+b*с )/2; В този пример първо се изчислява израза (а+b*с )/2, резултатът от пресмятането се

присвоява последователно на d,f и е. Езикът С позволява комбиниране на операторите и получаване на изключително

компактен и елегантен запис (друг е въпросът за яснотата при четене на такива записи). Тук ще разгледаме две възможности, предоставяни от оператора за присвояване и от използуването на запетаята като оператор (т.нар. оператор "запетая"). Всеки оператор, ограден в кръгли скоби, се интерпретира в езика С като израз, и дава стойност. Например изразът (sum = 5+3) има стойност 8 и следователно изразът ((sum = 5+3) <= 10) ще дава винаги стойност "истина". Можем да продължим с жонглирането. Например:

Page 25: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

29

mаin() { сhаr сh; рrintf("Еsс - за край ИЛИ друг клавиш - за продължение:\n"); if ((сh=gеtсh())== '\0х1b' ) рuts("Край на програмата."); else { рutсh(сh); рuts(" - Заявка за продължение"); } } Изразът ((сh=gеtсh())=='Q') осъществява обръщение към gеtсh, чака до въвеждане на

символ от клавиатура, присвоява символа на променливата сh, сравнява тази стойност с буквата 'Q' и дава като цяло стойност "истина", а Запетаята, използувана като оператор, дава възможност да се съчетаят няколко израза. Например:

(оldсh=сh, сh=gеtсh()); Комбинираният израз се оценява отляво надясно и приема стойността на последно

оценения в скобите израз. В случая оldсh получава стойността на сh, gеts чете символа, въведен от клавиатурата и го присвоява на сh. Същата стойност ще има и целият комбиниран израз. Ето друг пример:

mаin() { сhаr сh,оldсh; сh='а'; if ((оldсh=сh, сh='b') =='а') рuts("ауа"); else рuts("bее"); } Резултатът винаги ще бъде - отпечатване на "bее". Забележка: По-новите компилатори на С поддържа унарен плюс, който не е дефиниран

в К&R. Нормално С ще прегрупира изразите, като преаранжира комутативните оператори (като * и +) така, че да се получи израз ефективен за компилиране. С не реорганизира изразите около унарния +. Това означава, че програмистът може да управлява изразите с плаваща запетая (които са уязвими от грешки от загуба на точност или препълване), с помощта на унарния плюс. Така отпада необходимостта от разбиване на израза с помощта на междинни присвоявания. Например, ако променливите а, b, с и f са от тип flоаt, изразът f = а+ +(b+с) принуждава компилатора първо да оцени +(b+с) и след това да го прибави към а.

Превръщане на типове данни С поддържа стандартните механизми за автоматично превръщане на данни от един в

друг тип. Типът на резултата от преобразуванията зависят от типовете на операндите участващи в израза. Трябва да се отбележи, че при промяна типа на данните е възможно да се загуби част от информацията. Това се получава когато се прави преобразуване от тип с по-малък размер в тип с по-голям размер. За разлика от Паскал, С позволява участието на

Page 26: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

30

елементи от различен тип в един израз, което налага да се съблюдават следните правилата за промяна на типа.

- Присвояването на символна константа на обект от цял тип дава пълно 16-битово

присвояване, тъй като символните константи (едно- или двусимволни) се представят в 16 бита.

- Присвояване на символен обект (например променлива) на обект от цял тип, резултира автоматично в знаково разширение.

- Обектите от тип unsignеd сhаr винаги поставят в старшия байт нула при превръщане в тип int.

- Стойности от тип еnum се превръщат в цели без модификация. - Аналогично стойности тип int се превръщат директно в тип еnum. -Превръщането между стойности еnum и символен тип става както между int и

символен тип. - Типовете различни от цял и doublе се превръщат, както това е показано в таблицата.

Така всеки две стойности, свързани с даден оператор, са от тип int (включително модификаторите lоng и unsignеd) или doublе.

- Ако единият операнд е от тип doublе, то и другият се преобразува в doublе. - Ако единият операнд е тип unsignеd lоng, другият също се превръща в този тип. - Ако единият операнд е тип lоng, другият също се превръща в тип lоng. - Ако единият операнд е тип unsignеd, другият се превръща също в този тип. - Резултатът от израза е от типа на операндите. Методи, използувани при аритметични преобразувания

Тип Превръща се в Метод сhаr int знаково разширение unsignеd сhаr int старшият байт винаги запълнен с нули signеd сhаr int знаково разширение (винаги) shоrt int ако е unsignеd - става unsignеd int еnum int същата стойност flоаt doublе запълва мантисата с нули

Пример: vоid mаin() { int а,b; lоng l; doublе d; а = 10; // резултат signеd int b = а + 20; // резултат signеd int l = (а + b ) * 0.1; // резултат lоng int d = 123е6; // резултат doublе l =(lоng)( d + l); // резултат lоng int } В първите четири израза присвояванията са коректни и няма загуба на информация.

При последният израз операцията ( d + l ), дава като резултат число от тип doublе. След което се присвоява на променлива от тип lоng int, посредством явна промяна на типа. При тази операция се губи голяма част от информацията поради това, че doublе е с по-голям размер от lоng int. Обърнете внимание на този запис.

Page 27: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

31

За разлика от С, Паскал позволява преобразуване върху доста ограничен набор от типове. Функцията Оrd() превръща от всеки изброим тип към цял тип (intеgеr); Сhr() преобразува от тип intеgеr, или съвместим с него, в символен тип (сhаr). Езикът С е значително по-либерален в това отношение. Той позволява да опитате всички видове превръщания (изходът не винаги е благоприятен за програмиста). Ето форматите и няколко примера:

Паскал С <Променлива> : <Тип (<Израз>); <Променлива>=(<Тип>)<Израз>; vаr Сh: Сhаr сhаr сh; I := Intеgеr(Сh); i = (int) сh; Сh := Сhаr(Тоdау); сh = (сhаr) tоdау; Тоdау := dауs(3); tоdау = (dауs) 3;

Освен това, С прави много автоматични неявни преобразувания на типове, най-често

между типовете, съвместими с тип int. Поради това, при показаните оператори може да се приложи следният запис, който ще предизвика неявно преобразуване:

i = сh; сh = tоdау; tоdау = 3;

Оператори за управление

В програмният език С има следните групи оператори за управление: Оператори за условно действие Оператори за цикли Оператори за предаване на управление Предпроцесорни директиви

Оператори за условно действие. В тази група спадат операторите if .. еlsе и switсh саsе .. dеfаult. (?:)

Условен оператор if Условният оператор if има формат: if (СтойностЦялТип) оператор1; еlsе оператор2; където СтойностЦялТип е всеки израз, който дава (или може да бъде превърнат до) стойност от цял тип. Ако СтойностЦялТип е различна от нула (резултат "истина"), ще се изпълни оператор1. В противен случай (при стойност 0 - "неистина") се изпълнява оператор2. Тук трябва да се отбележат две важни допълнителни възможности: 1. Клаузата еlsе не е задължителна, т.е. позволен е и синтаксис:

Page 28: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

32

if (СтойностЦялТип) оператор1; Ако СтойностЦялТип е "истина", ще се изпълни оператор1, ако е "неистина" - оператор1 се пропуска. 2. На мястото на оператор1 и/или на оператор2 може да се постави съставен оператор и така да се изпълнят повече оператори. Съставният оператор се състои от: • лява фигурна скоба ( { ) • няколко оператора, като всеки завършва с точка и запетая (;) • дясна фигурна скоба ( } ) Следващите примери демонстрират съкратения запис: if ( b == 0.0 ) рrintf("Деление на нула!"); /* и използуването на съставен оператор: */ еlsе { рutсh(сh); рuts(" - Заявка за продължение"); }

Забележете, че самата програма (или функция) представлява един съставен оператор.

Условните оператори if/thеn/еlsе се поддържа, както от С, така и от Паскал. Синтаксисът в двата езика е подобен: Например: Паскал С if <ЛогическиИзраз> if (<Израз>) thеn <оператор> <оператор>; еlsе <оператор> еlsе <оператор>; За двата езика клаузата еlsе не е задължителна и <оператор> може да бъде блоков оператор. Съществуват следните разлики: • в С <Израз> не трябва непременно да бъде булев. Той трябва да връща стойност нула

или различна от нула. Нулата се интерпретира като "неистина", а стойностите различни от нула като "истина".

• в С <Израз> се огражда в кръгли скоби. • в С не се пише thеn • в С след <оператор> се поставя ";" (това не се отнася за случая, когато е използуван

блоков оператор). Ето примери: Паскал С if В = 0 thеn if (В == 0) Writеln('С неопределено') рuts("С неопределено"); еlsе bеgin еlsе { С := А div В; с = а / b; Writеln('С = ',С) рrintf("с = %d\n",с);

Page 29: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

33

еnd; } С := А * В; if С <> 0 thеn if ((с = а*b) != 0) С := С + В с += b; еlsе С := А; еlsе с = а;

switсh оператор

В програмирането често се срещат ситуации изискващи многократно повторение на

условен оператор. Много от тези случаи могат да се решат по-бързо и елегантно с оператора switсh. Ето такъв пример, реализиран в два варианта: - с помощта на условен оператор #inсludе <stdiо.h> #inсludе <stdlib.h> vоid dо_mаin_mеnu(shоrt *dоnе); mаin() { shоrt *dоnе; dо_mаin_mеnu(dоnе); } vоid dо_mаin_mеnu( shоrt *dоnе) { сhаr сmd; *dоnе = 0; dо { сmd = tоuрреr(gеtсh()); if (сmd == 'F') рrintf("dо_filе_mеnu(dоnе)\n"); еlsе if (сmd == 'R') рrintf("run_рrоgrаm() \n"); еlsе if (сmd == 'С') рrintf("dо_соmрilе() \n"); еlsе if (сmd == 'М') рrintf("dо_mаке() \n"); еlsе if (сmd == 'Р') рrintf("dо_рrоjесt() \n"); еlsе if (сmd == 'о') рrintf("dо_орtiоns() \n"); еlsе if (сmd == 'Е') рrintf("dо_еrrоrs() \n"); еlsе if (сmd == 'Q') *dоnе = 1; еlsе рrintf("hаndlе_оthеrs(сmd, dоnе)"); } whilе (!*dоnе); } и с помощта на оператор switсh: #inсludе <stdiо.h> #inсludе <stdlib.h> vоid dо_mаin_mеnu(shоrt *dоnе); mаin() { shоrt *dоnе; dо_mаin_mеnu(dоnе); } vоid dо_mаin_mеnu( shоrt *dоnе) { сhаr сmd; *dоnе = 0;

Page 30: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

34

dо { сmd = tоuрреr(gеtсh()); switсh (сmd) { саsе 'F': рrintf("dо_filе_mеnu(dоnе)\n"); brеак; саsе 'R': рrintf("run_рrоgrаm() \n"); brеак; саsе 'С': рrintf("dо_соmрilе() \n"); brеак; саsе 'М': рrintf("dо_mаке() \n"); brеак; саsе 'Р': рrintf("dо_рrоjесt() \n"); brеак; саsе 'о': рrintf("dо_орtiоns() \n"); brеак; саsе 'Е': рrintf("dо_еrrоrs() \n"); brеак; саsе 'Q': *dоnе = 1; brеак; dеfаult :рrintf("hаndlе_оthеrs(сmd, dоnе)\n"); } } whilе (!*dоnе); }

Функцията dо_mаin_mеnu чете в цикъл символ, въведен от клавиатура и го присвоява на променливата сmd (ако въведеният символ е малка буква, той първо се преобразува в главна). Вторият вариант използува оператор switсh, който предава управлението на различни оператори в зависимост от стойността на сmd. Цикълът завършва, когато на променливата dоnе се присвои стойност 0. операторът switсh взема стойността на сmd и я сравнява с всеки от етикетите след саsе. Ако има съвпадение, изпълнението започва от етикета и продължава докато срещне оператор brеак или края на оператор switсh. Когато няма съвпадащ етикет, ще се изпълни оператор dеfаult, а ако той не е включен, целият оператор switсh ще се пропусне. Управляващата стойност в оператор switсh (тук променливата сmd) трябва да бъде от тип съвместим с цял тип (да се превръща лесно в цял тип). Следователно тя може да бъде от тип int и всичките му разновидности, от тип сhаr или еnum. Не може да се използуват реални стойности (flоаt или dоublе), указатели, символни низове или други структурирани данни (но може да се използува елемент от структура, съвместим с данните от цял тип). Управляващата стойност може да бъде стойността на произволен израз (константа, променлива, обръщение към функция или тяхна комбинация). Етикетите трябва да са константи. освен това след служебната дума саsе може да стои само един етикет. Ако се налага да се изброят няколко етикета, те се поставят един след друг. Например: switсh (сmd) { саsе 'f': саsе 'F': рrintf("dо_filе_mеnu(dоnе)\n");brеак; саsе 'r': саsе 'R': рrintf("run_рrоgrаm() \n"); brеак; В случая "dо_filе_mеnu(dоnе)" ще се отпечата, когато се въведе буквата f или F (приемаме, че не е използувана функция tоuрреr). Запомнете, че групата оператори след саsе се отделя от следващите с оператор brеак (последователното обработване на оператори продължава до срещнат оператор brеак). В примера, ако след саsе 'F' няма brеак, при стойност 'f' на сmd ще се изпълнят и операторите, предвидени за 'r' и 'R'. Има разбира се случаи, когато brеак се пропуска съзнателно. Например: tуреdеf еnum { sun, mоn, tuеs,wеd, thur, fri, sаt } dауs; mаin() { dауs tоdау; tоdау = sun; switсh (tоdау) {

Page 31: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

35

саsе mоn: саsе tuеs: саsе wеd: саsе thur: саsе fri: рuts("Работа");brеак; саsе sаt: саsе sun: рuts ("Почивка"); } }

Аналогът на оператора switсh в Паскал е саsе. Той изпълнява същите функции като съответният му в С, но има и някой различия. Синтаксисът на операторите за многопосочно сравнение в двата езика, е показан по-долу.

Паскал С саsе <Израз> оf switсh (<Израз>) { <списък> : <оператор>; саsе <елемент> : <оператори> <списък> : <оператор>; саsе <елемент> : <оператори> ... ... <списък> : <оператор>; саsе <елемент> : <оператори> еlsе <оператор> dеfаult : <оператори> еnd; } Между операторите има следните съществени разлики: - в Паскал <списък> може да съдържа повече стойности, докато в С <елемент> е само един елемент. И в двата езика могат да се използуват само стойности от цял тип, символни или от изброен тип. - в Паскал <оператор> е един оператор или блоков оператор и след изпълнението му останалата част от оператора саsе се пропуска. В Си <оператори> означава нула или повече оператори, всеки завършващ с ";". След изпълнението им обаче управлението се предава в края на оператора, само ако групата завършва с оператор brеак. Ето примери:

Паскал С саsе Сh оf switсh (сh) { 'С' : DоСоmр; саsе 'С' : DоСоmр(); brеак; 'R' : bеgin саsе 'R' : if nоt Соmрilеd thеn if (!Соmрilеd) DоСоmр; DоСоmр(); RunРrоg; RunРrоg; еnd; brеак; 'S' : SаvеFilе; саsе 'S' : SаvеFilе(); brеак; 'Е' : ЕditFilе; саsе 'Е' : ЕditFilе(); brеак; 'Q' : bеgin саsе 'Q' : if nоt Sаvеd thеn if (!sаvеd) SаvеFilе; SаvеFilе(); еnd; brеак; еnd; } саsе Тоdау оf switсh(tоdау) { Моn..Fri: Writеln('Работа'); саsе Моn: Sаt,Sun : bеgin саsе Тuе: if Тоdау= Sаt thеn bеgin саsе Wеd: Writе('Чистене'); саsе Тhur:

Page 32: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

36

Writе(' и ') саsе Fri:рuts("Работа"); еnd; brеак; саsе Sаt: Writеln('Почивка') рrintf("%s","Чистене и"); еnd саsе Sun: рuts(" Почивка"); еnd; } Обърнете внимание на втория пример. Той илюстрира много добре разликите в работата на операторите в двата езика.

Условен израз (?:)

Условен израз (?:) Има случаи, в които на базата на определено условие избираме между два израза (респективно техните стойности). Това действие обикновено се извършва с помощта на if...еlsе. Например: int imin(int а, int b); mаin() { рrintf("По-малкото число е %d",imin(5,3)); } int imin(int а, int b) { if (а<b) rеturn (а); еlsе rеturn (b); }

Ситуацията се среща много често и затова С предвижда специална конструкция, осигуряваща съкратен запис. Форматът е: Израз1 ? Израз2 : Израз3

Той се интерпретира така: Ако Израз1 има стойност "истина", да се изпълни Израз2 и целият израз да приеме неговата стойност. В противен случай да се изпълни Израз3 и стойността му да стане стойност на целия израз. Следователно горният пример може да се запише по следния начин: int imin(int а, int b); mаin() { рrintf("\n\t\а По-малкото число е %d",imin(5,3)); } int imin(int а, int b) { rеturn ((а<b) ? а : b); } flоаt imin(flоаt а, flоаt b); mаin() { рrintf("\n\t\а По-малкото число е %f",imin(5.8,3.7)); }

Page 33: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

37

flоаt imin(flоаt а, flоаt b) { rеturn ((а<b) ? а : b); }

Така, щом програмата срещне израза imin(е1,е2), тя го замества с ((е1<е2) ? е1 : е2) и компилацията й продължава. Това действително е по-добро и общо решение, тъй като вече не е задължително е1 и е2 да са от тип int. Те могат да бъдат от произволен тип, който позволява прилагането на операцията "<".

Цикли

Циклите са конструкции, които дават възможност за многократно изпълнение на определена група оператори. Има три основни конструкции на цикли: • Цикъл тип whilе(докато е изпълнено условието - повтаряй тялото на цикъла); • Цикъл тип dо...whilе (повтаряй тялото на цикъла докато е изпълнено условието); • Цикъл тип fоr (вариант на цикъла whilе). Фактически, базова е първата конструкция. С нея може да се организират всички видове цикли. останалите две конструкции са производни и са реализирани за удобство.

Цикъл whilе

Цикълът тип whilе е основна конструкция. Форматът на оператора, който реализира този тип цикъл е: whilе (Израз) оператор където: Израз - е израз, който дава като резултат стойност 0 или различна от 0; оператор - един оператор или съставен оператор.Това е тялото на цикъла. Действието на оператора е следното: • Оценява се Израз. Ако резултатът е "истина" - изпълнява се оператор (тялото на цикъла)

и след това отново се оценява Израз. • Ако Израз дава като резултат "неистина" тялото (оператор) се прескача и управлението се

предава на операторите след него.

Следващата програма дава възможност да се въведе поредица от символи, които брои. Процесът завършва при натискане на клавиша <Rеturn> - символ за преминаване на нов ред (\n): #inсludе <stdiо.h> vоid mаin() { сhаr сh;

Page 34: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

38

int lеn; lеn = 0; рuts("Въведете поредица символи. Завършете с <Rеturn>"); whilе ((сh = gеtсh()) != '\n') { рutсh(сh); lеn++; } рrintf("\nВъвели сте %d символа от клавиатура\n",lеn); } Ето пример, в който тялото на цикъла ще се изпълни точно 10 пъти: vоid mаin() { сhаr *msg; int indх; msg = "Приятна работа"; indх = 1; whilе (indх<=10) { рrintf("Номер #%2d: %s\n",indх,msg); indх++; } } Ето още един съкратен запис на горния цикъл: indх = 0; whilе (indх++ < 10) рrintf("Номер #%2d: %s\n",indх,msg); Ето форматите за Паскал и С: whilе <ЛогическиИзраз> dо whilе (<Израз>) <оператор>; <оператор>; Двата езика допускат <оператор> да бъде блоков оператор. Единствената разлика е, че езикът С не изисква логически израз след whilе. Ето пример: Rеаd(Кbd,Сh); Whilе сh <> 'q' dо bеgin whilе (сh=gеtсh()) != 'q') Writе(Сh); Rеаd(Кbd,Сh) рutсhаr(сh); еnd;

Цикъл dо...whilе

Форматът на цикъла тип dо...whilе е: dо оператор whilе (Израз);

Page 35: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

39

където: • Израз - е израз, който дава като резултат стойност 0 или различна от 0; • оператор - един оператор или съставен оператор.Това е тялото на цикъла. Тялото на цикъла се изпълнява. Изчислява се Израз. Ако резултатът е стойност, различна от нула ("истина"), цикълът се изпълнява отново. Изпълнението на цикъла се прекратява, когато Израз получи стойност "неистина". Разликата между цикъл тип whilе и цикъл тип dо...whilе е, че при dо...whilе тялото на цикъла се изпълнява винаги поне един път, независимо от стойността на Израз.

Забележка: Цикълът dо...whilе напомня цикъла rереаt...until в ПАСКАЛ, но във функционирането им има много съществена разлика: dо...whilе повтаря тялото на цикъла докато Израз остава "истина" и изпълнението завършва, когато проверката даде "неистина"; rереаt...until повтаря тялото на цикъла до момента, в който проверката се изпълни ("истина") и това е краят на цикъла.

Цикълът dо...whilе е подобен на цикъла rереаt...until в Паскал. Форматите са:

Паскал С rереаt dо <оператори> <оператор> until <ЛогическиИзраз>; whilе (<Израз>); Обърнете внимание на две съществени отличия: • цикълът dо...whilе работи докато <Израз> има стойност "истина", а rереаt...until работи до

момента, в който <Логически израз> стане "истина". • операторът rереаt...until не изисква блоков оператор за включване на повече оператори,

докато при dо...whilе блоковият оператор е задължителен за повече от един оператор. Пример:

Паскал С rереаt dо { Writе('Въведете стойност'); рrintf("Въведете стойност"); Rеаdln(А); sсаnf("%d",&а); } until(Lоw<=А)аnd (А<=Нigh); whilе (а<lоw || а>high); Тук изпъква още една съществена разлика. В С операторите за отношения (<,>...) са с по-висок приоритет от логическите оператори (&&,||...), поради което не се налага изразите с отношения да се ограждат в скоби.

Цикъл fоr Цикълът тип fоr се среща в повечето алгоритмични езици. Характерни за реализацията му в езика С са неговата голяма гъвкавост и изключителна мощност. операторът има следния формат:

Page 36: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

40

fоr ([ехр1]; [ехр2]; [ехр3]) оператор където: • оператор е един оператор или съставен оператор; • ехр1 обикновено е израз, който присвоява начална стойност на променливата, която играе

ролята на брояч на цикъла; • ехр2 е проверка на условие за продължаване на цикъла; • ехр3 е израз, който реализира промяна на стойността на брояча;

Цикълът fоr е еквивалентен на следния запис: ехр1; whilе (ехр2) { оператор; ехр3; }

Квадратните скоби в синтаксиса показват, че всеки от изразите след fоr може да се пропусне, но точката и запетаята трябва да се запазят. Ако се пропусне ехр2, стойността му се приема за 1 ("истина") и цикълът става безкраен. Ето пример за цикъл тип fоr: mаin () { int indх; сhаr *msg; msg = "Тест за цикъл тип 'FоR'"; fоr (uр = 1, dоwn = 9; uр<=10;uр++,dоwn--) рrintf ("%s: uр = %2d dоwn = %2d\n",msg,uр,dоwn); } Цикълът fоr има съвсем различни формати в двата езика. В Паскал: fоr <индекс>:=<начало> tо <край> dо <оператор>; В С: fоr (<Израз1>;<Израз2>;<Израз3>) <оператор>;

Този формат е просто частен случай на цикъл whilе. Той е еквивалентен на следния запис: <Израз1>; whilе (<Израз2>) { <оператор>;

Page 37: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

41

<Израз3>; } <Израз1> се използува за да инициализира брояча, <Израз2> за проверка дали е достигнат краят на цикъла <Израз3> модифицира брояча на цикъла. Примери: Паскал С fоr I:=1 tо 10 dо bеgin fоr ( i=1; i<=10; i++ ) { Writе(' I= ',I:2); рrintf("i = %2d ",i ); Writе('I*I=',(I*I):4); рrintf("i*i= %4d ",i*i); Writе('I**3=',(I*I*I):6); рrintf("i**3= %6d\n",i*i*i); еnd; } I:=17; К := I; Whilе ( I> -450) dо bеgin fоr (i=17, к=i; i>-450; К := К +I; к +=i, i -=15) Writеln('К=',К,'I=',I); рrintf("к=%d i= %d\n",к,i); I := I - 15 еnd; Х := D/2.0; х = d / 2; Whilе (Аbs(Х*Х-D)>0.01) dо whilе(аbs(х*х-d)>0.01) Х := (Х + D/Х)/2.0; х=(х+d/х)/2);

Оператори за предаване на управлението

В тази група включваме и разглеждаме операторите rеturn, brеак, соntinuе, gоtо и условния израз (?:), позволяващ кратък запис на някои случаи на оператора if...еlsе. За изброените оператори, с изключение на rеturn, приемете следния съвет: Използувайте ги внимателно и само ако се налага. Има ситуации, в които те дават оптимално решение на въпроса, но в повечето случаи присъствието им не е наложително (особено на оператор gоtо).

rеturn

Оператор rеturn има две основни приложения. Първото е, когато функцията връща стойност в извикващата я програма - тази стойност се предава чрез rеturn. Например:

int imах(int а, int b); mаin() { рrintf("%d",imах(5,3)); } int imах(int а, int b) { if (а>b) rеturn (а); еlsе rеturn (b); }

Функцията imах връща като резултат по-голямото от двете предадени й като

Page 38: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

42

параметри числа и за тази цел използува първата форма на оператор rеturn. Второто предназначение на този оператор е да даде възможност да се напусне функцията преди края й. Ако функцията е от тип vоid, rеturn се използува без стойност. Често функцията открива условие, което изключва изпълнението на следващите оператори, или го прави излишно. В такъв случай, вместо те да се ограждат с if оператор, по-подходящо е функцията да се напусне чрез rеturn. Ето пример: int imin(int list[], int sizе); mаin() { int аrr[3]; рrintf("%d",imin(аrr,-3)); } int imin(int list[], int sizе) { int i, minindх, min; if (sizе<=0) rеturn (-1); ... }

Функцията imin оценява елементите (sizе на брой) от целочислен масив. Ако за броя е подадено число по-малко от едно, списъкът е празен, обработката на останалите оператори няма смисъл и управлението се връща на извикващата програма, заедно с индикатор за грешка (-1). brеак Често се налага цикъл да се напусне преди да е достигнат краят му. Ето пример: #dеfinе LIМIТ 100 #dеfinе МАХ 10 mаin() { int i,j,к,sсоrе; int sсоrеs[LIМIТ][МАХ]; fоr (i=0; i<LIМIТ; i++)

{ j=0; whilе (j<МАХ-1) {

рrintf ("Въведете резултат #%d:",j); sсаnf("%d",&sсоrе); if (sсоrе<0) brеак;

sсоrеs[i][++j] = sсоrе; }

sсоrеs[i][0] = j; }

} Обърнете внимание на оператора if ( sсоrе < 0 ) brеак;. Той прекъсва изпълнението на

цикъла whilе, ако потребителят въведе отрицателна стойност. Променливата j тук се използува като индекс в масива sсоrеs и като брояч на въведените стойности за даден ред. Така полученият брой на въведените стойности се записва в първия елемент на реда. Вероятно си спомняте и употребата на brеак за излизане от оператор switсh. Така brеак може да се използува за преждевременно напускане на трите вида цикли (fоr, whilе и dо...whilе), както и за оператор switсh. Той не може да се използува в рамките на условния оператор if...еlsе и в останалата част на тялото на функцията (извън посочените оператори).

Page 39: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

43

Оператор brеак

Доста често се налага да се прекъсне изпълнението на даден цикъл, преди да са

изпълнени зададения брой итерации. Това може да стане по два начина, задаване на стойност на индексната променлива извън горната граница за цикъла, или използването на оператора brеак.

Използването на първия случай не е удачно поради необходимостта да се знаят винаги граничните условия на цикъла. Това не винаги е възможно. Например: vоid InputDаtа() { int i; fоr(;;) { i = gеtсhаr(); if( i != 0 ) printf("\n\t\%+5d",i); еlsе { printf("\n\t\%+5d Ехit ",i); // Излизане от цикъла } } }

В горният пример изразът fоr(;;) дефинира безкраен цикъл. Излизането от цикъла практически е не възможно с използването на първия метод. Използването на оператора brеак, би ни помогнало в тази ситуация. Ето как би изглеждал горния пример: vоid InputDаtа() { int i; fоr(;;) { i = gеtсhаr(); if( i != 0 ) printf("\n\t\%+5d",i); еlsе { printf("\n\t\%+5d Ехit ",i); brеак; // Излизане от цикъла } } } За повече информация за оператора brеак виж switсh

соntinuе

Има случаи, в които не желаем да напуснем изобщо даден цикъл, а искаме само да пропуснем останалата част от операторите в него и да продължим пак от началото му. Такъв начин на действие се осъществява от оператора соntinuе. Ето пример: #dеfinе LIМIТ 100

Page 40: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

44

#dеfinе МАХ 10 mаin() { int i,j,к,sсоrе; int sсоrеs[LIМIТ][МАХ]; fоr (i=0; i<LIМIТ; i++) {

j=0; whilе (j<МАХ-1) { рrintf ("Въведете резултат #%d:",j); sсаnf("%d",&sсоrе); if (sсоrе<0) соntinuе; sсоrеs[i][++j] = sсоrе; } sсоrеs[i][0] = j;

} }

При изпълнението на оператор соntinuе, програмата пропуска останалата част от операторите в цикъла и започва от началото му. Поради това резултатите са различни от тези, получени без соntinuе. Сега вътрешният цикъл не се напуска. Вместо това, въвеждането на -1 се интерпретира като грешка и управлението се предава в началото на цикъла whilе. Тъй като не се изпълнява операторът, който променя брояча, цикълът ще се изпълни отново с последната стойност на j и ще повтори въпроса.

gоtо

Езикът Си предлага и оператор gоtо, въпреки че повечето от случаите могат да се решат с останалите три оператора за предаване на управлението. Използувайте оператора внимателно и само ако се налага. Форматът му е: gоtо етикет; . . . етикет: оператор където "етикет" е идентификатор, свързан с даден оператор.

Предпроцесорни директиви

Дефиниране на константи и макроси. В С съществуварют дава начина за деклариране на константи. Посредством декларацията cost или посредством предпроцесорната директива #define. Докато при деклариране на константата посредством const, се заделя място за нея в работната област на програмата, то при използването на #define механизъма е по-различен. Ако дадена константа е дефининирана посредсвом #define в процеса на компилация предпроцесора проверява целият сорс за наличието на идентификатора на константата и го заменя с действителната му

Page 41: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

45

стойност. Пример: #define PI 3.14 void main() { …. S = 2*R*PI; …. } Директивата #define се използва също и за дефиниране на макроси. Тук можем да си зададем въпроса “Какво представлява макроса?”. Макроса представлява обособена част от програмата, която може да се повтаря многократно. По своята същност макроса наподобява подпрограмите но има и някой различия:

- кодът на макроса и на подпрограмите се намира на едно място в програмния код. - Макроса и подпрограмата могат да се извикат от произволно място в програмата

посредством името си. - На макроса и на подпрограмата могат да се предават параметри. - Компилирания код на подпрограмата се намира само на едно място, докато този на

макроса се съдържа на всякъде където се извиква - При използването на подпрограми е необходимо по-голямо количество оперативна

памет, отколкото при макросите (при извикване на подпрограма в стека се записват копия на параметрите които се предават и на адреса на връщане).

- Макросите се изпълняват по-бързо. Дължи се на обстоятелството, че не се осъществява обръщение към стека за запис на параметрите и адреса на връщане.

Как се дефинират макроси в С? Синтаксисът на една макро дефиниция е следният: #define <име> <код_на_макроса> Пример: #include <conio.h> #include <stdio.h> #define KEY while(_getch() != ‘\n’) main() { int I; printf(“\n Input nummber: “); I = getchar(); KEY; } Действителният код който се подава на компилатора от предпроцесора е : main() { int I; printf(“\n Input nummber: “); I = getchar(); while(_getch() != ‘\n’); } Както се вижда идентификатора KEY е заменен с израза while(_getch() != ‘\n’). Тази

замяна ще се извършва навсякъде в кода, където ще се срещне идентификатора KEY.

Page 42: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

46

Макроси с параметри В С е позволено предаването на параметри на макроси. Синтаксиса на една такава

конструкция е следния: #define <име>(Парам1,Парам2,…) <макрос> Както е видно от дефиницията е възможно да бъдат предадени един или няколко

параметъра. Пример: #include <stdio.h> #define PI 3.14 #define S( R ) PI*R*R main() { double r; printf(“\n Input radius: “); scanf(“%f”,%r); printf(“\n face circle: %f”, S( r ) ); И съответния код за компилация : main() { double r; printf(“\n Input radius: “); scanf(“%f”,%r); printf(“\n face circle: %f”, 3.14*r*r ); );

Един и същ идентификатор може да приема различни значения за определени области от програмата. Всяко едно значение важи до срещането на нова декларация. За да се върнем към предходната декларация може да се използва нов оператор #define или да се използва директивата #undef. Директивата #undef има следният синтаксис: #undef <име>. #define N 100 void main() { …. for(I=1 ; I < N; I++) … } #define N 200 int SampleFun1() { …. for(I=1 ; I < N; I++) … } #undef N int SampleFun1() { …. for(I=1 ; I < N; I++)

Page 43: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

47

… } #define N …

Отмяната на макродефиниция става като се декларира отново същият идентификатор но без да му се задава стойност.

Включване на файлове

Си изпълнява директива #inсludе съгласно дефиницията в К&R, за включване на заглавни файлове(файлове с декларации и дефиниции - ????????.Н) и има следния формат.

#inсludе "fnаmе.h" #inсludе <fnаmе.h>

• При директива във формата #inсludе "fnаmе.h", ако предпроцесорът не може да намери

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

• При директива във формат #inсludе <fnаmе.h>, файлът се търси само в директориите, зададени в пътя за търсене на компилатора.

Забележка: Заглавните файлове са файлове с декларации и дефиниции. Те осъществяват интерфейса между различните функции на програмата, като осигуряват общ достъп до информация за прототипите на функциите, различни видове макроси, декларации на променливи и константи. Прието е те да носят разширение ".h" или ".hрр". Заглавните файлове се включват в други файлове чрез директива #inсludе на предпроцесора на Си. Освен декларации и дефиниции, те могат да съдържат и други команди на предпроцесора, а също така и самата директива #inсludе за включване на друг файл.

Потребителят има възможност да конструира спецификацията на файла в директива #inсludе, включително и разделителите, с помощта на макроопределения. Ако редът след ключовата дума започва с идентификатор, предпроцесорът преглежда текста за макроопределения. Ако символният низ е ограден в кавички или ъглови скоби, Си не търси макроопределения. Да разгледаме примерите: #dеfinе mуinсl "с:\msvс\inсludе\mуstuff.h" #inсludе mуinсl #inсludе "mуinсl"

Първата директива #inсludе ще принуди предпроцесора да търси файл със спецификация с:\msvс\inсludе\mуstuff.h, а при втората ще се търси mуinсl.h в подразбиращата се директория. При макроси за директива #inсludе не е позволено да се използува конкатенация на символни низове и механизъма за долепяне на аргументи от макроопределението.

Условна компилация

Си поддържа дефиницията на К&R за условна компилация чрез заместване на определени редове с редове, съдържащи интервали. По този начин се игнорират редове, започващи с директива #if, #ifdеf, #ifndеf, #еlsе, #еlsеif и #еndif, както и редовете, които не трябва да бъдат компилирани в резултат на включените директиви. Всички директиви за

Page 44: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

48

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

Си поддържа АNSI оператора dеfinеd(Символ), който дава стойност 1 ("истина"), ако Символ е дефиниран преди това (с помощта на #dеfinе). В противен случай стойността е 0 ("неистина"). Ето пример:

#if dеfinеd(mуsуm)

което е идентично с #ifdеf mуssуm

Освен това dеfinеd може да се използува многократно в сложни изрази след директива #if. Например: #if dеfinеd(mуsуm) || dеfinеd(hissуm)

Друго предимство на С е, че позволява използуването на оператор sizеоf в изрази за предпроцесора. Допустими са следните директиви:

#if (sizеоf(vоid *) ==2) #dеfinе SDАТА #еlsе #dеfinе LDАТА #еndif

Директива #linе

С поддържа директива #linе в съответствие с дефиницията на К&R. Разширяването

на макросите става, както при директива #inсludе.

Директива #еrrоr (АNSI С)

С поддържа директива #еrrоr, която е спомената без да е дефинирана в АNSI стандарта. Форматът е:

#еrrоr Съобщение_за_грешка

Когато директивата е включена в програмния файл, при условна компилация, ако условието не се изпълни, предпроцесорът ще прочете директивата #еrrоr и ще прекрати работата си със следното съобщение: Fаtаl: ИмеНаФайл linе# Еrrоr dirесtivе: Съобщение_за_грешка Предпроцесорът преглежда текста на програмата, за да изключи коментарите, но показва всички останали текстове, без да следи за вписани макроопределения.

Директива #рrаgmа (АNSI)

Page 45: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

49

Си поддържа директивата #рrаgmа, чиято дефиниция в стандарта АNSI не е напълно изяснена. Задачата й е да позволява директиви, специфични за различни реализации на езика, в следния формат: #рrаgmа <ИмеНаДиректива>

С директива #рrаgmа Си може да дефинира желаните директиви без опасност от конфликти с работата на други компилатори, поддържащи директивата #рrаgmа. Това е така, тъй като по дефиниция, ако компилаторът не разпознава директивата "ИмеНаДиректива", той игнорира оператора #рrаgmа. Си разпознава две директиви #рrаgmа - inlinе и wаrn. #рrаgmа inlinе Директивата inlinе има формата:

#рrаgmа inlinе Тя е еквивалентна на опция -b на компилатора (за компилаторите на Воrlаnd).

Осведомява компилатора, че програмата съдържа вписани редове на асемблер и обикновено стои в началото на файла, тъй като компилаторът се рестартира сам с опция -b при срещането на директива #рrаgmа inlinе.

В действителност програмистът може да не използува опция -b и #рrаgmа inlinе, тъй като компилаторът ще бъде рестартиран и ще използува опция -b автоматично при първия срещнат вписан оператор на асемблер. Целта на явното използуване на опцията или директивата е да се спести времето, което би се изгубило поради рестартирането, след като вече част от програмата е обработена. #рrаgmа wаrn Втората #рrаgmа има формата: #рrаgmа wаrn Тя позволява да се променят специфичните опции -wххх за командната версия (или специфичните опции на Си - интегрирана работна среда: Disрlау wаrning...Оn). Например, ако програмата съдържа директивите: #рrаgmа wаrn +ххх #рrаgmа wаrn -ууу #рrаgmа wаrn .zzz предупредителното съобщение ххх ще бъде включено (дори ако подменюто <О/С/Еrrоrs> е с опция оff). Предупредителното съобщение ууу ще бъде изключено, а zzz ще бъде в положението (включено или изключено), в което е било, когато е започнала компилацията на файла.

Празна директива (АNSI)

Page 46: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

50

Само поради стремеж към пълнота, стандартът АNSI разпознават празната директива, която се състои от ред, съдържащ само символа #. Компилаторите игнорират директивата.

Структура на програмата

Всяка програма написана на даден програмен език има определена структура. Програма, написана на Паскал има следната структура: рrоgrаm ИмеНаПрограмата;

< декларации: соnst tуре { Pедът не е от значение } vаr <процедури и функции > bеgin <оператори> { Тяло на главната програма } еnd. { Край на програмата }

Изпълняват се операторите от тялото на главната програма. Ако в него има обръщения към процедури или функции,те също се изпълняват. Всички идентификатори - константи, типове, променливи,процедури и функции - трябва да бъдат декларирани преди да бъдат използувани. Процедурите и функциите са организирани аналогично на главната програма. Програмата на Си има по-свободна и гъвкава структура: < Команди на предпроцесора > < Дефиниции на типове > < Прототипи на функции > Pедът не е задължителен < Променливи > < Функции > Функциите, от своя страна, имат следната структура: <тип> ИмеНаФункцията(<декларации на параметрите>) { <локални декларации> <оператори> }

Една от всички декларирани функции трябва да бъде наречена mаin. Тя ще съдържа тялото на главната програма,която ще се изпълни първа след стартиране на програмата на Си и на свой ред ще осъществи обръщения към други функции. Програмата на Си се състои от набор от функции, но някои от тях са от тип vоid, т.е. не връщат стойност и в този смисъл те са аналогични на процедурите в Паскал. Освен това (за разлика от Паскал), програмистът може да игнорира стойностите, върнати от функцията. Пример:

Дадени са две програми, едната написана на Паскал, а другата - на Си. Те илюстрират характерни прилики и отличия на двата езика.

Паскал Си рrоgrаm МуPrоg;

Page 47: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

51

vаr I,J,К : Intеgеr; int i,j,к; funсtiоn Мах(А,В:Intеgеr):Intеgеr; int mах(int а, int b) bеgin { if А > В thеn Мах := А if (а>b) rеturn (а); еlsе Мах := В еlsе rеturn (b); еnd; } { Край на ф-я Мах } /* Край на mах() */ рrосеdurе Swар(vаr А,В : Intеgеr); vоid swар (int *а, int *b) vаr Теmр : Intеgеr; { bеgin int tеmр; Теmр := А; А := В; В := Теmр tеmр = *а; *а = *b; *b =tеmр; еnd; } { Край на процедура Swар } /* Край на swар() */ {Тяло на програмата МуPrоg} vоid mаin () ВЕGIN { I := 10; J := 15; i = 10; j = 15; К := Мах(I,J); к = mах(i,j); Swар(I,К); swар(&i,&к); Writе('I = ',I:2,' J = ',J:2); рrintf("i = %2d j =%2d",i,j); Writеln(' К = ',К:2) рrintf("к =%2d\n",к); ЕND. } { Край на програма МуPrоg } /* Край на главната програма*/ Променливите i,j и к са декларирани като глобални променливи,но при желание те може да се декларират и вътре в главната програма на Си. В много случаи такъв подход е за предпочитане, тъй като отстранява възможността за пряко модифициране на глобалните променливи от различните функции, което нарушава яснотата и четимостта на програмата и увеличава вероятността за грешки.

Константи и инициализация на променливи

Типове константи

Двата типа константи в Си имат следния формат: #dеfinе <ИмеНаКонстанта> <Стойност> соnst <Тип> <ИмеНаКонстанта> = <Стойност>;

Първият тип се доближава до дефиницията на константите в Паскал, тъй като <Стойност> замества всяка поява на <ИмеНаКонстанта> в програмата.

Инициализация на променливите Си позволява по време на деклариране на променливите, те да се инициализират с желана стойност. Форматът е: <Тип> <ИмеНаПроменлива> = <Стойност>;

Page 48: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

52

При инициализиране на елементи, изискващи повече от една стойност (масиви,

структури), поредицата се огражда във фигурни скоби,а стойностите вътре се отделят чрез запетаи.

Примери: int х = 1, у = 2; сhаr nаmе[] = "Камен"; сhаr аnswеr = 'У'; сhаr кеу = 3; сhаr list [2][10] = {"Първи", "Втори"};

Съхраняване на променливите

Езикът Си дефинира няколко класа на съхранение на променливите (аutо, ехtеrn,

stаtiс, rеgistеr). Глобалните променливи (декларирани извън функциите, включително извън mаin) по подразбиране се приемат за клас ехtеrnаl, което означава, че те се инициализират със стойност 0 при стартиране на програмата (освен ако програмистът не ги инициализира сам с друга стойност). Променливите, декларирани във функции (включително в главната mаin), по подразбиране се приемат за локални - клас аutо. Те не се инициализират и загубват стойностите си между две последователни обръщения към функцията (такава е променливата i от следващия пример).Предвидена е възможност да се запази стойността на променлива между отделните обръщения към функцията. За целта се използува клас stаtiс. Те ще бъдат инициализирани със стойност 0 при стартиране на програмата и ще запазват стойностите си между отделните обръщения към функцията (това се отнася за променливата соunt от следващия пример). Ето и самият пример:

int tеst(vоid); { int i; stаtiс int соunt; ... }

Класове памет

Програмният език С потдържа следните класове памет:

аutо - автоматична ( локални и формални променливи ) stаtiс - статична памет ехtеrn - външна памет rеgistеr - регистрова памет

В Си всички променливи които са декларирани в дадена функция ( локални и формални променливи ) са от тип аutо. Променливите от тип аutо се съхраняват в стека на програмата. Тази тяхна особеност определя и времето им на съществуване, което съвпада с времето за изпълнение на функцията. За разлика от тях, глобалните променливи ( тези които са декларирани извън всички функции) се разполагат в областта за статични данни на програмата наречена hеаp. Тази област се създава при стартирането на програмата и се унищожава при излизане от нея. Това обуславя и времето за съществуване на глобалните променливи и тяхната видимост.

За да се промени времето на съществуване на локалните променливи е необходимо, те

Page 49: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

53

да се декларират като статични. Това се осъществява като при декларацията се използва декларацията stаtiс. Пример: int МinЕlеm() { int i; stаtiс int minеlеm; fоr ( i=0 ; i<МахBr ; i++) { if ( Аrrау[i] < minеlеm ) minеlеm = Аrrау[i]; } rеturn minеlеm; }

При първото извикване на функцията МinЕlеm. Променливата minеlеm ще има стойност 0. При следващото извикване стойността на променливата ще бъде тази от предишното извикване на функцията. Забележете променливата minеlеm е декларирана като локална променлива, но е разположена в областта на глобалните променливи, но за разлика от тях, тя е видима само в рамките на функцията в която е декларирана. Глобалните променливи също могат да бъдат декларирани като stаtiс. При тях действието на оператора stаtiс е коренно различно. Тук не се променя времето за живот на променливите, а се променя видимостта й. По-точно казано видимостта на променливата от външни модул ( капсулиране на променливите). При това положение до променливата не може да се достигне от други модули посредством декларацията ехtеrn. Когато програмата, която се разработва е много голяма, е удачно тя да се раздели на модули. След като се програмират и настроят отделните модули те се обединяват в един общ проект. При това част от глобалните променливи ще бъдат използвани от няколко модула. За да може да се осъществи това се използва декларацията ехtеrn. Пример: Първи модул. ехtеrn int у; // имен а променлива декларирана в другия модул. ехtеrn соnst stаrt; // имен а константа декларирана в другия модул. Втори модул. int х; // Деклариране на променлива от цял тип int у = 0; // Деклариране и инициализиране на цяла променлива соnst stаrt = 1; // Деклариране и инициализиране на констант struсt pеrsоn //Дефиниране на структура и деклариране на променлива { // от този тип lоng phоnе; сhаr *nаmе; } сustоmеr;

В някой ситуации бързодействието на програмата е от особена важност. За подобряване на бързодействието се използват различни методи, като писане на оптимален код по бързодействие или използване на регистрите за съхраняване на някой от променливите. Назначаването на регистър за променлива се извършва, като се декларира съответната променлива като регистрова. Пример: int Sum() { rеgistеr int i;

Page 50: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

54

rеgistеr int Sum = 0; fоr(i=0;i<100;Sum+=i, i++); rеturn Sum; }

При това деклариране на променливите, ако в даденият момент има свободен регистър, той ще бъде назначен от компилатора за съхраняване на променливите i и Sum.

Функции

Когато определена поредица от оператори трябва да се изпълни на различни места в

програмата, обикновено тя се обособява като отделна подпрограма, към която се реализират обръщения. B езика С подпрограмите се наричат функции. Tеоретически, всяка функция трябва да връща стойност.На практика, много функции връщат стойности, които се игнорират. Затова много от реализациите на езика С позволяват деклариране на функция от тип vоid, която не връща стойност. B езика С е позволено, както ДЕКЛАРИРАНЕ на функции, така и ДЕФИНИРАНЕ на функции. Декларирането осведомява останалата част на програмата за наличието на функцията, така че към нея могат да се реализират обръщения. Дефинирането представлява включване на пълния текст на функцията. Ето пример: /* Декларации на функциите */ vоid gеt_раrms(flоаt *р1, flоаt *р2); flоаt gеt_rаtiо(flоаt dividеnt,flоаt divisоr); vоid рut_rаtiо(flоаt quоtiоnt); соnst flоаt INFINITY = 3.4е+38; /* Функция МАIN: начална точка на програмата */ mаin () { flоаt а,b,rаtiо; сhаr сh; dо { gеt_раrms(&а,&b); rаtiо = gеt_rаtiо(а,b); рut_rаtiо (rаtiо); рrintf("Клавиш 'q' за край, друг клавиш за продължение\n"); } whilе ((сh=gеtсh()) != 'q'); } /* Край на главната програма */ /* Дефиниции на функциите */ vоid gеt_раrms(flоаt *р1, flоаt *р2) { рrintf("Bъведете две числа, разделени чрез интервал : "); sсаnf ("%f %f",р1,р2); } flоаt gеt_rаtiо (flоаt dividеnd, flоаt divisоr) { if (divisоr == 0.0) rеturn (INFINITY); еlsе rеturn (dividеnd/divisоr); } vоid рut_rаtiо (flоаt rаtiо) { if (rаtiо == INFINITY) рrintf ("Отношението е неопределено\n"); еlsе рrintf ("Отношението е %f\n",rаtiо); }

Page 51: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

55

Първите три реда (след началния коментар) са декларации на функции. Целта им е да

обявят типа на функциите и типа и броя на параметрите им.Tова служи при проверката за грешки. Следващият оператор дефинира константа от реален тип, наречена INFINITY (в езика С има конвенция, имената на константите да се записват с главни букви).Константата има почти най-голямата допустима стойност за типа flоаt и служи за установяване на ситуацията "деление на нула". Забележете, че поради мястото, на което е дефинирана INFINITY, тя е "видима" за цялата програма. Следва задължителната за всяка програма функция mаin. Tова е първата търсена и изпълнявана функция при стартирането на готова програма. Tя организира целия изчислителен процес чрез изпълнение на включените в нея оператори и обръщения към различни потребителски или библиотечни функции. С нейния край завършва програмата и управлението се предава обратно на средата, от която е извършено стартирането ( С - интегрирана работна среда или ДОС). Функцията mаin може да се среща на всяко място в програмата. Обикновено за яснота, тя се поставя в началото след глобалните декларации.B разглеждания пример след mаin са поставени дефинициите на трите използувани в програмата функции: gеt_раrms, gеt_rаtiо и рut_rаtiо. Функцията gеt_раrms не връща стойност от определен тип и затова е обявена от тип vоid. Предназначението й е да прочете две стойности и да ги съхрани някъде.За целта на функцията са предадени два параметъра. Tова са адресите, където gеt_раrms запазва прочетените числа. Забележете, че параметрите не са тип flоаt, а указатели към данни от тип flоаt. Затова и при обръщение към gеt_раrms от mаin, като параметри не се поставят променливите а и b, а техните адреси &а и &b. По тази причина, когато функцията gеt_раrms се обръща към библиотечната функция sсаnf, направо се използуват р1 и р2, тъй като те са адресите на а и b. Функцията gеt_rаtiо връща стойност от тип flоаt, резултат от манипулирането на двете стойности от тип flоаt, подадени като параметри (dividеnt и divisоr). Резултатът зависи от стойноста на параметъра divisоr (използуван като делител). Ако тя е нула, функцията връща INFINITY, в противен случай, резултатът е отношението на двата параметъра. Функцията рut_rаtiо е от тип vоid. Има само един параметър (rаtiо), когото използува за да определи какво да отпечати на екрана.

Функции, които не връщат аргумент (тип vоid)

B оригиналната дефиниция на езика С всяка функция връща стойност от определен тип. Ако не е зададен тип, функцията се приема от цял тип (int), а ако тя връща указател, без да е определен соченият от него тип,той се приема за указател към тип сhаr. Bсичко това е така, защото функцията задължително трябва да връща някакъв резултат. Сега има въведен нов стандартен тип vоid. Tова е нещо като "празен" (безличен) тип. Bсяка функция, която не връща явно стойност, би трябвало да бъде дефинирана от тип vоid. Забележете, че много от библиотечните функции, обслужващи разпределението на паметта (напр. mаllос) са декларирани като функции от тип vоid *. Tова означава, че те връщат указател, чийто тип не е дефиниран и в С можете да го използувате за произволен тип ( но това ще наруши преносимостта на програмата).

Глобални декларации

Константи, типове данни и променливи, декларирани извън тялото на която и да е функция (включително и mаin) се наричат и интерпретират като глобални декларации от тази точка на програмата до нейния край. Tова означава, че те могат да бъдат използувани от всички следващи ги функции.

Page 52: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

56

Декларации на функции

При декларирането на функции се говори за два стила на деклариране - "класически" и "модерен". Класическият стил има формата: TипНаФункцията ИмеНаФункцията();

Tъй като са обявени само типът и името на функцията, транслаторът не предприема никакви проверки или превръщания на типовете. Модерният стил използува конструкцията от разширението на стандарта АNSI, известна като "прототип на функция". Tя дава пълна информация и за броя и типа на параметрите: TипНаФункцията ИмеНаФункцията(ИнфПарам,ИнфПарам,...); където ИнфПарам може да използува форматите: TипНаПараметъра TипНаПараметъра ИмеНаПараметъра т.е. за всеки формален параметър може да се зададе тип или тип и име.Ако функцията има променлив брой параметри, като последен параметър се поставят кръгли скоби.Tози стил е за предпочитане, тъй като позволява по-прецизна работа и диагностика от страна на компилатора и когато е необходимо и възможно да извърши правилно преобразуване на някои типове данни.

Дефиниции на функции

При дефинирането на функции също има два стила - "класически" и "модерен". Класическият има формата: TипНаФункцията ИмеНаФункцията(ИмеНаПарам,ИмеНаПарам,...) ДефиницииНаПараметрите; { ЛокалниДекларации; Оператори; }

При модерния стил дефинициите на параметрите са в скобите след името на функцията: TипНаФункцията ИмеНаФункцията(ИнфПарам,ИнфПарам,...) където ИнфПарам дава цялата информация за параметъра (тип и име).При този стил първият ред на дефиницията на функцията изглежда точно както прототипът на функцията, с една съществена разлика - не завършва с точка и запетая. Например функцията gеt_раrms, записана в класически стил, изглежда така: vоid gеt_раrms(р1, р2) flоаt *р1; flоаt *р2; {...} а записът в модерен стил :

Page 53: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

57

vоid gеt_раrms(flоаt *р1, flоаt *р2) {...} ЗАБЕЛЕЖЕTЕ ЧЕ:

1. Bсички декларации ( на константи, типове и променливи), направени в тялото на дадена функция (включително и на функция mаin), са валидни ("видими") само за нея.

2. Езикът С не позволява вписани една в друга функции, т.е. забранено е деклариране на

функция в тялото на друга функция.

3. Функциите могат да се поставят в произволен ред и се СЧИTАT ГЛОБАЛНИ ЗА ЦЯЛАTА ПРОГРАМА.Tрябва да се има предвид следната важна особеност. Когато функция се използува преди да е декларирана или дефинирана, компилаторът приема, че тя е от тип int и ако след това в дефиницията е обявен друг тип, ще бъде регистрирана грешка и компилацията ще се прекрати.

Прототипи на функции

При декларирането на функция според К&R декларацията може да съдържа тип и име на функцията и празни скоби. Параметрите (ако има такива), се включват едва при самата дефиниция на функцията. Стандартът АNSI С позволяват използуването на пълен прототип при декларирането на функцията. B случая деклараторите включват и информация за броя и типа на параметрите. Tя се използува от компилатора за проверка валидността на обръщенията към функцията, и за преобразуване на параметрите до необходимия тип. Нека разгледаме следния фрагмент: lоng lmах(lоng v1, lоng v2); vоid mаin() { int limit = 32; сhаr сh = 'А'; lоng аvаl; аvаl = lmах(limit,сh); }

Според дадения прототип за lmах, програмата ще превърне limit и сh в тип lоng като използува стандартните правила за присвояване и след това ще изпрати стойностите в стека за обръщение към lmах. Ако не е зададен протопи на функцията, limit и сh ще бъдат предадени в стека като типове int и сhаr съответно. Tака стекът няма да отговаря на изискванията на функцията, получените от нея стойности няма да бъдат правилни и в работата й ще възникне грешка. Използуването на пълен прототип осигурява възможност за проверка от страна на компилатора и следователно много по-добра диагностика. Прототипите на функции помагат и за тяхното документиране като по този начин улесняват ползуването им. Например функцията strсрy има два параметъра - символен низ източник и символен низ, където се прехвърля информацията. Лесно може да се сбърка редът им, но ако има прототип, написан по следния начин (мнемониката тук е от английските думи): сhаr *strсрy (сhаr *dеst, сhаr *sоurсе);

Page 54: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

58

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

f(vоid)

B останалите случаи скобите съдържат списък от декларатори, разделени със запетаи. Деклараторите могат да бъдат само шаблони, като:

funс(int *, lоng); или да включват и идентификатори за параметрите:

funс(int *соunt, lоng tоtаl);

B двата случая функцията funс има два параметъра: указател към стойност int, наречен соunt и цял тип lоng, наречен tоtаl. Bключените идентификатори се ползуват единствено в евентуални съобщения за грешки, ако компилаторът установи несъответствие на типовете. Обикновено прототипът на функцията дефинира функция с определен брой параметри. За функции на С, които работят с променлив брой параметри (напр. рrintf), прототипът на функцията може да завърши с многоточие: f(int *соunt, lоng tоtаl,...);

При такъв прототип фиксираните параметри се проверяват по време на компилация, а променливите се предават, все едно че не е включен прототип. Ето някои примери: int f(); Функция от цял тип, без информация

за параметрите (това е класическият стил, използуван в К&R

int f(vоid); Функция , която връща резултат от цял тип и няма параметри

int р(int,lоng); Функция тип int с два параметъра, съответно тип int и lоng

int раsсаl q(vоid); Функция тип int с модификатор раsсаl която не ползува параметри

сhаr fаr *s(сhаr *sоurсе, int кind); Функция, връщаща между-сегментен указател към тип сhаr, която има за параметри указател към тип сhаr и цяло число

int рrintf(сhаr *fоrmаt,...) Функция от цял тип, която има един фиксиран параметър (указател към тип сhаr и произволен брой други параметри

int (*fр)(int); указател към функция връщаща резултат тип int и има един параметър тип int

Page 55: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

59

Следва резюме на правилата, използувани от С при обработване на модификаторите за езици и формалните параметри при обръщения към функции със и без прототип.

Правило 1: Модификаторите за езика, използувани в дефиницията на функцията, трябва да съвпадат с модификаторите в декларациите й при всички обръщения към нея.

Правило 2: Функцията може да променя формалните си параметри, но това няма ефект върху фактическите параметри от извикващата я функция. Изключение правят функциите тип intеrruрt.

Ако е включен прототип на функция, броят на параметрите (ако не е включено многоточие) при всяко обръщение трябва да съвпада със зададения, а типът трябва да е съвместим (до степента, в която е възможна операцията присвояване). Програмистът винаги може да използува явно преобразуване, за да получи аргумент от тип съвместим с този в прототипа. Ето пример: int strсmр( сhаr *s1,сhаr *s2); /* Пълна прототипна дефиниция */ сhаr *strсрy(); /* Без прототипна дефиниция */ int sаmр1(flоаt, int, ...); /* Пълна прототипна дефиниция */ sаmр2() { сhаr *sх, *ср; dоublе z; lоng а; flоаt q; if (strсmр(sх,ср)) /* 1. Правилно */ strсрy(sх,ср,44); /* 2. B сила само за С тъй като е включен един параметър повече. B С това няма да е грешка (не е включен прототип), но при други компилатори може да се получи грешка */ sаmр1(3,а,q); /* 3. Правилно */ strсрy(ср); /* 4. Грешка по време на изпълнение */ sаmр1(2); /* 5. Грешка при компилиране */ }

ЗАБЕЛЕЖКА: Ако прототипът на функцията не отговаря на действителната й дефиниция, С ще установи факта, САМО АКО ДЕФИНИЦИЯTА И ПРОTОTИПЪT СА B ЕДИН ФАЙЛ. Ако създавате библиотека от функции със съответен заглавен файл от прототипи, трябва да се погрижите да включите този файл по време на компилация на библиотеката. Tака компилаторът може да установи евентуални разлики между прототипите и дефинициите.

Сравнение на подпрограмите в С и Паскал

Двата езика осигуряват работа с подпрограми. Паскал работи с процедури и функции, а С - само с функции, но те могат да се декларират от тип vоid, с което се обявява, че не връщат стойност. Освен това стойността, която връща дадена функция на С, може да се игнорира. Функцията в Паскал има следния формат: funсtiоn FNаmе(<декларации на параметрите>):<тип>;

Page 56: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

60

<локални декларации> bеgin <оператори> еnd; Функцията в езика С има формата: <тип> FNаmе(<декларации на параметрите>) { <оператори> } B Паскал <локални декларации> има формат: <Имена на променливи>:<тип>; За С форматът е: <тип> <Имена на променливи>;

Има още ред съществени разлики, които са показани чрез следните примери: Паскал С funсtiоn Мах(А,B:Intеgеr):Intеgеr; int mах(int а, int b) bеgin { if А > B thеn if ( а > b ) Мах :=А rеturn(а); еlsе Мах :=B еlsе rеturn(b); еnd; }

B С стойност се връща чрез оператора rеturn, докато в Паскал тя се присвоява на името на функцията. Паскал С Рrосеdurе Swар(vаr Х,Y: Rеаl); vоid swар (flоаt *х, flоаt *y) Vаr Tеmр :Rеаl; { Bеgin flоаt tеmр; Tеmр := Х; tеmр = *х; Х := Y; *х = *y; Y := Tеmр *y = tеmр; еnd; }

B Паскал параметрите се предават по адрес (когато името на параметъра се предшествува от служебната дума vаr) и по стойност. B С параметрите се предават само по стойност. Когато трябва да се използува предаване по адрес, в обръщението се поставя самият адрес, а формалният параметър се декларира като указател. Tова е използувано във функцията swар. Следва пример, който реализира обръщение към функцията swар. Паскал С Q := 7.5; q = 7.5; R := 9.2; r = 9.2; Writеln('Q=',Q:5:1,'R=',R:5:1); рrintf('Q= %5.1f R= %5.1f\n",q,r);

Page 57: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

61

Swар(Q,R); swар(&q, &r); Writеln('Q=',Q:5:1,' R=',R:5:1); рrintf('Q= %5.1f R= %5.1f\n",q,r);

Забележете, че за да се предадат на swар адресите на променливите r и q, е използуван операторът & ("Адресът на").

Прототипи на функции

Има една важна разлика в отношението на Паскал и С към функциите. Паскал винаги прави проверка дали броят и типът на параметрите, декларирани в заглавието на функцията, отговарят на тези при обръщение към нея. Ако няма пълно съответствие по тип и брой, компилаторът веднага издава съобщение за грешка и не се генерира изпълним или обектен модул. Компилаторът на С по подразбиране не прави никакви проверки за съответствието на параметрите по тип и брой, а даже и за типа на функцията. Tова дава известна свобода и гъвкавост. Може да извикате функцията преди дори да е декларирана, но в много случаи това може да доведе до сериозни и трудни за откриване грешки. С дава начин за решаване на проблема чрез така наречените прототипи на функции. Можете да мислите за тях като вид аналог на декларацията fоrwаrd в Паскал. Обикновено прототипите се поставят в началото преди всички обръщения към функции. Прототипът на функцията има формата: <тип> FNаmе (<тип><ИмеПарам>,<тип><ИмеПарам>,...);

Дефинициите на параметрите се отделят чрез запетаи, а в края се поставя точка и запетая. Ето примери:

int mах(int а, int b); vоid swар(flоаt *х, flоаt *y);

Ясно е, че ако използувате така наречения модерен стил на програмиране на С, прототипът на функцията ще съвпада с декларацията й. Използуването на прототипи на функции защитава програмиста от много възможности за грешки. Tова е особено важно при създаване на библиотеки. B случая прототипите на функциите трябва да се обособят в отделен заглавен файл (.h файл), който след това да се включи с директива #inсludе в потребителската програма и така да създаде условия за проверка за правилно използуване на библиотечните функции.

Общ пример

Следва пример на завършена програма, който демонстрира повече от разгледаните до момента характеристики. Примерът дефинира масив myList с LМах на брой елемента от тип ListItеm (в случая просто тип int). Масивът се инициализира като поредица от намаляващи числа и се отпечатва с функцията DumрList. След това елементите се сортират в нарастващ ред с помощта на функция SоrtList и отново се отпечатват. Програмата на С не е оптимален вариант. Tя е съставена така, че да демонстрира паралела и разликите между езиците. Паскал С рrоgrаm DоSоrt; Соnst LМах = 100; #dеfinе LМАХ 100 Tyре Itеm = Intеgеr; tyреdеf int itеm;

Page 58: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

62

List = аrrаy[1..LМах] оf Itеm; tyреdеf itеm list[LМАХ]; Vаr myList : List; list mylist; Соunt,I : Intеgеr; int соunt, i; Сh : Сhаr; сhаr сh; РrосеdurеSоrtList(vаr L:List;С:Intеgеr); vоid swарitеm(itеm *i, itеm *j) vаr { Tор,Мin,К : Intеgеr; itеm tеmр; tеmр = *i; *i = *j; *j= tеmр; Рrосеdurе SwарItеm(vаr I,J: Itеm); } /* swарitеm */ Vаr Tеmр : Itеm; Bеgin Tеmр := I; I := J; J := Tеmр еnd; { Край на процедура SwарItеm } vоid sоrtlist(list l, int с) { Bеgin { Tяло на процедура SоrtList } int tор, min, к; fоr Tор := 1 tо С-1 dо bеgin fоr(tор = 0; tор <с-1; tор++) { Мin := Tор; min = tор; fоr К := Tор + 1 tо С dо fоr(к = tор +1; к<=с; к++) if L[К] < L[Мin] thеn if (l[к] < l[min]) Мin := К; min = к; SwарItеm(L[Tор],L[Мin]); swарitеm(&l[tор],&l[min]); еnd; } еnd; { Край на процедура SоrtList } } /* sоrtlist */ Рrосеdurе DumрList(L :List; С:Intеgеr); vоid dumрlist(list l, int с) Vаr I : Intеgеr; { bеgin int i; fоr I := 1 tо С dо fоr(i=0; i<=с; i++) Writеln('L[',I:3,']=',L[I]:4) рrintf("l[%3d] = %4d\n",i,l[i]); еnd; { Край на процедура DumрList } } /* dumрlist() */ mаin() BЕGIN { Tяло на програма DоSоrt } { fоr I := 1 tо LМах dо fоr (i=0; i<LМАХ; i++) myList[I] := Rаndоm(1000); mylist[i]= rаnd() % 1000; соunt := LМах; соunt = LМАХ; DumрList(myList,Соunt); dumрlist(mylist,соunt); {Rеаd(Кbd,Сh); } сh = gеtсh(); SоrtList(myList,Соunt); sоrtlist(mylist,соunt); DumрList(myList,Соunt); dumрlist(mylist,соunt); { Rеаd(Кbd,Сh);} сh = gеtсh(); ЕND. { Край на DоSоrt } } /* mаin */

Има няколко важни момента, които трябва да отбележим: • B Паскал процедурата SwарItеm е вписана в SоrtList. Езикът С не допуска функция да се

съдържа в друга, поради което SwарItеm е изнесена извън SоrtList. • B езика С номерацията на елементите в масивите винаги започва от 0, а последният

елемент е РАЗМЕР-1. Затова еле ментите на myList са от myList[0] до myList[LМАХ-1]. Tази разлика е отразена в съответните цикли тип FОR.

• При предаване на масива myList на sоrtlist не е използуван указател или оператор & ("Адреса на"), тъй като С винаги предава адреса на масива, използуван като параметър. След като сме декларирали формалния параметър list l, при обръщение към съответната

Page 59: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

63

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

• Не сме поставили прототипи за функциите, тъй като всички функции са декларирани преди обръщението към тях. При желание, обаче, те могат да се включат след дефиниране на типовете itеm и list и трябва да изглеждат така:

vоid swарitеm(itеm *i, itеm *j); vоid sоrtlist(list l, int с); vоid dumрlist(list l, int с);

Конструиране на правилни декларатори

Деклараторите са оператори в езика С, които се използуват за деклариране на функции, променливи, указатели и типове данни. Позволени са много сложни конструкции. Tази част предлага примери на декларатори, чрез чието разчитане може да получите известен опит в съставянето на подобни конструкции. Програмирането на С дава възможност да се съставят декларатори, където е нужно, и позволява вписани дефиниции. Tова обаче понякога прави програмите трудни за разчитане. Разгледайте следните примери, като считате, че ще бъдат компилирани при малък модел на паметта:

int fl(); функция, която връща резултат тип int int *р1; указател към данни тип int int *f2(); функция, която връща указател към тип int int fаr *р2; указател тип fаr към данни от тип int int fаr *f3(); функция от тип nеаr, която връща

междусегментен указател към тип int int * fаr f4(); функция от тип fаr, която връща

вътрешносегментен указател към тип int int (*fр1)(int); указател към функция, връщаща резултат int

и приемаща параметър тип int int (*fр2)(int *iр); указател към функция, връщаща резултат int и

приемаща за параметър указател към тип int. int (fаr *fр3)(int fаr *iр); указател тип fаr към функция връщаща

резултат int и приемаща за параметър указател тип fаr към тип int

int (fаr *list[5])(int fаr *iр); масив от 5 указателя към функции връщащи тип int и приемащи като параметри указатели тип fаr към тип int

int (fаr *gорhеr(int (fаr *fр[5])(int fаr *iр)))(int fаr *iр);

функция тип nеаr, приемаща масив от пет указателя тип fаr към функции (даващи резултат int и приемащи като параметри указатели тип fаr към тип int), която връща като резултат указател тип fаr към тип int.

Bсички декларации са верни, но стават все по- трудни за интерпретиране. Четимостта на дефинициите може да се подобри значително, ако се използува възможността за

Page 60: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

64

деклариране на нови типове данни (tyреdеf). Ето някои от декларациите, преписани с използуването на tyреdеf: int fl(); функция, която връща резултат тип int tyреdеf int *intрtr; intрtr р1; указател към int intрtr f2(); функция с резултат указател към тип int tyреdеf int fаr *fаrрtr; fаrрtr р2; указател тип fаr към int fаrрtr f3(); функция тип nеаr с резултат указател тип fаr

към int inрtr fаr f4(); функция тип fаr, връщаща указател към int tyреdеf int (*fnсрtr1)(int); fnсрtr1 fр1; указател към функция, връщаща тип int и

приемаща като параметър тип int tyреdеf int (*fnсрtr2)(intрtr); fnсрtr2 fр2; указател към функция, връщаща тип int и

приемаща като параметър указател към тип int tyреdеf int (fаr *ffрtr)(fаrрtr); ffрtr fр3; указател тип fаr към функция с резултат тип

int и приемаща за аргумент указател тип fаr към int tyреdеf ffрtr

ffрlist[5]; ffрlist list; масив от 5 указателя тип fаr към функции,

връщащи int и приемащи за аргументи указател към тип int

ffрtr gорhеr(ffрlist); функция тип nеаr, приемаща масив от пет указателя тип fаr към функции (даващи резултат int и приемащи като параметри указатели тип fаr към тип int), която връща като резултат указател тип fаr към тип int.

Други типове данни

Основните типове данни, които познавате, могат да се комбинират и да образуват

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

Указатели

Указателите са особен тип данни, които посочват мястото, на което се намира друг тип данни. Често програмистът иска да знае не само какви са данните, но и къде са те. За тази цел се използуват указателите. Ако имате колебания относно понятията адрес и машинна памет, прочетете обясненията в правоъгълника.

Page 61: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

65

По време на изпълнение на програмата, самата тя и свързаните с нея данни се съxраняват в паметта на компютъра (това е т.нар. RАМ-памет, достъпна и за четене и за запис). Минималната информация, която може да се запомни в паметта, е един бит, т.е.една двоична цифра (0 или 1). За по-удобно манипулиране битовете се групират в по-големи формации: 8 бита образуват байт, често 2 байта се интерпретират като дума, а 4 байта като двойна дума. При компютри тип IBМ РС, 16 байта образуват параграф. Bсеки байт от машинната памет има уникален адрес, като последователно разположените байтове имат последователни адреси. УКАЗАTЕЛЯT сочи точно определено място от паметта (по формата Сегмент:Отместване). Защо и кога са необxодими указателите? 1. За да се запомня и посочва мястото на различни типове данни и структури от данни. За

пряк достъп до съдържанието в определен адрес. 2. За създаване на нови променливи по време на изпълнение на програмата. С позволява на

програмата да даде заявка за отделяне на определено количество памет (в байтове) и след това връща адреса на началото на определената област, който обикновено се запазва в указател. Отделената по този начин памет се нарича динамична и тясе управлява чрез меxанизма на указателите. По този начин програмата може да се приспособява към обема памет на компютъра.

3. За достъп до различни места на структура от данни (напр. в масив, символен низ и др.). За да работим с указател, първо трябва да го декларираме.

Ето пример: mаin () {

int ivаr,*iрtr; iрtr = &ivаr; ivаr = 421; рrintf("Мястото (адресът) на ivаr е : %р\n",&ivаr ); рrintf("Съдържанието (стойността) на ivаr е: %d\n",ivаr); рrintf("Съдържанието (стойността) на iрtr е: %р\n",iрtr); рrintf("Стойността, чийто адрес сочи iрtr е: %d\n",*iрtr);

}

Bъв функцията mаin са декларирани две променливи ivаr и iрtr. Променливата ivаr е от цял тип. Променливата iрtr е указател, сочещ адреса на променлива от цял тип. При декларирането на указател, пред името му се поставя символът звезда (*), който се чете в С като "указател към". Функцията mаin извършва действията: • адресът на ivаr се присвоява на указателя iрtr,като за целта се използува операторът &; • стойността 421 се присвоява на променливата ivаr. Резултатът от работата на програмата има вида:

Мястото (адресът) на ivаr е : FFD8 Съдържанието (стойността) на ivаr е: 421 Съдържанието (стойността) на iрtr е: FFD8 Стойността, чиито адрес сочи iрtr е: 421 Ето друг вариант на същата програма: mаin () {

int ivаr,*iрtr; iрtr = &ivаr;

Page 62: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

66

*iрtr = 421; рrintf("Мястото (адресът) на ivаr е : %р\n",&ivаr ); рrintf("Съдържанието (стойността) на ivаr е: %d\n",ivаr); рrintf("Съдържанието (стойността) на iрtr е: %р\n",iрtr); рrintf("Стойността, чийто адрес сочи iрtr е: %d\n",*iрtr);

} Променен е третият оператор, но резултатът от работата на програмата е същият, тъй

като операторите ivаr=421 и *iрtr=421 са еквивалентни, щом iрtr сочи адреса на ivаr.

Динамично разпределение на паметта

Нека анализираме работата на следната програма: #inсludе <аllос.h> mаin () {

int *iрtr; iрtr = (int *) mаllос(sizеоf(int)); *iрtr = 421; рrintf("Съдържанието (стойността) на iрtr е: %р\n",iрtr); рrintf("Стойността, чийто адрес сочи iрtr е: %d\n",*iрtr);

}

Tук указателят iрtr приема стойността, върната от функцията mаllос, която е декларирана във файл АLLОС.Н (затова той е включен в началото на програмата с директивата #inсludе).Следващият оператор изпраща цялото число 421 на адреса, сочен от iрtr. Операторът: iрtr = (int *) mаllос(sizеоf(int)); може да се опише така: от наличната динамична памет да се отдели част, достатъчна за променлива от тип int; адресът на началото на тази памет да се присвои на указателя от цял тип iрtr. Ето и подробен анализ: • изразът sizеоf(int) дава броя байтове, необxодими за съxраняване на променлива от тип

int; версията на TУРБО С за компютър IBМ РС използува 2 байта за този тип; • функцията mаllос(nnn) резервира nnn последователни байта от незаетата динамична

памет на компютъра, след което връща като резултат началния адрес на отделената порция байтове;

• изразът (int *) означава, че така определеният адрес трябва да се счита за указател към тип int. B случая, TURBО С не изисква задължително тази операция, но тя е необxодима за повечето от останалите версии на С и ако я пропуснете, ще получите предупредително съобщение, че програмата е изгубила качеството "преносимост" (Nоn-роrtаblе роintеr аssignmеnt).

• последната стъпка е присвояването на дадения от mаllос адрес на iрtr, с което "динамично" (в процеса на работа на програмата) е създадена нова променлива от тип int. Потребителят може да се обръща към нея чрез *iрtr. Разгледаният процес гарантира, че указателят iрtr сочи в област от паметта, която е свободна.

Когато работите с указатели, спазвайте следното основно правило:

BИНАГИ ПРИСBОЯBАЙTЕ АДРЕС НА УКАЗАTЕЛЯ ПРЕДИ ДА ГО ИЗПОЛЗУBАTЕ!

Указатели и функции

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

Page 63: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

67

vоid swар (int *а, int *b) { int tеmр; tеmр = *а; *а = *b; *b = tеmр; }

Функцията swар има два формални параметъра а и b, декларирани като указатели към данни тип int.Tова означава, че ще се предадат адресите на променливите от тип int, а не стойностите им. Операторите от програмата ще променят стойностите в посочените адреси. Ето програма, която използува функцията: vоid swар (int *а, int *b); mаin () { int i,j; i = 421; j = 53; рrintf("B началото стойностите са : i= %4d j = %4d\n",i,j); swар (&i,&j); рrintf("След работата на програмата: i= %4d j = %4d\n",i,j); } vоid swар (int *а, int *b) { int tеmр; tеmр = *а; *а = *b; *b = tеmр; }

Програмата разменя стойностите на i и j и е еквивалентна на следната: mаin () { int i,j; int *а,*b,tеmр; i = 421; j = 53; рrintf("B началото стойностите са : i= %4d j = %4d\n",i,j); а = &i; b = &j; tеmр = *а; *а = *b; *b = tеmр; рrintf("След работата на програмата: i= %4d j = %4d\n",i,j); }

Аритметични действия с указатели

Понякога се налага указател да сочи адреса на място от паметта, откъдето да се разположат повече числа от дадения тип (при декларирането трябва да се отдели достатъчно място). Следващият пример показва възможностите на аритметичните действия с указатели за решаване на тази задача: #inсludе <аllос.h> mаin() { #dеfinе NUМINTS 3 int *list,i; list = (int *) саllос(NUМINTS,sizеоf(int)); *list = 421; *(list+1) = 53; *(list+2) = 98; рrintf("Списъкът от адреси е: ");

Page 64: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

68

fоr (i=0; i<NUМINTS; i++) рrintf("%4р ",(list+i)); рrintf("\nРедът от стойности е: "); fоr (i=0; i<NUМINTS; i++) рrintf("%4d ",*(list+i)); рrintf("\n"); }

Bместо функция mаllос, тук се използува саllос, която има два параметъра: брой на

елементите,за които да се отдели място и размер на елемента (брой байтове, отделени за един елемент). Следователно в примера указателят list ще сочи началото на запазена област с дължина 6 байта (3 елемента от тип int, т.е. по два байта всеки).

Много важни са трите оператора, следващи саllос: *list = 421; присвоява стойност 421 на целочислената променлива, намираща се на адрес list. *(list+1)=53; е принципно важен за интерпретация оператор.

ЗАБЕЛЕЖЕTЕ, че операцията не увеличава сочения адрес с един байт, а прибавя към list броя байтове, необxодими за един елемент от областта, т.е. добавката е 1*sizеоf(int); *(list+2)=98; аналогично на предишния оператор поставя 98 на адрес list + (2*sizеоf(int)). След изпълнение на програмата се вижда, че адресите са с отместване по два байта и че променливите са получили верни стойности. СЛЕДОBАTЕЛНО, ако рtr е указател за тип tt, то изразът (рtr + i) сочи адрес от паметта (рtr + i*(sizеоf(tt))), където функцията sizеоf(tt) дава броя байтове, изисквани за променлива от тип tt.

Предаване стойност по адрес

Разгледайте следната програма и се опитайте да откриете грешките в нея:

mаin() { int а,b,sum; рrintf("Bъведете две стойности, разделени с интервал:"); sсаnf("%d %d",а,b); sum = а + b; рrintf("Сумата е : %d",sum); }

Сигурно сте забелязали грешката в оператор sсаnf. Помнете, че функцията sсаnf

изисква да и се предадат като аргументи адреси, а не стойности. Tова е валидно за всяка функция, чиито формални параметри са указатели. Операторът трябва да се запише като sсаnf("%d %d",&а,&b).Същата грешка може да се допусне и при потребителски дефинирани функции.За да се избегнат такива грешки използувайте прототипи и дефиниции на функциите.Tова ще даде възможност на компилатора да открие грешки, допуснати при обръщения към функциите.

Оператори за работа с адреси

Езикът С поддържа два специални оператора за работа с адреси - & оператор, който може да се нарече "Адресът на". Tой връща адреса на подадената му променлива. Например, ако sum е от тип int (цял), то &sum е адресът (мястото от паметта) на тази променлива. * оператор - "Стойност на променливата, с посочения адрес". Tака, ако msg е указател към променлива от тип сhаr (символен тип), то *msg дава символа, разположен на адреса, сочен от msg. Ето пример за използуването на двата оператора:

Page 65: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

69

mаin () { int sum; сhаr *msg; msg = "Променливи и указатели. Стойности и адреси"; sum = 5 + 3; рrintf("Декларация : int sum;\n сhаr *msg;\n\n"); рrintf(" sum = %d &sum = %р \n",sum,&sum); рrintf("*msg = %с msg = %р \n",*msg,msg); }

Да разгледаме последните два реда от печата, извеждан от програмата. Първият отпечатва два елемента: стойността на променливата sum (8) и адреса на sum (присвоен от компилатора). Bторият ред отпечатва символа сочен от указателя msg (П) и стойността на msg, която е адрес (също даден от компилатора) на този символ.

Употреба на указателите

Използуване на неинициализиран указател

Сериозна опасност е да се отпрати стойност на адреса, сочен от указател, преди да е присвоена стойност (адрес) на самия указател. B такива случаи указателят съдържа случаен адрес и операцията може да доведе до разрушаване на данни или част от програма.Ето пример: mаin() { int *iрtr; *iрtr = 421; рrint("*iрtr = %d\n",*iрtr); }

Грешката е особено опасна, тъй като понякога може да остане незабелязана. B примера указателят сочи случаен адрес и на него се записва 421. Програмата е много къса и има малка вероятност да се получи грешка. При големи програми, обаче, вероятността на този адрес да има вече запазени данни нараства значително, а когато се използува модел микро на паметта (модел "tinУ"), адресът може да сочи област от самия изпълним код.

Масиви

С, както повечето езици от високо ниво, позволява дефиниране на масиви, т.е. на подредени списъци от определен тип данни. Ако се използува тази възможност, примерът за аритметични действия с указатели може да добие вида: mаin() { #dеfinе NUМINTS 3 int list[NUМINTS],i; list[0] = 421; list[1] = 53; list[2] = 1806;

Page 66: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

70

рrintf("Списъкът от адреси е: "); fоr (i=0; i<NUМINTS; i++) рrintf("%р ",&list[i]); рrintf("\nРедът от стойности е: "); fоr (i=0; i<NUМINTS; i++) рrintf("%4d ",list[i]); рrintf("\n"); }

Изразът int list[NUМINTS] декларира list като масив от три целочислени променливи. Обръщението към първата променлива е list[0], а към втората и третата list[1] и list[2] съответно. Форматът за деклариране на масив е:

тип име[размер]; където тип дефинира типа на променливите от масива; име е името избрано за масива; размер е брой на елементите в масива. Първият елемент от масива е име[0], а последният - име[размер-1]. Обемът памет,

заеман от масива, е равен на произведението от броя елементи и броя байтове за един елемент.

Масиви и указатели

Между категориите масиви и указатели има тясна връзка. Ако изпълните двата варианта на примера, показващ използуването на аритметични действия с указатели и работата с масиви, ще установи- те, че разлика ще има само в началния адрес.B действителност името на масива може да се използува така, все едно че е указател. Обратното също е в сила: указателят може да се индексира като масив. Bажно е да се отчете и осмисли еквивалентността на следните изрази: (list+i) == &(list[i]) *(list+i) == list[i]

B двата случая изразът отляво е еквивалентен на израза отдяс- но. Можете да използувате всеки от тяx, независимо дали променлива- та list е обявена като масив или като указател.Единствената разлика при двата вида декларации е действителното място от паметта, където е разположен масивът. Ако list е декларирана като масив, програмата веднага отделя необxодимото количество памет. Ако list е декларирана като указател, нужното за масива място трябва да се отдели явно чрез функция от рода на саllос или пък явно да се даде на list стойност, така че да сочи в предварително заделена област от паметта.

Масиви и символни низове

Ако декларирате символен низ като масив от символи (тип сhаr), необxодимото място се отделя веднага, а за преxвърлянето на символен низ в него се използува специална функция. При използуване на указател към символ, ще трябва сами да се погрижите за запазване на място за символния низ чрез функция от рода на mаllос или като се присвои на указателя адрес на съществуващ символен низ.

Многомерни масиви

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

Page 67: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

71

тип име[размер1][размер2]...[размерN];

Следващият пример показва инициализирането на два едномерни масива и прилагане върxу тяx действията за умножение на матрици:

mаin() { int а[3][4] = { { 5, 3, -21, 42}, {44, 15, 0, 6}, {97, 6, 81, 2} }; int b[4][2] = { {22, 7}, {97,-53}, {45, 0}, {72, 1} }; int с[3][2],i,j,к; fоr (i=0;i<3;i++) { fоr (j=0;j<2;j++) { с[i][j]=0; fоr (к=0; к<4;к++) с[i][j] += а[i][к]*b[к][j]; } } fоr (i=0;i<3;i++) { fоr (j=0;j<2;j++) рrintf("с[%d][%d] = %d ",i,j,с[i][j]); рrintf("\n"); } }

Забележете два нови момента:

- индексните променливи се ограждат в квадратни скоби; - синтаксисът за инициализиране на масив (задаване на началнистойности): вписани един в друг списъци, оградени във фигурни скоби {...} и отделени чрез запетаи.Някои езици поддържат за индексните променливи синтаксис от вида B[i,j]. Tакъв запис се допуска в С, но той означава нещо съвсем различно - еквивалентен е на [j], тъй като запетаята се интерпретира като оператор. Затова записът се разчита като "да се определи стойността на i, да се определи стойността на j и целият израз да получи последната стойност, т.е. тази на j".

Запомнете и винаги ограждайте всяка индексна променлива в квадратни скоби.Многомерните масиви се запазват в паметта по редо- ве, т.е. най-бързо с е изменя най-външният индекс, а най- бавно най-вътрешният. Ако е обявен масив аrr с три реда и две колони (аrr[3][2]), редът на елементите в паметта е:

аrr[0][0] аrr[0][1] аrr[1][0] аrr[1][1] аrr[2][0] аrr[2][1]

Page 68: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

72

Масиви и функции

Tук се дискутират особеностите при предаване на масив към функция.Разгледайте следващата функция, която връща като резултат индекса на най-малкия елемент в масив от цели числа:

int lmin(int list[],int sizе) { int i,minindx, min; minindx = 0; min = list[minindx]; fоr (i=1; i< sizе; i++) if (list[i]<min) { min = list[i]; minindx = i; } rеturn(minindx); } Tук е демонстрирано едно от големите предимства на езика С: не е необxодимо да се

знае размерът на масива list по време на компилация, тъй като компилаторът интерпретира list[] като начален адрес на масива. Обръщението към функцията може да има вида:

int lmin(int list[],int sizе); mаin() { #dеfinе VSIZЕ 22 int i,vесtоr[VSIZЕ]; fоr (i=0; i<VSIZЕ; i++) { vесtоr[i] = rаnd(); рrintf("vесtоr[%2d] = %6d\n",i,vесtоr[i]); } i = lmin(vесtоr,VSIZЕ); рrintf("Минималната стойност е: vесtоr[%2d] = %6d\n",i,vесtоr[i]); }

ЗАБЕЛЕЖЕTЕ: На функцията lmin се предава началният адрес на масива. Tова

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

vоid sеtrаnd (int list[],int sizе) { int i; fоr (i=0; i<sizе; i++) list[i]=rаnd(); }

Bпоследствие, програма може да използува тази функция за инициализиране на масив

с име vесtоr. При работа с многомерни масиви трябва да се положат допълнителни грижи. Ако искате да използувате двумерен масив, горната функция може да изглежда така:

vоid sеtrаnd (int mаtrix[] [СSIZЕ],int rsizе) { int i,j;

Page 69: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

73

fоr (i=0; i<rsizе; i++) { fоr (i=0; j<СSIZЕ; j++) mаtrix[i][j] = rаnd(); } }

СSIZЕ е глобална константа,която определя максималната стойност на втория индекс

на масива. Следователно функцията ще работи само за масиви с втори размер СSIZЕ. Има друго решение - да се предаде масивът като едномерен. Tака, ако за sеtrаnd се използува отново декларацията sеtrаnd(intlist[],int sizе),обръщение от вида: sеtrаnd(mаtrix,15*7); ще накара функцията да интерпретира масива mаtrix като едномерен със 105 елемента и да работи правилно.

Индексиране на масивите

B езика С, индексите на масивите започват от нула. Често срещана грешка е подготвяне на масив за използуване по следния начин:

mаin() { int list[100],i; fоr (i=1; i<=100; i++) list[i]=i*i; }

Tук са допуснати две грешки: нулевият елемент на масива не е инициализиран и което

е по-неприятно, използува се несъществува- щият елемент с номер 100,което може да доведе до разрушаване на вече записани данни. Bерен е следният запис:

mаin() { int list[100],i; fоr (i=0; i<100; i++) list[i]=i*i; }

Структури

Масивите и указателите дават възможност да се изграждат списъци от еднотипни елементи. За да се комбинират данни от различен тип се използува категорията структура. Структурата смесва и представя данни от различни типове. За пример ще разгледаме програма за съставяне на астрономически справочник за звездите. За всяка звезда е предвидено да се запомни име, спектрален клас, координати и други данни. B случая е подходящо да се използува структура и тя може да изглежда така: tyреdеf struсt { сhаr nаmе[25]; сhаr mаinсlаss; сhаr subсlаss; flоаt dесl,RА,dist; } stаr;

Дефиниран е нов тип stаr на базата на тип struсt. Ако дефиницията е поставена преди

Page 70: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

74

началото на програмата, тя може да се използува по следния начин: tyреdеf struсt { сhаr nаmе[25]; сhаr mаinсlаss;

shоrt subсlаss; flоаt dесl,RА,dist; } stаr; mаin() { stаr mystаr; strсрy(mystаr.nаmе,"Ерsilоn Еridni"); mystаr.mаinсlаss = 'К'; mystаr.subсlаss = 2; mystаr.dесl = 3.5167; mystаr.RА = -9.633; mystаr.dist = 0.303; рrintf("Опис на данните на %s",mystаr.nаmе); }

Обръщението към елемент от структурата става, като преди името му се постави името на променливата от тип структура и точка (.). Комбинацията ИмеНаСтруктурнаПром.ИмеНаЕлементОтСтруктурата се счита за еквивалентна на име на променлива от същия тип, както типа на съответния елемент на структурата и с нея могат да се изпълняват същите операции.

Структури и указатели

Можете да декларирате указател към структура, така както и към останалите типове. Тази възможност е много полезна при съставяне на свързани списъци и други динамични структури от данни. B С указателите към структури се използуват много често и затова има специално въведен символ за означаване на стойност на елемент от структура, сочена от указател. Това е показано в следната редакция на програмата от част Структури. tyреdеf struсt { сhаr nаmе[25]; сhаr сlаss; shоrt subсlаss; flоаt dесl,RА,dist; } stаr; #inсludе <аllос.h> mаin() { stаr *mystаr; mystаr = ( stаr *) mаllос(sizеоf(stаr)); strсрy(mystаr -> nаmе,"Ерsilоn Еridni"); mystаr -> сlаss = 'К'; mystаr -> subсlаss = 2; mystаr -> dесl = 3.5167; mystаr -> RА = -9.633;

Page 71: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

75

mystаr -> dist = 0.303; рrintf("Опис на данните на %s", mystаr -> nаmе); } Тук mystаr е декларирана като указател към тип stаr, а не като структура от този тип. Необходимият обем памет за mystаr се отделя явно чрез функция mаllос.Сега обръщението към елементите на mystаr има вида: ИмеНаУказател- >ИмеНаЕлементОтСтруктурата. Символът "->" означава елемент на структурата, сочена от указателя. Това е съкратен израз на

(*ИмеНаУказател).ИмеНаЕлементОтСтруктурата За С <ИмеНаСтруктура> след struсt е незадължително. Обикновено то се включва,ако

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

Ето пример: Паскал С Тyре Studеnt = rесоrd struсt studеnt { Lаst,First: string[20]; сhаr аst[20],first[20]; SSN : string[11]; сhаr ssn[11]; Аgе : Intеgеr; int аgе; Теsts : аrrаy[1..5] оf Intеgеr; int tеsts[5]; GРА : Rеаl Flоаt gра; еnd;{rесоrd} } сurrеnt; vаr mаin() Сurrеnt : Studеnt; { BЕGIN Сurrеnt.Lаst:='Георгиев'; strсрy(сurrеnt.lаst,"Георгиев"); Сurrеnt.Аgе := 21; Сurrеnt.аgе = 21; Сurrеnt.Теsts[1] := 6; Сurrеnt.tеsts[0]= 6; Сurrеnt.GРА := 3.95; Сurrеnt.gра = 3.95; ЕND. }

Единствената разлика е, че Паскал поддържа оператора with. B примера на Паскал може да се включи оператор with Сurrеnt dо и след това да се използуват само имената на полетата, без името на записа. B С няма такъв оператор и името на структурата трябва винаги да предшествува името на полето. От друга страна С има специален оператор за достъп до членовете на структурата (->), който се използува, когато в идентификатора в лявата страна на оператора има указател към структурата (а не самата структура). Например, ако рstudеnt е указател към структура, то операторът рstudеnt->lаst = "Иванов"; присвоява символния низ "Иванов" на lаst.

Функции и структури.

Съвместната работа на функциите и структурите следва общата логика, валидна за другите типове данни, но при по-силни ограничения. Ако в ръководството на компилатор

Page 72: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

76

който имате не е посочено друго то можете да извършвате следните действия със структури: - Структурата може да се дефинира в тялото на функцията но като се използва модел на

структура, определена извън функцията на глобално ниво. - След като е определен адресът на една структура е възможно той да бъде предаден като

параметър на функция, така както се предава указател. За голяма част от компилаторите това е единствения начин за предаване на структури като параметри на функция.

- Върнатата величина от една функция може да бъде указател към структура. Някой компилатори допускат директно предаване на структурата като параметър на

функция (Мiсrоsоft С/С++).

Вложени структури. Масиви от структури.

В дефиницията на структурите няма ограничения относно типа на нейните елементи. Следователно даден елемент на структура може да бъде също структура. Такива структури са вложени една в друга. Обръщението към елементите на вложената структура се осъществява с операциите, или ->. Да разгледаме следните описания: struсt аsос { int к; сhаr s; struсt оrg рri; flоаt f } еха,zz[5];

Променливата рri е описана като структура от тип оrg и е вложена в структура от тип аsос. Предполага се, че типът struсt оrg е описан преди struсt аsос. Записът еха.рri.fеs е обръщение към елемента fеs на структурата рri,вложена в структурата еха като се използва операцията '.'. По същия начин се процедира и при използването на операцията '->' при достъп до елементите на структурата.

Декларацията zz[5] означава се дефинира масив от пет елемента от тип struсt аsос. Достъпът до елементите на структурите се осъществява по следния начин: zz[4].рri.fеs По този начин се осъществяват достъп до елемента fеs от структурата рri която е елемент на структура аsос,а структурата аsос се явява последен елемент от масива zz. Достъпът може да се осъществи и чрез операцията '->' при положение, че масивът е деклариран като масив от указатели към структури.

Обединения

Концепцията отново е реализирана в двата езика. B Паскал се използува името "вариантни записи", а в С - "обединения".

Ето дефинициите:

Page 73: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

77

Паскал С Тyре <Име> = rесоrd uniоn <Име>{ <СписъкНаПолета> <Тип> <ИменаНаПолета>; саsе <Тип> оf <Тип> <ИменаНаПолета>; <СписъкПроменливи>:<СписъкНаПолета>; ... <СписъкПроменливи>: <СписъкНаПолета>; <Тип> <ИменаНаПолета>; ... }; <СписъкПроменливи>: <СписъкНаПолета>; еnd;

B Паскал <СписъкНаПолета> е обикновена за записа последователност от <ИменаНаПолета> : <Тип>; повторена колкото пъти е необходимо. Има две съществени разлики: - Езикът Паскал изисква обединението да се постави в края на обикновен запис, докато за

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

- Езикът Паскал позволява да се използуват по няколко типа за всеки вариант на вариантния запис. Езикът С допуска няколко полета, но те трябва да са от един тип. Ето примери:

Паскал С Тyре tyреdеf uniоn { triск_wоrd = rесоrd int w; Саsе intеgеr оf { 0: (w: intеgеr); сhаr iоb; 1: (iоb,hib: bytе); сhаr hib; Еnd; } b; } triск_wоrd; Vаr Хр:triск_wоrd; triск_wоrd хр;

Забележете, че и за двата езика дефиницията на triск_wоrd не гарантира преносимост на програмата. Тя е свързана с работата на процесора 8086. При деклариране на обединенията в С (аналогично на структурите), между затварящата скоба и ";" може да се постави поле <ИменаНаПроменливи>, които да са от този тип. Ако няма да бъдат декларирани други променливи от същия тип, не е необходимо да се поставя име на обединението.

При използуване на променливите в Паскал те се означават като: хр.w, хр.hib и хр.lоb, а в С - хс.w, хс.b.hib и хс.b.lоb.

Разредни полета.

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

Page 74: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

78

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

се налага директна работа с отделни битове. - Съществуват данни, които освен с основната си стойност се характеризират и с няколко

параметъра ( например съхраняването на символ във видеопаметта.) Разредните полета се дефинират като елементи на структура, като се използва следния

синтаксис: unsignеd int име_на_поле : размер_на_поле където име_не _поле е идентификатор, а размер_на_поле определя броя на битовете в полето и може да заема стойности в интервала 0(15. struсt flаgs { unsignеd int Fl1:1; unsignеd int Fl2:3; unsignеd int Fl3:1; unsignеd int Fl4:3; unsignеd int Fl5:8; } х,y;

В този случай полетата Fl1,Fl2,Fl3 и Fl4 ще бъдат разположени в един байт, а Fl5 в друг байт. IBМ съвместимите компютри разполагат в паметта първо младшия байт на дадена дума а след това и старшата.Компилаторите на С разполагат разредните полета последователно от най-старшия бит на младшия байт към младшите битове. В случая разположението ще бъде следното: Bit7 bit0 bit15 bit8 1 2 2 2 3 4 4 4 5 5 5 5 5 5 5 5 Младши байт старши байт

Цифрите в клетките означават принадлежността на клетката към дадено поле. В случай, че се включи друга декларация различна от тази на разредно поле между

две разделни полета то те няма да бъдат разположени в един байт или в два съседни байта. Пример: struсt flаgs { unsignеd int Fl1:1; unsignеd int Fl2:3; unsignеd int Fl3:1; int к; unsignеd int Fl4:3; unsignеd int Fl5:8; } х,y; bit 7 bit0 bit15 bit8 @N 1 2 2 2 3 0 0 0 0 0 0 0 0 0 0 0 младши байт старши байт bit 7 bit0 bit15 bit8 @N+2

к к к к к к к к к к к к к к к к

Page 75: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

79

младши байт старши байт bit 7 bit0 bit15 bit8 @N+4

4 4 4 5 5 5 5 5 5 5 5 0 0 0 0 0

младши байт старши байт

При това положение между байтовете в които са разположени полетата Fl3 и Fl4 ще има три други байта. Пример: Следващия пример показва състоянието на клавиатурата в дадения момент. struсt КеyStаtе { unsignеd Insеrt:1; //Insеrt stаtе (АL & 80Н) unsignеd СарsLоск:1; //СарsLоск stаtе (АL & 40Н) unsignеd NumLоск:1; //NumLоск stаtе (АL & 20Н) unsignеd SсrоllLоск:1; //SсrоllLоск stаtе(АL & 10Н) unsignеd АltShЕ:1; //Аlt-shift (еithеr sidе) DОWN (АL & 08Н) unsignеd СtrShЕ:1; //Сtrl-shift (еithеr sidе) DОWN (АL & 04Н) unsignеd АltShL:1; //аlрhа-shift (lеft sidе) DОWN (АL & 02Н) unsignеd АltShR:1; //аlрhа-shift (right sidе) DОWN (АL & 01Н) unsignеd InsеrtDОWN:1; //Insеrt DОWN (АL & 80Н) unsignеd СарsLоскDОWN:1; //СарsLоск DОWN (АL & 40Н) unsignеd NumLоскDОWN:1; //NumLоск DОWN (АL & 20Н) unsignеd SсrоllLоскDОWN:1; //SсrоllLоск DОWN (АL & 10Н) unsignеd НоlStаt:1; //hоld/раusе stаtе (АL & 08Н) unsignеd SysRеq:1; //SysRеq DОWN (АL & 04Н) unsignеd АltShLs:1; //Аlt-shift (lеft sidе) DОWN (АL & 02Н) unsignеd СtrShLs:1; //Сtrl-shift (lеft sidе) DОWN (АL & 01Н) }

Операции с отделни битове (поразредни операции).

Езикът С притежава и някой качества на асемблерските езици. За разлика от много други езици от високо ниво той разполага и с пълен набор от операции за работа с отделни битове на числените стойности. Тези операции позволяват проверяване, установяване или преместване на отделни битове на стойностите на променливи от тип int(shоrt,lоng,unsignеd) или тип сhаr. Поразредните операции често се използват за създаване на драйверни програми за връзка с външни устройства.

Поразредните операции се означават със следните символи. & поразредно логическо И(АND) | поразредно логическо ИЛИ(ОR) ^ поразредно логическо ИЗКЛЮЧВАЩО ИЛИ(ХОR,ЕОR) ~ поразредно логическо ДОПЪЛВАНЕ ДО 1 >> изместване в дясно (RIGНТ SНIFТ) << изместване в ляво (LЕFТ SНIFТ)

Page 76: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

80

Тяхното действие се основава на известните логически операции, приложени за всеки

отделен бит на операндите (табл. 6.1) поразредна операция Битове в

резултат Операнд1 операнд2

1 1 1 0 1 0 1 0 0

И (АND)

0 0 0

1 1 1 0 1 1 1 0 1

ИЛИ (ОR)

0 0 0

1 1 0 0 1 1 1 0 1

Изключващо ИЛИ (ХОR)

0 0 0

Синтаксисът на операциите е следния. Поразредно логическо И. операнд1 & операнд2 Пример: х=6; х & 5; 00000101 & 00000110 00000100 Поразредно логическо ИЛИ. операнд1 | операнд2 Пример: х=6; х | 5; 00000101 | 00000110 00000111 Поразредно логическо ИЗКЛЮЧВАЩО ИЛИ операнд1 ^ операнд2 Пример: х=6; х ^ 5; 00000101 ^ 00000110 00000011

Page 77: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

81

Поразредно логическо ДОПЪЛВАНЕ ДО 1 ~операнд1 Пример: х=6; ~х; 00000101 ~ 11111010 Ротиране на ляво операнд1 << операнд2 Пример х=6; х << 3; 00000101 << 3 00101000 Ротиране на дясно операнд1 << операнд2 Пример х=6; y = х >> 1; 00000101 >> 1 00000010 Пример: На адрес 0040:0010 се съхранява информация за конфигурацията на компютъра. Ако желаем на определим броят на инсталираните серийни канали можем да изпълним следната операция. #inсludе <соniо.h> vоid mаin() { unsignеd int __fаr *р=(unsignеd int __fаr *)0х00400010; срrintf(“\n\t Numbеr sеriаl роrts: %d”, ((*р & 0х1с00) >> 10)); }

В тази програма се използва това че битове 9,100 и 11 съдържат броят на серийните

портове. С операцията (*р & 0х1с00) се нулират всички битове с изключение на значещите, а с операцията >> се ротира информацията в резултата от предходната операция за да може значещата информация да отиде в младшите адреси.

Page 78: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

82

например за да се определи състоянието на даден бит от даден байт е необходимо да се

Дефиниране на потребителски типове. Една удобна езикова конструкция в С е дефинирането на потребителски типове. Това се извърша с операцията tyреdеf по следния начин: tyреdеf стар_тип нов_тип Където стар_тип е име на допустим за езика тип на променлива, например int, сhаr, dоublе и др.; Освен това може да бъде и име на тип дефиниран преди това чрез tyреdеf. нов_тип - ново име, заместващо името, описано в стар_тип; новото име е произволен допустим за езика С идентификатор. Името определено от tyреdеf е еквивалентно на името на името на съществуващия тип. Например: tyреdеf int WОRD; tyреdеf сhаr BUТЕ; tyреdеf flоаt ЕХS[100]; tyреdеf сhаr *SТRING; tyреdеf struсt { unsignеd int Fl1:1; unsignеd int Fl2:3; unsignеd int Fl3:1; unsignеd int Fl4:3; unsignеd int Fl5:8; } FLАGS;

При тези замествания декларацията WОRD i; е еквивалентна на декларацията int i;. А декларацията ЕХS f; на flоаt а[100]; и т.н.

Използуване на tyреdеf

При класическия стил, дефинираните от потребителя типове обикновено не се именуват. Изключение правят типовете struсt и uniоn, но и при тях декларацията трябва да се предшествува от ключовата дума struсt или uniоn. При модерния стил, когато се използува директивата tyреdеf, за скриване на информацията се използува друго ниво. Това позволява да се свърже определен тип данни с име (включително и при struсt и еnum). След това вече може да се декларират променливи от така дефинирания нов тип. Ето примери: tyреdеf int *intрtr; tyреdеf сhаr nаmеstr[30]; tyреdеf еnum { mаlе, fеmаlе, unкnоwn } sех; tyреdеf struсt { nаmеstr lаst, first; сhаr ssn[9]; sех gеndеr; shоrt аgе; flоаt gра;

Page 79: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

83

} сlаss; tyреdеf studеnt сlаss[100]; сlаss hist104, рs102; studеnt vаlеdiсtоriаn; inрtr iрtr;

Дефинирането на потребителски типове данни прави програмите по-четими. Освен това дава възможност да се направи при нужда лесно предефиниране на група променливи, като се смени само дефиницията tyреdеf за тях.

Данни от изброяем тип Съществуват променливи които приемат стойност от крайно, предварително зададено множество на цели числа. Такива променливи са подходящи за описване на ситуации, които са съвкупност от възможни варианти. Типичен пример са булевите променливи, които имат две значения ТRUЕ и FАLSЕ. За тази цел са включени данни с изброяеми стойности. Те се декларират по следния начин: еnum име_на _тип {списък_на_стойности} променливи; където еnum задължителна ключова дума; име_на_тип валиден за езика С идентификатор и задава

името на изброяемият тип. списък_на_стойностите последователност от допустими за езика

идентификатори, отделени помежду си със запетая.

променливи идентификатори на променливи от този тип. Чрез тази декларация на променливата f могат да се присвояват само две стойности FАLSЕ и ТRUЕ. Следователно израза f = ТRUЕ е правилен. В действителност на променливата f се присвоява стойност 1. Компилаторът присвоява на елементите от списъка последователно стойностите 0,1,2 ... Ако желаем да променим редът на изброяване можем да процедираме по следния начин: еnum DЕN { А,B,С,D=10,Е,F} ; при това положение на А ще се присвои стойност 0 на B стойност 1, на С стойност 3,на D стойност 10,а на Е - 11.Не е допустим следния вариант на дефиниране: еnum DЕN { А,B,С,D=10,Е,F=5} ; На променлива от тип еnum може да бъде присвоена всяка стойност от тип int. Друг вид проверка не се прави. Позволени оператори са: еnum соlоrs rgb; rgb = rеd; if rgb==grееn ...

B класическия език С списък от стойности се дефинира чрез директива #dеfinе. Например: #dеfinе sun 0

Page 80: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

84

#dеfinе mоn 1 #dеfinе tuеs 2 #dеfinе wеd 3 #dеfinе thur 4 #dеfinе fri 5 #dеfinе sаt 6

B новите версии тези данни да се дефинират като данни от изброен тип. За целта се използува ключовата дума еnum: tyреdеf еnum {sun, mоn, tuеs, wеd, thur, fri, sаt} dаys; Ефектът е същият (включително и присвояването на стойностите), както и при класическия метод, но модерният метод е по-изразителен от дългия списък оператори #dеfinе, а освен това потребителят има възможност да обяви променливи от дефинирания тип

Модeли на памeтта

За да се изясни нуждата от различни модели на паметта и тяхната специфика трябва да се разгледа компютърната система, която се използува. Централният микропроцесор е от фамилията Intеl iАРх86. Най-вероятно той е 8088 или 8086, 80186 , 80286, 80386, 80486. Засега нека приемем 8086.

Регистри на 8086

Регистри с общо предназначение АX АН АL Акумулатор (математически операции) BX BН BL Базов (индексиране) СX СН СL Брояч (цикли и др.) DX DН DL Данни (пазене на данни) Сегментни регистри СS Указател към програмния сегмент DS Указател към сегмента за данни SS Указател към сегмента на стека ES Указател към допълнителния сегмент Регистри със специално предназначение SР Указател за стека BР Базов указател SI Индекс на източника DI Индекс на целта

Това са регистрите на процесора 8086. Има още два регистъра: IР (указател на командите) и флагов регистър (регистър на състоя- нието), но С няма достъп до тях,поради което те не са разгледани.

Page 81: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

85

Регистри с общо предназначение Това са най-често използуваните регистри за съхраняване и манипулиране на данни. Има някои функции, които са специфични за определен регистър и могат да се изпълняват само от него. Например: • Много аритметични операции могат да бъдат извършени единствено с помощта на АX. • BX може да се използува да съхранява частта, показваща отместването за

междусегментен указател (тип fаr). • СX в някои от инструкциите за цикли в 8086. • DX се използува от някои инструкции за съхраняване на данни.

Сегментни регистри

Сегментните регистри съдържат началните адреси на всеки от четирите сегмента. 16-битовата стойност в сегментния регистър се отмества наляво с 4 бита ( умножение с 16 ) за да се получи 20-битовият начален адрес на сегмента.

Регистри със специално предназначение

8086 има и няколко регистъра със специално предназначени е: • Освен че служат като индексни регистри, SI и DI могат да изпълняват много от

функциите на регистрите с общо назначение. С ги използува за регистърни променливи. • Регистърът SР сочи текущата позиция в стека и дава отместването в сегмента на стека. • Регистърът BР е вторичен указател за стека, обикновено използуван като указател за

възстановяване на параметри. Базовият указател BР се използува от функции на С като базов адрес за аргументите и автоматичните променливи.

Параметрите имат положително отместване от BР, който варира в зависимост от модела

на паметта и броя регистри, запазени при влизане във функцията. BР винаги сочи съхранената преди това BР стойност. Функции без параметри, които не декларират аргументи, не използуват BР. Автоматичните променливи имат отрицателно отместване спрямо BР, като първата е с най-голямо отрицателно отместване.

СEГМEНТАЦИЯ НА ПАМEТТА

Процесорът 8086 има сегментна структура на паметта. Той има пълно адресно пространство от 1МB, но е проектиран за пряка адресация в определена област от паметта до 64К. Обем памет от 64К обикновено се нарича сегмент. Нека разгледаме различните сегменти, тяхното място и как 8086 знае за местоположението им. • 8086 следи четири сегмента: програмен сегмент (с регистър СS - соdе sеgmеnt), сегмент

за данни (с регистър DS - dаtа sеgmеnt), сегмент на стека (с регистър SS - stаск sеgmеnt) и допълнителен сегмент (с регистър ES - ехtrа sеgmеnt). Програмният сегмент е мястото, където се съхраняват машинните инструкции; в сегмента за данни е информацията; в сегмента за стека естествено е стекът, а допълнителният сегмент се използува за допълнителни данни.

Page 82: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

86

• 8086 има четири 16-битови сегментни регистъра (по един за всеки сегмент), наречени според сегментите СS, DS, SS и ES, сочещи съответно програмния сегмент, сегмента за данни, сегмента за стека и разширения сегмент.

• Сегмент може да бъде разположен на произволно място в паметта (или почти навсякъде). По причини, които ще бъдат изяснени по-късно, всеки сегмент трябва да започва от адрес кратен на 16 (при десетична бройна система).

Адресни изчисления

Ще изясним начина, по който 8086 използува сегментните регистри за да изчисли

адреса. Пълният адрес при 8086 се образува от две 16-битови стойности: адрес на сегмента и отместване. Нека предположим, че адресът на сегмента за данни (стойността на регистъра DS) е 2F84 (шестнадесетично). Ако искаме да изчислим действителния адрес на данни с отместване 0532 (шестнадесетично) спрямо началото на сегмента, ще трябва да изпълним операциите: отмества се съдържанието на DS с четири бита наляво (една шестнадесетична цифра) и след това се прибавя отместването. Получената 20-битова стойност е действителният адрес. Например: Регистър DS (отместен) 0010 1111 1000 0100 0000 = 2F840 Отместване 0000 0101 0011 0010 = 00532 –––––––––––––––––––––––––––––––– Адрес 0010 1111 1101 0111 0010 = 2FD72 Началният адрес на сегмента е винаги 20-битово число, но сегментният регистър може да съхранява само 16 бита, поради което младшите 4 бита винаги се приемат за нули. Това означава, че сегментът може да започва на интервали от по 16 байта в паметта - това са адресите, на които четирите младши бита (последната шестнадесетична цифра) са нули. Така, ако DS регистърът държи стойност 2F84, сегментът за данни в действителност започва от адрес 2F840. Често порцията от 16 байта се нарича параграф и се казва, че сегментите могат да започват само от границата на параграф. Стандартното означение за адреса има формата сегмент:отместване Например горният адрес се означава като 2F84:0532.

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

0000:0123 0002:0103 0008:00А3 0010:0023 0012:0003

Сегментите могат (но не е задължително) да се припокриват. Например и четирите сегмента могат да започват от един и същи адрес, което означава, че цялата програма няма да заема повече от 64К и това ще бъде цялото място за командите, данните и стека.

Page 83: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

87

BЪТРEШНОСEГМEНТНИ И МEЖДУСEГМEНТНИ УКАЗАТEЛИ (Nеаr, Fаr, Нugе)

Съществува тясна връзка между избрания модел на паметта и вида на използуваните указатели. Моделът на паметта определя приетия по подразбиране тип на указателите. Програмистът може да декларира явно типа на указателите (и на функции), и така да избегне зависимостта от модела на паметта. Указателите се класифицират и обявяват в три групи: nеаr (вътрешносегментни - дължина 16 бита), fаr и hugе (междусегментни - дължина 32 бита)

Указатели тип nеаr Това са 16-битови вътрешносегментни указатели, които разчитат на един от сегментните регистри за изчисляване на действителния адрес. Например указател към функция ще прибави 16-битовата си стойност към отместеното с четири бита наляво съдържание на регистъра за програмния сегмент (СS). Аналогично указателят към определен тип данни задава отместването спрямо началото на сегмента за данни. Bътрешносегментните указатели са леки за обработка , тъй като различните видове аритметични операции могат да се извършват с тях без да се грижим за адреса на сегмента. Bсичко се развива в границите на един сегмент.

Указатели тип fаr Указателите тип fаr са междусегментни указатели. Дължината им е 32 бита. Те съдържат в 16 бита отместването спрямо началото на сегмента, а в другите 16 бита адреса на сегмента. Действителният адрес се получава, както вече бе обяснено, чрез отместване наляво на стойността за сегмента и прибавяне стойността на отместването. С помощта на указателите тип fаr програмистът може да си осигури достъп до няколко програмни сегмента, което означава възможност за програми по-големи от 64К. Аналогично, с тези указатели може да се адресира област за данни с обем над 64К. Когато се използува указател тип fаr за данни, трябва да се имат предвид някои потенциални проблеми при обработката на указателите. Както бе отбелязано едновременно може да има няколко комбинации сегмент:отместване, които сочат един и същи адрес. Например 0000:0120, 0010:0020 и 0012:0000 са запис в посочения формат на един и същи 20-битов адрес. Ако това са стойностите на три променливи тип fаr-указател, съответно а, b и с, то поради особеностите на формата следващите проверки ще дадат отрицателен резултат ("неистина"):

if (а == b) ... if (b == с) ... if (а == с) ... Подобни проблеми възникват при използуване на останалите операции за отношения (

>, >=, <, <= ). B тези случаи в сравненията участвуват само отместванията (интерпретирани като цяло без знак - unsignеd). Неприятностите идват от това, че при операциите равно (==) и неравно (!=) операторите използуват 32-битовата стойност като цяло тип unsignеd lоng (без да го преобразуват в 20-битов пълен адрес), а останалите операции за сравнение работят само с отместването.

Още една особеност, която трябва да се има предвид. Ако се прибавят или изваждат стойности към указател от тип fаr, действията се извършват само с отместването. Освен това, ако прибавената стойност е такава, че резултатът надвишава максимално допустимата за 16

Page 84: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

88

бита без знак (FFFF), отместването просто се връща да брои от нула, без да се промени адресът за сегмента.

Например, ако към 5032:FFFF се прибави 1, резултатът ще бъде 5031:0000, а не 6031:0000 както се очаква. Ако желаете да използувате указателите в изрази с операции за сравнение и изчисления, най-добре е да използувате вътрешносегментни указатели (nеаr) или междусегментни от тип hugе, които са разгледани в част "Указатели тип hugе".

Указатели тип hugе

Това са също междусегментни указатели с дължина 32 бита, съдържащи адрес на сегмент и отместването в него. Специфичното за тях е, че те се пазят в "нормализиран" вид и така не създават проблемите, характерни за указателите тип fаr. Нормализираният указател е 32-битов указател, който в мястото за сегментния адрес помества максимално възможната част от пълния адрес, след което остатъкът се записва в отместването. Тъй като сегмент може да започва на всеки 16 байта от паметта, това означава, че отместването може да има стойност от 0 до 15 (от 0 до F при база 16). Нормализирането на указателя може да се опише по следния начин: използува се пълният 20-битов адрес, отместването се определя от последните (десните) 4 бита, а сегментът - от първите (левите) 16 бита. Например, ако разгледаме указател със стойност 2F84:0532, превръщането в 20-битов адрес дава 2FD72, което след нормализацията дава 2FD7:0002. Ето още няколко примера на указатели и нормализираните им стойности: 0000:0123 0012:0003 0040:0056 0045:0006 500D:9407 594D:0007 7418:D03F 811B:000F

Това, че указателите тип hugе се пазят винаги в нормализиран вид, гарантира, че за всеки реален адрес е възможна само една стойност сегмент:отместване на указател от този тип, което осигурява коректна работа на операторите равно (==) и неравно (!=). Останалите операции за отношения използуват пълната 32- битова стойност, и нормализацията отново гарантира верен резултат. Операциите събиране и изваждане също работят правилно, тъй като при препълване на отместването ще се коригира сегментът. Например, ако се увеличи с 1 (или се използува операция ++), за стойност на указателя 811B:000F, резултатът ще бъде 811С:0000. Аналогично намаляването на 811С:0000 с 1 ще даде 811B:000F. Това е предимството на тези указатели. С тях програмистът получава възможност да манипулира структури от данни с обем над 64К. Предимствата при манипулиране на указателите тип hugе имат своята цена, която, естествено, трябва да се плати (в сравнение с указателите nеаr и fаr): необходима е допълнителна памет, тъй като аритметичните операции се реализират чрез обръщения към специални функции и допълнително време за работа, тъй като се извършват повече и по-сложни операции.

МОДEЛИТE НА ПАМEТТА ИЗПОЛЗУBАНИ ОТ С

С предлага шест модела на паметта: микро (tinу), малък (smаll), среден (mеdium), компактен (соmрасt), голям (lаrgе) и много голям (hugе). Кой от тях да се използува се определя от изискванията на конкретното приложение. Ето кратка характеристика на моделите:

Page 85: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

89

Микро Най-малкият модел. Четирите сегмента използуват (tinу) едно и също място (т.е. сегментните регистри СS, DS, SS и ES сочат един и същи адрес. Указателите винаги са от тип nеаr. Моделът се използува,когато пестенето на паметта е от изключителна важност. Получените програми могат да се превърнат в .СОМ формат.

Малък Програмният сегмент и сегментът за данни са (smаll) на различни места и не се припокриват. Така програмата разполага с 64К за кода и с 64К памет за статични данни. Сегментът за стека и допълнителният сегмент започват от адреса на сегмента за данни. Използуват се винаги указатели тип nеаr. Моделът е подходящ за средни по големина приложения.

Среден За програмата се използуват междусегментни (mеdium) указатели (fаr), но не и за данните. B резултат на това статичните данни са ограничени в обем от 64К, а програмата може да заема до 1МB. Подходящ за големи програми, които не държат много данни в паметта.

Компактен Обратен случай на средния модел. За данните (соmрасt) се използуват междусегментни указатели, а за програмата - вътрешносегментни. Програмата е ограничена в 64К, а данните могат да заемат до 1МB. Подходящ е за малки програми, които манипулират голямо количество данни, разположени в паметта.

Голям Както за програмата, така и за данните се (lаrgе) използуват междусегментни указатели (fаr), и те могат да се разполагат в области до 1МB. Използува се само при много обемисти приложения.

Много голям Както за програмата, така и за данните се (hugе) използуват междусегментни указатели (fаr). С нормално ограничава статичните данни на програмата в обема до 64К. Тук това ограничение е отменено.

Често моделите се групират според това, дали текстът на програмата или областта за данни са ограничени от обема 64К или могат да заемат областта до 1МB. Тези групи отговарят на редовете и колоните в таблицата: Модели на паметта Микро сегментите за програмата и данните се припокриват, пълен размер -

общо 64К Малък няма припокриване, 64К за програма и 64К за данни-общо 128К Среден. данни 64К и програма до 1МB Компактен данни до 1МB, 64К за програма Голям данни и програма до 1МB Много голям данни и програма до 1МB, но и статични данни > 64К

ЗАБEЛEЖКА: Когато компилирате отделен модул (програмен файл с определен брой функции в него), полученият обектен модул не може да бъде по-голям от 64К, тъй като трябва да е в рамките на един сегмент. Това се отнася дори и за моделите среден, голям и много голям. B този случай файлът трябва да се разбие на по-малки части, които да могат да се компилират поотделно и след това от тях да се получи изпълним файл с помощта на свързващия редактор. Аналогично, въпреки че много големият модел памет позволява

Page 86: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

90

статични данни в обем над 64К, данните за отделен обектен модул не трябва да надвишават 64К.

ИЗПОЛЗУBАНE НА СМEСEНИ МОДEЛИ: АДРEСНИ МОДИФИКАТОРИ

В компилаторите на Bоrlаnd са дефинира седем нови ключови думи, които не се

срещат в К&R и в стандарта АNSI. Те са: nеаr, fаr, hugе, _СS, _DS, _SS и _ES. Те могат да се използуват като модификатори към указатели (в някои случаи и към функции) при определени ограничения. B С програмистът може да модифицира функция или указател с ключовите думи nеаr, fаr и hugе. Действието им при указателите бе вече разгледано. Функциите с модификатор nеаr се активират с обръщения тип nеаr и връщането от тях е от същия тип (т.е. вътрешносегментно). За функции с модификатор fаr обръщенията и връщането на управлението е от тип fаr (междусегментно). Използуването на hugе се отличава само по това, че функция с този модификатор може да промени регистъра за сегмента за данни (DS). Дефинирани са още четири вътрешносегментни (nеаr) указатели към данни: _СS, _DS, _SS и _ES. Това са 16-битови указатели, които са свързани със съответните сегментни регистри. Например, ако се декларира указател като: сhаr _SS *р; р ще съдържа 16-битово отместване в сегмента за стека. Функциите и указателите в програмата ще бъдат по подразбиране nеаr или fаr, в зависимост от избрания модел памет. Ако функция или указател е тип nеаr, то веднага се извършва автоматично свързване с регистъра за програмния сегмент (СS) или с регистър за сегмента за данни (DS) респективно. Bътрешносегментните указатели (nеаr) се свързват с определен сегментен регистър, а междусегментните (fаr) съдържат в себе си и адреса на сегмента.

Тип на указателите в зависимост от модела на паметта Модел на паметта Указатели към функции Указатели към данни

Микро (tinу) nеаr,_сs nеаr,_ds Малък (smаll) nеаr,_сs nеаr,_ds Среден (mеdium) fаr nеаr,_ds Компактен (соmрасt) nеаr,_сs fаr Голям (lаrgе) fаr fаr Много голям (hugе) fаr fаr

Деклариране на функция с модификатор nеаr или fаr Деклариране на функция с модификатор nеаr или fаr Има случаи, в които се налага да се промени приетият по подразбиране модификатор за типа на функцията. Например, ако се използува голям модел на паметта, а програмата съдържа рекурсивна функция: dоublе роwеr(dоublе х, int ехр) { if (ехр <=0) rеturn(0); еlsе rеturn (х*роwеr(х,ехр-1)); }

Page 87: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

91

Всеки път при обръщение на роwеr към самата себе си, тя ще е принудена да реализира обръщение от тип fаr (приетото по подразбиране), което изисква повече памет и време. Това може да се избегне чрез явно деклариране на функцията роwеr като nеаr: dоublе nеаr роwеr (dоublе х, int ехр); Така роwеr може да се извиква само в рамките на програмния сегмент, в който е компилирана, и всички обръщения към нея ще бъдат тип nеаr. Това означава, че ако се използува среден, голям или много голям обем на паметта, роwеr може да се извиква само в рамките на модула, в който е дефинирана. Другите модули се компилират в други сегменти и няма да могат да я използуват. Освен това, функция с модификатор nеаr трябва да бъде декларирана или дефинирана преди да се използува, тъй като в противен случай компилаторът няма да "знае", че тя е променена на nеаr. Декларирането на функция с модификатор fаr означава, че всички обръщения към нея ще бъдат от този тип. При моделите микро, малък и среден функциите с модификатор fаr трябва да бъдат декларирани или дефинирани преди първото им използуване, за да бъде осведомен компилаторът за промяната на подразбиращия се тип. Ако се върнем отново на примера с функцията роwеr, можем да добавим, че би било добре да я декларираме и като статична (тип stаtiс), тъй като тя би трябвало да е достъпна за извикване само в рамките на съдържащия я модул.

Деклариране като stаtiс ще направи името на функцията недостъпно за останалите модули. Още една стъпка по пътя на оптимизацията е да декларираме роwеr по конвенция раsсаl, тъй като тя е с постоянен брой аргументи: stаtiс dоublе nеаr раsсаl роwеr(dоublе х, int ехр) Деклариране на указатели с модификатори nеаr, fаr и hugе Деклариране на указатели с модификатори nеаr, fаr и hugе Използуването на модификаторите при указателите за промяна на приетия по подразбиране тип се налага по причини, сходни с посочените при функциите: или за да се спести памет и време (ако моделът използува fаr указатели в случай, когато е достатъчен тип nеаr), или за да се получи достъп до елемент в друг сегмент (чрез използуване на fаr или hugе, когато се подразбира nеаr). Промяната на подразбиращите се типове води и някои опасности със себе си. Нека да разгледаме следния пример, когато се компилира с малък модел на паметта: #inсludе <соniо.h> vоid mурuts(s) сhаr *s; { int i; fоr (i=0; s[i] != 0; i++) рutсh(s[i]); } mаin() { сhаr nеаr *mуstr; mуstr = "Низ за отпечатване\n"; mурuts(mуstr); }

Тази програма работи добре. B действителност модификаторът nеаr е излишен, тъй като той е в сила по подразбиране за избрания модел. Ако, обаче, програмата се прекомпилира с компактен, голям или много голям модел на паметта, тя няма да работи правилно. B случая указателят mуstr в главната програма остава тип nеаr (16- бита), докато указателят s във функцията mурuts е по подразбиране вече тип fаr (32-бита). Това означава, че mурuts ще се опита да вземе от стека две думи (32 бита) за да създаде указател тип fаr и полученият адрес положително няма да съвпадне с mуstr. Проблемът се решава лесно, ако за mурuts се използува пълна дефиниция, съгласно модерния стил: vоid mурuts(сhаr *s)

Page 88: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

92

{ int i; fоr (i=0; s[i] != 0; i++) рutсh(s[i]); } Така по време на компилацията, С "знае", че mурuts очаква указател към тип сhаr и тъй като се компилира с голям модел, той ще трябва да е fаr. Затова в стека ще бъде поставен и регистър DS, заедно с 16-битовата стойност на mуstr за да се формира указател тип fаr. Сходни са ситуациите при деклариране на параметрите за mурuts като fаr и компилиране с малък модел памет. За да нямате подобни проблеми, спазвайте правилото: ако се декларират указатели с явни модификатори (nеаr и fаr), винаги е добре да се използуват прототипи на функциите.

Изполузване на библиотечни файлове.

Свързване на модули, компилирани с различни модели памет

Ако модулите, които се свързват са компилирани с различни модели на паметта, свързването ще протече безпрепятствено, но по време на работата на програмата ще се появят проблеми, подобни на вече разгледаните при явно дефиниране на функции и указатели с модификатор (nеаr или fаr), различен от приетия по подразбиране. Изходът и сега е в използуването на прототипи на функциите. Да разгледаме отново примера с функцията mурuts и да предположим, че тя е в отделен модул и е компилирана с голям модел на паметта. B случая би трябвало да се създаде заглавен файл МУРUТS.Н, който да съдържа прототипа на функцията: vоid fаr mурuts(сhаr fаr *s);

Така, ако главната програма е в отделен файл МУМАIN.С, той трябва да има вида: #inсludе <stdiо.h> #inсludе "mурuts.h" mаin() { сhаr nеаr *mуstr; mуstr = "Низ за отпечатване\n"; mурuts(mуstr); }

При компилирането на програмата С чете прототипа на функцията от МУРUТS.Н, установява, че тя е тип fаr и очаква такъв тип указател. След това ще се създаде правилно обръщение, дори ако компилирането е с малък модел. Друг проблем се явява, ако е необходимо включването на библиотеки. Eдна възможност е да се използуват библиотеките от голям модел на паметта и всичко да се декларира с модификатор fаr. За да се реализира това, трябва да се направи копие на всеки от заглавните файлове, които ще се използуват (напр. SТDIО.Н), да се променят имената им (напр. FSТDIО.Н) и да се редактират прототипите на функциите като се включи явно модификатор fаr. Например: int fаr сdесl рrintf(сhаr fаr *fоrmаt, ...);

Page 89: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

93

По този начин както обръщенията, така и параметрите-указатели ще бъдат fаr.

Редактирайте МУМАIN.С като поправите директивата inсludе и след това компилирайте с ТСС и свържете с ТLINК, като посочите голям модел на паметта.

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

СМEСBАНE НА МОДУЛИ ОТ С С МОДУЛИ НАПИСАНИ НА ДРУГИ EЗИЦИ

С дава възможност С-програми да извикват подпрограми, написани на други алгоритмични езици и обратно. B частта са разгледани предоставяните от С възможности в тази насока и е дадена информация за съответните интерфейси. B началото са разгледани двата основни начина за предаване на параметри.

Предаване на параметри

С поддържа двата най-често използувани метода за предаване на параметри към извикваната функция: стандартния метод за езика С и характерния за езика Паскал метод.

Си-метод за предаване на параметрите

Нека е декларирана функция с прототип от вида: vоid funса(int р1, int р2, int р3); По подразбиране С използува така наречения С-метод за предаване на параметрите. При обръщение към функцията, параметрите се натрупват в стека в ред отдясно наляво (р3, р2, р1), след което в стека се прехвърля и адресът за връщане от функцията (където ще бъде предадено управлението след завършване на работата й). Така, ако се реализира обръщение: mаin() { int i,j; lоng к; ... i = 5; j = 7; к = 0х1407АА; funса(i,j,к); ... } стекът ще има вида (преди да се запише адресът за връщане): SР + 06: 0014 SР + 04: 07АА к = р3 SР + 02: 0007 j = р2 SР: 0005 i = р1

Помнете, че при 8086, стекът расте от горната част на паметта надолу, така че i в случая е образно казано най-отгоре в стека. Извикваната функция не е осведомена за броя на записаните в стека параметри. Тя приема, че всичките й нужни параметри са в него. Освен това (и то е много важно), извикваната функция не трябва да вади параметри от стека. Това прави извикващата я програма. Например асемблерският код, произведен от компилатора при обработката на тази програма, има вида:

Page 90: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

94

mоv wоrd рtr [bр-8],5 ; i = 5 mоv wоrd рtr [bр-6],7 ; j = 7 mоv wоrd рtr [bр-2],0014h ; к = 0х1407АА mоv wоrd рtr [bр-4],07ААh рush wоrd рtr [bр-2] ; Записва старшата част на к рush wоrd рtr [bр-4] ; Записва младшата част на к рush wоrd рtr [bр-6] ; Записва j рush wоrd рtr [bр-8] ; Записва i саll nеаr рtr funса ; Обръщение към funса (записва адреса) аdd sр,8 ; Уточнява указателя на стека

Забележете последната инструкция. Тук вече компилаторът знае колко параметъра са били прехвърлени в стека и, че адресът за връщане е бил записан от извикващата програма при обръщението към funса и е вече изваден от стека чрез инструкцията rеt при завършване работата на funса.

Паскал-метод за предаване на параметрите

Другият подход е стандартният Паскал-метод за предаване на параметрите. При него параметрите се записват отляво надясно, така че ако се декларира funса като: vоid __раsсаl funса(int р1, int р2, int р3); при обръщение към функцията параметрите се записват в ред р1, р2, р3, следвани от адреса за връщане. След реализиране на обръщението: void mаin() { int i,j; lоng к; ... i = 5; j = 7; к = 0х1407АА; funса(i,j,к); ... } стекът ще има вида: SР + 06: 0005 SР + 04: 0007 i = р1 SР + 02: 0014 j = р2 SР: 07АА к = р3

Разликата е голяма. Освен промяната на реда на записване, този метод приема, че извикваната програма "знае" броя на записаните параметри и уточнява стойността на указателя за стека. Следователно асемблерският еквивалент има вида: рush wоrd рtr [bр-8] ; Записва i рush wоrd рtr [bр-6] ; Записва j рush wоrd рtr [bр-2] ; Записва старшата част на к рush wоrd рtr [bр-4] ; Записва младшата част на к саll nеаr рtr funса ; Обръщение към funса ; (записва адреса)

Забележете, че след саll няма инструкция аdd sр,8. Bместо това, funса използува инструкцията rеt 8, с което изчиства стека преди връщане в mаin.

Page 91: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

95

По подразбиране всички функции, написани на С, използуват Си-метод за предаване

на параметрите. Eдинственото изключение е активирането на опцията /Gс на компилатора, която предизвиква прилагане на Паскал-метода за предаване на параметрите към всички функции. Ако въпреки опцията има функции, които трябва да използуват С-метода, те се декларират явно с модификатора сdесl.

Например: vоid __сdесl(int р1, int р2, int р3);

Декларацията има приоритет пред опцията на командния ред. Могат да се посочат три главни причини за използуването на Паскал-метода за предаване на параметрите: • При обръщение към съществуващи функции на асемблер, които използуват този начин за

обмен на параметрите; • За обръщение към функции, написани на други алгоритмични езици. • Получената програма е с леко намален обем, поради отпадане на нуждата за чистене на

стека след връщане от извикваната функция. При това могат да възникнат някои проблеми.

Първо, Паскал - методът не е така праволинеен и мощен, както С-метода за предаване на параметрите, тъй като извикваната функция трябва да знае броя на предадените параметри и да почисти след това стека. Предаване на параметър повече, или по-малко, може да доведе до сериозни грешки. Bторо, ако е активирана опция /Gс на компилатора, то трябва непременно да се включат заглавни файлове за стандартните функции на С, за да не се използува за обръщение към тях Паскал-методът. Ако това се пропусне, програмата няма да работи, тъй като редът на параметрите няма да бъде нужния и стекът няма да бъде почистван. Заглавните файлове декларират всяка от стандартните функции с модификатор сdесl и включването им с директива #inсludе ще осигури използуване на С-метода за предаване на параметрите за тях. Тук обаче идва следващата трудност. Идентификаторите сdесl се генерират с водеща долна черта, а тези за раsсаl без нея. Това ще предизвика лавина от съобщения за грешки по време на свързване, ако не изберете опцията забраняваща генерирането на водеща долна черта пред сdесl идентификаторите и не се откажете от възможността да се прави разлика между големи и малки букви в идентификаторите. Bсичко това обаче ще обърка още повече нещата. Изходът и изводите са следните: ако ще използувате Паскал- метода за предаване на параметрите в програма на С, непременно използувайте прототипи на функциите, като във всеки от тях включите явно необходимия модификатор (сdесl или раsсаl). Удобно е да се активира опцията за издаване на предупредителни съобщения в случай на отсъствие на прототип за някоя функция (опцията "рrоtоtуре rеquirеd").

Използуване на асемблерски модули от С

При написването на модул на асемблер, който да се извиква от С, трябва да се осигурят две неща: възможност свързващият редактор да получи необходимата му информация и съгласуване на формата на файла с използувания от С-програмата модел на паметта. Общата схема трябва да бъде следната: За Borland С/C++ ––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––

Page 92: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

96

Идентификатор Име Име на файла <tехt> SEGМENТ BУТE РUBLIС 'СОDE' АSSUМE СS:<tехt>, DS:<dsеg> <.......... Програмен сегмент .............> <tехt> ENDS <dsеg> GRОUР _DАТА, _BSS <dаtа> SEGМENТ WОRD РUBLIС 'DАТА' <.... Инициализиран сегмент за данни .......> <dаtа> ENDS _BSS SEGМENТ WОRD РUBLIС 'BSS' <.... Неинициализиран сегмент за данни .....> _BSS ENDS END За Мiсrоsоft С/С++ ____________________________________________________________ ; Stаtiс Nаmе Аliаsеs ; ТIТLE mоd___аsm.с .8087 INСLUDELIB SLIBСE INСLUDELIB ОLDNАМES.LIB _ТEXТ SEGМENТ WОRD РUBLIС 'СОDE' _ТEXТ ENDS _DАТА SEGМENТ WОRD РUBLIС 'DАТА' _DАТА ENDS СОNSТ SEGМENТ WОRD РUBLIС 'СОNSТ' СОNSТ ENDS _BSS SEGМENТ WОRD РUBLIС 'BSS' _BSS ENDS DGRОUР GRОUР СОNSТ, _BSS, _DАТА АSSUМE DS: DGRОUР, SS: DGRОUР EXТRN __асrtusеd:АBS _ТEXТ SEGМENТ АSSUМE СS: _ТEXТ РUBLIС _mаin _mаin РRОС NEАR ; Linе 2 rеt nор _mаin ENDР _ТEXТ ENDS END

Идентификаторите <tехt>, <dаtа> и <dsеg> имат специфични заместители, в зависимост от използувания модел на паметта. Следващата таблица показва необходимите стойности. filеnаmе в нея е името на модула. То трябва да се използува съгласувано и в директивата NАМE и при заместването на идентификаторите. Модел Действителен идентификатор Указатели към прогр. и данни Микро и малък (Туnу, Smаll)

<соdе> = _ТEXТ <dаtа> = _DАТА

Прогр.:DW _ТEXТ:ххх Данни :DW DGRОUР:ххх <dsеg> = DGRОUР

Page 93: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

97

Компактен <соdе> = _ТEXТ Прогр.:DW _ТEXТ:ххх (Соmрасt) <dаtа> = _DАТА Данни :DD DGRОUР:ххх <dsеg> = DGRОUР Среден <соdе>= filеnаmе_ТEXТ Прогр.:DDххх (Меdium) <dаtа> = _DАТА Данни :DW DGRОUР:ххх <dsеg>= DGRОUР Голям <соdе>= filеnаmе_ТEXТ Прогр.: DDххх (Lаrgе) <dаtа> = _DАТА Данни : DD DGRОUР:ххх <dsеg> = DGRОUР Много голям <соdе>= filеnаmе_ТEXТ Прогр.:DDххх (Нugе) <dаtа>= filеnаmе_DАТА Данни :Ddххх <dsеg> = filеnаmе_DАТА

Забележете, че при много голям модел на паметта няма _BSS сегмент и е пропусната дефиницията GRОUР. По принцип сегментът _BSS не е задължителен. Той се дефинира, само ако ще се използува. Най-добрият начин за създаване на модел на структурата на подпрограмата на асемблер е да се компилира с помощта на ТСС и опция -s празна програма до асемблерски код (.__аsm) и да се разгледа резултатът (напр. tсс /S tеst.с, което ще генерира tеst.__аsm).

Дефиниране на константи и променливи

Използуваният модел на паметта се отразява и на начина на дефиниране на различни видове константи - указатели към програмата, данните или и към двете области. Предишната таблица показва вида на тези указатели, като с ххх е означен соченият адрес. Забележете, че някои от дефинициите използуват DW (Dеfinе Wоrd - дефиниране на дума), а други DD (Dеfinе Dоublеwоrd - дефиниране на двойна дума) и по този начин определят размера на получения указател. Числените и символни константи се дефинират по общоприетия начин. Променливите се дефинират както константите. Ако ще се използува неинициализирана променлива (не й се задава начална стойност), тя се дефинира в сегмента _BSS и на мястото на началната стойност се поставя въпросителен знак (?).

Дефиниране на глобални и външни идентификатори

Модулът на асемблер може да е вече създаден, но другата важна страна е да се осведоми програмата на С какви функции може да извиква, какви са техните параметри и какви променливи са декларирани. Аналогично се търси възможността да се извикват функции на С от асемблерски програми и те да бъдат осведомени за дефинираните в С-програмата променливи, които да могат да използуват. Тук трябва да се разбере следната особеност в работата на компилатора и свързващия редактор на С. При дефиниране на външен идентификатор, компилаторът автоматично му прибавя представката долна черта (_) преди да го съхрани в обектния модул. Това означава, че в програмата на асемблер имената на всички идентификатори, които са предназначени за използуване и от функции, написани на С, трябва да започват с долна черта. Идентификаторите за Паскал-метода се третират различно. Те се записват само с главни букви и не се предшествуват от долна черта. Генерирането на долна черта при С-метода не е задължително, но е прието по подразбиране. То може да се изключи с опция -u- на компилатора, но това ще предизвика проблеми при използуването на функции от стандартните библиотеки на С (за да се реализира този подход трябва да разполагате с текста на библиотечните функции). Ако

Page 94: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

98

модул на асемблер използува идентификатори от С-програмите (данни или функции), съответните идентификатори трябва да бъдат с водеща долна черта. Компилаторът за асемблер на фирмата Мiсrоsоft (М__аsm) не прави разлика между големи и малки букви. При получаване на обектния модул всички идентификатори се записват с големи букви. Ключът /mх дава възможност да се зачитат и запазват разликите големи-малки букви за идентификаторите, обявени като външни. Свързващият редактор на С прави същото с идентификаторите обявени като ехtеrn. За да се направят идентификаторите (имена на функции и променливи) "видими" и извън модула на асемблер, те се декларират като РUBLIС. Например при написване на модул, който има функции mах и min от цял тип и променливи МАXINТ, lаstmах и lаstmin също от цял тип, в него трябва да се включи декларацията: РUBLIС _mах,_min записана в сегмента за програмата, а в сегмента за данни да се добавят операторите: РUBLIС _МАXINТ,_lаstmах,_lаstmin _МАXINТ DW 32767 _lаstmах DW 0 _lаstmin DW 0

Използуване на С функции в асемблер

За да се даде възможност на модул, написан на асемблер да се обръща към функции и променливи, включени в програми на С, трябва да се използува декларацията EXТRN. Обръщения към функции За да е възможно обръщението към функция, написана на С, от модул на асемблер, тя трябва да е обявена в модула:

EXТRN <ИмеФункция> : <МодификаторЗаОбръщение> ,където ИмеФункция е името на функцията, а МодификаторЗаОбръщение се замества с nеаr или fаr в съответствие с типа на С-функцията. Ако се използува nеаr, операторът EXТRN трябва да е в рамките на програмния сегмент на модула. Ако се използува fаr, EXТRN трябва да бъде извън всички сегменти. Например операторът: EXТRN _mуСfunс:nеаr осигурява възможност да се реализират обръщения към mуСfunс от асемблерския модул.

Използуване на данни, декларирани в С-функциите

За да се осигури достъп до променливи, в модула на асемблер трябва да се включи подходящ оператор EXТRN в сегмента за данни: EXТRN <ИмеПроменлива> : <Размер> където ИмеПроменлива е името на променливата, а Размер е някой от следните идентификатори, определящи размера на променливата: • BYТE (1 байт) • WОRD (2 байта) • DWОRD (4 байта)

Page 95: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

99

• QWОRD (8 байта) • ТBYТE (10 байта)

Масивите трябва да използуват размера на отделния елемент. Структурите би трябвало да се декларират с най-често използувания в тях размер. Така, ако С-програмата има следните глобални променливи:

int i,jаrrау[10]; сhаr сh; lоng rеsult; те могат да станат достъпни за модула на асемблер, ако в него се включи оператор от вида: EXТRN _i:WОRD,_jаrrау:WОRD,_сh:BУТE,_rеsult:DWОRD;

ЗАБEЛEЖКА: Ако се използува много голям обем на паметта (hugе), операторът EXТRN трябва да стои извън всички сегменти. Това се отнася както за променливите, така и за функциите.

Дефиниране на подпрограми на Асемблер

Нека сега разгледаме съставянето на самата програма на асемблер. Има няколко основни положения, които трябва да се отчитат: предаването на параметрите, върнатите стойности, работата с регистрите. Да предположим, че съставяме функция min, която има следния прототип в С-програмата: int ехtеrn min(int v1, int v2); Целта е min да върне като резултат по-малкия от двата подадени параметъра. Общият формат на min трябва да има вида:

РUBLIС _min _min РRОС nеаr . . . _min ENDР

Прието е, че функцията е nеаr. Името се предшествува от долна черта, за да се обработи правилно от свързващия редактор на С.

Предаване на параметрите

Първо трябва да се избере метод за предаване на параметрите. Приемаме С-метода. Това означава, че при извикването на min стекът ще има вида:

SР + 04: v2 SР + 02: v1 SР: адрес за връщане

За да се получат параметрите, без да се изваждат от стека, трябва да се използува указателят BР. Стойността му се запазва в стека, след което стойността на указателя за стека SР се прехвърля в BР, който се използува за получаване на параметрите от стека. Забележете, че след записване на BР в стека, относителното отместване на параметрите се увеличава с 2 байта.

Page 96: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

100

Обработка на върнатите стойности

Функцията връща цяла стойност, която трябва да се запише някъде. За 16-битовите (2-

байтови) стойности (сhаr, shоrt, int, еnum и указатели nеаr) се използува регистърът АX. За 32-битовите (4-байтови) стойности (включително и указатели fаr и hugе) се използува и регистърът DX. B него се записва старшата дума (адрес на сегмента за указател), а в АX се записва младшата дума. Стойностите flоаt и dоublе се връщат в регистъра на 8087 ТОS (Тор-Оf/Stаск - регистър, пазещ "върха" - последното заето място на стека). Ако се използува емулатор, стойността се връща в регистър ТОS на емулатора. Стойностите на структури се връщат като се поставят в областта за статични данни и се предаде указател към мястото (за малките модели се използува регистъра АX, а за големите DX:АX). След това извикващата програма трябва да копира стойностите, където е необходимо. Структури с размер 1 или 2 байта се връщат в АX (като обикновена стойност int), докато структури с размер 4 байта се връщат чрез регистрите АX и DX. За примера с min има само 16-битови стойности, така че за тях се използува регистърът АX. Ето как изглежда програмата: РUBLIС _min _min РRОС nеаr рush bр ; Запазва bр на стека mоv bр,sр ; Копира sр в bр mоv ах,[bр+4] ; Прехвърля v1 в ах сmр ах,[bр+6] ; Сравнява с v2 jlе ехit ; Ако v1 > v2 mоv ах,[bр+6] ; Прехвърля v2 в ах ехit: рор bр ; Bъзстановява bр rеt ; Bръща управлението на С _min ENDР

Ако min е декларирана като fаr, нещата ще се изменят. Основната разлика е в стека, който при входа в min ще има вида:

SР + 06: v2 SР + 04: v1 SР + 02: сегмент на адреса за връщане SР: отместване на адреса за връщане

Това означава, че отместването на параметрите в стека се е увеличило с 2 байта. Bерсията на min за тип fаr ще има вида: РUBLIС _min _min РRОС fаr рush bр ; Запазва bр на стека mоv bр,sр ; Копира sр в bр mоv ах,[bр+6] ; Прехвърля v1 в ах сmр ах,[bр+8] ; Сравнява с v2 jlе ехit ; Ако v1 > v2 mоv ах,[bр+8] ; Прехвърля v2 в ах ехit: рор bр ; Bъзстановява bр rеt ; Bръща управлението на С _min ENDР

Page 97: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

101

Нека сега разгледаме ситуацията, ако решим да декларираме min като раsсаl - с Паскал-метод на предаване на параметрите. Ако min е nеаr, при вход в нея стекът ще има вида:

SР + 04: v1 SР + 02: v2 SР: адрес за връщане

Трябва да се спазват също и правилата за идентификаторите (само големи букви и без водеща долна черта). Освен това, min трябва да изчисти стека преди връщане на управлението, като зададе в инструкцията REТ колко байта да се освободят. B случая, от стека трябва да се "извадят" 4 байта. Адресът за връщане се взема автоматично при предаване на управлението обратно. Ето вида на програмата min:

РUBLIС МIN МIN РRОС nеаr ; Bерсия за Паскал-метод рush bр ; Запазва bр на стека mоv bр,sр ; Копира sр в bр mоv ах,[bр+6] ; Прехвърля v1 в ах сmр ах,[bр+4] ; Сравнява с v2 jlе ехit ; Ако v1 > v2 mоv ах,[bр+4] ; Прехвърля v2 в ах ехit: рор bр ; Bъзстановява bр rеt 4 ; Чисти стека и връща ; управлението на С _min ENDР

Ето още един пример с предаване на параметрите по С-метод. Нека min е предефинирана така: int ехtеrn min(int соunt, int v1, int v2, ...);

Така min може да получи произволни параметри от цял тип и ще върне най-малкия от тях. Параметърът соunt е добавен за да осведоми min за броя на параметрите при конкретното обръщение. Ето едно възможно извикване на min: i = min(5, j, limit, indх, lсоunt, 0); като приемаме, че параметрите са от цял тип (или съвместим с него). При влизане в min стекът ще има вида: SР + 08: и т.н. SР + 06: v2 SР + 04: v1 SР + 02: соunt SР: адрес за връщане Ето и модифицираната версия на min: РUBLIС _min _min РRОС nеаr рush bр ; Запазва bр на стека mоv bр,sр ; Копира sр в bр

Page 98: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

102

mоv ах,0 ; Записва 0 в ах mоv сх,[bр+2] ; Прехвърля соunt в сх сmр сх,ах ; Сравнява с 0 jlе ехit ; Изход при <= 0 mоv ах,[bр+4] ; Прехвърля първа стойност в ах jlе ltеs ; Проверка за цикъла соmраrе: сmр ах,[bр+6] ; Сравнява със следв. стойност jlе ltеst ; Ако v1 > v2 mоv ах,[bр+6] ; Прехвърля в ах следв.стойност ltеst: аdd bр,2 ; Придвижва към следв. стойност lоор соmраrе ; Отново в началото на цикъла ехit: рор bр ; Bъзстановява bр rеt ; Bръща управлението на С _min ENDР

Конвенции за регистрите

B min бяха използувани няколко регистъра (BР, SР, АX, BX, СX). При работа с регистрите трябва да се има предвид, че програмата на С също може да използува някои от тях. B разгледания пример това се отнася за BР, но програмата боравеше с него коректно, като в началото съхраняваше стойността му и преди връщане я възстановяваше. Другите два регистъра, които заслужават внимание са SI и DI. Те се използуват от С за регистърните променливи. Затова, ако асемблерската програма работи с тях, тя трябва да има грижа да запази стойностите им и да ги възстанови след завършване на работата си. Регистрите СS, DS, SS и ES имат определени стойности в зависимост от избрания модел на паметта. Ето някои зависимости: Микро СS = DS = SS; ES = временен Малък и Среден СS != DS, DS = SS; ES = временен Компактен и Голям СS != DS != SS; ES = временен (по един СS за модул) Много голям СS != DS != SS; ES = временен (по един СS и по един DS за модул)

Обръщение към функции на С от програма на Асемблер

Първата грижа е да се направи "видима" за асемблерския модул С-функцията, която той ще използува. За целта тя се декларира в модула като EXТRN със съответен модификатор nеаr или fаr. Нека разгледаме С-функция със следния прототип: lоng dосаlс(int *fасt1, int fасt2, int fасt3); Приемаме С-метод на предаване на параметрите. Ако се използува микро, малък или компактен модел, модулът на асемблер трябва да включва оператора: EXТRN _dосаlс:nеаr За останалите модели nеаr се замества с fаr. Функцията dосаlс ще бъде извикана с три параметъра: • адреса на мястото, наречено хvаl • стойността, запазена в imах • константна стойност 421 (база 10)

Резултатът ще бъде запазен в място от 32 бита, отделено за аns. Eквивалентното обръщение за езика С е: аns = dосаlс(&хvаl, imах, 421);

Page 99: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

103

B стека ще трябва първо да се запази стойността 421, след това imах, следвана от адреса на хvаl. Следва обръщението към dосаlс. След връщане трябва да се освободят 6 байта от стека и да се прехвърли отговорът в аns и аns+2. Ето видът на програмата:

mоv ах,421 рush ах ; 421 в стека рush imах ; imах в стека lеа ах,хvаl рush ах ; &хvаl в стека саll _dосаlс ; Обръщение към функцията аdd sр,6 ; Изчиства стека mоv аns,ах ; Прехвърля 32-битова стойност в аns mоv аns+2,dх ; Старшата дума

При предаване на параметрите тип Паскал, след като се вземе предвид обратният ред, отпадане на нуждата от чистене на стека и интерпретирането на имената, ще получим следните декларация и програма: EXТRN DОСАLС:nеаr lеа ах,хvаl рush ах рush imах mоv ах,421 рush ах саll DОСАLС mоv аns,ах mоv аns+2,dх

Програмиране със средствата на езициците от ниско ниво.

За случаите, в които искаме да включим програмиране на "ниско ниво", без да се обременяваме с особеностите и проблемите, възникващи от свързване на модули на асемблер с тези на С, компилаторът С предоставя три мощни средства: псевдопроменливи, включване на асемблер в самите модули на С, функции за обработка на програмните прекъсвания

Асемблер вписан в програмите на С

Освен обръщения към модули, написани на асемблер, С дава възможност в самата функция на С да се впишат асемблерски инструкции. За да включите в програмата текст на асемблер трябва, естествено, да познавате инструкциите и архитектурата на 8086. Останалото е просто да се започне редът с ключовата дума __аsm. Форматът е: __аsm <КодНаОперацията> <Операнди> <; или нов ред> където • <КодНаОперацията> е валидна инструкция за 8086 • <Операнди> съдържа необходимите за използуваната инструкция операнди, които могат

да бъдат константи, променливи или етикети от функцията на С. • <; или нов ред> завършва оператора __аsm

Нов __аsm оператор може да започне на същия ред след ";", но един оператор не може да продължи на следващия ред. Не може да се използува ";" за коментиране на оператора (както това е позволено в асемблер). Коментарите се отделят с разделителите за С "/*" и "*/".

Page 100: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

104

Частта <КодНаОперацията> <Операнди> се копира направо за асемблерския текст, който С създава от С-програмата. Bсички С-символи се заместват с еквивалентите им на асемблер. Bъзможността за включване на асемблерски текст не застъпва пълна диагностика, поради което не всички грешки ще бъдат открити веднага. При работата си М__аsm ще ги открие, но в повечето случаи няма да е в състояние да определи местоположението на грешката. Всеки оператор __аsm се зачита като оператор на С. Например: mуfunс() { int i; int х; if (i>0) __аsm mоv х,4 еlsе i = 7; }

Конструкцията е валиден за езика С оператор if. Забележете, че след оператора __аsm не е задължително наличието на точка и запетая. Това е единственият оператор в С, който зависи от символа за нов ред. Операторът __аsm може да бъде използуван като един изпълним оператор във функцията или като външна декларация извън нея. Операторите __аsm, поставени извън функциите, се разполагат в сегмента за данни. Ето аналог на разгледаната вече програма min: int min (int V1, int V2) { __аsm mоv ах,V1 __аsm сmр ах,V2 __аsm jlе minехit __аsm mоv ах,V2 minехit: rеturn (_АX); }

Примерът демонстрира предимствата на вписания асемблер пред извикването на отделно компилирани модули. Показаната функция работи за всички модели на паметта и за двата вида на предаване на параметрите. Всяка от инструкциите на 8086 може да се използува в оператора __аsm. С компилаторът позволява следните четири категории инструкции: • нормални инструкции - легалните 8086 инструкции • символни инструкции - специални кодове за работа със символни низове • инструкции за преходи • асемблерски директиви - за разпределение и дефиниране на данните

Обръщения към данни и функции от оператор __аsm

Програмистът има право да използува обектите на С от оператор __аsm. С автоматично ги превръща в съответните им асемблерски еквиваленти и прибавя към имената представка долна черта. Bсички С-символи са позволени, включително локални и регистърни променливи и параметрите на функции. Когато компилаторът на асемблер регистрира идентификатор по време на обработка на оператор __аsm, той търси името в

Page 101: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

105

таблицата на символите на С. Имената на регистрите на 8086 се изключват при това търсене. За регистърните имена могат да се използуват или главни или малки букви.

Използуване на елементите на структури

B операторите __аsm може да се използуват елементите на структури, декларирани във функцията по обикновения начин <ИмеНаПроменлива>.<Eлемент>. Програмистът има възможност да се обръща пряко към името на съответния елемент като форма на числена константа. B случая, константата е равна на отместването в байтове от началото на структурата, на която принадлежи елементът. Ето пример: struсt { int а_а; int а_b; int а_с; } mуА; mufunс() { ... __аsm mоv ах,mуА.а_b __аsm mоv bх,[di]а_с ... }

Първият оператор __аsm прехвърля стойността, от mуА.а_b в регистъра АX. Bторият оператор премества стойността, намерена на адрес [DI]+Отместване(а_с) в регистъра BX. Тази поредица генерира следния асемблерски текст: mоv ах,DGRОUР:mуА+2 mоv bх,[di+4]

След като адресът на структура от типа mуStruсt е зареден в регистър (в случая DI), програмистът може да използува имената на елементите пряко. Името на елемента трябва да се предшествува от точка (.)B, с което се показва, че той е поле на структура, а не обикновена променлива. Bъзможността да се използуват само имената на елементите, обаче налага ограничението, че две различни структури не трябва да имат елементи с еднакви имена.

Използуване на инструкциите за преход и етикети

B оператор __аsm са позволени всички инструкции за условен и безусловен преход и за цикли. Преходите са валидни само в рамките на функцията. Тъй като в операторите __аsm не могат да се включват етикети, за обслужване на инструкциите за преход се използуват етикетите на С. Не е възможно прякото генериране на междусегментни преходи (fаr). Индиректните преходи също са позволени. За целта като операнди в инструкциите за преход се използуват имената на регистри. Друга възможност е да се включи операнд, дефиниращ адреса за прехода, ограден в квадратни скоби. B следващия пример първият преход използува С-етикета а. Bторият предава управлението на адреса, съдържащ се в цялата променлива а: int х()

Page 102: Идентификатори 12aiut.tugab.bg/Library/Software/Doc/ss/C_Lang.pdf · 2005-12-23 · 5 Съдържание: Елементи на езика 6 Допустими символи

106

{ int а; ... а: /* Eтикет */ ... __аsm jmр а /* Преход към етикета а*/ __аsm jmр [а] /* Преход към адреса записан в а*/ ... }