C Preprocessor, Gluebefehl ##

Gast #1076953
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?
Gast #1076998
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;
}
//----------------------------------
Gast #1077002
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;
}
//-------------------------------------
Gast #1077014
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!
Gast #1077165
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 ;-)
Moderator Persönliche Seite #1077173
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. :)
#1077175
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.
Gast #1077222
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.
Persönliche Seite #1077590
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 ;-)
#1387062
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.
#1387101
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.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren