You are on page 1of 166

Arhitecturi de sisteme

incorporate
Microcontrolere si sisteme
integrate
Programarea in C pentru sisteme
incorporate (SI): microcontrolere,
microprocesoare

Medii de programare IDE (Integrated


Development Environment), GCC Toolchains
(Re)introducere in limbajul C, standarde de
codare, MISRA C
1
Structura tipică a unui mediu integrat de dezvoltare
(IDE-Integrated Development Environment) pentru
aplicaţiile software de tip sistem incorporat-SI
IDE Editor si utilitar Make (procesor batch)

Compilator ANSI C
(Macro) Asamblor

Biblioteci
ANSI C (RT)OS
Bibliotecar

Linker (Editor de legături) / Locator programe

.hex sau .srec sau .elf


Programator
Debugger (depanator) EPROM/FLASH
sau Emulator JTAG/ICE

Simulator Monitor rezident / depanator /


CPU+resurse Bootloader
pe maşina ţinta 2
IDE (Integrated Development Environment)

• Procesul de dezvoltare este similar oricărei alte aplicaţii software şi


presupune etapele:
– crearea unui proiect, cu selectarea unei anumite variante de MPU/MCU/DSP
precum şi cu configurarea componentelor (uneltelor) soft ce vor fi utilizate
– crearea şi editarea fişierelor sursă în C sau/si limbaj de asamblare
– generarea codului pentru aplicaţie (se foloseşte termenul de construire Build =
asamblare/compilare urmată de link editare/locatare) prin intermediul unui
manager de proiecte; se bazează pe existenta (transparenta sau nu pentru
utilizator) a unui fişier de tip make
– corectarea erorilor
– testarea aplicaţiei cu ajutorul simulatorului si/sau al sistemului de dezvoltare si
/sau a simulatorului de sistem si/sau pe sistemul final (real)
– Mediul integrat de dezvoltare a aplicaţiilor (IDE) permite si
automatizarea asamblării/compilării urmată de link editare (editarea de
legături intre modulele obiect) şi locatarea codului în memoria program.
• El oferă utilizatorului si o interfaţa grafică interactivă (GUI-Graphical User
Interface), uşurând mult operarea
• Corectarea interactivă a erorilor, cu setarea diverselor opţiuni, sunt alte
facilităţi oferite de un IDE
Mai prezintă interes si eventuala existenta/disponibilitate a unui sistem de operare
de tip RTOS – sistem de operare in timp real care sunt larg utilizate in dezvoltarea
aplicaţiilor de tip SI …vom reveni
3
Toolchains
• In software un lanţ de unelte (toolchain) este un set de
unelte de programare care sunt utilizate pentru crearea
unui produs (tipic un alt program sau sistem de
programe)
• Aceste unelte sunt folosite tipic înlănţuit, adică ieşirea
unei unelte devine intrare pentru următoarea, dar
termenul este până la urmă generic, desemnând un set
de unelte de dezvoltare care au legătură intre ele
• Un astfel de lanţ simplu de unelte am mentionat si in
slide-ul anterior: un compilator si un editor de legături
(linker) care să transforme codul sursă intr-un program
executabil, un set de biblioteci care să asigure interfaţa
cu un eventual sistem de operare, un depanator de
programe, etc.
• http://en.wikipedia.org/wiki/Toolchain
Modelul simplificat de compilare C
Cod sursă C

Pre-procesor

Tipic acest ansamblu este numit


Compilator compilator

Cod sursă in limbajul de asamblare al maşinii ţintă

Asamblor
Biblioteci Cod obiect
(cod obiect)
Editor de
Legături (linker) Acesta se încarcă in memoria
(de program) a maşinii ţintă si se
Cod binar executabil
va executa acolo

5
SI-IDE- compilatorul
Compilatorul C si asamblorul (“compilatorul”)
• Fişierele sursă create la nivelul IDE sunt transmise asamblorului
şi/sau compilatorului pentru a fi translatate in limbaj maşină.
– Acestea prelucrează fişierele sursă şi creează, în principiu, fişiere
obiect relocatabile.
• Compilatorul C este de regulă o implementare completă in sens
ANSI (Kernighan & Ritchie) a limbajului de programare C.
– In plus, există o serie de caracteristici speciale asociate implementării
pentru unui MPU/MCU/DSP particular.
• (Macro) asamblorul suportă setul complet de instrucţiuni pentru
MPU/MCU/DSP şi toate derivatele acestuia.
• Este posibilă şi generarea directă de fişiere obiect absolute, la care
informaţia legată de plasarea codului în memoria program este
stabilită în faza de asamblare.
• De cele mai multe ori folosim de fapt un crosscompiler care
rulează pe o mașină gazdă (host, gen stație de lucru PC), la fel ca
intreg IDE-ul, si generează cod pentru o altă mașină (alt CPU),
mașina țintă (target)
6
SI-IDE - Linker
Editorul de legături/ (re)locatorul ( Linker/ReLocator)
• Fişierele obiect (inclusiv cele provenite din biblioteci) sunt prelucrate/combinate de
linker, rezultând fişiere binare absolute.
• Un fişier (sau modul) binar absolut conţine cod care nu este relocatabil.
• Acesta poate exista si se va executa de la anumite adrese fixe (absolute) în memoria
de program a maşinii ţintă; el este obținut cu ajutorul (re)locatorului
• Pentru simularea/depanarea simbolică se utilizează formate evoluate de
reprezentare
ELF - Extended Linker Format
COFF - Common Object File Format
care conţin si informaţia suplimentară necesară simulării si depanării interactive (cum ar fi
numele simbolurilor)
• De regulă nu se utilizează direct binarul ci există o prelucrare suplimentară (gen
BIN2HEX) intr-un format standardizat: fişier Intel HEX (I8HEX, I16HEX, I32HEX)
sau fişier S(REC) (.s19) Freescale (ex.Motorola),de tip S-record
– Fişierele respective sunt fişiere ASCII (text) care conţin o modalitate convenţională de
reprezentare a datelor si a locului (adresei) unde vor fi încărcate in memoria de program a
maşinii ţintă
– Această categorie de cod poate fi utilizată direct pentru a programa memorii de tip EPROM,
FLASH, etc.
– Aceste formate sunt insa nepractice daca dimensiunea imaginii executabile (binarului) din
care sunt generate este foarte mare (> xMOctetilor)
• Modulele obiect absolute pot fi folosite de depanator (debugger) si/sau de un
emulator in-circuit pentru depanarea/testarea aplicaţiei.
7
Linker-ul
• In contextul unui linker similar cu cel utilizat împreuna cu
compilatoarele GNU (gcc) el realizează următoarele:
• Combină fișierele obiect prin fuzionarea diverselor secțiuni ale fișierelor
obiect
– Secțiuni de tip .text pentru cod (program)
– Secțiuni de tip .data pentru variabile globale inițializate
– Secțiuni de tip .bss pentru variabile globale neinițializate
• Rezolvă toate simbolurile nerezolvate pana in acest moment:
– Variabile externe
– Apeluri de funcții (eventual via biblioteci)
• Raportează erorile legate de simbolurile nerezolvate
• Adaugă un cod de start – startup code , dacă e cazul
• Furnizează informația necesara pentru depanarea simbolică
• Linker-ul in sine produce un fișier binar relocatabil
• Pentru un sistem de operare standard care utilizează un program de
incărcare dinamic (dynamic loader), procesul este acum finalizat
• Pentru un sistem incorporat este nevoie însă de fișiere binare absolute
acesta fiind rolul (re)locatorului
(Re)locatorul
• Relocatorul convertește un binar relocatabil intr-unul
absolut
• El este o componentă a linker-ului care este “dirijată” cu
ajutorul unui script al acestuia, care specifică cel puţin:
– Secțiunile logice de memorie utilizate pentru program si pentru
date
– Memoria pentru program si pentru date a mașinii țintă (eventual
si natura ei, ROM, RAM, etc.)
– Dimensiunea si amplasamentul stivei/stivelor
• (Re)locatorul produce deci un cod binar “executabil”
(uneori numită si imagine executabilă) care poate fi
utilizat direct pe mașina țintă sau de un simulator al
acesteia
• Observație un limbaj de tip script este un limbaj de nivel înalt care este
interpretat de un alt program in momentul in care acesta se execută (at
runtime); el nu este compilat de către procesor, cum se întâmplă tipic cu
limbajul C/C++
Codul de start - startup code
• Codul de start este un mic fragment de cod scris in limbaj de
asamblare pentru mașina respectivă (CPU-ul respectiv) care are
rolul de a pregăti mașina pentru execuția unui program scris intr-un
limbaj de nivel înalt; de exemplu:
– pentru C in Unix/Linux este apelat crt1.o (fișier obiect) sau crt0.S (in
asamblare)
– pentru un microcontrolere poate fi tipic un fișier obiect specificat prin
intermediul unui script al linker-ului
• Sarcinile uzuale ale acestui cod:
– Dezactivarea tuturor întreruperilor
– Inițializarea unor periferice critice pentru aplicație (dacă e cazul)
– Inițializarea indicatorilor de stivă pentru toate stivele software
– Inițializarea secțiunilor de tip idata (secţiuni de date iniţializate, alocate
global)
– Inițializarea tuturor zonelor de date neinițializate din memoria de date
(conform standardului ANSI)
– Apelarea programului principal si a unei bucle fără sfârșit de genul :
main(); goto loop ;
SI-IDE- bibliotecarul
Bibliotecarul (Librarian, Library Manger)
• Fişierele obiect create de asamblor sau compilator pot fi
folosite de bibliotecar pentru a crea biblioteci obiect
(object libraries).
• Acestea sunt colecţii de module obiect, special ordonate
şi formatate, pe care editorul de legături le poate folosi.
• Atunci când editorul de legături prelucrează o bibliotecă,
doar acele module obiect care sunt necesare vor fi
folosite pentru crearea codului executabil
• Tot prin intermediul bibliotecilor se realizează si interfaţa
cu un eventual sistem de operare

11
Aşa arată un fişier Intel HEX (.hex)
:1000000012C02BC02AC029C028C027C026C025C0C6
:1000100024C023C022C021C020C01FC01EC01DC0DC
:100020001CC01BC01AC011241FBECFEDCDBF10E0F5
:10003000A0E6B0E0E0ECF0E003C0C89531960D9288
:10004000A036B107D1F710E0A0E6B0E001C01D92E4
:10005000A036B107E1F704D032C0D2CF0000089536
:1000600081B3806781BB87B38F6187BB82B38067B1
:1000700082BB88B38F6188BB949A80E090E0A0E057
:10008000B0E000000196A11DB11D803A26E892075C
:1000900021E0A20720E0B207A1F7949880E090E069
:1000A000A0E0B0E000000196A11DB11D80342DE05C
:1000B000920723E0A20720E0B207A1F7DDCFFFCF30
:00000001FF

12
Aşa arată un fişier SREC (.s sau .srec)
Fișierele .hex sau .s sunt fișiere text(reprezentare ASCII)!

S00F000068656C6C6F202020202000003C
S11F00007C0802A6900100049421FFF07C6C1B787C8C23783C6000003863000026
S11F001C4BFFFFE5398000007D83637880010014382100107C0803A64E800020E9
S111003848656C6C6F20776F726C642E0A0042
S5030003F9
S9030000FC

Fişierele hex/S conţin (in reprezentare ASCII hexazecimal):


- Date: ce si cat (ca număr de octeţi) se încarcă
- Adrese: unde se încarcă
- Sumede control(checksum): o modalitate de a verifica integritatea
datelor/adreselor conținute in totalitatea fișierului, cat si pe fiecare
linie
Pentru detalii vezi wikipedia:
http://en.wikipedia.org/wiki/Intel_HEX
http://en.wikipedia.org/wiki/SREC

13
SI-IDE
Depanator, simulator, monitor rezident, bootloader-ul
• Depanatorul (debugger) este de regula un depanator simbolic, permiţând
depanarea interactivă a programelor la nivelul sursei.
• El include de fapt şi un simulator care implementează o maşină
MPU/MCU/DSP virtuală.
– Prin intermediul unei baze de date pentru resurse incorporate el permite o
configurare corespunzătoare variantei de MPU/MCU/DSP utilizate precum şi a
hardware-ului exterior acestuia (memorie externa).
• Monitorul rezident este tot un program de depanare dar care va fi rezident
(există şi rulează) în memoria de program a unei maşini ţintă
(MPU/MCU/DSP).
– El utilizează anumite resurse ale maşinii ţinta (memorie, anumite periferice) care
astfel nu mai sunt disponibile pentru aplicaţie!
– El comunică cu mediul IDE prin intermediul unui port serial (COM) al PC-ului,
unui port serial emulat USB sau al unei conexiuni Ethernet a acestuia
– Este astfel posibilă depanarea interactivă, la nivel de sursă, (cu anumite limitări)
a programelor ce rulează pe un sistem ţintă real.
• Bootloader-ul este in mod tipic tot un mic program rezident in memoria de
program a maşinii ţinta, care va încărca “adevăratul” program al aplicaţiei
folosind un port de comunicaţie
• De multe ori există o combinaţie de monitor rezident/depanator si
bootloader ca o entitate unică cu aceste funcţii

