C variabele declareren ?

EricP, Trix is iemand die bepaalde dingen nog moet leren.

Ik heb in 1985 een programma zitten maken wat uiteindelijk "te groot" was geworden. Niet dat het niet paste, maar gewoon te veel regels code voor wat het deed zodat het niet onderhoudbaar was.

Ik had bijvoorbeeld voor een functie die vrijwel hetzelfde deed als een andere functie die functie gecopieerd en dan dat ene verschilletje geprogrammeerd. Uiteindelijk blijkt er een bug in dat gemeenschappelijke deel te zitten, daar loop je tegenaan, fix je en... dan loop je er nog een keer tegenaan.

Dit had dus anders gemoeten: Er had een parameter meegegeven moeten worden die bepaalde of ie het net zus of net zo moest doen, niet twee functies.

Trix moet daar nog achter komen. Bijvoorbeeld dit moet ook nog doordingen: "het is hoogst ongebruikelijk dat het NODIG is dat een functie langer dan een bladzijde moet worden".

Trix: Ik stel het volgende voor:

code:


// main.c

#include "a.h"
#include "b.h"
#include "c.h"

void main (void)
{
  // Eventueel wat algemene initializatie

  do_a ();
  do_b ();
  do_c ();
  // eventueel dat alles in een loop. 
}

In a.h doe je dan:

code:


// a.h

extern int a_somevar;
void do_a (void);

En in a.c:

code:


// a.c

#include "a.h"

// eventueel: 
#include "b.h" 
// maar uitsluitend als er naar module B gecommuniceerd moet worden.

// geen: #include "c.h" : module A moet niet met module C communiceren: Daar zit module B tussen. 


int a_somevar;
static int a_someothervar; // alleen hier nodig! 

void do_a (void)
{
  // hier dingen doen. 
}

En modules B en C vergelijkbaar.

four NANDS do make a NOR . Kijk ook eens in onze shop: http://www.bitwizard.nl/shop/
EricP

mét CE

Dat is dus zo ongeveer wat ik als demo al eerder schreef rew...

Maar dat is volgens TS ingewikkeld en complex... :/
Wellicht valt naar aanleiding van dit topic het muntje. En eh... die copy-paste code inclusief bug... Yep, ook een paar keer tegen gekomen. Zelfs de comments waren mee genomen: this piece of code should be moved to a function. :+

trix

Golden Member

Op 28 maart 2021 07:31:17 schreef EricP:
Maar eh... snap je het samenspel tussen pre-processor, compiler en linker?

blijkbaar niet voldoende :) en met name de linker.

eigenwijs = ook wijs
PE9SMS

Special Member

Volgens mij moet je eerst even dit doorhebben.

c code:

void main (void)
{
  long result=0;
  result = add(1, 2);
}

long add(int a, int b)
{
  return a+b;
}

Deze code geeft een error, de compiler werkt de code regel voor regel af en komt een functie tegen (add) die helemaal niet bekend is. Hoeveel parameters moeten er meegegeven worden, en van welk type? Welk type geeft de functie terug? De compiler weet het nog niet op de plek waar add aangeroepen wordt. Dus een error.

c code:

long add(int a, int b)
{
  return a+b;
}

void main (void)
{
  long result=0;
  result = add(1, 2);
}

Draai je de functies om, dan werkt het wel. Want de compiler komt nu eerst functie add tegen, en maakt daaruit op hoe deze functie werkt. Dus de aanroep in main is nu geen probleem. Terug naar het eerste voorbeeld, die krijg je op deze manier werkend:

c code:

long add(int, int);

void main (void)
{
  long result=0;
  result = add(1, 2);
}

long add(int a, int b)
{
  return a+b;
}

Want nu weet de compiler als 'ie in main komt al hoe de add functie werkt, namelijk er gaan 2 int's in en de return is een long. De interface van en naar de functie is bekend. De invulling van de functie komt verderop.

En als je dan verder nog begrijpt dat de pre-processor een simpele replace doet van de regel #include "bla.h" met de inhoud van bla.h dan ben je er. Dat resultaat gaat dan door naar de compiler.

This signature is intentionally left blank.
trix

Golden Member

@pe9sms, lees ik zo door, tnx.
gedaan, inderdaad de volgorde hoe alles staat is super belangrijk, dat moet ik goed tot me laten doordringen. de compiler gaat stap voor stap door de code. begrijp ook niet waarom dat in mijn code niet werkt :? maar laat ik het doen zoals het hoort.

ik heb geprobeerd om een tekeningetje te maken hoe ik denk dat het werkt, met variabelen van hoofdprogramma naar de tabbladen en variabelen terug.
alleen de juiste instructies nog op de juiste plek.
zit ik met deze gedachte spinsel een beetje in de juiste richting ?

