Beste CO,
Ik heb een STM32f3 discovery aangeschaft voor mijn quadcopter, deze ga ik in C programmeren (in de atollic omgeving).
Ik heb nu al ervaring met programmeren, maar nog bijna geen ervaring met C. Daarom ben ik nu wat aan het oefenen met dummy programmas, ik kwam net op een vraag met het initialiseren van integers.
static void TIM_Config(void)
{
int timconfig;
timconfig ++;
}static void TIM_Config(void)
{
int timconfig = 0;
timconfig ++;
}Dit stukje code is bedoeld om te meten met de debugger, hoe vaak een subroutine aangeroepen wordt.
Bij het eerste stuk wordt er netjes elke keer dat de loop doorlopen wordt er 1 bij opgeteld.
Maar dan plaatst de compiler twee waarschuwing's tekens bij de int; en de ++; lijnen, omdat de variabele niet goed geïnitialiseerd wordt.
De tweede daarin tegen is wel correct, maar die doet weer niet wat ik wil. Immers elke keer dat de loop doorlopen wordt, wordt de waarde eerst gereset, en daarna pas 1 er bij opgeteld deze staat dus (bijna) altijd op 1.
Mijn vraag is dus als volgt, hoe initialiseer ik de waarde correct (zonder er ene globale variabele van te maken) en dat hij de werking van de 1e quote heeft?
MVG,
Ruben
Ik neem aan dat je de functie TIM_Config binnen een loop aanroept. Je variable timconfig is een automatische variable, dwz staat op de stack. In C/C++ kun je een variabele lokaal static maken zodat de initialisatie maar 1 keer uitgevoerd wordt. Je code ziet er dan zo uit:
static void TIM_Config(void)
{
static int timconfig = 0;
timconfig ++;
}
Je functie TIM_Config hoeft niet per se static te zijn. In dit geval betekent static dat de TIM_Config functie locaal per compilation unit is. Dwz, je kunt meerdere TIM_Config functies per C file hebben zonder dat je linker in de problemen komt met meerdere TIM_Config definities binnen een executable. Het keyword static is helaas binnen C/C++ voor verschillende dingen her-(mis)gebruikt.
In beide stukken code declareer je een lokale variabele. Die variabele bestaat pas zodra je de functie "binnen" komt. In het eertse geval initialiseer je 'm niet, en zal de waarde dus "willekeurig" zijn: vandaar de warning bij compileren. In het tweede geval initialiseer je de variabele wel netjes.
Maar dat doet in beide gevallen inderdaad niet wat je wil 
Je wil een "globale" variabele, waarvan de waarde behouden blijft buiten te functie. Hiertoe zijn twee mogelijkheden:
1. Je declareert de variabele daadwerkelijk buiten je functie:
int timconfig=0;
static void TIM_Config(void)
{
timconfig++;
}
De variabele timconfig is nu echter ook vanuit andere functies benaderbaar. of dat wenselijk is, kan ik niet voor je beoordelen.
De andere optie is om de variabele als "static" te declareren. Dat wil zeggen dat de variabele statisch wordt gealloceerd bij het starten van je programma. Hij wordt dan ook slechts 1 keer geinitialiseerd en de waarde blijft behouden tussen functieaanroepen door. Doordat je hem binnen de functie declareerd, weet je zeker dat geen andere functie erbij kan, en het veroozaakt ook geen mogelijke naam conflicten:
static void TIM_Config(void)
{
static int timconfig=0;
timconfig++;
}
Met static er voor doet hij inderdaad wat ik wil.
@Hewlett
<sarcasm> Dat ze de syntax static meerdere keren gebruiken, zal wel zijn om het makkelijk te houden </sarcasm> 
@KaRaMBa
De debugger laad automatisch lokale variabele, om globale variabele te lezen moet ik waarschijnlijk nog een paar handelingen uitvoeren (die ik nog niet weet).
Dat is nu de enige reden waarom ik lokale variabele gebruik.
EricP
mét CE
Het keyword static is helaas binnen C/C++ voor verschillende dingen her-(mis)gebruikt.
Dat valt toch wel mee? Ik weet zo niet wat de letterlijke definitie is, maar zie het als iets globals met een local scope (dat klinkt tegenstrijdig... en eigenlijk is het dat ook. Maar soms is het wel handig, zoals bij die variabele).
Verder heb ik het idee dat je die counter bijhoudt in een ISR. Als dat zo is, kijk dan ook nog even naar het gebruik van 'volatile'. Het is in *deze* context niet relevant, maar als je het ding als global definieert, kan het dat wel worden!
Dat is nu de enige reden waarom ik lokale variabele gebruik.
Globals wil je helemaal niet. Het is maar een hele enkele keer handig en zit meestal in de weg. Zoveel mogelijk local houden. Evt. met constructies als static.
Globals wil je helemaal niet.
Is dat niet een beetje kort door de bocht? Ook een beetje kwestie van persoonlijke stijl, en smaak, en gewoonte? Ik heb jarenlang geprogrammeerd in een omgeving die het idee van lokale varibelen niet kende, en waar dus alles globaal was. In mijn eerste C-schrijfsels heb ik het mezelf veel te moeilijk gemaakt door, zoals inderdaad overal wordt voorgeschreven, zoveel mogelijk lokale variabelen te gebruiken. Alles globaal en HOP, werken deed het!
Let wel, ik ga grif akkoord met de theoretische voordelen van lokale definities. Mondjesmaat begin ik er nu ook mee te werken, en ik zie wel dat het zijn charmes heeft. Maar men mag er geen wet van Meden en Perzen van maken, laat staan een religie. In dit geval, waar de bedoeling duidelijk is om de variabele ook buiten de functie beschikbaar te hebben, lijkt het me voor de hand te liggen om ze globaal te declareren. Local declareren is ok, is heel goed, maar dan enkel voor wat werkelijk lokaal is.
[[edit: ik hoop maar dat we niet voor onze kop krijgen dat dit soort discussies op dit forum helemaal niet thuishoort...]]
[[edit: dit is een boeiende discussie, en om educatieve redenen kan ze ook relevant zijn. Maar in de praktijk wil ik wel eens het voordeel weten van een functie waarin niet meer gebeurt dan het ophogen van een teller. Dan zet je toch net zo goed die variabele++ in de aanroepende code, ipv een functie aan te roepen? KISS, weet je wel?]]
[Bericht gewijzigd door big_fat_mama op (13%)]
EricP
mét CE
Is dat niet een beetje kort door de bocht? Ook een beetje kwestie van persoonlijke stijl, en smaak, en gewoonte?
Nee, absoluut niet.
Ik heb jarenlang geprogrammeerd in een omgeving die het idee van lokale varibelen niet kende, en waar dus alles globaal was.
Hier dus niet van toepassing. En ook niet geschikt voor een 'groter' iets. Ik zie me het al doen... Voor elke loop een andere control variable. Vreet memory en maakt code onoverzichtelijk.
In mijn eerste C-schrijfsels heb ik het mezelf veel te moeilijk gemaakt door, zoals inderdaad overal wordt voorgeschreven, zoveel mogelijk lokale variabelen te gebruiken. Alles globaal en HOP, werken deed het!
Stierepoep. Het wil hoogstens zeggen dat je destijds echt geen (C) code kon maken. Geeft niet. Al doende leert men.
Let wel, ik ga grif akkoord met de theoretische voordelen van lokale definities.
Die theoretische voordelen interesseren me geen hout. Met name praktische voordelen en niet het gezeur hebben dat als je een keer een stuk code uit een ander project mee neemt, de boel omvalt omdat ze in dezelfde soep zitten te roeren.
In dit geval, waar de bedoeling duidelijk is om de variabele ook buiten de functie beschikbaar te hebben,
Waar staat dat??
lijkt het me voor de hand te liggen om ze globaal te declareren. Local declareren is ok, is heel goed, maar dan enkel voor wat werkelijk lokaal is.
Dat is het dus. Zoals bijna alles. En als de de startpost had gelezen, dan had je geweten dat het doel is om met een debugger te kijken hoe vaak een stukkie code gelopen heeft. Uitsluitend local dus...
[[edit: dit is een boeiende discussie, en om educatieve redenen kan ze ook relevant zijn. Maar in de praktijk wil ik wel eens het voordeel weten van een functie waarin niet meer gebeurt dan het ophogen van een teller. Dan zet je toch net zo goed die variabele++ in de aanroepende code, ipv een functie aan te roepen? KISS, weet je wel?]]
Het is een zinloze discussie. En wederom: als je de startpost gelezen zou hebben, dan snap je het doel.
Verder zijn er nog wel wat doelen waarin je het zo wilt - performance hoort daar duidelijk niet bij. Dat heeft niks met KISS te maken (overigens een heel goed principe), maar wel met code schrijven die het doel dient en zoveel mogelijk autonoom is.
@ erik 2 posts terug, (moest tijdens het typen van dit stuk, eten koken)
[
Mijn programma is grotendeels een gedownloade voorbeeld programma. Het programma combineert 2 timers, en maakt daar een pwm van.
Ik ben dus aan het onderzoeken hoe dit precies werkt, als ik het tot nu toe goed begrijp, wordt aan het begin de timconfig 1 keer aangeroepen. waar de PWM geconfigureerd wordt. vervolgens worden timers geconfigureerd. En daarna komt het programma in een oneindige loop.
De pwm is volgens mij dus volledig hardware matig, en heb ik dus geen interrupts tussen door. Ik zal zo even kijken wat dat volatile precies inhoudt.
]
[update]
Klopt het dat als ik in een subroutine een variabele schrijf en vervolgens uit de routine ga, en dan het hele geheugen vol schrijf. Dat ik dan als ik de routine opnieuw in ga de variabele weer kwijt ben.
Oftewel, dit zijn geen gereserveerde adressen toch?
[Bericht gewijzigd door flamingo op (18%)]
EricP
mét CE
Klopt het dat als ik in een subroutine een variabele schrijf en vervolgens uit de routine ga, en dan het hele geheugen vol schrijf. Dat ik dan als ik de routine opnieuw in ga de variabele weer kwijt ben.
Oftewel, dit zijn geen gereserveerde adressen toch?
Ja, nou... beetje kromme manier van denken...
Zoek uit hoe een stack werkt. Heap is net zoiets (maar dan anders
) En dit soort vragen is nou precies waarom ik van mening ben dat je ook assembly moet kunnen...
void someRoutine(void)
{
int bla;
...
}Ergens in je code roep je dit aan:
...
someRoutine();
.... Wat er nu gebeurt, is het volgende:
- De processor mikt het address van de volgende instructie op de stack (en past daarbij de stack pointer aan)
- De processor doet een jump naar het address van someRoutine (en deze 2 samen noemen we een 'call')
- De waarde van de stackpointer wordt met 2 verlaagd (uitgaande van een 16-bit integer op een 8-bit processor). Op het huidige address van de stackpointer staat 'bla'.
- Je doet wat in je routine
- Bij het einde wordt de stackpointer terug gezet naar waar-ie stond voordat 'bla' geallocceerd werd
- Er volgt een ret. Die doet feitelijk niks anders dan in 1 instructie het voorheen opgeslagen address van de stack halen, de stackpointer aanpassen en een jump naar het net terug gehaalde address
Je snapt wel dat de volgende die de stack gebruikt daar z'n eigen data achter laat. Waarmee die van jou dus effectief weg is.
Overigens is het doen van een jump technisch gezien niet helemaal correct. Wat er feitelijk gebeurt is dat het betreffende address in de program counter wordt gedouwd - wat een jump effectief ook doet.
Als je nou iets zo
static int bla=0;declareert, dan staat het ding op de heap. Ofwel: het ding is er altijd. Bij het starten van je programma wordt die op 0 gezet. Daar is een routine voor die loopt voordat jouw programma aan de gang mag. Regelt de compiler voor je.
Het grote verschil met een global is dat andere routines niet weten waar het ding staat - er is geen reference naar. Je kunt dus prima 10x een 'counter' hebben die static binnen een function gedefinieerd is (de andere functions 'zien' het ding niet), terwijl je het als global maar 1x kunt doen.
Volatile doet niks anders dan wat het woord in het Engels betekent. Het ding is 'vluchtig' (Wat een kromme vertaling! Maar zo schijnt het in het Nederlands te heten). Die is interessant voor de compiler, omdat deze wel eens wat optimization doet.
De meeste berekeningen (if not all...) vinden plaats in registers. Als je nou een variabele hebt, dan staat die in memory. Voor elke bewerking is het scenario
- Haal var op
- Doe wat je ermee wilt doen
- Breng 'm weer naar memory
Dat werkt prima, maar kost tijd. De optimization zegt heel simpel... Als ik in deze scope de variabele nou eens in een register houd? Dat scheelt me elke keer de copy naar mem en daarna weer de copy van mem. Het gaat zelfs zover dat een local wel eens helemaal niet kan bestaan als variabele - de handling wordt puur in een register gedaan.
Dat werkt prima, totdat... jij een ISR hebt die òòk aan die variabele zit. Daar weet die compiler niks van - het kan een heel ander source file zijn. Dan heb je dus een discrepantie tussen wat er in een register zit en wat er in memory zit. En dat is doorgaans 'oops'.
Met volatile, vertel je de compiler dat die variabele wel eens 'spontaan' van waarde kan veranderen. En dat optimization hier niet gebruikt moet worden.
Een andere situatie is wanneer je memory-mapped I/O hebt. In de buitenwereld kan er wat veranderen. Als de compiler niet weet dat er ook nog wel eens wat anders aan die variabele staat te trekken, dan sta jij leuk naar een register te kijken (wat echt niet gaat veranderen), en die I/O pin heeft al lang z'n bitje op het betreffende address geset (of gecleared).
Volatile is dus bedoeld om erg specifiek optimization van de compiler uit te zetten. Als je op de commandline optimization volledig uit zet, en de compiler houdt zich daaraan, dan heb je volatile niet nodig. Echter, vaak wil je juist optimization gebruiken. Dus dat is niet gebruikelijk.
Op 24 februari 2013 16:40:37 schreef EricP:
...Als je nou iets zo
static int bla=0;declareert, dan staat het ding op de heap. Ofwel: het ding is er altijd. Bij het starten van je programma wordt die op 0 gezet. Daar is een routine voor die loopt voordat jouw programma aan de gang mag. Regelt de compiler voor je.
Dat is geen heap hoor, globale variabelen staan in het BSS segment en worden door de program loader initieel op 0 gezet.
rew
four NANDS do make a NOR . Kijk ook eens in onze shop: http://www.bitwizard.nl/shop/
Dat stukje code
static int bla;
specificeert niet waar dit staat.... En dat maakt uit.
static int flut;
int main ()
{
static int flap;
...
}Hierbij is "flut" een globale variable die alleen binnen dit sourcecode file gevonden kan worden. "flap" is een variabele die alleen binnen de functie main () gevonden kan worden.
En dan over volatile:
int flut;
int main ()
{
//flut = 0;
while (1) {
if (flut) {
// doe iets;
flut = 0;
}
}
}
De compiler kan op twee manieren optimaliseren dat het niet goed gaat: Ten eerste kan de compiler constateren dat flut op nul begint, en nooit gezet wordt. Dus de hele "if (flut) " kan er uit. Anderzijds, een makkelijkere optimalisatie is dat flut binnen de while lus niet kan veranderen, dus VOOR de while lus wordt flut uit het geheugen in een register geladen, en vanaf daar wordt alleen naar het register gekeken (en op de flut=0 regel op nul gezet en weer naar het geheugen teruggeschreven).
Zet je nu volatile voor de declaratie van de flut variabele, dan weet de compiler dat ie achter z'n rug om kan veranderen. Dat kan een interrupt zijn, maar soms ook hardware.
EricP
mét CE
Op 24 februari 2013 17:06:32 schreef Hewlett:
[...]Dat is geen heap hoor, globale variabelen staan in het BSS segment en worden door de program loader initieel op 0 gezet.
Dat is afhankelijk van je compiler. Voor gcc voor een AVR kan ik je niet anders dan gelijk geven.
Is overigens voor de strekking niet zo relevant - stack of 'ergens vast' is de essentie waar het om draait.
Heb je niet iets wat je in de interrupthandler kan zetten, zodat de waarde die door het optimaliseren in het register blijft staan, naar het memory geschreven wordt, en aan het einde van de interrupt weer terug gezet wordt? (volgens mij heb je dit in picbasic, en heet dat daar context save & restore)
[Bericht gewijzigd door flamingo op (93%)]
EricP
mét CE
Op 24 februari 2013 19:20:17 schreef flamingo:
Heb je niet iets wat je in de interrupthandler kan zetten, zodat de waarde die door het optimaliseren in het register blijft staan, naar het memory geschreven wordt, en aan het einde van de interrupt weer terug gezet wordt? (volgens mij heb je dit in picbasic, en heet dat daar context save & restore)
picding basic is niet vergelijkbaar. Ik weet niet precies wat die context save exact doet (en wil dat ook graag zo houden...). waarschijnlijk is het een halfbakken oplossing voor een ISR om je registers heel te houden - daar moet-ie met z'n p*ten vanaf blijven omdat een IRQ ook tussen een compare en de daarop volgende conditional branch op kan treden. Als een ISR dan lekker in de registers gaat zitten harken, dan is je compare result naar de vaantjes (en ga dan maar eens uit zoeken waarom die conditional branch de mist in gaat).
Voor wat betreft volatile: ik denk dat je het nog niet helemaal begrepen hebt. Het probleem zit niet in de ISR. Die frut het aan het einde van de routine wel terug naar mem. Het probleem ontstaat bij 'iets anders' wat er naar die variabele kijkt - kijk ook nog even naar de post van rew. Dat is zich niet bewust van een ISR (en dat wil je ook, die dingen moeten volledig 'hidden' zijn...).
Dag flamingo,
Nog wat relevante info:
1) Bij een functieoproep worden de lokale variabelen en functieparameters op de stack "gecreëerd". Deze worden terug "vernietigd" bij het verlaten van die functie. De wijze waarop parameters worden gecreëerd en vernietigd
- volgorde op de stack
- wie deze creëert en vernietigt (oproeper of opgeroepen functie)
noemt men calling convention
De meest courante zijn:
- pascal
- cdecl
- stdcall
Windows bv gebruikt stdcall als default calling convention. Je kan dit echter veranderen in het functieprototype. De calling convention van de oproep moet wel overeenstemmen met deze van de functie. Bij "vreemde" DLLs en bij het gebruik van een C-dll in Basic moet je hier wel op letten.
int __cdecl functie(void)2)
Op 24 februari 2013 16:04:17 schreef flamingo:Klopt het dat als ik in een subroutine een variabele schrijf en vervolgens uit de routine ga, en dan het hele geheugen vol schrijf. Dat ik dan als ik de routine opnieuw in ga de variabele weer kwijt ben.Oftewel, dit zijn geen gereserveerde adressen toch?
Dan ben je waarschijnlijk ook je programma kwijt
. Bij systemen met een OS is dit echter niet zo eenvoudig om zoiets voor elkaar te krijgen.
Wat wel kan is een stackoverflow bij een teveel aan genestelde functies. Dit gebeurt niet snel tenzij je met recursieve functies werkt zoals in dit voorbeeld. Heel wat professionele SW bedrijven verbieden hun programmeurs trouwens om recursie toe te passen. In de linker kan je trouwens meestal de stackgrootte instellen.
void recFunc(long * lCount)
{
(*lCount)++;
if (*lCount < 10000000L)
{
recFunc(lCount);
}
return;
}
main ()
{
long lCount = 0L;
recFunc(&lCount);
return;
}
Wat ook kan zijn zogenaamde memory leaks waarbij je een locale variabele (pointer naar een malloc) wist zonder het bijbehorende geheugenblok. In een loop kan dit op lange termijn leiden tot een heapoverflow.
3) Globale statische variabelen:
static int count;
int count;
Compilers reageren verschillend op globale statische variabelen als je deze in een andere *.c(cpp) file wil gebruiken omdat static "impliceert" dat je in een andere *.c(cpp) file onder geen enkele voorwaarde toegang kan krijgen tot deze variabele. Sommige platformen zoeken naar een andere count en genereren een linker error. Soms krijg je een compiler error, terwijl ik ook al heb meegemaakt dat static int count gewoon int count is.
4) De heap is de locatie voor toegewezen geheugen (via malloc of new in C++). Hiervan kan je de grootte trouwens ook instellen.
Succes ermee
void recFunc(long * lCount)
{
(*lCount)++;
if (*lCount < 10000000L)
{
recFunc(lCount);
}
return;
}Hier roept recfunc (10000000x) zichzelf op, wist niet dat dat kon/mocht. Maar dit zuipt geheugen inderdaad
rew
four NANDS do make a NOR . Kijk ook eens in onze shop: http://www.bitwizard.nl/shop/
Op een STMF3 gaat je dat dus niet lukken: te weinig geheugen. Op z'n minst moet de PC voor het terugkeren opgeslagen worden. Bij moderne CPUs soms nog wat meer.
De klassieke methode om jou recursieve programma te schrijven is:
void recfunc (int count)
{
if (count <= 0) return;
recfunc (count-1);
}Oei! mogelijk spreek ik mezelf hiermee tegen. Je loopt het risico dat de compiler herkent: Hey, ik moet na het aanroepen van de functie de functie call afronden. Dat kan ik net zo goed door de aangeroepen functie laten doen.
Dus in plaats van:
call somefunc
ret
doet hij:
jmp somefunc
dus dat betekent dat mijn "recfunc" dus iets kan worden als:
; allocate stack frame for local variables: 0 bytes.
cmp R3, #0 ; neem even aan dat R20 gebruikt wordt voor het eerste argument.
beq .L4 ; skip if zero
ret
.L4: dec R3
jmp somefunc
Ik verzin even ter plekke een calling conventie en een assembly taal. Geen zin om uit te zoeken hoe het op arm er nu precies uitziet.
In de praktijk is gcc zo slim om te constateren dat die hele recursieve call tzt gewoon terugkomt, en hij elimineert in mijn voorbeeld (met -O2 op ARM) de hele recursieve aanroep van alle subcalls!
roadrunner84
Meep! Meep!
Bedoel je te zeggen dat de "jmp" i.p.v. "call...ret" er voor zorgt dat het feitelijk van een recursieve functie in een iteratieve functie omgezet wordt? Dan kan het dus prima op een kleine controller, gezien je stack niet explodeert, toch?
Ja, een tail recursion kan idd relatief eenvoudig naar een loop omgezet worden. Een beetje optimalizerende backend doet dat al op een laag optimize niveau. Voordeel is dat er runtime geen stack frame opgebouwd en afgebouwd hoeft te worden.
rew
four NANDS do make a NOR . Kijk ook eens in onze shop: http://www.bitwizard.nl/shop/
Maarrrr!
ik heb functioneel dezelfde code geschreven als flamingo, maar zijn code gaat wel degelijk de recursie in en de mijne niet (bij GCC op -O2). Kortom, je kan er niet echt op vertrouwen wat de compiler zal doen.
Stel je voor je hebt mijn code, en denkt: "waarom is ie zo snel klaar, volgens mij wordt ie niet eens aangeroepen!?!?" Als je dan debug code toevoegt:
printf ("%d\n", count);
dan wordt het ineens een heel ander verhaal! Ineens gaat ie WEL recursie doen!
Wellicht is er dan wat anders aan de hand. Jouw code
void recfunc (int count)
{
if (count <= 0) return;
recfunc (count-1);
}verandert geen state en doet verder niets met de count value parameter, het is gewoon een delay. Een goede optimizer op een hoog optimize niveau kan besluiten deze functie helemaal niet uit te voeren. Voeg je een printf op count toe dat moet er iets met de variable count gedaan worden. In dat geval kan de recursieve functie nog steeds naar een loop omgezet worden. Weet je zeker dat dat niet gebeurt? Een printf is een relatief langzame standard functie, wellicht lijkt het zo. Assembly liegt niet 
In Flamingo's code fragment:
void recFunc(long * lCount)
{
(*lCount)++;
if (*lCount < 10000000L)
{
recFunc(lCount);
}
return;
}
heeft de recursieve functie wel een effect, namelijk het verhogen via de pointer parameter lCount die naar een long verwijst. In dit geval is het voor een optimizer wat moeilijker om er een loop van te maken. Wellicht past dit type functie niet in de optimize strategie. Een automatische tail recursie naar een loop conversie werkt beter met functies die een return waarde recursief opleveren, bijv een Fibonacci reeks of een triviale faculteit berekening.
Ik wist niet dat je zoveel rekening moest houden met de optimiser. Als je niet oplet kan dit vervelende bugs op leveren.
roadrunner84
Meep! Meep!
Je hoeft er geen rekening mee te houden, alleen moet je er niet op rekenen dat alle "slimmigheden" uit jouw hoofd ook zo geinterpreteerd zullen worden door je compiler.