14
SI-IDE
• AVR Studio 4 este un
IDE freeware pentru
familia de microcontrolere
AVR 8 biti, dar care nu +
are decât un asamblor
• AVR Studio 4 +
compilatorul C WinAVR
este un IDE in sensul
celor prezentate anterior
• Pentru versiunile
ulterioare ale AVR Studio,
de exemplu Atmel
Studio 6/7 compilatorul
Win AVR este deja
integrat in IDE
15
GCC Toolchain, un exemplu
• Pentru ilustrare vom alege asa zisul GCC
Toolchain pentru familia de microprocesoare/
microcontrolere de 32 de biti ARM
• Este vorba de fapt de un set întreg de programe
care alcătuiesc un set de unelte GCC (GCC
Toolchain) pentru o anumită mașină țintă, in
cazul acestui exemplu, un CPU de tip ARM
(generic)
– Deși mai puțin vizibile pentru utilizatorul obișnuit AVR
Studio 4, o bună parte din cele prezentate sunt
adevărate si pentru compilatorul gcc WinAVR (AVR 8
biți)
Care este drumul de la un program scris in limbaj de
asamblare la imaginea executabilă a programului?
Fişier program
binare (.bin)

Fişier imagine
executabila (.elf,
Fişiere sursă Fişiere obiect .o, .so)
asamblare (.s) (.o)
ld
(linker-
editorul
as de
(asamblorul) legături)

Memory
layout

Fişier cu codul
Script Linker dezasamblat (.lst)
(.ld)

17
Care este drumul de la un program scris in limbaj C si in
asamblare la imaginea executabilă a programului?
Fișier sursa C Fişiere sursă in
(.c) asamblare(.s) Fişiere
as obiect (.o) Fişier program
(assembler)
binare (.bin)

gcc
(compiler)
Fişiere sursă in Fişiere obiect
Fişier imagine
asamblare(.s) (.o)
executabila
(.elf, .o, .so)
ld
(linker-
as editorul
(assembler) de
legături)

Fişiere obiect Configuraţie


biblioteci memorie
(.o, .so) Fişier cu
codul
Script Linker dezasamblat
(.ld) (.lst, .lss) 18
Care sunt numele fişierelor executabile din Toolchain-ul
(setul/lanţul de unelte) GNU C (GCC) pentru ARM?
• Pentru toate există un prefix “arm-none-eabi-” care identifică maşina ţintă;
– EABI = Embedded Application Binary Interface
• Prefixe similare există si pentru celelalte familii de microprocesoare
(maşini ţintă - targets) pentru care există portări ale GCC (powerpc,
mips, x86)!
• Asamblorul (as)
– arm-none-eabi-as
• Linker – editorul de legături (ld)
– arm-none-eabi-ld
• Generare binar program - Object copy (objcopy)
– arm-none-eabi-objcopy
• Dezasamblorul - Object dump (objdump)
– arm-none-eabi-objdump
• Compilatorul C (gcc)
– arm-none-eabi-gcc
• Compilatorul C++ (g++)
– arm-none-eabi-g++
19
Un exemplu de fişier Makefile

all:
arm-none-eabi-as -mcpu=cortex-m3 -mthumb example1.s -o example1.o
arm-none-eabi-ld -Ttext 0x0 -o example1.out example1.o
arm-none-eabi-objcopy -Obinary example1.out example1.bin
arm-none-eabi-objdump -S example1.out > example1.lst

• Nota
• Make este un program utilitar (provenit si larg utilizat in contextul
Unix/Linux) care construieşte in mod automat (asta face si un IDE)
programele executabile si bibliotecile prin utilizarea unor fişiere
numite Makefile care specifică cum anume se obţine imaginea
executabilă pentru maşina ţintă.
• Formatul ELF (Executable and Linkable Format) este un format
standardizat utilizat pentru fişierele obiect, executabile, biblioteci
partajate (shared libraries), fişiere de tip “core dump” (Linux),
– Extensiile acestui tip de fişiere pot fi diverse: .elf, .o, .so, .prx, .puff sau
fără nici o extensie !
20
Ce informaţii ne furnizează fişierul cu codul
dezasamblat?
all:
arm-none-eabi-as -mcpu=cortex-m3 -mthumb example1.s -o example1.o
arm-none-eabi-ld -Ttext 0x0 -o example1.out example1.o
arm-none-eabi-objcopy -Obinary example1.out example1.bin
arm-none-eabi-objdump -S example1.out > example1.lst

example1.out: file format elf32-littlearm


.equ STACK_TOP, 0x20000800
.text
.syntax unified Disassembly of section .text:
.thumb
.global _start 00000000 <_start>:
.type start, %function 0: 20000800 .word 0x20000800
4: 00000009 .word 0x00000009
_start:
.word STACK_TOP, start 00000008 <start>:
start: 8: 200a movs r0, #10
movs r0, #10 a: 2100 movs r1, #0
movs r1, #0
loop: 0000000c <loop>:
adds r1, r0 c: 1809 adds r1, r1, r0
subs r0, #1 e: 3801 subs r0, #1
bne loop 10: d1fc bne.n c <loop>
deadloop:
b deadloop 00000012 <deadloop>:
.end 12: e7fe b.n 12 <deadloop>

21
Cum poate fi citit conţinutul unui fişier obiect?

• $ readelf –a example.o

• Putem citi si afişa:


– Header (antetul) fişierului ELF
– Header program
– Header secţiuni
– Tabela de simboluri
– Atribute ale fişierelor

• Alte opţiuni
– -s afişează si simbolurile
– -S afişează header-ele secţiunilor

22
Ce conţine fişierul obiect?

$ readelf -S example1.o
There are 9 section headers, starting at offset 0xac:

Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS 00000000 000034 000014 00 AX 0 0 1
[ 2] .rel.text REL 00000000 000300 000008 08 7 1 4
[ 3] .data PROGBITS 00000000 000048 000000 00 WA 0 0 1
[ 4] .bss NOBITS 00000000 000048 000000 00 WA 0 0 1
[ 5] .ARM.attributes ARM_ATTRIBUTES 00000000 000048 000021 00 0 0 1
[ 6] .shstrtab STRTAB 00000000 000069 000040 00 0 0 1
[ 7] .symtab SYMTAB 00000000 000214 0000c0 10 8 11 4
[ 8] .strtab STRTAB 00000000 0002d4 00002c 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)

23
Care este conţinutul unui script tipic pentru linker?
OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(main)

MEMORY
{
ram (rwx) : ORIGIN = 0x20000000, LENGTH = 64k
}

SECTIONS
{
.text :
{
. = ALIGN(4);
*(.text*)
. = ALIGN(4);
_etext = .;
} >ram
}
end = .;

24
Ce conţine un fişier cu o imagine executabilă ?

• Segmentul .text
– Codul executabil
– Vectorul iniţial de reset (adresa de unde se
executa prima instructiune a programului)
• Segmentul .data (.rodata in ELF)
– Variabile statice (iniţializate)
• Segmentul .bss
– Variabile statice (neiniţializate)
– Iniţializat cu zero-uri de către sistemul de
operare sau bibliotecile C de runtime
– Denumirea provine de la: Block Started by
Symbol
• NU conţine stivă/stive sau zona de memorie
pentru alocare dinamică (heap) !
• Vezi si:
/usr/include/linux/elf.h 25
Cum poate fi citit conținutul unui fișier imagine
executabilă?

• Exact la fel ca un fişier obiect (adică cu readelf)!


• Aceleaşi opţiuni utile (câteva):
– -a afişează toata informaţia
– -s afişează simbolurile
– -S afişează header-ele secţiunilor
• OBSERVATIE Un fișier imagine executabilă poate avea
câteva zeci de octeți dacă este vorba de o aplicație
simplă pentru un microcontroler AVR de 8 biţi sau sute de
MOcteţi dacă este vorba de imaginea unui sistem de
operare Linux pentru un microprocesor ARM sau
similar…
26
Câteva exemple pentru fișierele aferente
Toolchain-ului GCC in cazul WinAVR

• Pentru proiectul BlinkyX acestea sunt


fișierele care se găsesc in sub-directorul
…/default
• *.eep, *.elf, *.hex sunt fișiere care se
utilizează pentru programarea
microcontrolerului (.elf sau .hex pentru
memoria Flash, .eep pentru memoria
EEPROM)
• *.lss este fișierul listing, in el găsim si codul
dezasamblat, in limbaj de asamblare
• Fișierele Makefile, • *.o este fișierul obiect, care va fi locatat
*.map, *.lss , *.hex, (plasat in memorie la locul potrivit) de
*.eep sunt fișiere text, linker; este un fișier binar
care se pot deschide cu • *.map este fișierul de raportare al linker-
orice editor de texte
ului, care arata si cum au fost plasate in
memorie diversele segmente
• Makefile este fișierul Make, care nu are nici 27
o extensie
Să începem cu un clasic “Hello
World”!
#include <stdio.h>
main()
{
printf(“hello, world\n”);
}

Pentru a compila si rula pe o maşina Linux/Unix (in linie de


comanda):
$ gcc –o helloworld helloworld.c
$ ./helloworld
hello, world
Fără un IDE pare ca e ceva mai greu de lucrat…dar folosind
Makefile-uri se poate.
Ca editoare de text pentru surse se pot folosi editoare freeware
orientate (si) pe limbajul C cum ar fi: Programmer Notepad,
PSPad, etc.
28
Programarea in C pentru
sisteme incorporate
Este pana la urma tot programare
in limbajul C..

29
C si Java/C++
C este un limbaj procedural:
– Nu există clase si obiecte
– Cărămida de bază este “funcția”

Filozofia limbajului C:
– Cât mai simplu posibil
– Un set minim de constructori pentru limbaj
– Permite ușor un acces la nivel scăzut la resursele
sistemului de calcul!
• Un lucru bun, pentru programatorul de sisteme incorporate
• Un lucru rău, deoarece se pot si scrie cu relativa ușurință viruși
(in Java e mult mai greu de scris viruși..)
– Se bazează masiv pe utilizarea bibliotecilor

Discutăm despre ANSI C (K&R): marea majoritate a


compilatoarelor C pentru MPU/MCU/DSP sunt conforme
acestui standard dar atenţie, pot avea si caracteristici
care nu sunt conforme (mai ales in cazul MPU/MCU de
8/16 biţi) 30
Funcţii in C
Sunt echivalentele metodelor din Java, sau a
funcţiilor/procedurilor din PASCAL:

TipReturnat NumeFunctie (
Tip NumeParametru1,
Tip NumeParametru2, …)
{
return expresie de TipReturnat;
}

31
..Funcţii
/* calculeaza media lui x si y */
int average(int x, int y)
{
int avg_value;
avg_value = (x + y) / 2;
return avg_value;
}

/* mai simplu */
int average2(int x, int y)
{
return (x + y)/2;
}

32
..Funcţii
Echivalentul unei proceduri din Pascal este
o funcţie cu tipul de valoare returnat “void”
/* o procedura/functie care apeleaza alte trei */
void do_tasks(int x, int y, int z)
{
do_task1();
do_task2(x, y);
do_task3(z);
}

33
..Funcţii
Funcţia “main”: este punctul de start (de intrare) al
oricărui program in C
int main(int argc, char *argv[])
{
int i;
for (i = 0; i < argc; i++)
fprintf(“parameter %d is %s\n”, i, argv[i]);
}
Alte forme
int main();
main();

ATENTIE: Tipic pentru unele aplicaţii de tip SI, mai poate exista o funcţie care se
execută înainte de main (), de genul start_up (), funcţie scrisă in cea mai mare
parte in limbaj de asamblare. Ea realizează iniţializarea unor resurse critice,
imediat după reset-ul MCU/MPU/DSP si apoi apelează pe main(). Vezi partea
introductivă!
34
Funcţii
Prototipul unei funcții: se declară mai întâi, dar se
defineşte mai târziu
int func_apelata(int x, int y); //este declarat prototipul
….

int func_apelanta()
{

avg = func_apelata (a, b); // aici e apelata

}
….
int func_apelata(int x, int y) // mai tarziu definita
{

}

35
Tipuri, variabile si constante
Variabile scalare Structuri si uniuni

int i, j, k; struct {
char c; int x, y;
float x, y; float weight;
}
double z;

union {
Tablouri (vectori) char ch_value;
int int_value;
float X[100];
float float_value;
float pixel[1024][768];
}

