Forum: Compiler & IDEs Zwei Fragen: Konstruktor für Arduino DUE (gcc-arm-none-eabi-4.8.3) macht Probleme


von Kassiopeia (Gast)


Lesenswert?

Hallo,

ich bin gerade dabei, einige I2C-Bibliotheken, die ich zunächst unter 
AVR-Arduinos eingesetzt habe, auf den ARM basierten Arduino DUE zu 
portieren.

Dabei stosse ich auf zwei Probleme, für welche ich bislang keine Lösung 
gefunden habe.  Das Problem habe ich bereits unter der IDE 1.5.6r1 und 
r2 festgestellt, es tritt auch noch unter dem aktuellen 1.5.7 auf. Diese 
nehme ich nun als Referenz.

Die IDE setzt für den ARM Compiler gcc-arm-none-eabi-4.8.3 ein.  Als 
AVR-Compiler kommt die Version 4.8.1 zum Einsatz.

Während die Bibliotheken auf verschiedenen AVR basierten Boards 
problemlos laufen, hängen sich die damit übersetzten Programme auf dem 
DUE auf, so dass die Initialisierung des Boards bereits hängen bleibt.

Ich habe die problematische Stelle nun einmal in ein Programm 
zusammengefasst (dabei lösen sich viele Probleme mitunter), komme dabei 
aber noch zu einer weiteren Frage.

Beim Programm handelt es sich um das übliche Blink-Beispiel.  Dies dient 
dem Feedback, ob das Programm läuft und über die Blinkfrequenz noch der 
schnellen Information, ob der Konstruktor aufgerufen wird.

Die eigentliche Klasse ist auf einen Standardkonstruktor und einen 
Konstrukt, der einen int entgegennimmt reduziert:
1
#include <Wire.h> 
2
3
int led      =   13;
4
int duration = 1000;
5
6
//============  reduced h-file of library ==
7
class libTWI{
8
  public:
9
    libTWI();
10
    libTWI(int d);
11
    // other methods removed
12
  private:
13
    unsigned char dummy;
14
};
15
16
//============  reduced cpp-file of library ==
17
libTWI::libTWI() {
18
   Wire.begin();
19
   duration = 500;
20
}  
21
libTWI::libTWI(int d) {
22
   Wire.begin();
23
   duration = d;
24
}  
25
//=============================================
26
27
// A) explicit call of standard constructor => Wire.begin() hangs
28
// libTWI mySensor = libTWI();  
29
30
// B) implicit call of standard constructor => constructor not called
31
// libTWI mySensor();  
32
33
// C) implicit call of constructor with int_value => Wire.begin() hangs
34
// libTWI mySensor(200);  
35
36
// D) implicit call of standard constructor => Wire.begin() hangs
37
// libTWI mySensor;  
38
39
// E) implicit call of constructor with int_value => Wire.begin() hangs
40
// libTWI mySensor = 200;  
41
42
43
void setup() {
44
  pinMode(led, OUTPUT);
45
}
46
47
void loop() {
48
  digitalWrite(led, HIGH); 
49
  delay(duration);         
50
  digitalWrite(led, LOW);  
51
  delay(duration);         
52
}

Sobald nun der Konstruktor aufgerufen wird, bleibt das Board hängen. 
Dies liegt am Aufruf von Wire.begin(). Wird diese Zeile auskommentiert, 
wird das Board korrekt initialisiert.

Daher die erste Frage:  Warum bleibt das Board auf dem ARM basierten 
Arduino hängen, während dies auf dem AVR basierten Arduino so 
funktioniert?
Die Wire-Bibliothek ist für den ARM angepasst, wenn ich das Wire.begin() 
in setup() aufrufe, klappt das Ganze auch.

Die zweite Frage: Warum wird bei der Schreibweise
1
libTWI mySensor();
der Konstruktor nicht aufgerufen?  Nach meinem Stroustrup (2. Auflage) 
ist das eigentlich zulässig.  Dieses Verhalten ist zwischen AVR und ARM 
Architektur gleich, diese Variante wird generell nicht aufgerufen.

Schon mal vielen Dank für alle ernsthaften Antworten. Arduino-Bashing 
etc. bitte ins off-topic Forum!

: Verschoben durch User
von Karl H. (kbuchegg)


Lesenswert?

Kassiopeia schrieb:

