Ik probeer een project te porten van een eng windows-IDE omgeving ding naar gewone gcc.... Daarin zit code als:


typedef enum
{
  LS_DISABLED = 0,                            
  LS_PWM_TIMER = !0,                     
  ES_GPIO = 2        
}

[...]
//configure mode: 
#define LOW_SIDE_SIGNALS_ENABLING        ES_GPIO

[...]
// generate code depening on config: 
#if ((LOW_SIDE_SIGNALS_ENABLING == ENABLE)||(LOW_SIDE_SIGNALS_ENABLING == LS_PWM_TIMER))

Naar mijn idee is "ES_GPIO" dan in de COMPILER als de constante 2 gedefinieerd, maar de C preprocessor, die dan later die #if (LOW_SIDE_SIGNALS_ENABLING) moet evalueren die weet dan van niets.

Dit kan toch niet mogen volgens de standaard? Hoe kan dit dan ooit gecompileerd hebben?

[Bericht gewijzigd door rew op (88%)]

Als ik het goed begrijp is een define niets anders dan een search/replace in de preprocessor.

Omdat die niets met de enum doet, ziet je compiler geen "2", maar "ES_GPIO"

Volgens mij kan het goed gaan. De compiler ziet ES_GPIO als een constante met waarde 2, in de juiste context. Misschien dat het daar misgaaat.

Nee, op het moment dat er #if (...) staat waar in de evaluatie die ES_GPIO staat, is de PREPROCESSOR aan de beurt om iets te doen.

De preprocessor ziet:


typedef enum
{
  LS_DISABLED = 0,                            
  LS_PWM_TIMER = !0,                     
  ES_GPIO = 2        
} ....

en geeft dat gewoon letterlijk aan de compiler.
Maar bij:

#if (.... == ES_GPIO)   

gaat het fout: de preprocessor heeft geen weet van die ES_GPIO: die is nooit in een #define langsgekomen.

P.S. Met veel workarounds lijkt het nu te compileren. Te veel van die hacks naar mijn idee. De kans dat 1 van de tientallen dingen die ik heb moeten veranderen zodanig fout is dat het helemaal niet werkt is best groot. We zullen zien.

Ik kan het mis hebben, maar...
Is het niet zo dat je met typedef een type definieert? In dit geval zonder naam. Nou, dat is mooi. Er is een type zonder naam. En nu?

Als je die typedef weg laat (en dus alleen 'enum' laat staan) zou het zomaar wel eens kunnen werken. Iets als

typedef enum
{
  NU=0,
  EN=1,
  TW=2,
  DR=3
} bla;

doet het waarschijnlijk wel voor variable 'bla'.

Waar het mij om gaat is dat na een ENUM definitie, de COMPILER (niet de preprocessor) weet dat jou naampjes NU EN TW en DR een waarde hebben. Dan kan je toch niet met een preprocessor directive

#define OPTIE TW

#if (OPTIE == 2) 
  printf ("Twee");
#endif

conditioneel bepaalde code gaan genereren? Bij de definitie van de enum heeft de preprocessor gewoon wat C-code doorgegeven naar de compiler zonder er zelf naar te kijken. Dus tegen de tijd dat de preprocessor bij de #if komt, komt daar te staan #if (TW == 2), waarbij TW simpelweg niet als preprocessor-symbool gedefinieerd is.

Compilerbouw is ff te lang terug. Maar... is die enum wel iets wat je compiler oplost?? Ik *denk* het wel, maar een heel groot deel zou ook prima door je pre-processor opgelost kunnen worden.

Als je het nou eens als een pre-processor issue beschouwt, klopt het dan wel? (je zegt zelf... het komt uit een M$-ding. Die hebben doorgaans lak aan wat handig is en wat de industrie doet, maar verzinnen niet zelden zelf maar wat).

Het is een zooi "niet-van-mij" code. die ik gecompileerd wil krijgen. Origineel compileert het met een IDE onder windows.

Nu kan ik een trial-windows downloaded of zo, een trial-IDE downloaden en dan tot 32k code compileren met dat ding. Helaas het mini-demo-project wat ik hier probeer te compileren is 48k.

Anyway, als ik vanalles in de systeemfiles ga zitten veranderen, dan kan ik dat weer overnieuw doen als er een nieuwe versie van die systeemfiles komt.

Een deel van het probleem van de bestaande code is dat de mensen die het gemaakt hebben te makkelijk: "ok, dan verander ik dat even" doen. Dus allerlei "library" files zijn gecopieerd naar allerlei subprojecten en dan steeds aangepast voor het subproject.

Als ik dan in zo'n file een aanpassing moet doen, dan moet dat dus weer overnieuw als ik een ander voorbeeld-project wil compileren. Niet handig.