36
Tipuri, variabile si constante
Câteva reguli in ce priveşte denumirea variabilelor
Ce se foloseşte: A-Z, a-z, 0-9, _ (underscore)
Prima litera trebuie sa fie A-Z, a-z, sau _ underscore
Contează litera mică sau mare (este case sensitive): MyVariable si
myvariable sunt diferite!!
Lungime: poate avea oricate caractere, dar tipic numai primele 31 contează

Convenţional si ca regulă de bună practică trebuie să se folosească nume


semnificative/relevante:
int inaltime;
float suprafata;
int dimensiune_vector;
Un stil de notare de bună practică care ne dă si o informaţie despre tip este:
int nInaltime; //prefixul n spune tipul intreg
float fSuprafata; //prefixul f spune tipul flotant
int nDimensiune_vector;
int *pnValoare; /*prefixul p ne spune ca avem un
pointer, n catre Valoare de tipul intreg */

37
Tipuri, variabile si constante
Tipuri scalare (implicit cu semn, signed): întreg (sau virgulă fixă), caracter si
virgulă mobilă
Întregi:
int întreg normal, 2, 4 sau 8-octeti
short întreg scurt, 2-octeti (evitati-l !)
long întreg lung/ tipic dublu fata int, 4, 8
Caracter:
char un singur caracter, 1-octet/Byte
Virgulă mobilă (VM)-IEEE 754:
float simplă precizie, 4-octeti
double dublă precizie , 8-octeti

Dimensiunea tipului int/long este dependentă de arhitectura CPU (MPU/MCU/DSP) !!


Pentru Win AVR/AVR 8 biți dimensiunea tipului int este de 2 octeți, lucru obișnuit
pentru microcontolerele de 8 biti 38
Tipuri, variabile si constante
Modificatorul unsigned (fără semn)
unsigned short short_value;
unsigned int int_value;
unsigned char char_value;

1. Afectează/modifică domeniul de reprezentare; de exemplu,


dacă int are 2 octeţi:
unsigned int avem 0 … 65535; int avem -32 768 … +32 767.
2. Contează si atunci când variabilele sunt folosite in comparaţii,
condiţii

39
Tipuri, variabile si constante: număr
de octeți (bytes) si domeniul de
reprezentare!
Name Bytes Range
char 1 -128 to 127
unsigned 1 0 to 255
char
short 2 -32,768 to 32,767
int varies may be same as
short
long 4
float 4 7 significant digits
double 8 15 significant digits
* (pointer) width of range of memory
memory
Cunoașterea numărului de octeți si a domeniului de
reprezentare (range) pentru un anumit tip este de
esențială in programarea SI! 40
Tipuri, variabile si constante
Iniţializarea variabilelor, se face la declarare
sau la utilizare:
int n = 100; //facuta la declarare
float x = 20.5;
char ch = ‘X’;

int avg = average(n, m);// sau la utilizare


int m = n/2, k;

41
Tipuri, variabile si constante
Conversia de tip (type casting): conversia unei
valori de un tip intr-o valoare de un alt tip
Atenţie: poate avea ca efecte colaterale, trunchieri, rotunjiri,
modificări ale domeniului de reprezentare (vezi unsigned)
(tip) expresie

pi = (double) 3.1415926;

size = (int) (3.14 * radius * radius);

if ((unsigned) x > (unsigned) y) …

42
Tipuri, variabile si constante
• typedef permite introducerea de “sinonime” pentru tipuri care deja există
sau au fost declarate intr-un fel sau altul, mărind lizibilitatea codului
• De exemplu pentru compilatorul WinAVR (de fapt pentru toate
compilatoarele gcc) in fișierul header stdint.h găsim următoarele re-
definiri de tipuri (cu denumiri de tip mai sugestive decât cele standard
din C, incluzând si numărul de biti):

typedef signed char int8_t;

typedef unsigned char uint8_t;

typedef signed int int16_t;

typedef unsigned int uint16_t;

typedef signed long int int32_t;

typedef unsigned long int uint32_t;

43
Tipuri, variabile si constante
Cum se folosesc/definesc constantele?

Folosim macroinstrucţiuni (directive) de tip #define :

#define N 1024 // N=1024


...
int array[N];

Folosim variabile constante (există si aşa ceva..):

const double Pi = 3.1415926;// la declarare

44
O paranteză: pre-procesorul
• Modifică codul sursă înainte de al transfera
compilatorului
• Trei utilizări:
– directive,
– constante si
– macroinstrucţiuni
• Directivele sunt comenzi care spun pre-procesorului:
– să “sară” anumite porţiuni ale fişierului sursa,
– să includă un alt fisier,
– să definească (#define) constante sau macroinstrucţiuni
• Încep cu simbolul # si tipic utilizează numai litere mari,
pentru identificarea rolului lor special

45
Pre-procesorul: compilarea
condiţională
• Există un set larg de opţiuni (directive) care pot fi
utilizate pentru a determina dacă pre-procesorul va
înlătura anumite linii de cod înainte de trece mai departe
codul sursă compilatorului
• Acestea sunt: #if, #elif, #else, #ifdef, si #ifndef.
• Un bloc #if sau #if / #elif / #else sau un bloc #ifdef
sau #ifndef trebuie să fie terminat (închis) cu un
#endif.
• Directiva #if aşteaptă un argument numeric pe care-l
evaluează ca adevărat(TRUE) dacă este diferit de zero,
caz in care codul care urmează pana la directivele #else,
#elif, sau #endif, va fi păstrat.
• Dacă argumentul este evaluat ca fals (FALSE), egal cu
0, atunci codul care urmează, pană la directivele #else,
#elif, sau #endif, va fi eliminat.
46
Pre-procesorul: compilarea
condiţională
Un exemplu de cod care execută tipărirea unei informatii care e utilă doar in
faza de testare/depanare (debug) a aplicatiei:

#ifdef DEBUG
printf("debug:x = %d, y = %f\n", x, y);
...
#endif

Activarea presupune definirea anterioara a lui DEBUG


#define DEBUG 1
Sau e suficient ca DEBUG să fie doar definită:
#define DEBUG
Dezactivarea presupune:
#define DEBUG 0
Sau
#undef DEBUG//se inlatura definitia anterioara a lui DEBUG
47
Pre-procesorul: compilarea condiţională
# define TINY13 0
# define MEGA8 1
# define MEGA32 2

# define MyAVR MEGA8

#if MyAVR == TINY13
# define TXD 12
# define RXD 13
#elif MyAVR == MEGA8
# define TXD 1
# define RXD 2
#elif MyAVR == MEGA32
# define TXD 5
# define RXD 6
#else
# error “Nu avem o definitie pentru TxD si RxD”
#endif
Dacă ne-ar interesa o definire a poziţiilor pinilor TxD sau RxD (port serial AVR) funcţie de
ce varianta de AVR utilizăm
Directiva #error va afişa un mesaj la compilare, dacă definirea lipseşte in cod
48
Pre-procesorul: evitarea includerii multiple

• O problema comună care apare intr-un proiect care


utilizează mai multe fişiere header/sursă: un fişier
header poate fi necesar in mai multe alte fişiere header,
care la rândul lor vor fi incluse mai târziu intr-un fişier
sursă
• Variabile, constante, structuri sau funcţii pot apărea
definite de mai multe ori, cate odată pentru fiecare fişier
inclus
• Rezultatul: erori si dificultăţi in compilarea programului
• Prin folosirea directivei #ifndef, se poate include un bloc
de text doar dacă o expresie particulară nu este definită:
apoi in fişierul header dorit se defineşte această
expresie.
• Aceasta ne va asigura că un cod sursă corespunzător lui
#ifndef este inclus doar prima dată, când este încărcat
(inclus) fişierul 49
Pre-procesorul: evitarea includerii multiple

#ifndef _FILE_NAME_H_
#define _FILE_NAME_H_
/* aici urmeaza codul */
#endif // #ifndef _FILE_NAME_H_
• O modalitate similară poate fi utilizată pentru a evita
definirea multiplă a unor constante specifice, cum ar fi de
exemplu, constanta NULL:
Litere MARI
#ifndef NULL
#define NULL (void *)0
#endif // #ifndef NULL
50
Pre-procesorul:macroinstrucţiuni
• O macroinstrucțiune are uzual forma :
#define NUME_MACRO(arg1, arg2, ...) [codul sursa care va fi generat]
Exemple:
#define INCREMENT(x) x++
#define MULT(x, y) x * y
Instanțierea /utilizarea (nu este vorba de o apelare!) unei macroinstrucţiuni înseamnă
înlocuirea ei cu codul sursă descris in corpul ei si cu valorile invocate ale
parametrilor formali, ea nu implică o încărcare suplimentară a unității
centrale, ca in cazul apelării unei funcții.
In schimb, codul unei funcții apare o singură dată in codul programului, pe când
codul aferent macroinstrucțiunii apare de atâtea ori de câte ori este utilizată
Este deci eficienta ca timp de execuție dar ineficienta ca grad de ocupare a memoriei

Aşa am utiliza pe MULT:


int z = MULT(3 + 2, 4 + 2);
// asa se instantiaza in codul sursa
int z = 3 + 2 * 4 + 2; /* dar 2 * 4 va fi evaluat primul!*/

Modalitatea corectă de definire este:


#define MULT(x, y) (x) * (y)
/* acum MULT(3 + 2, 4 + 2) va genera (3 + 2) * (4 + 2) cea ce este 51
corect */
Pre-procesorul:macroinstrucţiuni, alte
exemple
/* Folosim un SAU-EXLUSIV pentru a permuta-swap-
cele 2 variabile */
#define SWAP(a, b) a ^= b; b ^= a; a ^= b;
//utilizare
int x = 10;
int y = 5;
SWAP(x, y);

• Definirea unei macroinstrucţiuni pe mai multe linii!!


#define SWAP(a, b)
{ \
a ^= b; \
b ^= a; \
a ^= b; \
}

52
Pre-procesorul:macroinstrucţiuni
(WinAVR)
• Un exemplu util, in contextul compilatorului WinAVR, sunt
macroinstrucțiunile utilizate pentru manipularea biților, din biblioteci
definite in diverse fișiere header (de ex. io.h, sfr_defs.h, s.a)

• Macroinstrucțiunea:
#define _BV( bit) (1 << (bit))
– convertește valoarea de bit intr-un octet: un octet doar cu un “1” pe
poziția “bit”, restul biților fiind “0”
• Macroinstrucțiunea:
#define _SFR_BYTE(sfr) _MMIO_BYTE(_SFR_ADDR(sfr))
– Returnează valoarea octetului din registrul sfr; care la rândul ei folosește
_MMIO_BYTE si _SFR_ADDR prin care se face trecerea de la numele registrului
la adresa lui din memoria de date (zona I/O)

• Un exemplu de utilizare directă a lui _BV pentru setarea bitului 2 din portul
C ar fi:
PORTC |= _BV(PC2);
Pre-procesorul:macroinstrucţiuni
(WinAVR)
• Ea la rândul ei este utilizată de alte macroinstrucțiuni de manipulare explicită a biților:
– setare bit din sfr
#define bit_is_set(sfr, bit) (_SFR_BYTE(sfr) & _BV(bit))
– ștergere bit din sfr
#define bit_is_clear(sfr, bit) (!(_SFR_BYTE(sfr) & _BV(bit)))
– buclare pana bit-ul din sfr este 0
#define loop_until_bit_is_set(sfr, bit) do { } while
(bit_is_clear(sfr, bit))
– buclare pana bit din sfr este 1
#define loop_until_bit_is_clear(sfr, bit) do { } while
(bit_is_set(sfr, bit))

• Există si o formă mai veche a unor macroinstrucțiuni similare;


– Ștergere (clear) bit din sfr
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= -_BV(bit))
– setare bit din sfr
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
Tipuri, variabile si constante
Constante speciale: tipul enumerat (enum)
enum color_type {RED, YELLOW, BLUE};
enum color_type wall_color;

Echivalent cu
#define RED 0
#define YELLOW 1
#define BLUE 2

Asignarea automată a valorii constantelor unui enum se


face in ordinea enumerării, începând de la 0
55
Tipuri, variabile si constante
• Exemplu: definirea stărilor unei maşini
secvenţiale, definim un nou tip numit
STARI, de tip enumerat:
typedef enum {
STARE_1, STARE_2,
STARE_3, STARE_4,
STARE_5, STARE_6,
STARE_7, STARE_8
} STARI;

56
Tipuri, variabile si constante: baze
de numerație
Valori constante in C : întregi
zecimal 100
hex 0x64
octal 0144

Hex începe cu “0x”, octal începe cu “0”!


0144 este 10010

57
Tipuri, variabile si constante
Valori constante in C : caracterele
‘\0’ caracterul NULL (cod ASCII =00)
‘\n’ newline (LF)
‘\r’ return (CR)
‘a’ litera “a”
‘\t’ tab
‘\32’ caracterul al cărui cod ASCII este
3210 (spaţiu, space)