> Daher die erste Frage:  Warum bleibt das Board auf dem ARM basierten
> Arduino hängen, während dies auf dem AVR basierten Arduino so
> funktioniert?
> Die Wire-Bibliothek ist für den ARM angepasst, wenn ich das Wire.begin()
> in setup() aufrufe, klappt das Ganze auch.


Tja.
Es gibt aber einen wesentlichen Unterschied.
Du hast hier mit deinem mySensor eine globale Variable geschaffen. Deren 
Konstruktor (und die Konstruktoren aller anderen globalen Objekte) 
werden aufgerufen noch ehe die Programmausführung überhaupt begonnen 
hat.

Das bedeutet aber auch, dass du hier keinerlei Kontrolle darüber hast, 
in welcher Reihenfolge die Konstruktoren der diversen gobalen Objekte 
aufgerufen werden. Offenbar ist es in deinem konkretem System auf dem 
ARM der Fall, dass zuerst das TWI Objekt konstruiert wird, noch ehe der 
Konstruktor des Wire Objektes gelaufen ist. Und nur Gott alleine weiß, 
was dann alles passiert.
Nicht umsonst hat ja auch das Wire Objekt eine begin() Methode. Damit du 
aus dem Programm heraus in setup() die Initialisierung der Objekte in 
einer geordneten Reihenfolge durchführen kannst. Im Konstruktor 
initialisierst du deine Member-Variablen. Aber du machst keine Aktionen, 
die von anderen Objekten abhängen.

>
> Die zweite Frage: Warum wird bei der Schreibweise
>
1
> libTWI mySensor();
2
>
> der Konstruktor nicht aufgerufen?

Weil das keine Objektdefinition ist, sondern der Protoyp einer Funktion, 
die keine Argumente übernimmt und ein libTWI Objekt zurückliefert.

Merke: Wenn in C++ etwas wie eine Funktionsdeklaration aussieht, dann 
ist es auch eine.

Gewöhn dir die Schreibweise mit den Klammern wieder ab. Wenn ein 
Konstruktor keine Argumente nimmt, dann lautet die Objektdefinition
1
   libTWI mySensor;

Nur dann, wenn er Argumente nimmt, dann schreibst du die Argumente in 
Klammern
1
   libTWI mySensor( 100 );

Alle anderen Schreibweisen vergisst du gleich wieder.

: Bearbeitet durch User
von Kassiopeia (Gast)


Lesenswert?

Vielen Dank,

ist eigentlich einleuchtend. Aufgrund der Tatsache, dass es unter AVR 
bisher funktioniert (und in etliche Bibliotheken auch so gemacht wird), 
habe ich den Aufruf anderer Klassen in der Initialisierungsfrage nicht 
kritisch genug hinterfragt!

Zum Konstruktoraufruf:

Müsste ein Funktionsprototyp nicht einen vorangestellten Typ (und sei es 
void ) haben?  Nur Konstruktoren sind eigentlichen typlos.

Bei nochmaligem Nachlesen habe ich den Aufruf mit leerer runder Klammer 
in den Stroustrup hineininterpretiert. Dieser wird im speziellen nicht 
behandelt, sondern nur der Aufruf mit einem oder mehreren Werten. Ich 
habe dann den Aufruf ohne Parameter als identisch angenommen.

von Karl H. (kbuchegg)


Lesenswert?

Kassiopeia schrieb:
> Vielen Dank,
>
> ist eigentlich einleuchtend. Aufgrund der Tatsache, dass es unter AVR
> bisher funktioniert (und in etliche Bibliotheken auch so gemacht wird),
> habe ich den Aufruf anderer Klassen in der Initialisierungsfrage nicht
> kritisch genug hinterfragt!
>
> Zum Konstruktoraufruf:
>
> Müsste ein Funktionsprototyp nicht einen vorangestellten Typ (und sei es
> void ) haben?

hat er doch.

Es gibt keinen syntaktischen Unterschied zwischen
1
void foo();
oder
1
int foo();

und
1
libTWI mySensor();

void, int bzw. libTWI ist der Datentyp des Rückgabewertes, foo. bzw. 
mySensor ist der Name der Funktion und die () bedeuten, dass die 
Funktion keine Argumente nimmt.

Das sieht aus wie eine Funktionsdeklaration (ein Protoyp) und damit ist 
es auch eine.

