Forum: Compiler & IDEs C Preprocessor, Gluebefehl ##


von sous (Gast)


Lesenswert?

Frage: Warum macht der Präprozessor nicht das was ich will, bzw. kann 
ich ihn irgendwie dazu bringen, es doch zu tun?

Mein Beispiel-Quelltext (pre.c):
//---------------------------------------
#define PORTCHAR B

#define PORTDIR  DDR##PORTCHAR
#define OUTPORT  PORT##PORTCHAR
#define INPORT  PIN##PORTCHAR



int main()
{
  PORTDIR = 0xff;
  OUTPORT = 0x01;

  while(1);
  return 0;
}
//------------------------------------------

Wir durch folgenden Aufruf
avr-gcc pre.c -E >> pre_out.c

umgewandelt in das hier (pre_out.c):
//------------------------------------------

# 1 "prep.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "prep.c"
# 9 "prep.c"
int main()
{
 DDRPORTCHAR = 0xff;
 PORTPORTCHAR = 0x01;

 while(1);
 return 0;
}
//------------------------------------------

Was ich gerne gehabt hätte:
Dass da statt 'DDRPORTCHAR = 0xff;'
'DDRB = 0xff;' steht usw.

Mal abgesehen davon, dass mir im Nachhinein noch ein Hinderniss 
aufgefallen ist, selbst wenn er jetzt 'DDRB' aus 'DDR##PORTCHAR' macht, 
müsste er dieses dann auch noch durch die Definitionen des 
avr/io.h-Files austauschen (habe ich hier erstmal mit Absicht nicht 
includiert, ich wollte zunächst die reine Glue-Operation erforschen).

Kann jemand etwas dazu sagen oder weiß, wie man sowas richtig macht?

von Stefan E. (sternst)


Lesenswert?

1
#define CONCATx(a,b) a##b
2
#define CONCAT(a,b) CONCATx(a,b)
3
4
#define PORTCHAR B
5
6
#define PORTDIR  CONCAT(DDR,PORTCHAR)
7
#define OUTPORT  CONCAT(PORT,PORTCHAR)
8
#define INPORT   CONCAT(PIN,PORTCHAR)

von sous (Gast)


Lesenswert?

Vielen Dank für Eure Hilfe!

@ Johannes M.:
Das funktioniert leider nicht:

aus
//----------------------------------
#define PORTCHAR B

#define PORTDIR1(x) DDR##(x)
#define PORTDIR2(x) DDR##x

int main()
{
  PORTDIR1(PORTCHAR) = 0xff;
  PORTDIR2(PORTCHAR) = 0xff;

  PORTDIR1(B) = 0xff;
  PORTDIR2(B) = 0xff;
}
//----------------------------------

wird

//----------------------------------
int main()
{
 DDR(B) = 0xff;
 DDRPORTCHAR = 0xff;

 DDR(B) = 0xff;
 DDRB = 0xff;
}
//----------------------------------

von sous (Gast)


Lesenswert?

@Stefan Ernst:

Juhu, das funktioniert!
Ich bin zwar leider g'rad zu blöd, zu verstehen, warum (so und nicht 
anders) und werde noch eine Weile darüber nachdenken aber auf jeden Fall 
kann man es so machen!

Vielen Dank!  :)


aus
//-------------------------------------
#define CONCATx(a,b) a##b
#define CONCAT(a,b) CONCATx(a,b)

#define PORTCHAR B

#define PORTDIR  CONCAT(DDR,PORTCHAR)
#define OUTPORT  CONCAT(PORT,PORTCHAR)
#define INPORT   CONCAT(PIN,PORTCHAR)


int main()
{
  PORTDIR = 0xff;
  OUTPORT = 0x01;
}

//-------------------------------------

wird (wie gewünscht)
//-------------------------------------
int main()
{
 DDRB = 0xff;
 PORTB = 0x01;
}
//-------------------------------------

von Johannes M. (johnny-m)


Lesenswert?

sous wrote:
> @ Johannes M.:
> Das funktioniert leider nicht:
Das habe ich auch gemerkt.

von sous (Gast)


Lesenswert?

...und meine Befürchtung bzgl. des Ersetzens durch die io.h-Definitionen 
waren auch unbegründet:
Nach Einfügen von #include <avr/io.h> und Preprocessor-Aufruf mit der 
entsprechenden -mmcu-Spezifikation ergibt sich das hier:

int main()
{
 (*(volatile uint8_t *)((0x17) + 0x20)) = 0xff;
 (*(volatile uint8_t *)((0x18) + 0x20)) = 0x01;
}

und ich denke, das sieht gut aus!

Danke nochmals Euch beiden, die Ihr mir geantwortet habt!

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

sous wrote:

> Ich bin zwar leider g'rad zu blöd, zu verstehen, warum (so und nicht
> anders) und werde noch eine Weile darüber nachdenken aber auf jeden Fall
> kann man es so machen!