Ik kan het wel werkend krijgen door de code aan te passen, maar dan moet ik dus in de systeem-files klooten, hetgeen op de lange duur een vervelende strategie is. Ik heb dat nu zo veel mogelijk proberen te vermijden door defines en compiler opties in de Makefile te zetten.

Maar kennelijk zijn er dus diverse IDEs onder windows die toestaan dat je ENUMs in #if directives gebruikt. Geen aparte preprocessor-stage meer. Hoe dat kan werken op de gcc-gebaseerde compilers onder windows snap ik niet.

Het bovenstaande voorbeeld is een knap staaltje van bagger code.
Waarom:
1- In de C89 definitie is het verboden om aan enums waardes toe te kennen, behalve de eerste entry. (In C99 mag het weer wel)

2- Dit slaat echt alles in die enum: LS_PWM_TIMER = !0,
Ik heb veel zooi gezien de laatste 30 jaar maar dit is echt totaal van de ratten besnuffelt.

3- Enums worden compiletime gebruikt. Dus zou dat niet in een preprocessor mogen werken. Werkt het wel heb je puur geluk. Niet portable.

P.S. Een enum evalueert in de C-compiler per definitie naar een 'int'. Van type checking is geen sprake, ook al maak je er een typedef van. Is helaas een manco van C.

@rew: Je zegt dat je soms nieuw files krijgt, dan neem ik aan dat je weet van wie je die troep gekregen hebt? => Onder hun gat schoppen zou ik zeggen.

Dit heeft helemaal niks met die enum te maken, de preprocessor heeft helemaal geen weet van die enum. Overigens heeft dit ook helemaal niks met geluk te maken :-) Volgens de C taaldefinitie krijgt een ongedefinieerd pre-processor symbool automatisch de waarde 0 toegekend. In jouw voorbeeld wordt symbool LOW_SIDE_SIGNALS_ENABLING gelijk aan ES_GPIO, ES_GPIO heeft voor de pre-processor geen waarde, de enum wordt niet door de preprocessor geavalueerd en ES_GPIO heeft dus de waarde 0. Als ENABLE of LS_PWM_TIMER ook geen waarde krijgen evalueert de pre-processor conditie naar ((0 == 0) || (0 == 0)), wat waar is en anders afhankelijk van de waarde van ENABLE of LS_PWM_TIMER mogelijk naar false.


//configure mode: 
#define LOW_SIDE_SIGNALS_ENABLING        ES_GPIO
 
[...]
// generate code depening on config: 
#if ((LOW_SIDE_SIGNALS_ENABLING == ENABLE)||(LOW_SIDE_SIGNALS_ENABLING == LS_PWM_TIMER))

Wel brakke code, daar ben ik het mee eens :-)

In C (en C++) is het dus zo dat wanneer pre-processor symbolen niet gedefinieerd zijn een pre-processor vergelijking daartussen altijd true oplevert. Hier moet je mee oppassen en afvangen met #ifdef directives.

Op 20 december 2015 14:26:34 schreef Hewlett:
Volgens de C taaldefinitie krijgt een ongedefinieerd pre-processor symbool automatisch de waarde 0 toegekend.

Dat is niet zo, ongedefineerd is ongedefinieerd en zeker niet nul.

Probeer maar een een #if DOEMAARIETS daar krijg je keihard een foutmelding op!

Om op een undefinded preprocessor waarde te testen doe je:
#ifdef DOEMAARIETS
<doe iets als deze defined is>
#endif

(Het alternatief is : #if defined ...)

Zucht :-) Dat is wel zo.


#if (DOEMAARIETS == BETERLEZEN)
Dit gaat naar de compiler
#endif

#if conditie evalueert naar true wanneer beide pre-processor symbolen geen definitie hebben, ze zijn beide 0. En hier gaat Rev zijn probleem over.

Toch maar eens getest op een gcc preprocessor, en waarempel ik krijg geen foutmelding, Hewlett je hebt gelijk!

Blijkbaar is dit dus toch gcc-preprocessor specifiek want bij veel compilers krijg je normaal een foutmelding.
Ik heb even geen andere compiler bij de hand om het daar op te proberen.

Eens de GCC-preprocessor opgezocht wat die er van zegt:
https://gcc.gnu.org/onlinedocs/cpp/If.html

Verder is deze optie van belang:
-Wundef
Warn if an undefined identifier is evaluated in an #if directive.

Dus als je die eens aanzet komt alle "shit" er uit wat niet deugd.

Misschien dat deze optie er later ingebouwd is om het gedrag van #if weer terug naar het origineel te brengen (dus wel errors).

Wat ook zo is, de preprocessor helaas niet gestandaardiseerd is en iedere compiler die iets anders kan implementeren. Overdaad aan macro's is altijd een bron van ellende om code te porten.

Verder is deze optie van belang:
-Wundef
Warn if an undefined identifier is evaluated in an #if directive.

Krijg je die dan niet cadeau bij een -Wall?

Dat dacht ik eerst ook, omdat ik die namelijk altijd aan heb staan. Maar kan het niet vinden in de documentatie. Waarschijnlijk niet.

-edit- net geprobeerd: Nee dus.

[Bericht gewijzigd door henri62 op (12%)]

Ik kreeg warnings op m'n compile voor die dingen. Ik heb inderdaad -Wundef in m'n opties staan. :-) Die heb ik "blind" gecopieerd uit een ander project, dus: mazzel dat het aanstaat. (anders had ie simpelweg stilletjes sommige foute code-paths gecompileerd).

Huidige status van het project:

  struct iets {
     void (*initfunction)(struct iets *);
     ...
  }

wordt geinitializeerd met:

    iets->initfunction = My_Init_function; 

dit alles in een library waar ik de source niet van heb.

en dat is kennelijk gecompileerd naar:

   0x080064a6 <+46>:    addw    r0, pc, #112    ; 0x70
   0x080064aa <+50>:    str     r0, [r4, #4]

Die initfunctie is 0x70 voorbij waar die initializatie plaatsvindt.

De optelling pc+0x70 komt op 0x080064a8+0x70 = 0x08006518 het begin van die initializatiefunctie. Maar als ie aangeroepen wordt, moet je jumpen naar 0x08006519 en niet 0x08006518 want de boel draait in thumb mode. Zucht.

Op 20 december 2015 19:02:08 schreef rew:
dit alles in een library waar ik de source niet van heb.
...

Ah een ARM CPU. Maar dan moet die library toch ook in thumb mode gecompileerd worden, anders werkt het toch voor geen meter?

OK. De hele CPU doet /alleen/ maar thumb. De library IS dus in thumb mode gecompileerd. Dat kan niet anders.

Maar... Ik zou verwachten dat als ik de PC uitlees, het onderste bitje bij zo'n "alleen thumb" CPU altijd HOOG is. Handig voor call-return achtige dingen als niet-alleen-thumb processoren een return-naar-thumb willen doen vanuit ARM code.

Maar zoals het staat is er kennelijk een linker-directive die "adres-van-target-current-PC" invult in een instructie, waarbij IAR dus een 1 er bij optelt.

Dit bizarre gedrag is onderdeel van de C taal definitie en elke naar de C standaard conformerende compiler gedraagt zich zo (althans dat is de bedoeling). De standaard schijft: "After all replacements due to macro expansion and the defined unary operator have been performed, all remaining identifiers are replaced with the pre-processor number 0". Ja, je kunt hele gekke dingen krijgen, o.a. in het onderstaande (wel erg foute, dan moet je als ontwikkelaar direct een schop krijgen) code fragment waarbij true en false geen preprocessor definitie hebben:


#if (false == 0)
dit is waar
#endif

#if (true == 0)
dit is ook waar
#endif

#if (true == false)
ja, ook dit is waar
#endif

gcc -E geeft dan:


dit is waar
dit is ook waar
ja, ook dit is waar

Maarrrr, voor de C++ taaldefinitie is er voor true en false een uitzondering gemaakt, deze twee macro's zijn in C++ per definitie ongelijk ook al zijn ze niet gedefinieerd, g++ -E geeft dan in dit geval alleen:


dit is waar

Te gek voor woorden :-)

Voor zover ik weet zijn true en false in C++ keywords en geen macro's dus dat is appels en peren vergelijken (en die zijn niet hetzelfde).

[Bericht gewijzigd door henri62 op (12%)]

Ja, maar daar heeft de preprocessor geen weet van, dat is ook de reden van de uitzondering.

Daar zeg je ook wat, je zou een fout moeten krijgen omdat het reserved words zijn. Dat is ook de reden dat je een harde foutmelding ZOU MOETEN krijgen op undefined MACROS.

Ik moet nog wel ergens een C99 spec hebben liggen en zou die er op na moeten slaan wat nu de officiele definitie is.

Verder is de C preprocessor in GCC helemaal verpest die doet al lang niet meer wat je van een preprocessor zou moeten verwachten: Alleen preprocessen dus. De grens is vervaagd.

Haal maar eens een stuk C code door GCC -E en pak de output en probeer die opnieuw te compileren. Ik heb regelmatig gezien het het niet meer compileert. (Was ivm instrumentatie van code dat ik dat eens gezien heb)