58
Tipuri, variabile si constante
Virgula mobilă (VM) = se utilizează un standard de
reprezentare numit IEEE 754
– Folosită mai puţin in aplicaţii de tip sistem incorporat
cu MPU/MCU “mici”, de 8 sau 16 biți
– Foarte “costisitoare” in termen de resurse utilizate in
comparaţie cu numerele întregi (in virgula fixă - VF) dacă nu
există suport hardware pentru ea
Simpla precizie utilizează pentru reprezentare 32-biti
(4 octeţi), iar dubla precizie 64-biti (8 octeţi)
Exemplu:
AVR(MCU 8 biţi): op VM vs. op VF (întregi), o adunare
pe 32 de biţi (4 octeţi):
Execuția durează 160 cicluri maşină vs. 3 cicluri maşină!
Evitaţi utilizarea VM pentru aplicaţiile AVR 8 biti

59
Tablouri (arrays) si pointeri
Tip NumeVariabila [Dimensiune];
Tip NumeVariabila [Dimensiune1][Dimensiune2];
Tip NumeVariabila [Dimensiune1][Dimensiune2] .. [DimensiuneN];

• O înşiruire a unei variabile specifice având acelaşi tip, memorată in memorie


• Un tablou poate fi undimensional, adica un vector, sau multidimensional
• In limbajul C sunt Zero-indexate: adică indexarea elementelor incepe 0 si se
termină la N-1 !

int vector[8];
int vector2[4][4];
int vector3[2][2][2];
int n,k,l;

vector[0] = 1; /* primul element */
n = vector[7]; /* ultimul element */
k = vector2[3][3];
l = vector3[1][1][1];
vector[8] = -1; /* ce se intampla?! */
60
Tablouri si pointeri
Atenţie la limite (graniţe) in C!
In C nu există un mecanism intrinsec de a
semnala/verifica accesul, la scriere sau citire,
dincolo de limitele unui tablou!
Scrierea dincolo de limitele unui tablou = un dezastru
potenţial! Acolo se pot afla alte date esențiale pentru
funcționarea corecta a programului!

Ce este exact un tablou?


Nu este un tip specific
Este de fapt un pointer la un bloc de memorie
Nu există un mecanism intrinsec pentru copierea
tablourilor (asignarea lor ca un tot)
61
Tablouri: atribuirea/asignarea lor se
face element cu element!

Exemple

/* Asa e bine */
nTestArray1[0] = nTestArray2[0];

/* Asa nu este bine, eroare la compilare


!!!!!!!!!*/
nTestArray1 = nTestArray2;

62
Pointeri
O variabilă este doar un bloc de (sau in) memorie,
caracterizat de:
– Adresă (adresa de început): Unde începe blocul in spaţiul de
adrese ?
– Dimensiune: Cât loc el ocupă in memorie, incepand de la
adresa de inceput?
– Tip: Cum trebuie interpretată valoarea/conținutul blocului?

Un pointer este o adresă de început a unui bloc de


memorie:
– Valoarea pointerului: adresa de (din, in) memorie
– Variabila de tip pointer: o variabilă care conţine o adresă de
memorie

63
Pointeri - dimensiunea lor
• Cât de “mare” este un pointer?
• Cât loc ocupă el in memorie?
• Răspunsul este că depinde de dimensiunea spaţiului
de adrese al mașinii țintă (MPU/MCU/DSP)
– In slide-ul anterior este vorba de o adresă exprimată pe 32 de
biţi (un spaţiu de adrese de 232 adică 4GB): un pointer va ocupa
4 octeţi
– Dacă spaţiul de adrese este de 64kB (adrese de 16 biți) el va
avea doar 2 octeţi
– Un pointer pentru compilatorul WinAVR are întotdeauna 16 biți!
• Un cuvânt de program AVR (unitatea adresabilă in memoria
FLASH) are întotdeauna dimensiunea de 16 biți/ 2 octeți
• Aceasta poate pune unele probleme pentru variantele la care
memoria de program implementată depășește limita de 64 de
kilocuvinte, dar acum Win AVR oferă acum mijloacele de a le
conturna
– Mai ales in acest caz, trebuie citită si documentaţia
compilatorului! 64
Pointeri
int nVal;
int *pnVal;
pnVal = &nVal;/* fie de ex. adresa 0x2000 */
nVal = 10;
pnVal este 0x2000 (adresa)
*pnVal este 10 (ce se afla la adresa)

*pnVal = 5;

pnVal este inca 0x2000


*pnVal este 5
nVal va fi si el 5
65
Pointeri- utilizare
Trei puncte cheie când utilizam pointerii:
1. Declararea pointerului
tip *pNume;
char *pChar;
long *pHistory;
2. Iniţializarea pointerului
Pentru a putea fi utilizat el trebui să indice ceva.
pChar = (char *) 0x00001800;
pHistory = &lValue;
Castingul (char *) spune compilatorului că este vorba de o adresă de
memorie de 32 de biţi (adresa unui char), nu de o valoare de 32 de biţi.
3. Accesarea pointerului (Read/Write)
Pentru a utiliza valoarea, trebuie utilizat un * in fata numelui.
n = *pChar & 0x80;
if( (*pHistory + 25) > TOL_HISTORY )
*pHistory = TOL_MINIMUM;

Pointerii trebuie iniţializaţi: un pointer neiniţializat= un potenţial dezastru!


66
Pointeri- utilizare 2
Declararea unui pointer: Tip *NumeVariabila;
char *pLED;
char *pSwitch;

Iniţializarea unei variabile pointer (memorie unificata)


pLED = 0x0f001000;//adresa locatie LED- iesire
pSwitch = 0x0f001004;//adresa locatie switch- intrare

Utilizarea (de-referirea) unei variabile pointer


*pLED = 0xff; /* stinge LED-urile, actualizeaza o
iesire */
ch = *pSwitch;/*citeste stare comutator – citeste o
intrare */

Înţelegerea si utilizarea pointerilor este esenţială pentru


un programator C! 67
Pointeri: void, NULL
• Un pointer de tipul void este un tip special de pointer si aceasta
arată ca el poate indica orice tip de dată.
int i;
float f;

void* variabila_pointer;
..
variabila_pointer =&i;/*ambele atribuiri, initializari sunt corecte*/
variabila_pointer=&f;
• Pointerii NULL pot fi pointeri pentru orice tip de dată, dar ei nu
indică nici o adresă de memorie sau referinţă validă
• Există mai multe modalităţi de a declara/iniţializa un pointer NULL:
– Prin utilizarea macroinstrucţiunii (constantei ) NULL:
void * NULL
int* exemplu_pointer;
exemplu_pointer =0; //forme echivalente
exemplu_pointer=NULL;
• Un pointer NULL este diferit de un pointer neiniţializat, el poate
fi folosit in operaţiile aritmetice sau de comparare, având valoarea 0
– Un exemplu: o funcţie care returnează un pointer, poate returna un
pointer NULL in cazul in care de fapt nu poate returna un pointer valid;
pointerul returnat poate fi testat dacă este == NULL înainte de utilizare68
Pointeri – implementarea memoriei
Unde indică sau ce indică de fapt un pointer corect iniţializat?
Depinde de sistem, dar nu întotdeauna in memoria RAM sau ROM
Există două tipuri de arhitecturi de bază, mai ales la MPU:
Memorie unificată – Freescale (Power PC dar si HC05)
Toate dispozitivele, RAM, ROM si I/O ocupă un spaţiu de
adresare unic
0x2000 poate fi o locaţie in RAM, ROM, un “LED” – o ieşire (O)
sau un “comutator” – o intrare (I)
Singura modalitate de a accesa I/O este prin intermediul
pointerilor
Memorie separată pentru I/O – Intel (80x86)
Adrese separate pentru I/O si memorie
Avem porturi de I/O, instrucţiuni distincte pentru lucrul cu ele
Dar la AVR 8 biti?
Are memorie de date (SRAM), de program (ROM) si registre I/O in
spaţiul memoriei de date si instrucţiuni separate pentru lucrul cu ele
69
Pointeri – implementarea memoriei
• Dacă citim o o locaţie din memoria RAM vom
obţine ultima valoare care a fost scrisă in acea
locaţie
• In cazul insă in care locaţia este asociată unui
dispozitiv de I/O aceasta se poate comporta la o
scriere si citire in mod specific, deoarece apare o
interacțiune cu ce se poate afla si in afara
sistemului de calcul.
– O operaţie de scriere a unui octet cu bitul 7 =1 poate
avea efecte diferite decât cea a unei scrieri cu bitul 7 =0
– Trebuie înţeles modelul programatorului pentru
acel dispozitiv I/O si/sau natura acelei interfeţe cu
mediul exterior
70
Pointeri - aritmetica
Aritmetica cu pointeri si compararea lor
– Se poate face adunare si scădere; pentru aceasta contează
dimensiunea (tipul) elementului adresat
– Se poate testa egalitatea sau inegalitatea
int *pInt = 0x0f000000;//un CPU la care int are 4 oct.
char *pChar = 0x0f000004;
*(pInt +1) indica 0x0f000004 (tipul int avand aici 4
octeti)
*(pChar+1) indica 0x0f000005 (tipul char avand 1 octet)
“pChar > pInt” este adevărat
“pChar == pInt + 1” este adevărat

Pointerii către tipuri diferite sunt diferiţi!


Nu există o conversie implicită de la un tip la altul, cum se
întâmplă la tipurile aritmetice!

71
Tablouri si Pointeri
O variabilă tablou are asociată o valoare de tip pointer;
int X[N];
int *pX = X;

Următoarele atribuiri sunt echivalente:


X[2] = 100;
*(X+2) = 100;
*(pX+2) = 100;
pX[2] = 100;

Un tablou nu este in realitate o variabilă!


X = 0xf0000000;/* eroare de compilare!!!!! */
Când este utilizat intr-o expresie el este de fapt un pointer la primul
element al tabloului !

72
Structuri (struct) si uniuni
Sunt utilizate pentru definirea unor
componente complexe:
struct point { //asa se defineste o structura
int x; //elementele nu este necesar sa fie de
int y; //toate acelasi tip!
char z;
};

struct point p1 = {10, 20};//ex. declarare cu initializare
struct point p2, p3; //declarare
p2.x = 10; //
p2.y = -p1.y; //asa se acceseaza elementele
p3 = p2; //asignare structuri

73
Structuri si uniuni
Structuri si pointeri la structuri

//fie structura p1 de tip point, declarat anterior


struct point p1 = {10, 20, 30};
//declararea pointerului la structura de tip point si
// initializarea lui se face astfel
struct point *ptr = &p1;

ptr->x = 20; //accesare elemente structura –elementul x
(*ptr).y = 50;//modalitate echivalenta - elementul y

OBSERVATIE
• Limbajul C nu permite definirea recursivă de structuri, adică o structură nu
poate conține ca element/ câmpuri tot o structură
• Si din acest motiv sunt utili pointerii la strucuturi

74
Structuri si uniuni

Structuri si tablouri
struct key {
char *word;
int count;
} keytab[NKEYS];//asa definim un tablou de structuri
//…
keytab[0].word = “auto”;
keytab[0].count = 5;
//asa se acceseaza elementele tabloului de structuri

75
Structuri si uniuni (union)
Uniunea: permite “fuzionarea” la nivelul memoriei
ocupat a spațiului ocupat mai multor componente
(ocupa de fapt același spațiu)
Permit o oarecare economie de memorie, dar utilizarea lor poate avea si
efecte secundare
union u_tag {
int ival;
float fval; //acest element are dimensiunea maxima
// si el va da dimensiunea pe care u_tag o va ocupa
char *sval;
}

Dimensiunea unei variabile de tip union este dimensiunea celei


mai mari componente ale sale.
Utile atunci când resursele de memorie RAM sunt limitate, de
exemplu la MCU de 8 biţi, dar încercați să evitați utilizarea lor76
Structuri si uniuni
O uniune se poate utiliza si intr-o structură:
Struct {
char *name;
int flags;
int utype;
union { //uniune membra a structuri symtab
int ival;
float fval;
char *sval;
}
} symtab[NSYM];
//avem un tablou de structuri cu NSYM elemente

77
Expresii in limbajul C
Operatori aritmetici:
* / % (modulo, rest)
+ -
Care este rezultatul (vezi si precedenţa
operatorilor precum si asociativitatea)?
10 % 4
10 % 3 / 2