eigenwijs = ook wijs

Bijna goed.

De gele is je main.c

De groene bevatten wel code. Maar om daar een aparte module van te maken moet die code in een funktie zitten.

De declaratie van die funktie moet je dan toevoegen aan de blauwe.

Vervolgens kun je de blauwe includen bovenaan de gele (main).

Maar dan moet je nog wel bedenken hoe je de data doorgeeft van main naar de subroutines. Dat gaat bij voorkeur dmv parameters voor de funkties.

PS
En 'hier moet je dan iets doen' is de betreffende funktie aanroepen.

trix

Golden Member

kijk met kleurtjes word het overzichtelijk (praat makkelijker)
net als bij de jostiband, elke toets een eigen kleur :) :) :)

ik ga kijken of ik er de juiste code bij kan zetten.

eigenwijs = ook wijs

Ik vind ook dat in includes alleen complete functies thuis horen, geen 'afgeknipte' losse stukken van main()

Daarbij is de vraag of dit echt handiger is. Stel je wilt iets wijzigen in rood.c
Het is dan bijna zeker dat dat ook in wit.c en blauw.c moet gebeuren, omdat die identiek zijn. Is juist meer werk als minder...

Nog beter: 1 functie ervan maken met als parameter of het rood, wit, of blauw betreft...

Arco - "Simplicity is a prerequisite for reliability" - hard en software ontwikkeling: www.arcovox.com

9SMS slaat hierboven wel de spijker op z'n kop vind ik. Dat is hoe het in essentie zit. @TS, leer wat "declareren" betekent. De volgorde van je functies zou niet uit moeten maken. Dmv declaraties zorg je daarvoor. Die declaraties zitten in je .h files, de code zelf gewoon in .c files.

Verder is (volgens mij) nog niet gezegd hoe je de code kunt verdelen in verschillende files. Je kunt dat het best verdelen in functionaliteit. Dus bv alles wat met je display te maken heeft zet je in display.c (en .h), alles rond motorsturing staat in motor.c etc. Wat je nu hebt is gewoon de hele zwik bij elkaar in 1 file, die je vervolgens in 3-en knipt. Dat is niet overzichtelijk.

"We cannot solve our problems with the same thinking we used when we created them" - Albert Einstein

Kleine aanvulling.

Module variabelen moet je 2 keer invoeren,
- in de blauwe .h file. Met als extra 'extern'
Zodat main.c vie de include weet dat die variabele bestaat.
- en dan in de groene nog een keer.
Maar dan zonder 'extern'. Om de variabele daadwerkelijk aan te maken.

Feitelijk dus hetzelfde als met funkties.
- In de blauwe header, met alleen funktie naam en lijst van parameters
- En in de groene met de complete code erbij.

Zie ook de voorbeelde van EricP en rew.

trix

Golden Member

post gekruisd

oke,.....maar hierboven is ook maar een opzetje om het linken inzichtelijk te krijgen.

post van rew met de code: kwartje begint nu te vallen denk ik.

[Bericht gewijzigd door trix op 28 maart 2021 13:22:24 (24%)]

eigenwijs = ook wijs

Ik vind ook dat in includes alleen complete functies thuis horen

Ja inderdaad, een enkele keer valt het echter niet zo fraai te organiseren.

1 functie ervan maken met als parameter of het rood, wit, of blauw betreft

Essatamento!

hoe beter de vraag geschreven, zoveel te meer kans op goed antwoord
trix

Golden Member

ik heb het een en ander toegevoegd (alleen in 1e tabblad) met de rode kleur, gebaseerd op de code van rew.
vraag 1: do_rood (); klopt dat ?
vraag 2: de variabele: sorterenklaar gaat dat goed zo, kan ik die in main gebruiken ?

eigenwijs = ook wijs

Wat ik met het ballenvoorbeeld niet snap is waarom je niet een functie "doe bal".

code:


if (bal_is_rood)  bak=1;
if (bal_is_wit)   bak = 2;
if (bal_is_blauw) bak=3;

doe_bal (bak);

waarbij dus doe bal de grootte gaat checken en hem in de juiste bak doet.

code:


void do_bal (int bak)
{
  groot = bal_is_groot ();
  switch (bak) {
    case 1: if (groot) select_rood_groot();
            else       select_rood_klein();
            break;
    case 2: if (groot) select_rood_groot();
            else       select_rood_klein();
            break;
    case 3: if (groot) select_rood_groot();
            else       select_rood_klein();
            break;
   }
}