Das muss man glaub ich auch nicht verstehen, es genügt, wenn man
weiß, wie's geht. ;-)

von sous (Gast)


Lesenswert?

Danke für die aufmunternden Worte. :)
Ich bin aber so einer, der sich wohler fühlt, wenn er auch (wenigstens 
halbwegs) versteht, was er da tut....

von yalu (Gast)


Lesenswert?

> Das muss man glaub ich auch nicht verstehen, es genügt, wenn man weiß,
> wie's geht. ;-)

Man kann es temporär verstehen, wenn man sich den Präprozesorabschnitt
im C-Standard oder folgenden Abschnitt im GNU-CPP-Manual durchliest:

  http://gcc.gnu.org/onlinedocs/gcc-4.3.2/cpp/Argument-Prescan.html#Argument-Prescan

Das Verständnis verblasst aber typischerweise schon nach etwa einer
Stunde, da die Reihenfolge der Expansion von Makros und Makroargumenten
etwas undurchsichtig ist, vor allem im Zusammenhang mit dem #- und dem
##-Operator ;-)

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

yalu wrote:

> Das Verständnis verblasst aber typischerweise schon nach etwa einer
> Stunde, ...

Das deckt sich ziemlich mit meinen Erfahrungen beim Lesen dieser
Beschreibungen. ;-)  Seitdem versuche ich das gar nicht mehr zu
verstehen; mir genügt es vollauf zu wissen, dass es (irgendwie)
schon geht, dass dieses ,,es geht'' gesetzmäßig (bzw. standardgemäß)
ist und dass man es im Zweifelsfall durch Probieren rausfinden kann,
wie es genau anzustellen ist. :)

von (prx) A. K. (prx)


Lesenswert?

Mir ist es eigentlich auch lieber wenn ich den Dinge so verstehe, dass 
ich weiss wann was geht oder nicht geht und warum. Bei den Compilern 
selbst gelingt mir das auch einigermassen. Von ebendiesen Aspekten des 
C-Präprozessors kann ich das leider nicht behaupten.

Dazu kommt, dass der exakte Umgang des Präprozessors mit geschachtelten 
Makros und dem ## Operator im Standard nicht wirklich ins Gesicht 
springt. Ich ware nicht erstaunt, wenn eine kunstvoll zurecht 
geschnörkelte Version bei einem anderen Compiler nur zu einem hässlichen 
Ätsch! führt.

Bei AVRs verwende ich gerne Makros für Portzugriffe, so dass zentral
  #define ONEW_PORT A
  #define ONEW_PIN  2
konfiguriert wird, und im Code dann
  setPortBit(ONEW_PORT, ONEW_BIT);
  setDirBit(ONEW_PORT, ONEW_BIT);
für
  PORTA |= 1<<2;
  DDRA |= 1<<2;
steht.

Ein virtueller Preis gebührt dem, der mir daraus Microchips Konvention 
ähnlich
  portAbits.portAbit2 = 1
  trisAbits.trisAbit2 = 1
zimmern kann, bei dem das A links wie rechts mitten im Namen vorkommt. 
Müsste eigentlich irgendwie gehen, hab's aber nicht hinbekommen.

Drum also: viel Glück.

von Stefan E. (sternst)


Lesenswert?

Aus
1
setPortBit(ONEW_PORT, ONEW_BIT);
soll
1
portAbits.portAbit2 = 1;
werden?

1
#define CONCAT6x(a,b,c,d,e,f) a##b##c##d##e##f
2
#define CONCAT6(a,b,c,d,e,f) CONCAT6x(a,b,c,d,e,f)
3
4
#define setPortBit(a,b) (CONCAT6(port,a,bits.port,a,bit,b) = 1)
5
6
#define ONEW_PORT A
7
#define ONEW_BIT  2

von yalu (Gast)


Lesenswert?

A. K. schrieb:
> Ich ware nicht erstaunt, wenn eine kunstvoll zurecht geschnörkelte
> Version bei einem anderen Compiler nur zu einem hässlichen Ätsch!
> führt.

Das war in der Vergangenheit tatsächlich ein Problem. In einem
GNU-CPP-Manual aus dem vorigen Jahrtausend gab es eine Passage, wo sich
die GNU-Entwickler damit brüsteten, im Gegensatz zu den Wettbewerbern
den Standard verstanden zu haben. Sie führten ein Beispiel¹ an, was von
den C-Präprozessoren der meisten anderen Hersteller nicht korrekt
verarbeitet wurde.

Ich könnte mir gut vorstellen, dass diese Passage den anderen
Compilerentwicklern erstmals das "Obacht" zurief, das im Standard nicht
oder nicht laut genug zum Ausdruck kommt, und deswegen übersehen wurde.
Einige Jahre später hat's dann auch der MS-Compiler richtig gemacht.

———————————————
¹) Ich glaube, es hatte etwas mit dem Stringifizierungsmakro zu tun, das
   mit dem hier besprochenen verwandt ist.

von (prx) A. K. (prx)


Lesenswert?

Stefan Ernst wrote:

 #define setPortBit(a,b) (CONCAT6(port,a,bits.port,a,bit,b) = 1)

Danke. Ich war da wohl etwas zu kompliziert unterwegs.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Ich verwende auch solche Makros, um die Definition eines Ports an 
einer zentralen Stelle zu habn. So ist es in der Entwicklungsphase 
einfach umzustellen, vor allem auch zwischen normalen Ports wie PORTC_3 
und virtuellen Ports (SERPA), die z.B. über SPI an einen 
seriell-parallel-Wandler wie 74*595 ausgegeben werden.
1
enum
2
{
3
  PORT_LED = PORTC_2,
4
  
5
  PORT_SENSE = PORTC_3,
6
  PORT_NIXIE_POWER = PORTC_5,
7
  
8
  PORT_PWMA = PORTB_1,
9
  PORT_PWMB = PORTB_2,  // SS
10
  PORT_PWMC = SERPA2_0,
11
  PORT_PWMD = SERPA2_1,
12
  PORT_PWME = SERPA2_2,
13
...

Das wird dann so verwendet:
1
   MAKE_OUT (PORT_LED); // Port als Ausgang
2
   SET      (PORT_LED); // Port HIGH
3
   CLR      (PORT_LED); // Port LOW
4
   MAKE_IN (PORT_SENSE); // Port als Eingang...
5
   SET     (PORT_SENSE); // ...mit PullUp
6
   if (IS_SET (PORT_SENSE)) // SENSE == 1 ?
7
       TOGGLE (PORT_PWMA); // PWM.A = !PWM.A

Aber die Makros dazu will man sich nicht wirklich antun ;-)

von bronkopavel (Gast)


Lesenswert?

Nabend,

hab folgendes Problem:

#define ARGUMENT                   (D, 4)
#define CONCAT(a,b)                 a##b
#define SET_ARGUMENT(x, y)    CONCAT(PRE, x)  = y;

SET_ARGUMENT(ARGUMENT)

Irgendwie hab ich da nen Fehler, der Compiler sagt, das ich im nur 1 
Argument liefere nicht 2?!

Hat einer ne Idee?

von (prx) A. K. (prx)


Lesenswert?

Ich sehe da auch nur ein Argument. Ist eine Frage, in welcher 
Reihenfolge die Ersetzungen des Präprozessors stattfinden.

Und wenn man Pech hat, macht der Präprozessor das auch noch falch 
(Microchip C18).

von Karl H. (kbuchegg)


Lesenswert?

bronkopavel schrieb:
> Nabend,
>
> hab folgendes Problem:
>
> #define ARGUMENT                   (D, 4)
> #define CONCAT(a,b)                 a##b
> #define SET_ARGUMENT(x, y)    CONCAT(PRE, x)  = y;
>
> SET_ARGUMENT(ARGUMENT)
>

Du musst eine Zwischenstufe einlegen und die Klammern bei ARGUMENT 
entfernen
1
#define ARGUMENT                   D, 4
2
#define CONCAT(a,b)                 a##b
3
#define SET_ARG(x, y)    CONCAT(PRE, x)  = y;
4
#define SET_ARGUMENT(x)  SET_ARG(x)
5
6
SET_ARGUMENT(ARGUMENT)

Der Zwischenschritt ist notwendig um dem Präprozessor Gelegenheit zu 
geben x durch D, 4 zu ersetzen.

von bronkopavel (Gast)


Lesenswert?

Danke,

klappt perfekt, leider verstehe ich es nicht (die Zwischenstufe).
Ich dachte bei sowas wird einfach blind der "Text ersetzt"?

von Peter D. (peda)


Lesenswert?

bronkopavel schrieb:
> klappt perfekt, leider verstehe ich es nicht (die Zwischenstufe).

Meine Annahme:
Die Überprüfung der Argumentenzahl wird nur bei der ersten 
Macroexpansion gemacht.
D.h. egal wieviel Macros geschachtelt sind, es reicht nur beim ersten 
mal die richtige Argumentenzahl vorzugaukeln.


Peter

von Karl H. (kbuchegg)


Lesenswert?

bronkopavel schrieb:
> Danke,
>
> klappt perfekt, leider verstehe ich es nicht (die Zwischenstufe).
> Ich dachte bei sowas wird einfach blind der "Text ersetzt"?

Schon.
Die Frage ist, wie A.K. schon angedeutet hat, welche Ersetzung zuerst 
gemacht wird. Die Regeln dafür sind etwas kompliziert und 
undurchsichtig. Würde mich wundern, wenn es auf diesem Planeten mehr als 
eine Handvoll Leute gibt, die sie komplett verstanden haben :-) Ich 
gehöre auf jeden Fall nicht dazu. Aber mit der Attitüde "Wenns direkt 
nicht geht, für ein Zwischendefine ein", kommt man eigentlich in den 
meisten Fällen ganz gut zurecht.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.