ik ben een wat groter programma aan het schrijven in C. en wat ik al vaker ben tegen gekomen is dat het lastig is om dat overzichtelijk te doen.
als je gewoon voor de voet weg begint dan krijg je al snel dat er een hoop word "genesteld" en daar word het niet duidelijker op.
ik heb wel ideeen hoe je dat kan doen, maar ik kan ze niet goed "vertalen" in C. ik wil eigenlijk 3 voorbeelden geven, en hoop dan van julie te horen hoe je dat zou kunnen doen.

-1- je hebt ergens geprogr. dat je aan verschilende voorwaarde moet voldoen voordat je aan de volgende stap mag gaan beginnen. b.v. geen N.S. - stuurstroom in - arm in rust pos. enz. enz.
dat zou ik graag ergens apart gaan afvragen en dat je dan de uitkomst met een flag (of merker) bij een volgend blok eenvoudig kan toevoegen.
je kan daar natuurlijk een apparte variabele voor aan maken, maar dan heb je een integer om 1 bit te onthouden.

-2- je progr. bestaat uit meerdere blokken die na elkaar moeten worden uitgevoerd dus eerst A en dan B vervolgens C enz. enz. wanneer A klaar is hoef ik daar niet meer naar terug, maar de pointer komt er wel steeds langs. dat kan lastig zijn wanneer ik zowel in blok A als B dezelfde uitgangen gebruik.

-3- je hebt een taak die je op verschillende punten in het programma wil laten uitvoeren. voor die taak progr. je 1 blok. hoe kan ik dat ene blok op verschillende plekken in het progr. het best aanroepen ? hier zou een goto funtie handig zijn. al weet ik dat het dan snel een spaggeti word.

Dat geldt in principe voor alle talen, niet alleen C...

-1- Je kan daar natuurlijk een apparte variabele voor aan maken, maar dan heb je een integer om 1 bit te onthouden.

Hoezo? Je kunt de overige bits van die integer toch ook nog gebruiken?

-2- begrijp ik niet...

-3- je hebt een taak die je op verschillende punten in het programma wil laten uitvoeren. voor die taak progr. je 1 blok. hoe kan ik dat ene blok op verschillende plekken in het progr. het best aanroepr ? hier zou een goto funtie handig zijn. al weet ik dat het dan snel een spaggeti word.

Functie of subroutine?

Je kan in een integer ook 16 boolean kwijt. Of dit de zaak overzichtelijker maakt is wat anders.

Onze docent java maakte steeds de opmerking, als je 3 of 4 keer dezelfde code moet schrijven, maak er dan een functie of methode van.

Ik doe een poging om je vragen te ontcijferen, want je formuleert alles nogal cryptisch... Als je je C-code op dezelfde manier schrijft als je post kan ik begrijpen dat het snel onleesbaar wordt.

Op 5 augustus 2019 19:40:39 schreef trix:

-1- je hebt ergens geprogr. dat je aan verschilende voorwaarde moet voldoen voordat je aan de volgende stap mag gaan beginnen. b.v. geen N.S. - stuurstroom in - arm in rust pos. enz. enz.
dat zou ik graag ergens apart gaan afvragen en dat je dan de uitkomst met een flag (of merker) bij een volgend blok eenvoudig kan toevoegen.
je kan daar natuurlijk een apparte variabele voor aan maken, maar dan heb je een integer om 1 bit te onthouden.

Je kan doormiddel van bitoperaties elke bit afzonderlijk setten, resetten, toggelen en nog veel meer van eender welk datatype. Kijk bijvoorbeeld maar eens naar een Datasheet van een microcontroller. Daar zijn honderden registers, die allemaal een bepaalde grootte hebben (8-bit in dit geval). Elke flag een volledige byte geven zou verspilling zijn, dus stoppen ze in elke byte 8 afzonderlijke flags op vooraf bepaalde plaatsen.

-2- je progr. bestaat uit meerdere blokken die na elkaar moeten worden uitgevoerd dus eerst A en dan B vervolgens C enz. enz. wanneer A klaar is hoef ik daar niet meer naar terug, maar de pointer komt er wel steeds langs. dat kan lastig zijn wanneer ik zowel in blok A als B dezelfde uitgangen gebruik.