78
Expresii C
Incrementarea si decrementarea,
exista in varianta post- si pre-
++ --
Exemple:
n++;//doar incr.
//asignare cu post incrementare, atribuim si apoi ++
x = n++;
//asignare cu pre incrementare, ++ si apoi atribuim
x = ++n;
for (i = 0; i < N; i++) …
for (i = N-1; i > 0; i--) …
79
Expresii C
In limbajul C nu există tipul de date boolean!
Există însă operatori relaționali (de comparatie)
pentru care daca rezultatul evaluării =0 el fi
interpretat ca FALSE-fals si daca este ≠0 va fi
interpretat ca TRUE- adevarat
- 0x01 sau 0x235E sunt amândouă valori, care daca rezulta in
urma unei astfel de comparări, vor fi interpretate ca FALSE
Operatorii relaționali: produc un rezultat =0
(FALSE) sau 1 (≠0) (TRUE), deci valori booleene
> >= < <= == !=
Care este rezultatul?
10 > 5
(unsigned) -1 > 1
Exemplu
if (ch != EOF)
80
Expresii C
Operatorii logici (booleeni): Tratează orice valoare ne
nulă (adica ≠0) ca TRUE si orice valoare nulă (=0) ca
FALSE
! (NOT) “NU”
&& (AND) “SI”
|| (OR) “SAU”
Care este rezultatul?
(10 > 5) && (-1 > -2)
(10 > 5) || (-1 > -2) && (2 > 1)

Un exemplu mai puţin obişnuit ?


if (10 && 20)

81
Expresii C
Operatorii logici sunt evaluaţi folosind ce s-ar numi evaluarea
“leneşului” (lazy evaluation).
Lazy evaluation – Odată ce a apărut o valoare care garantează
îndeplinirea unei condiții TRUE sau FALSE oprim evaluarea
OR(SAU) când oricare din condiții este identificată ca TRUE
AND (SI) când oricare din condiții este identificată ca FALSE
- De exemplu:
- in expresia: A OR B OR 1 OR C OR D ne oprim odată ce am ajuns la 1, fără sa
mai evaluam si pe C si D deoarece rezultatul va fi oricum 1 (TRUE)
- in expresia: A AND 0 AND B AND C ne oprim odată ce am ajuns la 0, fără sa mai
evaluam si pe B si C deoarece rezultatul va fi 0 (FALSE)

De ce este evaluarea “leneșului” importantă?


- Codul se execută mai rapid – se “sare” peste porțiunile de cod ne
necesare
- Odată ce o condiție este evaluată/îndeplinită nu mai are sens sa
evaluam si ceilalți termeni
- Se poate valorifica aceasta modalitate de evaluare pentru evitarea
unor condiții nedorite: verificând, de ex., dacă un pointer este NULL
înainte de a-l folosi

82
Expresii C
Instrucţiuni si expresii
expresie-instructiuni ::= expresie ;
Care din următoarele sunt instrucţiuni
valide?
10 + 20;
average(2, 3);
10;
a = b = c = 10;
a = 10, b = 20;
83
Expresii C
Operatorul de asignare (atribuire):
Asignarea este o formă a unei expresii
Exemple:
a = (b = (c = 10))
if ((ch = getchar()) == EOF)

84
Expresii C
L-valoare (L- value): o expresie care se referă la o
regiune(entitate) care are capacitatea de
memorare; doar L-valori pot fi folosite in partea
stânga (L vine de la Left) a operatorului “=”
De exemplu o memorie de tip RAM are capacitatea de memorare (in ea
se poate scrie), iar una de tip ROM (din ea se poate doar citi), in
acest context (la execuția programului), tipic nu are , in ea nu se
poate scrie ; deci daca “variabila” este in alocata într-o memorie
ROM ea nu poate fi o L-valoare
Sunt următoarele utilizări valide ale unei L-valori?
int a;
(float) a = 3.14;
* ((float*) &a) = 3.14;

85
Expresii C
Expresii condiționale: dacă expr1 nu este 0 (este
≠0) se evaluează expr2, altcumva (este =0) se
evaluează expr3
expr1 ? expr2 : expr3
Exemple
x_abs = (x >= 0) ? x : -x;

Evitați utilizarea acestei forme, care este foarte putin


lizibila!

Ea este echivalentă cu:


if (x >= 0) //o forma mult mai lizibila!!
x_abs = x;
else
x_abs = -x;

86
Precedenţa operatorilor
Cea mai mare
~ ! - (unari) ++ --
* / % aritmetici
+ -
<< >> deplasare biţi
< <= > >= relaţionali
== !=
& logici la nivel de bit
^
|
&& logici (booleeni)
||
87
Precedenţa operatorilor
Ce vrem sa facem: vrem sa realizăm un SI
(AND) la nivel de bit intre X si o mască (o
constantă cu dimensiunea lui X), apoi
verificăm dacă rezultatul este 0
Cum este corect?
if (X & MASK = = 0)
if ( (X & MASK) = = 0)

88
Precedenţa operatorilor
• Contează si asociativitatea (ordinea de
evaluare): de la stânga la dreapta sau de la
dreapta la stânga!
• Cu excepţia unor cazuri simple (ca la
aritmetica..), sunt destul de greu de ţinut minte;
nu încercaţi neapărat să le țineți minte: folosiţi
din belşug parantezele ( , )!

• Parantezele pot fi uneori derutante, dar mult mai


derutantă poate fi absenţa lor!
(10 % 3 ) / 2 sau
10 % (3 / 2) unde este mult mai clar ce dorim
de fapt
89
Operaţii la nivel de bit (bitwise)
O notaţie este doar o modalitate de reprezentare a unei
mărimi.
Programatorul trebuie să înţeleagă si să interpreteze
corect cum este reprezentată informaţia si cum să
procedeze cu o variabilă

Exemple:
Dacă ar fi să reprezentăm valoarea 50 utilizând 8 biţi
– Binar = 00110010
– Hex = 0x32
– Zecimal = 50
Poate reprezenta pur si simplu valoarea 50:
x = x + 50
Sau starea biţilor poate să însemne ceva:
Daca bitul 6 este 1, se face ceva..
Sau poate fi o combinaţie a celor două scenarii anterioare:
Daca bitul 6 este 1, x = x + 2 (cei 4 biţi inferiori din valoare) 90
Operaţii la nivel de bit (bitwise)
Operatori la nivel de bit: Manipularea biților este o componentă
cheie a programării sistemelor incorporate (in special pentru MCU!)
Majoritatea resurselor periferice pentru un microcontroler sunt accesibile
prin intermediul unor biți de stare (care prezinta interes in a fi citiți) si
control (care prezinta interes in a fi scriși), grupați sub forma unor
registre de stare si control
Prin utilizarea unor variabile de tip bit se mai câștigă eventual si spaţiu
de memorare – In loc să folosim 8 biţi pentru o singură valoare, ii folosim
ca o colecție (un set) de 8 valori booleene distincte
Dar atenție, in limbajul C nu exista un tip de date bit, așa ca este
responsabilitatea utilizatorului de a-l gestiona

Operaţiile sunt executate bit cu bit, la nivel de bit – bitwise

In mod tipic, si in această situație, se utilizează tot notația


hexazecimală care este de regulă mai practică decât cea binară, fiind
mai compactă:
0xFF este mai comod/compact decât 0b11111111 in binar
0x10 este mai comod/compact decât 0b00010000 in binar
Stăpâniți conversia binar  hex si invers?

91
Operaţii la nivel de bit (bitwise)
Operatorul AND(SI) & – Tipic utilizat pentru ștergerea
si/sau testarea biților individuali
0 AND (SI) cu orice altceva ne dă întotdeauna 0
1 AND (SI) cu orice altceva ne dă aceiași valoare (0 sau 1) :

0x10 & 0x10 = 0x10


0x01 & 0x10 = 0x00
0xFF & 0x00 = 0x00

Pentru a șterge biți:


1. Se șterg biții pe care vrem să-i aducem in 0 (Șterge)
2. Toți ceilalți biți sunt in 1 (Păstrează)

92
Operaţii la nivel de bit (bitwise)
Exemplu AND: Șterge doar biții 2, 3 ai unui număr
reprezentat pe 8 biți (char)
Se utilizează o “mască” (o constanta) cu “0”-uri
doar in pozițiile pe care vrem să le ștergem:
1111 0011
0x F 3

byVal = byVal & 0xF3;


byVal & = 0xF3;/*forma echivalenta prin
combinarea operatorului bitwise cu cel de
asignare*/

93
Operaţii la nivel de bit (bitwise)
Operatorul OR (SAU) | – Tipic utilizat pentru
setarea (doar) a unor biţi individuali
1 OR (SAU) cu orice altceva ne dă întotdeauna 1:

0x10 | 0x10 = 0x10


0x01 | 0x10 = 0x11
0xFF | 0x00 = 0xFF

Pentru a seta biţi:


1. Se setează biții pe care vrem sa-i aducem in 1 (Setare)
2. Toți ceilalți biți sunt in 0 (Păstrează) 94
Operaţii la nivel de bit (bitwise)
Exemplu OR : Setează ( 1) biţii 7 si 5 ai unui
număr de 8 biți
Masca care are biții setați doar in pozițiile
dorite:
1010 0000
0x A 0

byVal = byVal | 0xA0;


byVal |= 0xA0; /*forma echivalenta prin
combinarea operatorului bitwise cu cel de
asignare*/
95
Operaţii la nivel de bit (bitwise)
Operatorul XOR (SAU EXCLUSIV) ^ – Tipic utilizat
pentru a comuta/toggle un bit (a-i schimba starea)

1 XOR cu orice altceva inversează /comută (toggle) bitul (dacă e 0 îl


face 1, dacă e 1 îl face 0)
nu-l confundaţi cu ridicarea la putere ()..

0x10 ^ 0x10 = 0x00


0x01 ^ 0x10 = 0x11
0xFF ^ 0x00 = 0xFF

Pentru a comuta biţi:


1. Biţii care vrem să comute se setează in 1 (Comută)
2. Toţi ceilalţi biţi sunt in 0 (Păstrează)

96
Operaţii la nivel de bit (bitwise)
Operatorul de inversare/complement față de 1 ~ , fiecare bit este
complementat/inversat
~(0x10) = 0xEF;
Atenție, nu exista si un complement fata de 2!

Operatorii de deplasare (binara)


Sunt utilizați pentru a deplasa (shift) un sir de biți la stânga sau la dreapta:
>> sau <<
Sintaxa este:
Variabila/Valoare << Numar de pozitii binare
sau
Variabila/Valoare >> Numar de pozitii binare
Fiind vorba de deplasare (nu rotire) prin stânga (<<) sau prin dreapta se introduce
valoarea 0 (un bit 0) la fiecare deplasare!

nVal = nVal >> 4; /* Deplaseaza nVal la dreapta cu 4 pozitii */


nVal >> = 4; /*forma echivalenta prin combinarea operatorului bitwise cu cel de
asignare*/
nVal = 1 << 4; /* nVal = 00..10000 */
De ce să-l utilizăm?
Am putea număra numărul de biţi in 1 din nVal
Intr-o buclă, vom putea itera ceva pentru fiecare bit al lui nVal
Putem, in anumite limite, rearanja biții
Putem realiza uşor si rapid înmulțiri (<<) sau împărțiri (>>) ale lui nVal cu puteri ale lui 2 97
Operaţii la nivel de bit (bitwise)
a: 1 0 1 0 1 0 1 0 0xAA
• & AND (SI) b: 0 0 0 0 1 1 1 1 0x0F
• | OR (SAU inclusiv)
a&b: 0 0 0 0 1 0 1 0 0x0A
• ^ XOR (SAU
a|b: 1 0 1 0 1 1 1 1 0xAF
exclusiv)
a^b: 1 0 1 0 0 1 0 1 0xA5
• << deplasare stânga
a<<1: 0 1 0 1 0 1 0 0 0x54
• >> deplasare dreapta
• ~ complement faţă b>>2: 0 0 0 0 0 0 1 1 0x03

de 1 ~b: 1 1 1 1 0 0 0 0 0xF0

From Stroustrup/Programming
98
Condiţiile si testarea lor
Compararea – Utilizarea de condiţii multiple
Condițiile sunt “legate” intre ele cu operatorii booleeni (logici):
&& AND (SI)
|| OR (SAU)
! NOT (complement/negare)
Argumentele si rezultatul poate fi doar TRUE (Adevărat) si FALSE
(Fals)
De fapt in limbajul C nu există tipul boolean sau conceptul de
variabilă booleană!
Se folosesc valori numerice, orice valoare diferită de 0 se evaluează
ca TRUE(adevarat), iar orice valoare = 0 se evaluează ca FALSE(fals)!

Astfel, if(a != 0) este de fapt același lucru cu if(a), dar este mult mai
lizibil ce se dorește!

Exemple de comparari:
if ((nVal > 0) && (nArea < 10))
if ((nVal < 3) || (nVal > 50))
if ( ! (nVal <= 10))
99
Condiţiile si testarea lor
Un exemplu in care se testează dacă, într-o
variabila, un singur bit este setat
- Se “șterg” (nedestructiv!) biții care nu ne interesează, se
testează dacă rezultatul este zero (FALSE) sau diferit de
zero (TRUE)