Merk op dat ik hier demonstreer hoe ik het zou doen als de code voor de bakken echt allemaal anders moet en niet te parametriseren is. Het liefste doe ik natuurlijk:

code:


  selecteer_bak (bak * 2 + groot);

in plaats van de switch. Bak 2 is nu rood-klein, 3 is rood-groot, 4 is wit-klein enz.

four NANDS do make a NOR . Kijk ook eens in onze shop: http://www.bitwizard.nl/shop/
trix

Golden Member

het gaat hier niet over de werking van het programma op zich, dat heb ik supersnel uit de mouw geschud om een voorbeeld te verkrijgen voor het linken.
anders dwalen we snel af van de eigenlijke vraag.

eigenwijs = ook wijs

Paar problemen nog:

- SorterenKlaar heb je nu in elke header, Maar dat kan niet. Een variabele kan maar één keer aangemaakt worden. Dus die moeten verschillende namen hebben.

Maar in dit geval zou ik 'SorterenKlaar' als return value teruggeven, zodat de SorterenKlaar variabelen alleen nog in main nodig zijn.

- bak1, bak2 en bak3 heb je aangemaakt in de main. En dan zijn ze alleen zichtbaar in de main. In de groene modules kun je ze dan niet gebruiken. Bij voorkeur geef je die mee als parameter met de funktie aanroep. Dan moet je wel de declaratie van DoRood() daarop aanpassen.

Dus de funktie-aanroep in main wordt dan:

main.c

code:


#include <rood.h>
...

int bak1;
int SorterenKlaarRood;

...
int main()
{
   ...
   SorterenKlaarRood = DoRood(bak1);
   ...
}

Op 28 maart 2021 19:54:51 schreef trix:
het gaat hier niet over de werking van het programma op zich, dat heb ik supersnel uit de mouw geschud om een voorbeeld te verkrijgen voor het linken.
anders dwalen we snel af van de eigenlijke vraag.

Het antwoord op de eigenlijke vraag is dat je nog een en ander te leren hebt :).

En om eerlijk te zijn weet ik niet waar te beginnen. Hoe je een en ander toepast is een zwaar doodlopende weg. Een start:

Probeer een source file en header als 'paar' te zien. In de header staat de declaratie van functies en variabelen. In de source file de implementatie van de functie.

Stop functionaliteit als behapbare blokjes in een source/header file.

Waar je de functionaliteit nodig hebt include je de header en roep je de functies aan. Dit kan main.c zijn, maar ook een andere source file.

Verder: Besef dat waar je een header include, alles in die header door de compiler wordt uitgevoerd. Hier een voorbeeldje:

Header 'calcsurface.h'

c code:


uint32_t heigth;
uint32_t width;
uint32_t GetSurface();

Header 'main.c'

c code:


#include "calcsurface.h"
#include "calcsurface.h"

void main()
{
uint32_t oppervlakte = GetSurface();
}

In dit voorbeeld gaat je compiler zeuren dat de variabelen 'heigth' en 'width' 2x worden gedeclareerd. Dat komt omdat (per ongeluk expres) de header 2x wordt geinclude.

Nu is dit fout, maar het kan zijn dat je de betreffende header vaker dan eens include, omdat je deze in verschillende sourcefiles nodig hebt. Dit kun je oplossen door het volgende toe te passen in de header:

Header 'calcsurface.h'

c code:


#ifndef CALCSURFACE_H
#define CALCSURFACE_H

uint32_t heigth;
uint32_t width;
uint32_t GetSurface();

#endif

Bovenstaande toevoeging zorgt er voor dat de code tussen #ifndef en #endif slechts 1x wordt gedaan, ookal wordt de header vaker geinclude.

Op 29 maart 2021 13:44:47 schreef M14:[...]

c code:


#ifndef CALCSURFACE_H
#define CALCSURFACE_H

uint32_t heigth;
uint32_t width;
uint32_t GetSurface();

#endif

Bovenstaande toevoeging zorgt er voor dat de code tussen #ifndef en #endif slechts 1x wordt gedaan, ookal wordt de header vaker geinclude.

Dat is de klassieke manier, tegenwoordig kan je bij bijna elke compiler het regeltje:

code:

#pragma once

in de header file gebruiken.

Op 29 maart 2021 14:13:36 schreef JoWi:
[...]

Dat is de klassieke manier, tegenwoordig kan je bij bijna elke compiler het regeltje:

code:

#pragma once

in de header file gebruiken.

Het is afhankelijk van de compiler, het wordt niet altijd ondersteund.

Daarbij is bij de 'klassieke manier' meteen duidelijk wat er gebeurt. En gezien het niveau van de topicstarter lijkt me dat ook niet onbelangrijk.

Op 29 maart 2021 13:44:47 schreef M14:

Header 'calcsurface.h'

c code:


uint32_t heigth;
uint32_t width;
uint32_t GetSurface();

Dit is een extreem-onhandige manier om het te proberen te doen. :-( Niet het juiste voorbeeld.

Die "calcsurface.h" moet je nu dus in je main includen, dan krijg je die variabelen gedeclareerd, maar dan kan je hem niet meer includen vanuit "calcsurface.c" omdat je dan de variabelen dubbel declareert.

c code:


extern uint32_t heigth;
extern uint32_t width;
uint32_t GetSurface(void);

en dan zonder de extern in calcsurface.c.

four NANDS do make a NOR . Kijk ook eens in onze shop: http://www.bitwizard.nl/shop/

Op 29 maart 2021 15:54:28 schreef rew:
[...]
Dit is een extreem-onhandige manier om het te proberen te doen. :-( Niet het juiste voorbeeld.

Die "calcsurface.h" moet je nu dus in je main includen, dan krijg je die variabelen gedeclareerd, maar dan kan je hem niet meer includen vanuit "calcsurface.c" omdat je dan de variabelen dubbel declareert.

c code:


extern uint32_t heigth;
extern uint32_t width;
uint32_t GetSurface(void);

en dan zonder de extern in calcsurface.c.

Heb je wel goed gelezen? Iets van het voorbeeld wat ik beschrijf? Waarom vaker includen van dezelfde header file fout gaat? Wat dat tot gevolg heeft en hoe je dat kunt oplossen (hint: include guards), bijvoorbeeld in het laatste voorbeeld?

Wat ik omschrijf is een goede en veelgebruikte manier om het probleem van dubbel includen/declareren op te lossen. Echter door jouw reactie wordt er weer een hoop ruis in deze discussie gegooid waardoor het er voor de topicstarter niet duidelijker op wordt.

Heb je wel goed gelezen? Iets van het voorbeeld wat ik beschrijf?

??? Dat werkt niet zoals je beschrijft. Die #if's helpen alleen tegen meer keer includen in dezelfde compile slag.

Maar hier moet je includen in main.c en nog een keer in calcsurface.c
En dan helpen de include guards niet. En dan snapt de linker het niet meer.

Ik weet wel dat het tegen de bijbel van K&R ingaat, maar ik declareer de meeste variabelen globaal. In een #include, meestal, die voor niks anders dient. Laat het dan tegen de canon zijn, of zelfs "vloeken in de kerk", voor mij werkt het, en daar komt het maar op aan. Een lustellertje of zo zal ik al wel eens locaal declareren, niet meer dan dat, meestal.

Het stoort me nogal dat er gezwaaid wordt met "zo moet het", ik zeg liever "zo kan het". De meeste programmeertalen laten nogal wat ruimte voor persoonlijke stijl en dat vind ik ook prima. Zeker als men alleen aan een project werkt kan men eigenlijk alle kanten uit; naarmate de code complexer wordt zullen er meer betrokkenen zijn en dan wordt eenheid van stijl belangrijk. Maar dan spreken we over projecten van vele duizenden regels code, dat is voor mij niet relevant. En zo is het voor de grote meerderheid alhier, lijkt me.

[Bericht gewijzigd door big_fat_mama op 29 maart 2021 18:47:11 (24%)]

hoe beter de vraag geschreven, zoveel te meer kans op goed antwoord

Dit is geen kwestie van kiezen, het werkt gewoon niet.

Als je de header van Jowi include in main.c en ook in Calc..c
dan heb je de variabelen 2 keer aangemaakt. De linker kan het dan nooit meer aan elkaar knopen en die klaagt dan over dubbele definities. Daar krijg je dus nooit een executable uit.

Als je alle variabelen globaal declareert hoeven ze allemaal maar 1 keer te worden gedeclareerd; daarmee wordt alle verdere gez__k overbodig.

Voorbeeldje uit mijn moestuin - en jawel, dit compileert met de glimlach! - :

c code:

 {{main}}
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <gtk/gtk.h>
#include <cairo.h>
#include <pango/pangocairo.h>

#include "A10_vars_gps"
#include "A20_vars_map"
#include "A90_config"
#include "B01_math"

c code:

 {{A10_vars_gps}}
// variables for GPS data
// all of these are global!

FILE *gpslastpos;
FILE *lastenv;
FILE *navdat;
FILE *route;

int gps_date,gps_time;
int gps_lat,gps_lon;
int gps_hdg,gps_spd,gps_alt;
int gps_nsat;

int env_time;
float env_tcpu,env_tamb,env_vbat;
hoe beter de vraag geschreven, zoveel te meer kans op goed antwoord