Met "De pointer" vermoed ik dat je de Instruction Pointer bedoelt?
Wat dacht je hiervan?


int main(void)
{
  doeIetsEenmalig();

  while(1)
  {
    doeDeRestVanHetProgrammaOneindig();
  }


}

-3- je hebt een taak die je op verschillende punten in het programma wil laten uitvoeren. voor die taak progr. je 1 blok. hoe kan ik dat ene blok op verschillende plekken in het progr. het best aanroepen ? hier zou een goto funtie handig zijn. al weet ik dat het dan snel een spaggeti word.

Een goto heb ik nog nooit in mijn leven nodig gehad in geen enkele programmeertaal behalve Assembler. Want inderdaad... goto's maken je code verschrikkelijk onleesbaar. Waarom denk je dat nodig te hebben? Wat is er mis met functie calls, conditionele statements en loops?

Hierboven zijn er twee die aannames doen die gelden voor een "AVR".

int is 16 bits is een typische AVR situatie. Vrijwel alle compilers hebben tegenwoordig een 32-bit int, alleen de AVR-gcc blijft achter.

"alle registers 8 bits" klinkt ook als een 8-bitter aanname. Mijn peripheral registers zijn meestal 16 of 32bit.

Op 5 augustus 2019 19:40:39 schreef trix:
je kan daar natuurlijk een apparte variabele voor aan maken, maar dan heb je een integer om 1 bit te onthouden.

Right! Gewoon doen! Hierboven zeggen er meerdere dat je meerdere (bit-)variabelen in die ene integer kwijt kan, maar dat maakt de code minder overzichtelijk.

Jou tijd, ook al is het hobby, is meer waard dan die paar bits aan geheugen.

Als je de "apollo guidance computer" aan het programmeren bent en maar 2kbyte RAM hebt, tja, dan moet je zuinig zijn op je variabelen. Nu is de kans dat jij met "enkele tientallen" booleaanse variabelen je RAM volkrijgt nihil. Alles is relatief: een atmega328 heeft ook 2k RAM en dat is zeeen van ruimte.

Ik zou denk ik, als je op een aantal condities wil testen iets schrijven als:

   if (a-moet-gebeuren && algemene_condities ()) {
      // doe a. 
   }
   if (B_moet_gebeuren && algemenen_condities ()) {
      // doe B;
   }

Merk op dat alleen als a-moet-gebeuren waar is, dat ie dan de functie algemene_condities () aan gaat roepen.