Das hier
1
libTWI mySensor( 100 );
kann keine Funktionsdeklaration sein. Denn in der Argumentliste einer 
Funktion können keine Zahlen auftauchen. Da müsste ein Datentyp stehen, 
so wie in
1
libTWI mySensor( int a );
dann wäre das eine Funktionsdeklaration. Aber so
1
libTWI mySensor( 100 );
passt das nicht auf eine Funktionsdeklaration, also ist es auch keine. 
Das vereinbart tatsächlich ein Objekt namens mySensor, welches vom 
Datentyp libTWI ist, und dessen Konstruktor zur Initialisierung 100 
mitkriegt.

>  Nur Konstruktoren sind eigentlichen typlos.

Davon ist aber nicht die Rede.
Dein Protoyp ist der Prototyp einer freistehenden Funktion
1
libTWI mySensor()
2
{
3
  libTWI dasObjekt;
4
5
  // mach irgendwas
6
7
  return dasObjekt;
8
}

Studier noch mal dein C Buch zum Thema Funktionsdeklaration bzw. 
Prototyp.
Ein Protoyp ist die Information für den Compiler, wie eine Funktion 
aussieht, welchen Rückgabewert sie hat und welche Argumente sie nimmt, 
damit der Compiler diese Daten kennt ohne die eigentliche Funktion 
gesehen zu haben.
So wie in
1
void bar();     // Protoyp der Funktion. Die Funktion ist zu diesem Zeitpunkt noch nicht bekannt.
2
                // Damit sie aber aufgerufen werden kann, teilen wir dem Compiler
3
                // die für den Aufruf wesentlichen Details mit
4
5
void foo()
6
{
7
  bar();        // Da der Compiler den Protoyp gesehen hat, kann er diesen Aufruf
8
                // kontrollieren. Er weiss, dass
9
                // * es eine Funktion namens bar gibt
10
                // * die Funktion keine Argumente nimmt
11
                // * und nichts liefert
12
                // Dieser Funktionsaufruf passt dazu, daher ist alles in Ordnung
13
14
  baz();        // wohingegen der Compiler von einer Funktion baz() noch nie
15
                // etwas gehört hat. Das setzt also einen Fehler
16
}
17
18
void bar()
19
{
20
  // und hier ist sie dann tatsächlich, die Funktion bar
21
}
22
23
void baz()
24
{
25
  // das ist zwar schön, dass es die Funktion baz tatsächlich gibt, hilft
26
  // abere nicht weiter. Der Compiler liest den Quelltext von oben nach unten.
27
  // Wenn hier baz  zum ersten mal auftaucht, dann ist das zu spät für den Aufruf
28
  // dieser Funktion da oben. Entweder muss die Funktion nach oben verschoben
29
  // werden, vor foo ... oder es muss ein Protoyp an den Anfang kommen, der
30
  // den Compiler darüber informiert, dass es diese Funktion tatsächlich gibt
31
  // und welche Signatur sie hat
32
}


> Bei nochmaligem Nachlesen habe ich den Aufruf mit leerer
> runder Klammer in den Stroustrup hineininterpretiert.

Sei ein bischen vorsichtig.
Es gibt einen kleinen subtilen Unterschied zwischen
1
  myClass * pA = new myClass;
und
1
  myClass * pA = new myClass();
Je nach genauem Aufbau von myClass, ob sie eine sog. POD ist oder nicht, 
passieren hier subtil unterschiedliche Dinge.
Lass dich darauf nicht ein! Das schafft nur Verwirrung. Zumal die 
meisten C++ Programmierer von einer POD (Plain Old Datatype) noch nie 
etwas gehört haben und nicht wissen, dass es da je nach Schreibweise 
kleine Unterschiede in der Initialisierung gibt.

: Bearbeitet durch User
von Kassiopeia (Gast)


Lesenswert?

Nochmals vielen Dank,

für die ausführlichen Erklärungen, insbesondere den Hinweis auf die POD.
Für mein aktuelles Problem ist mir nun alles klar. Dass gegenseitiges 
Aufrufen von Klassenmethoden in der Initialisierungsphase ein 
Henne-Ei-Problem darstellt, ist eigentlich logisch.  Da liegt der Fehler 
bei mir im unreflektierten Übernehmen von (zufällig) funktionierendem 
Code.

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.