Verifică dacă bitul 7 al variabilei nVal, de tip char, este


setat:

if (nVal & 0x80){



}
100
Condiţiile si testarea lor
Un exemplu in care se testează dacă, într-o
variabila, oricare din biții doriti este setat:
– Se “șterg” biții care nu ne interesează, se testează
dacă rezultatul este zero

Verifică pentru variabila nVal, de tip char, dacă bitul 2 sau


bitul 3 este setat :
if (nVal & 0x0C) {

}

101
Condiţiile si testarea lor
Un exemplu in care se testează dacă, într-o
variabila, toţi biţii doriti sunt setaţi :
– Se “şterg” biţii care nu ne interesează, se
testează dacă rezultatul este identic cu masca

Verifică dacă biţii 2 si 3 sunt setaţi (egalitate cu


masca):

if ((nVal & 0x0C) == 0x0C) {



}
102
Controlul fluxului program (program
flow)
Este realizat cu instrucţiuni compuse si
constructori specifici
{
declaratii;
instructiuni;
}
Exemple:
if (n > 0) {
int count = 0, i;

}
103
Controlul fluxului program: constructori
Instructiunea If-Else
if (expresie)
instructiune1
else
instructiune2
Exemplu: Exemplu mai bun:
if (n > 0) if (n > 0) {
if (a > b) if (a > b)
z = a; z = a;
else else
z = b; z = b;
}
104
Controlul fluxului program: constructori
Stilul Else-If
if (expresie1)
instructiune1
else if (expresie2)
instructiune2
else if (expresie3)
instructiune3
else
instructiune4

105
Stilul Else-If

if (tasta== T1) PORTA=0x01;


else if (tasta== T2) PORTA=0x02;
else if (tasta== T3) PORTA=0x04;
else if (tasta== T4) PORTA=0x08;
else if (tasta== T5) PORTA=0x10;

T1 ..T5 sunt constante, tasta este o variabilă, PORTA este portul A


de la un AVR

Dacă prima expresie este evaluată adevărată celelalte linii nu se


mai execută, s.a.m.d
Else –ul final poate lipsi dacă in cazul in care toate evaluările sunt
false nu dorim să facem nimic altceva.
106
Controlul fluxului program: constructori
Instrucţiunea Switch
switch (expresie) {
case expr-const: instructiuni
case expr-const: instructiuni

default: instructiuni
}

107
Controlul fluxului program: switch
Exemplu Switch : numără spaţiile, caracterele “0” si “1”,
precum si restul caracterelor
- Contoarele (numărătoarele) , presupuse declarate si iniţializate anterior,
sunt: nspace (spatii), nbinary (“0” si “1”) si nother(restul).

switch (ch = getchar()) {


case ‘ ’: nspace++;
break;
case ‘0’:
case ‘1’: nbinary++;
break;
default: nother++;
}

108
Exemplu de Switch
switch (tasta){
case T1: PORTA=0x01;
break;
case T2: PORTA=0x02;
break;
case T3: PORTA=0x04;
break;
case T4: PORTA=0x08;
break;
case T5: PORTA=0x10;
break;
default:
}

Realizează același lucru ca exemplul de la else-if


109
Switch: o maşină secvenţială
//o masina secventiala cu 4 stari, switch-ul descrie tranzitia de stare
//tranzitia de stare nu depinde de intrari!!
typedef enum {STAREA1, STAREA2, STAREA3, STAREA4} stare;
stare stare_curenta; // definire variabila de stare

stare_curenta=STAREA1; //initializare stare




switch(stare_curenta) {
case STAREA1:
//..prelucrari aferente starii 1
stare_curenta =STAREA2;//starea urmatoare, tranzitia de stare
break;
case STAREA2:
//..prelucrari aferente starii 2...
stare_curenta =STAREA4;
break;
case STAREA3:
//..prelucrari aferente starii 3...
stare_curenta =STAREA1;
break;
case STAREA4:
//..prelucrari aferente starii 4..
stare_curenta =STAREA3;
break;
default:
break;
} 110
Controlul fluxului program:
constructori, while
Bucle While
while (expresie)
instructiune

Atâta timp cât expresie este adevărată se execută instrucțiunea sau blocul
de instrucțiuni
Expresia este evaluată înainte de instrucțiune, astfel încât dacă ea este
falsă instrucțiunea nu se va executa (niciodată)

Exemplu:
i = 0, count = 0;
while (i < N) {
if (X[i++] > 0)
count++;
}

//in programarea SI mai intalnim si bucle fara sfarsit


while (1) {
bla_bla();
}
111
Controlul fluxului program:
constructori, do-while
Bucle Do-While
do
instructiune
while (expresie)

Atâta timp cât expresie este adevărată se execută


instrucțiunea (sau blocul de instrucţiuni)
Dar expresia este evaluată după execuția instrucțiunii,
astfel încât dacă ea este falsă instrucțiunea se va
executa (cel puțin odată)

Exemplu:
i = 0, count = 0;
do {
if (X[i] > 0)
count ++;
} while (++i < N); 112
Controlul fluxului program:
constructori, for
Bucle For

for (expr1; expr2; expr3)


instructiune

Un constructor echivalent ar fi:


expr1; //initializare
while (expr2) { //expresie conditionala
instructiune
expr3; //controlul buclarii
}

Exemplu:
//in programarea SI intalnim bucle fara sfarsit
for ( ;; ) bla_bla(); //
113
Controlul fluxului program:
constructori, break, continue
Break: Ieşire imediată din buclă sau din switch
Continue: Începe următoarea iteraţie imediat

/* “sarim” peste valorile nule si ne oprim


daca intalnim valori negative */
for (i = 0; i < N; i ++) {
if (X[i] == 0)
continue;
if (X[i] < 0)
break;
… /* prelucreaza datele X[i] */
}
114
Controlul fluxului program:
constructori, goto
Etichetele(labels) si instrucţiunea Goto (din păcate ea există
si in ANSI C!)
Trebuie utilizate foarte rar sau si mai bine niciodată!
Orice standard decent de codare C interzice utilizarea lor
Exemplu(unde state_i: sunt etichetele):
state_0:
if ((ch = getchar()) == ‘a’)
goto state_1;
else
goto state_2;
state_1:

state_2:

115
Controlul fluxului program
O greșeală comună cu efecte interesante, ce se va întâmpla daca
scriem :

if (x = 0)
{

}

In loc de

if (x == 0);
{

}

Poate explicați si de ce …
116
Programarea in C pentru
sisteme incorporate
Memoria de date, funcţiile si stiva,
funcţii si biblioteci

117
Utilizarea memoriei
Secţiunile tipice ale memoriei din punct de vedere al
programului sunt (denumirile pot diferi):
– Cod(program, code): aici sunt memorate instrucţiunile (binare)
pentru toate funcţiile
– Date statice (static data): aici sunt memorate datele care sunt
accesibile tuturor funcţiilor (alocarea este făcută o singură data
si rămâne valabilă pe toată durata execuţiei)
– Stiva (stack): aici se memorează informaţia din timpul execuţiei,
care este proprie fiecărei funcţii
– Zona de alocare dinamică (Heap): aici se află un spaţiu de
memorie care se alocă dinamic (in cursul execuţiei programului,
când e nevoie de el), spaţiu de memorie neorganizat pus la
dispoziţia programului; tipic se alocă cu malloc ( ) si se de-
alocă (eliberează) cu free ( );
– Intrări/ieșiri (I/O): adresele dispozitivelor I/O (pentru
microprocesoarele cu spațiu de adrese unificat), aici de fapt nu e
memorat nimic
118
Utilizarea memoriei
Adrese superioare
static char[] greeting
Adrese I/O
=“Hello world!”;
Stiva
main() (creste in jos)
{ Aici se pot
“întâlni”!!
int i; Si nu este bine!
char bVal; Heap (creste in sus)

Date Statice
LCD_init();
LCD_PutString(greeting);

} Cod

Cat se poate aloca/utiliza din RAM depinde Adrese inferioare


de MPU/MCU si de hardware-ul aferent SI: pot fi 119
x10kocteti..x10MOcteti sau doar x10..x100 octeţi
“Vizibilitatea” variabilelor si
memorarea (alocarea) lor
Datele (variabilele) globale (deși in C nu exista cuvântul
cheie global !):
– Declarate in afara funcţiilor C
– Vizibile pentru toate funcţiile C, de tot “programul”!
– Ocupă permanent un loc in memorie, in segmentul de date
– Reprezintă o modalitatea utilizata extensiv de a transfera
informație intre secțiuni de cod care nu se afla in relația
apelat/apelant
• De aici pot apărea unele probleme legate de partajarea lor, de
protecție a accesului prin excluziune; ce se întâmpla atunci când o
secțiune de cod scrie in ea si o alta citește din ea?
– Utilizarea lor in exces poate epuiza încet dar sigur memoria
RAM disponibilă (mai ales pentru un sistem cu resurse de
memorie RAM modeste)

120
“Vizibilitatea” variabilelor si
memorarea lor
Variabile cu alocare statică (există cuvântul cheie static)
– Spaţiu de memorare alocat permanent in secţiunile de date statice, prin
contrast cu variabilele “automate” alocate in stiva de apelare si cu cele
alocate dinamic in heap
– Este ocupat odată cu încărcarea programului executabil in memorie, in
zona de date (statice) sau in segmentul numit .BSS (pentru unele
compilatoare parte a segmentului de date, inițializat cu 0)
– Durata lor de viaţă este de la începutul execuţiei programului pană la
sfârşitul execuţiei
– Pot exista variabile statice globale, “vizibile” de întreg programul sau
variabile static locale, “vizibile” doar la nivelul unei funcții
– O variabilă declarată static in afara funcțiilor C, este vizibilă doar la
nivelul unității de compilare (a fișierului .c respectiv)
– Dacă însă vrem ca ea să fie vizibilă la nivelul tuturor unităților de
compilare, ea trebuie declarată cu atributul extern
– Utilizarea lor in exces poate epuiza încet dar sigur memoria RAM
disponibilă (mai ales pentru un sistem cu resurse de memorie RAM
modeste)

121
“Vizibilitatea” variabilelor si memorarea lor

Variabile in stivă(stive):
– Variabilele locale pentru o funcție sunt memorate intr-
o stivă proprie funcției
– Ele sunt declarate in interiorul unei funcții si sunt
vizibile doar in interiorul acestei funcții
– Ele sunt alocate si respectiv de-alocate de fiecare
dată când o funcţie este apelată
– Durata lor de viață este de la alocare pană la de-
alocare (când nu mai este nevoie de ele, până când
se iese din funcția respectiva), dacă nu cumva li s-a
dat atributul static
122
“Vizibilitatea” variabilelor si memorarea lor

Unde este variabila “vizibilă”, care este


domeniul ei de vizibilitate? (variable
scope)

int m; //m global

int any_func()
{
int m; //m local, sunt variabile diferite!
m = n = 5;
}
123
“Vizibilitatea” variabilelor si memorarea lor

Avem variabile C globale (vizibile pentru toate fișierele programului):

int global_var;

Avem variabile C statice la nivel de fişier (vizibile doar in fişierul-


unitatea de compilare- unde au fost declarate):

static int static_var;

Avem variabile locale statice:

any_func()
{
static int static_var;

}

124
“Vizibilitatea” variabilelor si memorarea lor

Cum să definim si să utilizăm variabilele globale?

In fişierul header myvar.h :

extern int global_var;//vazuta din toate fiserele

In fişierul care conţine programul myvar.c :

#include “myvar.h”
int global_var;
… /* utilizare myvar */

125
O paranteză: fişierele header
• Reprezintă un loc convenabil in care putem să
grupăm cam tot ce ar apărea sau ar trebui
declarat înaintea funcţiei main ();
• Au sufixul .h si includerea lor de către pre-
procesor se face cu directive:
#include <antet1.h>
#include “antet2.h”
• Dacă declaraţia este de forma <..> pre-procesorul îl caută intr-o
locaţie pre definită, tipic un director numit ../include
• Dacă declaraţia este de forma “ ..” pre-procesorul îl caută in
directorul in care se găseşte fişierul sursă propriu-zis (.c)

126
const, volatile si register
• const si volatile nu sunt tipuri, ci mai degrabă calificatori
pentru un tip
• Nu există nici o legătură intre ele
• Pot fi adăugate oricărei declaraţii

volatile float i;

volatile int j;

const long q;

const volatile unsigned long int rt_clk;

