wat gebeurt er in dit stukje code #define als Macro?

Hallo mensen.

In navolging op een eerder vraagstuk aangaande de code C, heb ik nu een stukje code waarbij ik niet precies begrijp wat een specifiek stuk code doet.

Het gaat om Define, welke als Macro wordt gebruikt.
Nu weet ik dat Define een vertaalslag is van een woord naar een waarde of een stukje code, maar dat hier ook een functie of macro(?) in verwerkt kan worden, wist ik niet.

En nu ik ook nog eens de code van deze functie/macro niet begrijp, kom ik er niet meer uit.
Kan iemand me uitleggen wat hier gebeurt?

MvG
Fantomaz

Ik moet hier weer vaker komen... Wat kun je zo'n forum als deze gaan missen. :-)
Schimanski

Golden Member

de regel k = A > B ? A : B is een zg. compound statement. Het is een verkorte schrijfwijze voor


if (A > B)
   k = A;
else
   k = B;

Er zijn in C veel verkorte schrijfwijzen, voor een C kenner geen probleem, maar het maakt code niet altijd leesbaarder. Er zijn zelfs wedstrijden in meest "onleesbare" code. De z.g. obfuscated C contest.

Een #define wordt voor de compilatie door de pre-processor afgehandeld. Deze doet een zoek-en-vervang in de source-code, na de pre-processor ziet de code er als volgt uit.


void main()
{
   ...
   k = ((i++) > (j++) ? (i++) : (j++));
   ...
}

Het gebruik van de extra haakjes in de macro is om deze ook bij complexe parameters correct te laten werken. Maar desondanks kan het gebruik van macro's ook fouten genereren die moeilijk te vinden zijn als je niet weet hoe compiler en pre-processor werken. Het probleem wat hier optreed is dat i en j twee maal verhoogd worden terwijl je verwacht dat dit maar eenmaal gebeurd.

-edit-
Ik gebruik weinig #define's, zeker niet als het een verkapte variable constant is. Je kunt dan beter een echte constant gebruiken, dat heeft ook voordeel dat er een type aan hangt en de compiler beter errorchecking kan doen.
-edit-

What, me worry? // Radiozendamateur - PA2HGJ // Stuff is the junk you keep -- Junk is the stuff you throw away // Tinkeo Ergo Sum
buckfast_beekeeper

Golden Member

De functie MAXI met als 2 parameters A en B. Als je de functie/macro gebruikt moet je dus 2 waardes meegeven.

Het 2de deel is eigenlijk al beantwoord in je vorige vraag. Als A > B wordt A terug gegeven als grootste anders B.

Van Lambiek wordt goede geuze gemaakt.

Kan iemand me uitleggen wat hier gebeurt?

Ja, maar ik denk dat niemand het beter kan dan Kernighan & Ritchie themselves....zie foto hieronder.

Zoals ik in de vorige thread al zei, het 'C handboek' aanschaffen lijkt me zeer verstandig als u C wilt leren. Sowieso lijkt het erop dat de schrijver van uw voorbeeldprogramma's deze rechtstreeks uit dit boek heeft gehaald.... 4.11 (De preprocessor van C), paragraaf 4.11.2 'Macrosubstitutie', p.121:

set SCE to AUX.
  k = ((i++) > (j++) ? (i++) : (j++));

En nu zie je ook meteen wat het probleem is met deze #defines.
Zowel i als j worden ge-incrementeerd, en de grootste van de twee zelfs twee keer.

En misschien is dat ook de bedoeling maar dat lijkt me sterk.

Dus bijv
i = 10
j = 5
Dan wordt k = 11, i = 12, en j= 6

Correctie..
-- Dat lijkt hier wel de bedoeling, denk ik, want het stukje code wil juist aantonen hoe die macro fout kan gaan.