Als je dat met een variabele zou doen dan krijg je iets als:

   ac = algemene_condities ();
   if (a_moet_gebeuren && ac) { ... 
   if (b_moet_gebueren && ac) { ...

maar als je nu ook een c hebt waarvoor de condities NIET gechecked hoeven te worden, dan schrijf JIJ:

   if (c_moet_gebeuren) { ...

zonder de variabele ac te checken. Maar de functie "algemene_condities" is nu al WEL aangeroepen, nodeloos, want het was neit nodig. In mijn voorstel schrijf je precies hetzelfde, maar dan wordt algemene_condities () dus NIET aangeroepen.

Hierboven zeggen er meerdere dat je meerdere (bit-)variabelen in die ene integer kwijt kan, maar dat maakt de code minder overzichtelijk.

???

Je geeft ieder flagbit toch zijn eigen naam? In welke variabele 'ie zit maakt totaal niks uit.
(1 bit per variabele is eigenlijk geen bit meer maar een boolean)

Op 5 augustus 2019 20:59:06 schreef rew:
Hierboven zijn er twee die aannames doen die gelden voor een "AVR".

int is 16 bits is een typische AVR situatie. Vrijwel alle compilers hebben tegenwoordig een 32-bit int, alleen de AVR-gcc blijft achter.

"alle registers 8 bits" klinkt ook als een 8-bitter aanname. Mijn peripheral registers zijn meestal 16 of 32bit.

Waarom zou je op een 16- of 32-bit register geen bitoperaties kunnen uitvoeren? Of je nu op een AVR, of op een computer werkt. Maakt toch niet uit?

Het kernwoordje dat je gemist hebt was "in dit geval", omdat ik verwees naar de registers in een datasheet, dat hier toevallig een 8-bitter was. Maar dit kon evengoed een 32-bitter zijn.

[...]
Right! Gewoon doen! Hierboven zeggen er meerdere dat je meerdere (bit-)variabelen in die ene integer kwijt kan, maar dat maakt de code minder overzichtelijk.

Jou tijd, ook al is het hobby, is meer waard dan die paar bits aan geheugen.

Als je de "apollo guidance computer" aan het programmeren bent en maar 2kbyte RAM hebt, tja, dan moet je zuinig zijn op je variabelen. Nu is de kans dat jij met "enkele tientallen" booleaanse variabelen je RAM volkrijgt nihil. Alles is relatief: een atmega328 heeft ook 2k RAM en dat is zeeen van ruimte.

Hier kan ik nog wel mee akkoord gaan: in de meeste gevallen is er geen reden om zo hard te letten op geheugen, want inderdaad, het kan je code heel onoverzichtelijk maken en je krijgt je program memory toch zelden vol.
Maar omdat TS vroeg naar optimalere methodes, zijn bitoperaties zeker niet ongebruikelijk. Zoals Arco hierboven ook reeds zei kan dat met moderne compilers toch al een heel stuk overzichtelijker dan vroeger toen je alles manueel moest doen. Als je alle flags die gerelateerd zijn aan één iets combineert in één byte (of int, whatever) kan dat de code ook overzichtelijker maken, ipv. net niet.

inderdaad slechts 1 bit van een integer gebruiken, daar moet je niet moeilijk over doen. het "voelt"een beetje onlogisch.

vinden julie het geoorloofd om 1 en de zelfde uitgang op meerdere plekken aan te roepen ?

je kan daar natuurlijk een apparte variabele voor aan maken, maar dan heb je een integer om 1 bit te onthouden.

C kent anders de bit-field variabele, kan je variabelen van 1,2,3 of wat dan ook in bit lengte maken.
Even iets willekeurigs geplakt:

union
{
  struct
  {
    unsigned Second:1;        					// flag to indicate a second has expired
    unsigned Pulse:1;						// flag to indicata a solar pulse detected
    unsigned Command:1;						// Host command available in command buffer
    unsigned SampleMode:2;					// Sample Mode (4 modes)
  } Bit;
  unsigned char Byte;
} Flags;

Dat ik er een union omheen leg om snel alles te clearen met Flags.byte=0. Individuele flags adreseer je met Flags.bit.Pulse = 1 (of testen op een bit).
Bij een kleine micro (die vaak bit instructies hebben) geeft dat ook nog optimale code.

Op 5 augustus 2019 21:36:04 schreef trix:
vinden julie het geoorloofd om 1 en de zelfde uitgang op meerdere plekken aan te roepen ?

Om hem te besturen, dwz setten en resetten? Nee! Kijken wat de waarde is, dat mag overal.

Tja e.e.a. Hangt beetje af van je programma.

Ik wil nog wel eens zoiets doen


Void main() 
{
  Unsigned char status; //status: 0 rust, 1 omhoog, 2 naar rechts, 3 grijpen, 4 links, 5 omlaag. 

  Init();
  status = 0;

  While(1)
  {
     Switch (status) 
     {
        Case 0:           //rust
          If (start == 1)
          {
            Status = 1;
          } 

        Case 1:           //omhoog
          If (limit-up == 1)
          {
            Status = 2;
          } 
Etc etc... 

 } 


} 

Bovenstaande code is gewoon goed.
Gelukkig staat de boel wel bij elkaar. Dan is het nog wel te doen, maar als je een programma hebt van 20 pagina's en op sommige pagina's set je de waarde en op andere pagina's reset je de waarde, dan wordt het een onoverzichtelijke bende. Met een beetje pech ziet de buitenwereld de uitgang flipperen.

voordeel is dat je door status altijd kan zien wat je aan het doen bent.

maar vergeet niet de default case.

klassiekertje is deze:

je hebt een 3 bits op portA van je arduino om een lijnvolger te bouwen op respectievelijk bit 0,1 en 2



switch (portA & 0b00000111)
{
  case 0: // geen lijn

    break;

  case 1: // lijn uiterst links
    // stuur scherp rechts

  case 2: // lijn midden
    // rechtdoor

  case 3: // lijn beetje links
    // stuur beetje rechts

  case 4: // lijn uiterst rechts
    //  stuur scherp links

  case 5: // lijn links en rechts? Splitsing!!!
    // whatever needed

  case 6: // lijn beetje rechts
    // stuur beetje links

  case 7: // dwars op lijn of te brede lijn
    // WHATEVER

  case default: // error state
    printf "errorstate";
}

je zou zeggen dat hij nooit errorstate kan printen

Niets is meer waar!

na enteren case kan de status van port A veranderenvan bij 5 naar 0: de check van 0 is dan al geweest

ALTIJD zo oplossen


status = portA & 0b00000111;
switch (status)
{
  case 0: // geen lijn

    break;

  case 1: // lijn uiterst links
    // stuur scherp rechts

  case 2: // lijn midden
    // rechtdoor

  case 3: // lijn beetje links
    // stuur beetje rechts

  case 4: // lijn uiterst rechts
    //  stuur scherp links

  case 5: // lijn links en rechts? Splitsing!!!
    // whatever needed

  case 6: // lijn beetje rechts
    // stuur beetje links

  case 7: // dwars op lijn of te brede lijn
    // WHATEVER

  case default: // error state
    printf "errorstate";
}

nee...setten en resetten op de zelfde plek, en even later weer op een andere plek.

het blijkt wel dat structuur in een programma brengen, op meerdere manieren kan. en wat ik duidelijk vind, vind een ander weer onduidelijk.

komt bij dat ik het op hobby nivo doe en dus met 1 persoon, maar als je met meerdere programeurs bij b.v. bij een machine fabriek zit, word het heel lastig om allemaal programmas te schrijven die eenduidig zijn van opzet. en dat is wel nodig omdat jantje wel in het progamma van pietje moet kunnen werken. waar ik eerst werkte (als service monteur) pakte ze wanneer een nieuwe mach. moest worden geprogrameerd een programma van een eerdere mach. die er sterk op leek en die gingen ze dan wijzigen. dit is vaak sneller, maar je houd zo ook uniformiteit in de programmas.

Een state machine dus. Handig om non-blocking software te maken (met DOS moest je het wel zo doen om een goed programma te maken)

Bits setten/resetten hoeft niet op dezelfde plaats.
Ik set vaak een bit in het hoofdprogramma om een interrupt timer te starten. In de interrupt zelf reset ik die dan om aan te geven dat de timer afgelopen is.

ik gaf idd een voorbeeld voor een statemachine en de klassieke fout

je hebt natuurlijk ook programma's die meer multi tasking lijken te zijn. Ik ben nu bezig met een intelligente zekeringkast, die dus alles in mijn complete auto moet bewaken: die kan je niet op deze manier aanpakken.

maak dan subroutines

blokje om de analoge ingangen te lezen in een priorteit die ik wil.
blokje om de kachel status uit te lezen
blokje om snelheid en toeren in te lezen
blokje om defecte lampen te detecteren
blokje om schakelaars in te lezen
blokje om verlichting te bedienen
blokje om ramen aan te sturen
blokje om ruitenwissers te bedienen
blokje om dash aan te sturen
etc etc

etc. In die blokjes doe ik vaak ook een stukje initialisatie, wat ik gewoon met een eenmalige variabele doen: begin met 0 en als hij 1 is, voer ik dat niet meer uit, maar dan houd je je intitlaisaties ok in eigen hand. Dat is een keuze. Als je de variabele volatile maakt kan dat wel een probleem geven.

met toetsen en displays wil je dat ook eigenlijk wel.

Vaak wordt bij ingedrukte toetsen gewacht tot die weer wordt losgelaten, da's natuurlijk een no-no bij fatsoenlijke software... ;)

Vaak wordt bij ingedrukte toetsen gewacht tot die weer wordt losgelaten, da's natuurlijk een no-no bij fatsoenlijke software...

Ik heb altijd wat moeite met dit soort strakke regeltjes. Er zijn best wel situaties waarbij dit wel klopt, maar er zijn evengoed situaties waarbij zo een wachtlust geen enkel probleem is. Hangt helemaal af van de requirements.


void main()
{
   for ( ;; )
   {  Funktie1();
      Funktie2();
      Funktie3();
   }
}

Hierbij een programmatje met 3 funkties. Zonder te weten hoe die funkties zijn opgebouwd kun je er weinig van zeggen. Misschien zijn de funkties blocking. Dan begint Funktie2() pas nadat Funktie1() klaar is. Maar misschien zijn de funkties non-blocking. Dan worden de funkties alle drie in hoog tempo uitgevoerd. Non-blocking funkties zijn handig als je meerdere dingen 'tegelijk' wilt controleren. Maar is ook lastiger omdat je dan state machines moet inbouwen. Blocking funkties zij meestal overzichtelijker.

Het is maar net wat er per geval gevraagd wordt.

Wachten op key loslaten zonder verdere controle is ALTIJD een slechte zaak.
Als de toets door vuil of een ander defect ingedrukt blijft, hangt je programma voor eeuwig. Ik vind dat niet acceptabel...
(er moet minimaal een time-out op zitten)

Op 5 augustus 2019 22:29:06 schreef High met Henk:
je hebt een 3 bits op portA van je arduino om een lijnvolger te bouwen op respectievelijk bit 0,1 en 2



switch (portA & 0b00000111)
{
  case 0: // geen lijn
...
  case 7: // dwars op lijn of te brede lijn
    // WHATEVER

  case default: // error state
    printf "errorstate";
}

je zou zeggen dat hij nooit errorstate kan printen

Niets is meer waar!

Jou stelling is ONJUIST. De compiler MAG de uitdrukking "PORTA & 7" maar 1x evalueren. De compiler maakt er van:


        in r20,0x1b
        mov r30,r20
        andi r30,lo8(7)

en gebruikt de rest van de tijd "r30" (die weer teruggeschoven wordt naar r20, en vervolgens NOG een keer via r30 gebruikt wordt...).

Verder ziet de compiler dat ALLE 8 de gevallen zijn afgevangen en dat de negende case, je printf nooit voor kan komen. Die laat ie dan ook gewoon weg. In mijn testcode krijg ik nog wel de waarschuwing dat ik printf moet laten declareren door stdio te includen, maar in de resulterende assembly staat ie niet meer.

De volledige code:

        in r20,0x1b
        mov r30,r20
        andi r30,lo8(7)
        mov r20,r30
        ldi r21,0
        movw r30,r20
        sbiw r30,1
        cpi r30,7
        cpc r31,__zero_reg__
        brsh .L2
        movw r28,r24
        subi r30,lo8(-(gs(.L4)))
        sbci r31,hi8(-(gs(.L4)))
        ijmp
        .section        .progmem.gcc_sw_table,"ax",@progbits
        .p2align        1
.L4:
        rjmp .L3
        rjmp .L5
        rjmp .L6
        rjmp .L7
        rjmp .L10
        rjmp .L9
        rjmp .L10
        .text

Hiervoor pusht ie nog R28 en R29 op de stack: onnodig want die worden nergens gebruikt. (waarschijnlijk wel voordat er geoptimaliseerd werd). Raar dat ie een special case voor de "case 0:" maakt en de rest met de tabel oplost. L2 is voor case0, L3 voor case1 enz. Hmm. Ik ben wel precies 1 "break" vergeten te typen. Misschien is dat het. (dus dan zit er 1 fallthrough in de case).

Op 6 augustus 2019 02:08:49 schreef Arco:
Als de toets door vuil of een ander defect ingedrukt blijft, hangt je programma voor eeuwig. Ik vind dat niet acceptabel...
(er moet minimaal een time-out op zitten)

Als de hardware faalt, kan je niet verwachten dat de software blijft werken. Als je knop na het indrukken voor de 12345e keer ineens blijft hangen, dan kan jij 2 seconden later de foutmelding geven: "Je drukt lang op de knop of de knop is zojuist kapot gegaan". En misschien nog 1 laatste keer de actie uitvoeren die met die bij het indrukken van de knop hoort. Maar als de knop "stuck open" kapot gaat, merk je niets, en moet de gebruiker bij het indrukken van de knop constateren: "hey hij doet het niet meer". Enals je op het loslaten van de knop reageert gebeurd datzelfde dus ook bij een "stuck closed" kapotte knop.

Kijk: Het is iets anders als je ook nog allerlei andere dingen in de gaten aan het houden bent: Dan mag je niet je monitoring laten vallen "zolang de gebruiker op de toets blijft drukken". Die monitoring moet zowiezo doorgaan.

[Bericht gewijzigd door rew op (21%)]

Op 6 augustus 2019 07:13:30 schreef rew:
Jou stelling is ONJUIST. De compiler MAG de uitdrukking "PORTA & 7" maar 1x evalueren. De compiler maakt er van:

Volgens mij is dit afhankelijk van de compiler-optimization instellingen. Afhankelijk van hoe agressief die is ingesteld zal hij door middel van code-motion de test automatisch buiten de switch plaatsen of niet. (Dit vermoed ik toch, maar durf het ook niet met zekerheid zeggen.)

edit: Ik was zelf benieuwd hoe de standaardinstellingen van een moderne compiler het zouden aanpakken, en heb het dus eens uitgetest. Rew blijkt het bij het juiste eind te hebben :)
Probeer het hier zelf uit

[Bericht gewijzigd door Opifex op (29%)]

In de arduino ide werkte heb ik het zo in de praktijk zo fout zien gaan..

Edit.. My mistake..
Dat was met else if..... Dan check je wel vaker

De switch case werd door de studenten niet begrepen...

Jullie hebben gelijk..

Soms gooi ik weleens dingen door elkaar. Evengoed neem ik altijd case default op in geval status waarde wijzigen ook wel eens een tikfout kan bevatten

[Bericht gewijzigd door High met Henk op (68%)]

Op 6 augustus 2019 09:18:20 schreef Opifex:
Afhankelijk van hoe agressief die is ingesteld zal hij door middel van code-motion de test automatisch buiten de switch plaatsen of niet.

Als er een


switch (a) {
  case 0:this ();break;
  case 1:that ();break;
  case 2:somethingelse ();break;
}

staat dan kan je zeggen dat dit vergelijkbaar is met

if (a == 0) this ();
else if (a == 1) that ();
else if (a == 2) somethingelse ();

En dat is zo: Zolang a geen bij-effecten heeft of op een andere manier plots kan veranderen dan doet het wel ongeveer hetzelfde.

Maar om de boel echt hetzelfde te krijgen moet je het zo schrijven:

temp123 = a;
if (temp123 == 0) this ();
else if (temp123 == 1) that ();
else if (temp123 == 2) somethingelse ();

Als de "a" bij-effecten heeft, zoals met "a++" of een functieaanroep is of bevat, dan mag een compiler ook in het if (a == 0) geval de boel niet optimaliseren en de functieaanroep of bij-effecten maar 1x doen. De "a" staat daar drie keer en dan moet eea ook gewoon drie keer gebeuren.

De standaar "instinker" is als je max(a,b) gaat definieren met een preprocessor makro:

#define max(a,b) (a>b?a:b)

Dan krijg je dat als je

if (max (a, b) > 10) 

doet, als a de grootste is deze twee keer geevalueerd moet worden. Geen keuze van de compiler, dat MOET.

Op 6 augustus 2019 00:22:14 schreef deKees:
Misschien zijn de funkties blocking. Dan begint Funktie2() pas nadat Funktie1() klaar is. Maar misschien zijn de funkties non-blocking. Dan worden de funkties alle drie in hoog tempo uitgevoerd.

dit is eigenlijk een gedeelte. v/h antwoord op mijn eerste (-1-) vraag.
in ieder geval begrijpelijke termen.
bereik je "blocking" dan het best om b.v. in een integer variabele 1 bitje hoog te maken. of zijn er betere manieren.
ik weet dat dit al bevestigd is, maar wellicht zit er in de andere antwoorden een betere hint. de reacties zijn voor mij op een behoorlijk nivo waardoor ik niet alles in 1x snap.

Op 6 augustus 2019 11:12:27 schreef trix:
de reacties zijn voor mij op een behoorlijk nivo waardoor ik niet alles in 1x snap.

Als ik voor mezelf spreek heb ik eerder de indruk dat wij het zijn die je vragen niet snappen. Ze komen heel cryptisch over, en het lijkt me dat je met je vragen iets anders bedoelde dan wij ze interpreteren.