struct{
const int li;
unsigned char sc; }
volatile vs; 127
const, volatile si register
• const înseamnă ceva care nu poate fi modificat,
astfel încât, o dată care este declarată cu acest
calificator pentru tipul său, nu îi poate fi atribuită
o valoare (cu excepția inițializării) pe durata
execuției unui program.
• Principalul motiv pentru utilizarea datelor având
acest calificator este ca ele să fie memorate in
memoria de tip ROM (împreună cu codul
programului) si să permită compilatorului să facă
si verificări de consistență suplimentare ale
programului (să verifice că nu este o L-valoare!)
128
const, volatile si register
• Principalul motiv pentru care exista calificatorul volatile are legătura cu
programarea sistemelor incorporate si programarea de timp real
• El este utilizat in faza de optimizare a programului de către compilator
• El ii spune compilatorului că data calificată astfel poate fi obiectul unor
modificări care nu pot fi prezise doar din analiza codului programului,
forţând ca orice referinţă la această dată să fie una adevărată
• Cu alte cuvinte, in urma optimizării, suportul real al acestei date (locații de
memorie sau registre) nu trebuie să “dispară” ca rezultat al optimizării in
sensul minimizării volumului de memorie de date utilizat

void wait (void)


{
asm volatile ("nop"::); /*in urma optimizarii codul
corespunzator instructiunii nop ar fi fost eliminat, pt
ca aparent nu face nimic util*/
}

129
const, volatile si register
In mod normal compilatorul este cel care determină care anume date (variabile)
trebui să fie memorate in registrele CPU si nu in memoria RAM, intr-un
anumit moment al execuţiei programului.
In limbajul C există calificatorul register astfel incat programatorul să poată
“sugera” compilatorului ca pentru anumite variabile ar trebui să aloce
registre ale CPU, nu memorie, dacă este posibil
Astfel variabilele calificate cu register permit, intr-o anumită măsură, controlul
eficienţei execuţiei unui program.
Variabilele care sunt utilizate in mod repetat sau pentru care timpul de
acces mic este critic sunt candidate naturale pentru acest calificator.

#include <stdio.h>
main() {
register int i = 0;
for( i = 0; i < 2; i++) {
printf("i = %d\n",i);
}
}
130
Cum este “ocupată” memoria de date
(variabile)
Dacă “memoria” este o colecţie de octeţi adresabili, cum
anume sunt memorate variabilele care au mai mult de 1
octet?

Este o problemă de aliniere: o variabilă scalară de n


octeţi trebuie să fie aliniată la o graniţă de n-octeţi

Exemple:
char: nici o restricţie deoarece are doar 1 octet
short: graniţe de 2 octeţi (adrese pare)
float: graniţe de 4 octeţi (adrese pare divizibile cu 4)

131
Cum este “ocupată” memoria de date
(variabile)
Exemplu:
Care este adresa fiecărei variabile din cele de mai
jos?
Să presupunem că prima variabila / tabloul a
începe la adresa 0x20000000 (o adresă de 32
de biţi).
char a[5];
short b;
int d;
double e;
short f;
char *g;

132
Cum este “ocupată” memoria de date
(variabile)?
Ordonarea octeților pentru o anumită arhitectură de
calcul (endianess): Big-endian vs. Little-endian

– Big-endian: Octetul 0 (de la cea mai mică adresă) al unei


variabile de n- octeți este cel mai semnificativ: Most
Significant Byte (MSB)

– Little-endian: Octetul 0 (de la cea mai mică adresă) al unei


variabile de n-octeti este cel mai puţin semnificativ:
Least Significant Byte (LSB)

Arhitectura CPU Power PC si AVR 32 biți sunt nativ Big-


endian, arhitectura Intel 80x86 este Little –endian la fel
si AVR 8 biți

133
134
Cum este “ocupată” memoria de
date (variabile): endianess
Un număr pe 32-biti 0xDEADBEEF adică 4 octeți (bytes) poate fi:
MSB LSB

Big Endian: MSB (partea mare a


numărului..) este memorat la cea mai
mică adresă +0, adresă care devine și
adresa structurii multi-octet

Little Endian: LSB (partea mică a


numărului…) este memorat la cea mai mică
adresă +0, adresă care devine și adresa
structurii multi-octet

135
Cum este “ocupată” memoria de date
(variabile)
Care sunt valorile memorate in variabila string
pentru cele două modalităti de reprezentare?

static char string[8];


* (int*) string = 0x41424344;
string[4] = ‘\0’;

Codul ASCII 0x41 este pentru litera ‘A’.


136
Cum este “ocupată” memoria de date
(variabile)- big endian
Adresa Conținut memorie
+0 +1 +2 +3 +4 +5 +6 +7
30001FF8 25 24 5B 1A AC 57 9C 8D
30002000 00 FE 25 24 5B 1A EE 05
30002008 AC AE 81 83 45 67 89 25
30002010 01 23 55 3F AC CB F0 8D

Care este dispunerea, alinierea si conținutul variabilelor pentru un MPU Power


PC (magistrala sa de adrese are 32 de biti)?
char a[5]; // char 1 octet
short b; // short are 16 de biti-2 octeti (Power PC)
int d; // int are 32 de biti-4 octeti pt Power PC
double e; //are 8 octeti
short f;
char *g; //pointer la char, adresa are 32 de biti-4
octeti
137
Cum este “ocupată” memoria de date
(variabile)- little endian
Adresa Conținut memorie
+0 +1 +2 +3 +4 +5 +6 +7
30001FF8 57 AC 1A 5B 24 25 8D 9C
30002000 24 25 FE 00 05 EE 1A 5B
30002008 25 89 67 45 83 81 AE AC
30002010 23 01 3F 55 8D F0 CB AC

Care este dispunerea, alinierea si conținutul variabilelor pentru un MPU ARM


sau 80x86 (magistrala sa de adrese are 32 de biti)?
char a[5]; // char 1 octet
short b; // short are 16 de biti-2 octeti (Power PC)
int d; // int are 32 de biti-4 octeti pt Power PC
double e; //are 8 octeti
short f;
char *g; //pointer la char, adresa are 32 de biti-4
octeti
138
Cum este “ocupată” memoria de date
(variabile)
Care este dimensiunea următoarei structuri?
– Fiecare componentă trebuie să fie aliniată
– Dimensiunea totală trebuie să fie un multiplu de 8

struct mystruct {
int a;
short b;
double c;
char d;
}

139
Funcţii si stivă
Stiva (Stack): este structură de tip LIFO (Last-in, First-out), ultimul
intrat, primul ieşit
– Vârful si baza sunt utilizate pentru a determina cum este folosită
– Operație de tip Push (împinge): se adaugă/se scrie un element in vârf si
se actualizează vârful
– Operaţie de tip Pop (extrage): se înlătură/ se citeste un element din vârf
si se actualizează vârful

Poate implementată fizic cu ajutorul unei memorii de tip RAM static


sau al unui set de registre

Este esențială pentru a se putea implementa un limbaj procedural:


face posibil apelul de proceduri/funcții, si întoarcerea din
proceduri/funcții, cu salvarea si restaurarea corespunzătoare a
cadrului/contextului de execuție (cel putin a adresei de
intoarcere )

Aceasta salvare a cadrului/contextului de execuție reprezintă un


efort suplimentar ca timp de execuție a programului (scrieri si
citiri in/din stivă) si ca spațiu de memorie SRAM necesar
(pentru stiva propriu-zisă)
140
Funcţii si stivă

Stiva convenţională creşte in jos


Adrese superioare

Vârful stivei
- indicatorul Creştere stiva
de stiva (SP)

Adrese inferioare

Exista si rare exemple de


arhitecturi de calcul la care stiva 141
creste in sus (ex. 8051)
Funcţii si stivă
Cadrul/contextul de execuţie (execution
framework) al unei funcţii: alocarea si de-
alocarea locală de memorie pentru o
funcţie
Exemplu: 1. A este apelată; 2. A apelează pe B; 3.
B apelează pe C; 4. ne întoarcem din C

Cu fiecare apel succesiv, stiva crește in jos, ocupând din ce in ce mai multa142
memorie!
Funcţii si stivă
De ce se utilizeaza o stivă?
– Organizarea de tip LIFO se potriveşte
perfect cu ordinea de apelare a funcţiilor si
întoarcerea din funcţie
– Permite o alocare/de-alocare automată
eficientă si comodă a memoriei

143
Funcţii si stivă
Determinarea dimensiunii cadrului de execuție a unei funcții
– Fiecare variabilă este aliniată
– Fiecare cadru trebuie să fie aliniat la o graniță de 8 octeți
Exemplu: Care este dimensiunea cadrului? ( Se vor adăuga 8 octeți pentru
adresa de întoarcere si identificare cadrului la editarea de legături – pt. link-
editor)

int myfunc()
{
char byVal;
long lVal;
char szName[10];
… /* instructiunile incep aici */
}

De ce ne-ar interesa: pentru ca ne spune câtă memorie se alocă si este necesară la


fiecare apel al funcției
La unele aplicații dimensiunea RAM-ului disponibil este limitată si s-ar putea să se
termine repede!
Un MPU de 32 biti poate avea si x 100 MOcteti de (SD)RAM
Un AVR ATMega 32 are 2048 octeţi (2kOcteti) de (S)RAM
Un AVR ATtiny 2313 are doar 128 octeţi de (S)RAM
Un ATtiny15 nu are de loc (S)RAM, doar cele 32 de registre!
144
Funcţii si stivă: apelul recursiv
• In principiu apelul recursiv al unei funcții este posibil
in limbajul C:
void functie_recursiva (int data)
{ int alta_data;
// facem cate ceva aici cu alta_data
functie_recursiva (alta_data)
//mai facem si altceva in continuare
}

Este un exemplu in care dimensiunea RAM-ului alocat si a stivei, creste la


fiecare apel de funcție, poate să ne scape repede de sub control cu
consecințe neprevăzute!

In concluzie încercați să NU folosiți apelul recursiv, mai ales când


programați pentru un microcontroler de 8/16 biţi, cu resurse relativ
limitate de memorie de date!

145
Funcţii si stivă: recursiv si re-
entrant
• Ideea de “re-entranţă“ (reentrancy), termen cam forţat in limba română,
pentru o funcţie apare atunci când o funcţie este apelată înainte ca să fi
avut loc întoarcerea din invocarea(apelarea) precedentă
• Există trei motive pentru care aceasta se poate întâmpla:
– Apelul recursiv: funcția se apelează pe ea însăşi
– Utilizarea programării concurente (multi-fir, multi-threading): mai multe “fire” de
execuție apelează aceiași funcţie
– Întreruperile: funcția este întreruptă de o cerere de întrerupere, fiind apoi apelată
din nou in rutina de tratare a întreruperii
• Cazul cel mai simplu este cel al apelului recursiv, deoarece aici este clar
unde/când se va re-intra in funcție, sincron cu evoluția programului
• Cazul programării concurente si al utilizării întreruperilor este mai complicat,
deoarece aici procesul de re-intrare in funcție are loc asincron cu evoluția
programului.
• In mod tipic aceste funcții nu trebuie să modifice date (variabile) globale
– Citirea variabilelor globale este posibilă
– Scrierea lor va fi posibilă numai dacă există si un mecanism adecvat de
protecție al secțiunilor critice ( dezactivare întreruperi, excluziune prin
semafoare, mutex,, etc.).

146
Funcţii si stivă
Pointeri de/la funcții: Cum funcționează?
– O funcție este translatată/tradusă (prin compilare,
asamblare) intr-un bloc de instrucțiuni mașină
care va fi memorat undeva in memorie
– Apelarea funcției presupune transferul controlului
programului la adresa de început a acestui bloc
memorat
• La nivel hardware asta presupune aducerea acestei valori de
adresa intr-un numărător de program (PC)
• In limbaj de asamblare asta înseamnă execuţia unei
instrucțiuni de salt (ramificare) la această adresă
– O variabilă de tip pointer de/la funcție memorează
de fapt adresa de început a acestui bloc de
instrucțiuni
147
Pointeri de(la) funcţii (function
pointers)
Pointeri de funcţii: exemplu de declarare si utilizare

int average(int x, int y)


{

}
….

int main()
{
int (*pFunc)(int x, int y) = average; /*aici se
declara pointerul la functia average*/

int a = (*pFunc) (10, 20);/*aici functia este


apelata, cu parametrii actuali 10 si 20*/
}

148
Pointeri de(la) funcții: declararea
corectă
• Să presupunem că avem o funcţie care returnează un int si
pentru care vrem să definim/utilizăm un pointer pentru ea:
int func(int a, char b);

• Din cauza precedentei operatorilor, dacă numele funcţiei nu


este pus in paranteze se va declara de fapt o funcţie care
returnează un pointer la un int !

/* aceasta este o functie care returneaza un


pointer la int */
int *func(int a, char b);

/* acesta este un pointer la o functie care


returneaza un int */
int (*func)(int a, char b);
149
Pointeri de(la) funcţii
• Una din aplicațiile interesante din punct de vedere al SI
este utilizarea unui pointer de funcție pentru alegerea
dinamică (contextuală) a unei funcții care trebuie să se
execute la un moment dat, ținând cont de apariția unor
evenimente externe
– Cu alte cuvinte atunci când de fapt nu știm a-priori care anume
funcție, dintr-un set de funcții, se va executa (trebuie sa se
execute) intr-un anume punct al programului !
• O astfel de aplicație tipică este la realizarea /
implementarea software a unor mașini secvențiale
• Un astfel de exemplu de mașină secvențială (in contextul
AVR 8 biți) găsim in codul sistemului de dezvoltare AVR
Butterfly, fiind utilizată la controlul meniurilor de afișare
prin intermediul joystick-ului
(http://www.atmel.com/tools/AVRBUTTERFLY.aspx)
150
Biblioteci de funcţii in C
In limbajul C foarte multe lucruri utile sunt realizate de
funcții care sunt conținute si grupate in
biblioteci(libraries) de funcții
– C este un limbaj simplu, dar tipic implementările lui au si
biblioteci bogate!
Biblioteci utilizate in mod obișnuit:
– Intrare/ieşire fișiere - File I/O (nu este vorba numai de
fișiere propriu-zise, ele includ intrările/ieşirile utilizator)
– Manipulare șiruri (strings)
– Funcții matematice in virgula fixa, funcții matematice in
virgula mobila
– Gestiunea proceselor
– Suport pentru periferice, pentru rețea, etc
Biblioteci utilizate se declara cu ajutorul unor
directive de tip #include
151
Biblioteci de funcţii in C, exemple (ANSI
C)
Fişiere de intrare/ieşire (standard file I/O):

/* incluziune fisier header pentru


biblioteca I/O */
#include <stdio.h>
main()
{
/* utilizarea functiei fprintf */
fprintf(stdout, “%s\n”, “Hello World\n”);
}

152
Biblioteci de funcţii in C, exemple (ANSI
C)
Formatarea ieşirii utilizator: printf, fprintf, sprintf si altele; utilizează
specificatori de conversie (format) cum ar fi:
%s şir
%d zecimal cu semn
%u zecimal fără semn
%x hexazecimal
%f virgulă mobilă (float sau double)

Cum “tipărim” următoarele variabile in formatul


“a = …, b =…, c = …, str = …” pe o singură linie?

int a;
float b;
int *c;
char str[10];
153
Biblioteci de funcţii in C, exemple (ANSI
C)

Formatarea intrării utilizator: scanf, fscanf, sscanf si altele


– Se folosesc specificatori de format (conversie) similari cu cei
pentru familia printf

Cum să “citim” (cu scanf) următoarele variabile in formatul


“a = …, b =…, c = …, str = …” intr-o singură linie?

int a, b;
float c;
char str[10];

154
Biblioteci de funcţii in C (ANSI C)
• Atenție la funcțiile clasice de intrare/ ieșire de/la “consola”:
printf, scanf, cin, cout, etc.
• De cele mai multe ori pentru un SI sau un MCU “consola/tastatura”
utilizator este de fapt un port serial (de natura unui UART) asincron:
– Printf sau cout “scrie”, adică trimite/emite caractere (sau octeți) printr-un
port serial nu la consola
– Scanf sau cin “citeşte” ce se primeşte/recepţionează (sau octeți) pe un
port serial, nu de la o tastatura
– Portul serial utilizat (adica perifericul de tip UART- Universal
Asynchronous Receiver Transmitter) mai trebuie si configurat/programat
inainte de utilizare: viteza de comunicaţie, număr de biţi de date,
paritate, etc.

• De foarte multe ori , pentru un MPU/MCU “mic” de 8/16 biți, este


mult mai avantajos să ne scriem propriile funcţii echivalente,
mai simple, de I/O (emisie/recepție caractere/octeți, echivalentul
lui cin sau cout): dimensiunea codului generat va fi mai mică si
se va executa mai repede
155
Biblioteci de funcţii in C (ANSI C)
Operaţii tipice cu şiruri de caractere: copiere,
comparare, analiza conţinut si altele

#include <string.h>
– strcpy: copiază un şir intr-altul
– strcmp: compară două şiruri (ce înseamnă a
compara 2 şiruri? ‘ABC’ cu ‘abc’ ?)
– strlen: calculează lungimea unui şir
– strstr: caută intr-un şir existenta unui alt sir (sub-
şir)

156
Strlen (din K&R)
int strLen(char s[])
{int i;
i = 0;
while(s[i] != '\0')
++i;
return i
}
Un şir este un tablou de caractere care se termină implicit cu caracterul
‘\0’ adică cu 0x00 (terminator şir); acest caracter ‘\0’ poate fi utilizat
pentru identificarea sfârșitului unui şir cu lungimea necunoscută
apriori

Funcția strlen returnează lungimea șirului (numărul de caractere) fără a


include in acest numar si terminatorul de şir

157
Biblioteci de funcţii in C (ANSI C)
Prelucrarea erorilor si raportarea lor: utilizarea funcţiei exit
#include <stdio.h>
#include <stdlib.h>
int main ()
{
...
void myfunc(int x)
{
if (x < 0) {
fprintf(stderr, “%s\n”,
“x este in afara domeniului”);
exit(-1);
}
}

}

Provoacă terminarea imediată a programului, valoarea este returnată lui main


(procesului)

Un program de tip SI nu ar trebui să se termine niciodată așa!


Un program main () de tip SI nu ar trebui să ajungă niciodată la instructiunea return () !
158
Biblioteci de funcţii in C, exemple (ANSI C)

Funcţii matematice (tipic in virgula mobilă):

#include <math.h>

n = round (x); /* functie de rotunjire a
rezultatului in virgula mobila */

Atunci când veţi utiliza compilatorul WinAVR, veţi
utiliza si cel puţin una una din bibliotecile
specifice numită AVR libc care conţine practic
toate funcţiile destinate acestei familii de
microcontrolere
159
Standarde pentru scrierea codului C
• Un standard pentru scrierea codului (coding standard)
este un set de reguli care ne spune cum ar trebui să arate si
să fie scris acel cod intr-un limbaj specific (C in cazul nostru)
– Tipic specifică reguli pentru denumiri si pentru indentarea textului
(decalarea textului la dreapta sau la stânga)
• Ex. Se utilizează numai macheta (layout-ul) “Stroustrup”
– Tipic specifică un subset (mai restrictiv) al limbajului
• Nu se utilizeaza goto si etichete (labels)!
• Nu se utilizeaza operatori ternari!
• Nu se vor utiliza funcţiile new si throw pentru a evita problemele
de predictibilitate
– Tipic specifică reguli pentru comentarii
• Fiecare funcție trebuie să aibă un comentariu in care se explică de
face
– Adesea impun utilizarea unor anumite biblioteci
• Ex., se utilizează <iostream.h> si NU <stdio.h>
• La nivel de organizație deseori se încearcă să se gestioneze
complexitatea prin utilizarea unor astfel de standarde
– Nu doar aceste standarde pot rezolva problema, uneori ele creează
mai multă complexitate decât sunt in stare să gestioneze
160
From Stroustrup/Programming
Standarde pentru scrierea codului
• Existența unui standard bun pentru scrierea codului este mai bună
decât absența lui
– Un proiect industrial de anvergură (cu mai mulți programatori, pe mai mulți
ani) este de neconceput fără existenta/utilizarea unor astfel de standarde
• Un standard prost de scriere a codului poate fi mai rău decât absența
unui standard
– Un standard pentru C++ care restrânge programarea in acest limbaj la
ceva ce seamănă cu un subset al C-ului, face mai mult rău decât bine
– Din păcate există exemple in acest sens!
• Programatorii nu iubesc nici un standard de scriere a codului, cei
care o să utilizeze acest cod au însă altă părere!
– Chiar si cei mai buni!
– Toți programatorii vor să scrie cod exact in propriul lor stil
• Un standard bun este in același timp si prescriptiv si restrictiv:
– “Așa se face bine acest lucru, se recomandă, aceasta e o regulă de bună
practică”
– “Este interzis să…””
• Un standard bun are justificări pentru reguli
– Precum si exemple!

161
From Stroustrup/Programming
Standarde pentru scrierea codului
• Utilizarea unor astfel de standarde de codare
permite să se obțină pentru codul produs:
– Fiabilitate si robustețe
– Testabilitate (ușor de testat)
– Lizibilitate (ușor de citit si înțeles)
– Mentenabilitate (ușor de întreținut)
– Portabilitate (ușurința execuției pe o altă mașină-
CPU)
– Eventuala reutilizare in alta aplicație
– Extensibilitate, scalabilitate (capacitatea de a
suporta mărirea sau extinderea)

From Stroustrup/Programming

162
Câteva reguli simple
• Nici o funcție nu trebuie să aibă mai mult de 300 de linii (poate
limita de 30 ar fi si mai bună..)
– Asta ar înseamnă cam 200 de linii (fără comentarii)
• Fiecare nouă instrucțiune trebuie sa inceapa pe o linie nouă
– Ex. int a = 7; x = a+7; f(x,9); // gresit!
• Nu utilizați macroinstrucțiuni decât pentru controlul surselor
– utilizarea #ifdef si #ifndef
• Identificatorii trebuie să aibă nume descriptive
– Pot conține abrevieri comune si acronime
– Atunci când sunt folosite in mod convențional, nume ca x, y, i, j, etc.,
sunt descriptive!
– Utilizați stilul de scriere numar_de_elemente mai degrabă decât stilul
numarDeElemente
– Numele de tipuri si constantele trebuie să înceapă cu litera MARE
• Ex., Device_driver si Pi
– Identificatorii nu trebuie să difere doar prin tipul de literă (mică/MARE)
• Ex., Head si head // gresit!!
From Stroustrup/Programming 163
Alte câteva reguli simple
• Identificatori cu un domeniu de vizibilitate internă (in jos) nu trebuie
să fie identici cu cei cu un domeniu de vizibilitate externă (in sus)
– Ex. int var = 9; { int var = 7; ++var; }/* gresit: var il poate ascunde pe var
*/
• Declarațiile trebuie făcute cu cel mai mic domeniu de vizibilitate
posibil
• Toate variabilele trebuie inițializate
– Ex. int var; // nu e bine: var nu este initializata
• Conversia de tip (typecast) trebuie folosită doar acolo unde este
esențială
• Codul nu trebuie să depindă de regulile de precedență, cu excepția
celui din expresiile aritmetice
E.g. x = a*b+c; // ok
if( a<b || c<=d) // gresit: trebuie paranteze pt (a<b) si (c<=d)
• Incrementarea si decrementarea nu trebuie folosite ca sub-expresii
– Ex., int x = v[++i]; // gresit ( incrementul poate sa nu aiba loc)

Stroustrup/Programming
164
Exemple de standarde pentru limbajul C:
MISRA C

• MISRA C este un standard pentru dezvoltarea aplicaţiilor scrise in


limbajul C, propus si realizat de MISRA (Motor Industry Software
Reliability Association).
• Scopul lui a fost de a îmbunătăţii fiabilitatea, portabilitatea si
securitatea codului in contextul sistemelor incorporate (embedded
systems) , in mod specific a acelor programate in ANSI/ISO C.
• Prima ediţie a standardului MISRA C, "Guidelines for the use of the C
language in vehicle based software", datează din 1998 si este
cunoscută ca MISRA-C:1998
• In 2004, a apărut o a doua ediţie "Guidelines for the use of the C
language in critical systems", sau MISRA-C:2004 cu multe modificări
substanţiale, inclusiv o renumerotare completă a regulilor
• Pe 18 Martie 2013, a fost anunţată varianta MISRA C:2012, cu
accentul pus din nou pe utilizarea sigură a codului C pentru toate
sistemele critice (critical systems), nu numai cele din industria
automobilului
Exemple de standarde pentru limbajul C:
MISRA C
• Exista multe unelte software care verifică pentru un cod produs
“conformitatea cu MISRA”, deşi nu există un proces de aşa zisa
certificare MISRA
• Marea majoritate a regulilor poate fi verificată cu unelte de analiză
statică a codului (static code analysis), fără să fie nevoie ca să se
execute codul, iar restul regulilor cu unelte de analiză dinamică a
codului (dynamic code analysis), care presupun si execuţia codului
– http://en.wikipedia.org/wiki/MISRA_C
• OBS. ISO/ANSI C este o familie de standarde succesive (publicate
de ANSI- American National Standards Institute si apoi adoptate de
ISO-International Organization for Standardization) utilizate pentru
definirea limbajului C, având ca scop, printre altele si îmbunătăţirea
portabilităţii codului intre diverse compilatoare C.
– Variantele generice sunt cunoscute ca C89, C90, C99 si actualmente
C11 (cifrele codifica anul adoptării)

Dacă va interesează subiectul puteți (încă) găsi gratis o copie a MISRA C


2004:
http://caxapa.ru/thumbs/468328/misra-c-2004.pdf
Despre statutul acestei copii vă las să-l găsiți singuri…

You might also like