Henri, met -Wundef krijg je warnings op de undefined macros. Ikzelf behandel warnings gewoon ZELF als errors, maar als je wilt kan je -Werror aanzetten, dan maakt de compiler er zelf errors van.

(Ik vind dat je dat NIET moet doen in code/Makefiles die je publiceert. Een ander kan een andere compiler hebben die net op wat anders warnings genereert zal dan ineens niet meer compileren.)

Op 22 december 2015 00:10:52 schreef rew:
Henri, met -Wundef krijg je warnings op de undefined macros. Ikzelf behandel warnings gewoon ZELF als errors, maar als je wilt kan je -Werror aanzetten, dan maakt de compiler er zelf errors van.

Weet ik, zet ik altijd aan als het kan, en dat is in bijna alle gevallen.

(Ik vind dat je dat NIET moet doen in code/Makefiles die je publiceert. Een ander kan een andere compiler hebben die net op wat anders warnings genereert zal dan ineens niet meer compileren.)

Dan is de code niet goed genoeg portable gemaakt. Vrijwel altijd is een warning wel weg te werken. Soms verdient het niet de schoonheidsprijs.

Verder zitten er in systeem header files soms ook bugs. En dan is het een geklooi om het goed te krijgen of zelfs onmogelijk. Dan moet je op basis van het OS andere typedefs gaan maken voor returnwaarden van sommige calls omdat "het ene OS" er een zooitje van maakt.

Op 22 december 2015 00:18:17 schreef henri62:
[...] Dan is de code niet goed genoeg portable gemaakt. Vrijwel altijd is een warning wel weg te werken. Soms verdient het niet de schoonheidsprijs.

Stel jij schrijft:

for (i=0;i<25;i++)
  // iets voor 25x

Is dat goede code? Oh? Je wilt de definitie van i nog zien? OK... omdat i toch niet negatief kan worden:

unsigned int i;

Is het een stukje goede code?

Volgens de GCC jongens, is het met de laatste definitie NIET meer goed: "comparison between signed and unsigned object".

Dat soort warnings kan je niet zien aankomen. En als je me gaat vertellen dat dit "les 2" programma uit de C-cursus niet portable is, dan kan je de boom in (les 1 is hello world).

Daar komt nog bij dat niet alle code 'in den beginne' gemaakt is om portable te zijn. Moet je het dan zo gebruiken? Tsja... Leg dat een manager maar eens uit... 'Maar we hebben al een lib die dat doet en dat is ook C, dus die hoef je alleen maar opnieuw te compileren'. Afijn, wat hacks omdat het anders niet bouwt (en wellicht ook niet doet wat het doen moet) en een jaar later komt de volgende manager die heel graag alles 'open source' maakt, en dit is wel zo'n handige lib. Het is portable, want het draait inmiddels op 2 platformen.

En daar sta je dan met een stuk code wat op zich niet slecht hoeft te zijn, maar duidelijk niet geschreven is met het gebruik zoals het nu plaats vindt in gedachten.

Bovenstaand scenario heb ik meerdere keren op iets kleinere schaal zien gebeuren binnen een multinational: de ene vestiging klust iets. Dat is voor hun product prima geschikt. De volgende heeft iets soortgelijks nodig, dus dat moet bij de buren gehaald worden. Maar die hebben het nooit configureerbaar gemaakt, want 'generic' was niet wat ze in hun achterhoofd hadden toen ze het schreven. Afdeling 2 klust er een berg scripts bij die het configureerbaar maken. Het baasje van afdeling 3 krijgt daar lucht van en denkt 'hey, dit hoeven we dan niet meer te maken'. Helaas, het is net niet configureerbaar genoeg, dus afdeling 3 klust er nog ene paar scripts bij. Vervolgens verandert afdeling 1 wat - ongedocumenteerd wat uiteraard, want het is niet 'generic'. Dat gaat naar afdeling 1 en 3 en beiden komen erachter dat hun scripts niet meer helemaal lekker werken. Dus die klussen onafhankelijk van elkaar weer wat om het weer werkbaar te krijgen. Houd dat een paar jaar vol en je kunt je wel iets voorstellen bij de puinhoop die dan ontstaat. Maar ja, 'iedereen' gebruikt het binnen het bedrijf, dus is het generic en goed (waarbij de oplossing natuurlijk was geweest dat afdeling 2 iets nieuws had verzonnen met de goede dingen van wat afdeling 2 gemaakt heeft, maar nu wel goed configureerbaar en niet met 'search & replace' scripts en dat ook als doel stelt). Is het portable? Tuurlijk, het is allemaal ksh met een klein beetje tcl-tk. Dat draait dus (in die tijd) op Solaris, HP-UX en Linux. Toch?