[Bericht gewijzigd door deKees op vrijdag 11 oktober 2024 22:13:48 (16%)

Op vrijdag 11 oktober 2024 21:44:55 schreef Schimanski:
de regel k = A > B ? A : B is een zg. compound statement.

Dit is de ternary operator. Een compound statement is een gecombineerde expressie en assignment, zoals x *= 2;

Zo'n macro is soms handig, en maakt code juist leesbaarder, als de naam duidelijk is en je het verder niet te complex maakt.

Dit in mijn beleving prima leesbaar:

x = MIN( input, 10 );

Je moet wel bedenken dat een functie die je binnen de macro aanroept, 2 keer wordt uitgevoerd.

Dit zal niet het verwachte effect hebben:

x = MAX( rand(), 10 );

In plaats van een random waarde beperkt tot 10, zul je nu random een andere random waarde krijgen (vaak groter dan 10), of de waarde 10.

Als je het zelf zou uitschrijven, zou je het resultaat van rand() ook eerst in een variabele stoppen, maar het zou dan direct duidelijk zijn waarom. Juist om dit soort dingen te voorkomen is het gebruikelijk om macro's helemaal met hoofdletters te schrijven, om ze te onderscheiden van reguliere functies. Ook zouden macro's hele simpele dingen moeten doen, als het groter of complexer wordt is het meestal beter om het gewoon in een functie te stoppen.

In plaats van een macro is een inline functie vaak een goed idee; dat werkt in principe zoals een functie, maar in plaats van een functie aanroep, met de overhead daarvan, wordt de inhoud van de functie op die plaats gekopieerd door de compiler. Dit is dus niet handig voor een functie die op veel plaatsen wordt gebruikt, maar vooral voor functies die op één of een paar plaatsen heel vaak wordt aangeroepen.

Dat zou dan dit worden

inline int Max( int a, int b )
{
  if( a > b ) return a;
  else return b;
}
Een manager is iemand die denkt dat negen vrouwen in één maand een kind kunnen maken

Ik heb een hekel aan macro's.
De C-code is niet wat het lijkt.
In het gegeven voorbeeld lijkt MAXI een functie, maar dat is het helemaal niet. Macro's zullen wel hun nut hebben, maar 9 van de 10 macro's kan je vervangen door een functie.

Het lijkt helemaal geen functie, want de naam is helemaal geschreven in hoofdletters, dat lijkt me moeilijk te missen.

Ik ben wel het je eens dat een inline functie in het overgrote deel van de gevallen beter is. Eigenlijk kan ik zo direct geen voorbeeld bedenken waarbij een macro de voorkeur zou hebben.

Een manager is iemand die denkt dat negen vrouwen in één maand een kind kunnen maken

Ik heb nooit geweten dat in C een functienaam niet uit uitsluitend hoofdletters mag bestaan.
Je bent nooit te oud om iets te leren. Dat is het mooie van dit forum.

Mag best, maar is wel ongebruikelijk. :)

Precies, dat mag best, maar de gangbare conventie is om defines en macro's in hoofdletters te schrijven, terwijl functienamen meestal met hoofdletters voor elk woord wordt geschreven (maar soms het eerst woord niet, zoals "analogRead()" in de arduino wereld), en/of met underscores.

Een manager is iemand die denkt dat negen vrouwen in één maand een kind kunnen maken
benleentje

Golden Member

Nu weet ik dat Define een vertaalslag is van een woord naar een waarde of een stukje code,

#define is meer een search en replace functie.

Dus overal waar MAXI(waarde1, waarde2) staat word deze vervangen door de formule ((A) > (B)) ? (A) : (B))

Een meer normaal gebruik van #define is bv

#define ADC_CS_Pin 9

Dan word overal waar je ADC_CS_Pin hebt staan simpel vervangen door een 9

Op zaterdag 12 oktober 2024 22:16:16 schreef benleentje:
Dus overal waar MAXI(waarde1, waarde2) staat word deze vervangen door de formule ((A) > (B)) ? (A) : (B))

Iets nauwkeuriger:
Dus overal waar MAXI(waarde1, waarde2) staat word deze vervangen door de tekst
((waarde1) > (waarde2)) ? (waarde1) : (waarde2))

benleentje

Golden Member

word deze vervangen door de tekst

Toch wel een belangrijk detail. Het is enkel exact het vervangen van het ene stuk tekst door het andere.

Mijn dank voor de correctie. _/-\o_

Ik moet zeggen dat ik tot vandaag ook niet wist wat #define precies inhield

Op zaterdag 12 oktober 2024 22:48:12 schreef ohm pi:
[...]Iets nauwkeuriger:
Dus overal waar MAXI(waarde1, waarde2) staat word deze vervangen door de tekst
((waarde1) > (waarde2)) ? (waarde1) : (waarde2))

Ik denk dat het nog beter moet....:
Overal waar MAXI(string1,string2) staat wordt
((string1) > (string2))?(string1):(string2 )
Neergezet. Als je waarde schrijft kan de lezer denken dat de /waarde/ van i++ zou worden ingevuld. Dat is niet zo: de string i++ wordt ingevuld!

Op zaterdag 12 oktober 2024 08:15:37 schreef SparkyGSX:
Eigenlijk kan ik zo direct geen voorbeeld bedenken waarbij een macro de voorkeur zou hebben.

Ik heb ooit een memory test ding geschreven. Die dingen heten dan MATS+ e.d. De verschillende varianten moesten er allemaal in. Ook verschillende definities van "van beneden naar boven" (zoals pseudorandom). En het moest snel. In die tijd was er nog single cycle RAM te koop. 30MHz cpu en 30ns sRAM... Om niet 50kB aan saaie code te moeten schrijven heb ik iets met anderhalve kB aan preprocessor spul geschreven. In de boeken staat dan dat MATS+ begint met iets van W0<pijlomhoog> , en dat stond dan in de code als MEMTEST("mats+", W0UP R0DOWN ...) zodat niet programmeurs kon zien dat mats+ volgens het boek was geimplementeerd. Dit kon in die tijd niet anders dan met makros waar dan uiteindelijk meerdere functies per macroaanroep uitkwam...

Extreem geval.

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

Op vrijdag 11 oktober 2024 21:56:23 schreef nonius:
Zoals ik in de vorige thread al zei, het 'C handboek' aanschaffen lijkt me zeer verstandig als u C wilt leren.

Het is een deel van mijn studie waarbij ik niet altijd kan terugvallen op mijn leraren. Avondstudie, je kent het wel...
Dus raadpleeg ik Youtube of meer interactief, start ik een topic op circuitsonline.
Het gaat me te ver om helemaal vast te bijten in C. Misschien in de toekomst. ;-)

Op zaterdag 12 oktober 2024 22:16:16 schreef benleentje:
[...]
#define is meer een search en replace functie.
Een meer normaal gebruik van #define is bv

#define ADC_CS_Pin 9

Dan word overal waar je ADC_CS_Pin hebt staan simpel vervangen door een 9

Zo kende ik het ook, BL. Maar dit nieuwe en dan met name de A>B ? A : B was nieuw voor me.

Verder mijn dank voor jullie uitgebreide input cq reacties!!

Ik moet hier weer vaker komen... Wat kun je zo'n forum als deze gaan missen. :-)
benleentje

Golden Member

Het gaat me te ver om helemaal vast te bijten in C. Misschien in de toekomst.

Ik heb 3 jaar gelden een online cursus C++ gevolgd. Na ongeveer 3/4 van de cursus ben ik gestopt. Ik had alles wel geleerd wat je voor het programmeren voor een Arduino nodig hebt, maar de rest ging dan vooral over zaken die je nodig hebt als je heel ingewikkelde software wilt schrijven. Misschien komt dat ooit maar voor nu ben ik blij met die 3/4 cursus.

Moraal van het verhaal het is genoeg als je slaagt voor je examen voor je studie en je hoeft zeker niet alles te weten. De rest komt eventueel nog wel een keer als je daaraan toe bent.


#define MAXI(A, B) ((A) > (B) ? (A) : (B))

k = MAXI(i++, j++);

Ja, dat is een typish voorbeeld van hoe macros niet gebruikt moeten worden.
De macro wordt op de plaats van aanroepen ingevuld, dus je krijgt:


k = ((i++) > (j++) ? (i++) : (j++));

Zie je hoe het hier fout gaat? Je intentie was om i en j allebei eenmaal op te hogen, maar in dit geval zal de hoogste tweemaal opgehoogd worden!
Je zou dus kunne kiezen om de macro door een functie te vervangen, of je zou de ++ in een losse regel te doen.


#define MAXI(A, B) ((A) > (B) ? (A) : (B))

i++;
j++;
k = MAXI(i, j);

of


int maxi(int a, int b){
  return a > b ? a : b;
}

k = maxi(i, j);
Meep! Meep!

Op maandag 14 oktober 2024 10:28:15 schreef roadrunner84:


int maxi(int a, int b){
  return a > b ? a : b;
}

k = maxi(i, j);

static inline int maxi(int a, int b) {
  return a > b ? a : b;
}

Nu is het dus ook nog zo dat je de functie-aanroep-en-return weg-optimaliseert net als bij de macro.

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

Klopt! Je optimaliseert wel de functie call en return weg (wat modernere compilers vaak ook zelf al doen!) maar niet het perongeluk dubbel uitvoeren van de ++ voert niet perongeluk de ++ dubbel uit.

[Bericht gewijzigd door roadrunner84 op dinsdag 15 oktober 2024 14:47:29 (15%)

Meep! Meep!

Op maandag 14 oktober 2024 17:20:13 schreef roadrunner84:
Klopt! Je optimaliseert wel de functie call en return weg (wat modernere compilers vaak ook zelf al doen!) maar niet het perongeluk dubbel uitvoeren van de ++.

Tuurlijk wel. Ook de dubbele ++ verdwijnt. Het blijft een echte functie.

Oke, ik heb het een beetje knullig verwoord. Wat ik bedoelde is dat de static inline functie niet de ++ dubbel uitvoert, zoals dat bij de macro wel het geval was. Dus hij gedraagt zich als een echte functie, zelfs als de functiecall en het return statement weggeoptimaliseerd worden.

Meep! Meep!