Hallo zusammen,
ich bringe mir gerade selbst C++ bei, und bin auf ein Problem bei der
Initialisierung von Objekten gestoßen.
1
struct fifo_char_t Empfangskanal;
2
static Shell prompt(&Empfangskanal);
Ich erzeuge eine globale Variable vom Typ 'struct fifo_char_t'.
Anschließend erzeuge ich ein 'prompt'-Objekt und übergebe die Adresse
der erwähnten Variable dem Initializer. Diese soll dann als data member
im Objekt abgelegt werden.
Der Initializer sieht folendermaßen aus:
1
class Shell {
2
public:
3
Shell(struct fifo_char_t *);
4
void UpdateBuffer();
5
bool IsFullLineAvailable();
6
bool IsBufferEmpty();
7
char ReadBuffer();
8
private:
9
struct fifo_char_t * input_buffer_;
10
struct fifo_char_t buffer_;
11
bool line_received_;
12
};
1
Shell::Shell(struct fifo_char_t * input_buffer)
2
{
3
input_buffer_ = input_buffer;
4
}
Im Debugger sehe ich, dass 'Empfangskanal' auch korrekt in dem RAM
meines Mikrocontrollers gelinkt wurde. Das Befüllen dieser Variable
klappt reibungslos.
Allerdings: Wenn ich mir zur Laufzeit die data member meines Objektes
'prompt' ansehe, sehe ich, dass 'input_buffer_' auf 0x0 zeigt.
Wie gesagt bin ich noch recht neu was C++ anbelangt. Hat das
irgendwelche offensichtlichen Gründe?
Danke
Manuel
Rufus Τ. F. schrieb:> Manuel W. schrieb:>> Allerdings: Wenn ich mir zur Laufzeit die data member meines Objektes>> 'prompt' ansehe,>> Wie und womit?
Mit dem Debugger. Ich benutze Em::Blocks. Das Ding hat ein GCC-Backend
und verwendet zum debuggen soweit ich weiß GDB.
Sven B. schrieb:> Soweit ich weiß findet die Initialisierung aller statics statt,> bevor> deine globale Variable initialisiert wird. Bin aber nicht sicher.
Also ich habe jetzt mal testweise das 'static' weggenommen, leider
besteht das Problem jedoch weiterhin.
Manuel W. schrieb:> Hat das irgendwelche offensichtlichen Gründe?
Wenn es welche gibt, dann sehe ich sie auch nicht.
Da du dem constructor eine Adresse übergibst, kann der Wert niemals NULL
sein.
Es gibt keinen (impliziten) default constructor, der auf NULL
initialisieren könnte.
Der implizite copy constructor kopiert die Adresse (kann also auch in
der Kopie nicht NULL sein).
Da brauchen wir wohl mehr Infos.
Mikro 7. schrieb:> Manuel W. schrieb:>> Hat das irgendwelche offensichtlichen Gründe?> Da du dem constructor eine Adresse übergibst, kann der Wert niemals NULL> sein.
??
Foo(nullptr);
Sven B. schrieb:> ??
Du hast die Formulierung nicht verstanden.
Natürlich kann man einen Nullpointer übergeben. Das aber geschieht hier
nicht, hier wird die Adresse eines existierenden Objektes übergeben. Und
die kann nicht NULL sein.
Manuel W. schrieb:> Mit dem Debugger.
Und was bedeutet in diesem Kontext Deine Formulierung "zur Laufzeit"?
Hast du mal einen Breakpoint (oder LED/printf-Debug) in den Konstruktor
gesetzt, um zu schauen, ob dieser überhaupt aufgerufen wird? Wenn er
nicht aufgerufen wird, stimmt wahrscheinlich mit deinem Startup-Code
etwas nicht.
Wird dein Objekt korrekt initialisiert, wenn du es innerhalb einer
Funktion (z.B. main) auf dem Stack anlegst (ohne new)?
Rufus Τ. F. schrieb:> Manuel W. schrieb:>> Mit dem Debugger.>> Und was bedeutet in diesem Kontext Deine Formulierung "zur Laufzeit"?
Damit meine ich, dass ich einen Breakpoint auf den Beginn der
main()-Funktion gelegt habe, und mir Inhalt des Objektes anzeigen hab
lassen.
Sebastian E. schrieb:> Hast du mal einen Breakpoint (oder LED/printf-Debug) in den Konstruktor> gesetzt, um zu schauen, ob dieser überhaupt aufgerufen wird? Wenn er> nicht aufgerufen wird, stimmt wahrscheinlich mit deinem Startup-Code> etwas nicht.
Nein, habe ich nicht. Mein Verständnis war, dass Objekte die global
angelegt werden (also außerhalb einer Funktion) bereits zur
Kompilierzeit instanziert werden. (Dh. die dahinterliegenden Strukturen
werden so wie .data oder .bss section vom Startup Code initialisiert.)
Ich hatte nicht angenommen, dass tatsächlich ein Funktionseinsprung in
den Initializer erfolgt, da zu diesem Zeitpunkt die Vorraussetzungen zum
Ausführen einer C-Funktion ja evtl. noch nicht gegeben sind.
(Initialisierter Stackpointer usw.)
Sebastian E. schrieb:> Wird dein Objekt korrekt initialisiert, wenn du es innerhalb einer> Funktion (z.B. main) auf dem Stack anlegst (ohne new)?
Ja, dann funktioniert es es reibungslos!
Manuel W. schrieb:> Ich hatte nicht angenommen, dass tatsächlich ein Funktionseinsprung in> den Initializer erfolgt, da zu diesem Zeitpunkt die Vorraussetzungen zum> Ausführen einer C-Funktion ja evtl. noch nicht gegeben sind.> (Initialisierter Stackpointer usw.)
Deshalb passiert das ja auch erst später. Aber wie soll der Konstruktor
zur Compilezeit ausgeführt werden? Das passiert nur, wenn er als
constexpr markiert ist.
Kann dein Debugger einen watchpoint auf input_buffer_ setzen, so dass du
siehst wenn dieser gesetzt wird? Dann könnte man die Addresse von this
ausgeben und ein backtrace machen. Wenn die Variable wieder auf 0
gesetzt wird könnte man das wiederholen, und wenn man später die
Addresse von prompt ausgibt wenn input_buffer_ 0 ist kann man schauen,
ob es das gleiche Objekt wie bei bei der Ausgabe von this damals war,
indem man die Addresse mit der von damals vergleicht.
Sven B. schrieb:> Manuel W. schrieb:>> Ich hatte nicht angenommen, dass tatsächlich ein Funktionseinsprung in>> den Initializer erfolgt, da zu diesem Zeitpunkt die Vorraussetzungen zum>> Ausführen einer C-Funktion ja evtl. noch nicht gegeben sind.>> (Initialisierter Stackpointer usw.)>> Deshalb passiert das ja auch erst später. Aber wie soll der Konstruktor> zur Compilezeit ausgeführt werden? Das passiert nur, wenn er als> constexpr markiert ist.
Ich dachte, dass der Konstruktor dann nicht im eigentlichen Sinne
"ausgeführt" wird, sondern sich der Compiler bereits zur Kompilierzeit
herleiten würde, was dann an den entsprechenden Speicherstellen zu
stehen hat. In meinem Fall wäre das einfach: Da das Object als data
member die Adresse der anderen globalen Struktur 'Empfangskanal' hat,
müsste der Compiler wohl nur das symbol dieser anderen Struktur
referenzieren. Das Eintragen der konkreten Adresse macht dann der
Linker.
Von constexpr habe ich schonmal gehört. Das kam mit C++11, oder? Derzeit
bin ich in meinem C++-Verständnis noch meilenweit davon entfernt...
Leider.
Was ist denn nun eigentlich das Problem? Kommt beim Zugriff Mist raus?
Wird überhaupt zugegriffen? Oder hast du ein im Programm komplett
ungenutztes Objekt per Debugger angeschaut?
Manuel W. schrieb:> Ich dachte, dass der Konstruktor dann nicht im eigentlichen Sinne> "ausgeführt" wird, sondern sich der Compiler bereits zur Kompilierzeit> herleiten würde, was dann an den entsprechenden Speicherstellen zu> stehen hat.
Meine Vermutung ist, dass er noch einen Schritt weiter geht: Er erkennt,
dass die Variable nirgends benutzt wird und der Konstruktor keine
Nebeneffekte hat und optimiert deshalb die Initialisierung komplett weg.
Was nicht gebraucht wird, muss ja nicht extra befüllt werden.
Ich benutze das Objekt tatsächlich. Der Mikrocontroller dereferenziert
den Pointer und schreibt in's Nirvana.
Ich danke euch allen vielmals für die Unterstützung! Leider muss ich
mich jetzt auf den Weg machen zu einer 30er-Feier. Sofern es mein
Zustand zulässt melde ich mich morgen wieder!
Danke!!
Manuel W. schrieb:> Ich dachte, dass der Konstruktor dann nicht im eigentlichen Sinne> "ausgeführt" wird, sondern sich der Compiler bereits zur Kompilierzeit> herleiten würde, was dann an den entsprechenden Speicherstellen zu> stehen hat. In meinem Fall wäre das einfach: Da das Object als data> member die Adresse der anderen globalen Struktur 'Empfangskanal' hat,> müsste der Compiler wohl nur das symbol dieser anderen Struktur> referenzieren. Das Eintragen der konkreten Adresse macht dann der> Linker.
Nein, i.A. geht das nicht. Der Konstruktur kann ja die Member auch mit
Sachen initialisieren die er aus einer Datei liest, oder vom Netzwerk,
oder sonstwoher. Was du beschreibst geht nur in eng eingegrenzten
Spezialfällen, nämlich wenn der Konstruktur constexpr ist.
> Von constexpr habe ich schonmal gehört. Das kam mit C++11, oder? Derzeit> bin ich in meinem C++-Verständnis noch meilenweit davon entfernt...
Ja, in 11 kann man es aber wirklich nur auf sehr wenige Dinge anwenden.
In C++14 ist es etwas flexibler.
Sven B. schrieb:> Nein, i.A. geht das nicht. Der Konstruktur kann ja die Member auch mit> Sachen initialisieren die er aus einer Datei liest, oder vom Netzwerk,> oder sonstwoher. Was du beschreibst geht nur in eng eingegrenzten> Spezialfällen, nämlich wenn der Konstruktur constexpr ist.
Er kann es als Optimierung auch ohne constexpr machen, wenn er den
Inhalt des Konstruktors kennt und da eben die oben genannten Aktionen
nicht gemacht werden.
Manuel W. schrieb:> Ich benutze das Objekt tatsächlich. Der Mikrocontroller dereferenziert> den Pointer und schreibt in's Nirvana.
Hmm, dann würde mir nur noch einfallen, dass im Startup-Code deiner
nicht näher genannten Laufzeitumgebung die Konstruktoren von statischen
Objekten nicht aufruft. Manche handhaben C++-Code da nicht korrekt.
Rolf M. schrieb:> Manuel W. schrieb:>> Ich benutze das Objekt tatsächlich. Der Mikrocontroller dereferenziert>> den Pointer und schreibt in's Nirvana.>> Hmm, dann würde mir nur noch einfallen, dass im Startup-Code deiner> nicht näher genannten Laufzeitumgebung die Konstruktoren von statischen> Objekten nicht aufruft. Manche handhaben C++-Code da nicht korrekt.
Ich benutze Em::Blocks mit dem mitgelieferten GCC.
gcc.exe (EmBlocks ARM Embedded Processors GNU tools) 4.7.3
Der Startup-Code ist anbei. Bei der Durchsicht habe ich tatsächlich
nichts gefunden was einen Aufruf von Konstruktoren andeutet...
Interessant.
Dieser Startup Code war bei Em::Blocks dabei. Ich habe mir jetzt
allerdings auch den angesehen, der bei CMSIS von ARM und bei der
Standard-Peripheral-Library von ST dabei ist, im wesentlichen dürfte
sich der aber nicht all zu groß unterscheiden.
Auch das im Map-File 'prompt' (das ist der Name des Objekts dessen
Konstruktor wohl nicht aufgerufenn wird) nur an einer Stelle vorkommt
wundert mich.
P.S.: Bei dem Mikrocontroller handelt es sich um einen STM32F103.
(Cortex-M3)
Welchen Compiler verwendest Du?
Wenn Du nicht mit dem g++ (C++) sondern dem gcc (C) Frontend
compilierst, werden die static initializer nicht aufgerufen.
Wenn Du's tust, wirst Du wahrscheinlich feststellen, daß dein
Startupcode nicht ausreichend ist.
Markus F. schrieb:> Welchen Compiler verwendest Du?>> Wenn Du nicht mit dem g++ (C++) sondern dem gcc (C) Frontend> compilierst, werden die static initializer nicht aufgerufen.> Wenn Du's tust, wirst Du wahrscheinlich feststellen, daß dein> Startupcode nicht ausreichend ist.
Um ehrlich zu sein: Ich weiß es nicht genau.
Ich denke, ich bin schon langsam so weit, dass mir die Abstraktion die
mir ein fertiges Buildsystem bietet mehr schadet als nutzt.
Standardmäßig war für alle Dateien in der IDE (= Em::Blocks) als
"compiler variable" 'CC' angegeben. Für meine C-Files habe ich das so
gelassen, für die .cpp-Dateien habe ich das jedoch auf 'CPP' geändert.
Über Google habe ich jetzt folgendes gefunden: (Em::Blocks benutzt make
im Hintergrund.)
https://www.gnu.org/software/make/manual/html_node/Implicit-Variables.html
Anscheinend steht 'CC' für den C-Compiler, während 'CPP' für den
C-Preprozessor steht. Für meine C++-Dateien hätte ich also wohl 'CXX'
benutzen müssen.
Gesagt getan -- jetzt bekomme ich im Startup Code jedoch tatsächlich
eine 'undefined reference to `main''-Fehlermeldung! War das das was du
meintest? Liegt das daran, dass main() jetzt ge-name-mangled wird? Aber
warum war das dann bis dato nicht der Fall? C++-Code (der auch wirklich
C++-Features (Klassen) benutzt) war's ja trotzdem.
(Und wie die GCC im Hintergrund funktioniert ist mir ehrlich gesagt ein
Rätsel: C, CC, CXX, CPP, c++, g++, usw. usf.)
Manuel W. schrieb:> Gesagt getan -- jetzt bekomme ich im Startup Code jedoch tatsächlich> eine 'undefined reference to `main''-Fehlermeldung!
main, _main, oder __main?
Markus F. schrieb:> Manuel W. schrieb:>> Gesagt getan -- jetzt bekomme ich im Startup Code jedoch tatsächlich>> eine 'undefined reference to `main''-Fehlermeldung!>> main, _main, oder __main?
'main'.
Startup code:
1
_start:
2
3
/* Zero fill the bss segment. */
4
ldr r1, = __bss_start__
5
ldr r2, = __bss_end__
6
movs r3, #0
7
b .fill_zero_bss
8
.loop_zero_bss:
9
str r3, [r1], #4
10
11
.fill_zero_bss:
12
cmp r1, r2
13
bcc .loop_zero_bss
14
15
/* Jump to our main */
16
bl main
17
b .
18
.size _start, . - _start
19
20
/* Macro to define default handlers. Default handler
21
* will be weak symbol and just dead loops. They can be
22
* overwritten by other handlers */
23
blablabla
C++-Code:
1
int main(void)
2
{
3
blablbala
4
}
Ein 'extern "C"' (um das name mangling zu deaktivieren) ändert aber auch
nichts.
Ich habe jetzt mal testweise folgenden Code ausgeführt, und mir am
Haltepunkt den Inhalt des Objects angesehen. Ergebnis: 'run_' war 0, dh.
der Konstruktor wurde nicht ausgeführt.
Sehr seltsam das Ganze.
Mit meinem Hinweis auf g++ als Frontend habe ich wahrscheinlich in die
falsche Richtung geschickt. Genauso wie mit dem Hinweis auf _main(). Ich
habe einen älteren (non-elf) Compiler, bei dem das nur so geht, das ist
aber nicht (mehr) Standard. Sorry dafür.
Bei moderneren gcc-xxx-xxx-elf-Compilern ist es offensichtlich wurscht,
welches Frontend man benutzt. Trotzdem müssen die static initializers
vor dem Start von main() (also noch im Startupcode) aufgerufen werden.
Wie das geht, ist - so meine ich - immer noch vom Compiler abhängig.
Der arm-none-eabi-g++ packt die in die .init_array section (die
natürlich gelinkt werden und deshalb im Linker Skript extra
berücksichtigt werden muß).
Wenn Du normalerweise mit -nostdlib linkst, mußt Du zusätzlich die
Funktionszeiger in dieser Section im Startupcode "von Hand" aufrufen
(sonst würde das die C/C++-Library erledigen):
1
extern"C"
2
{
3
voidstatic_init(void)
4
{
5
typedefvoid(InitFunc)(void);
6
externInitFunc__init_array_start;
7
externInitFunc__init_array_end;
8
9
InitFuncpFunc=&__init_array_start;
10
11
for(;pFunc<&__init_array_end;++pFunc)
12
{
13
(*pFunc)();
14
}
15
}
16
}
Ich hab's nicht komplett selbst durchgespielt, denke aber, daß Du damit
weiterkommen müsstest. Laß' hören, ob's funktioniert.
Manuel W. schrieb:> 'main'.>> Startup code:
Das ist ein reiner "C" Startup Code. Für die C++ Static Konstruktoren
muss das etwa so aussehen:
1
/* Call static constructors */
2
bl __libc_init_array
3
/* Call the application's entry point.*/
4
bl main
5
6
b .
Anderenfalls wundert man sich wieso da was nicht tut.
Viele ARM (Cortex M) Startups sehen so aus,
weil vorher noch Low Level Initialisierung nötig ist - wie Clocks oder
FPU:
1
/* Call the clock system intitialization function.*/
Markus F. schrieb:> Mit meinem Hinweis auf g++ als Frontend habe ich wahrscheinlich in die> falsche Richtung geschickt. Genauso wie mit dem Hinweis auf _main(). Ich> habe einen älteren (non-elf) Compiler, bei dem das nur so geht, das ist> aber nicht (mehr) Standard. Sorry dafür.
Kein Problem! War gut gemeint, und zumindest hatte ich somit Anlass mich
in die Unterschiede zwischen das gcc- und g++-Frontend einzuarbeiten.
Das wird sicher nicht umsonst gewesen sein.
~~~
Der Tipp von dir und Jim Meba war goldrichtig!
Ich musste einfach nur ein
1
bl __libc_init_array
unmittelbar vor den Aufruf meiner main() in meinen Startup-Code
einfügen, und schon war ich annähernd am Ziel.
Der Linker beschwerte sich dann nur noch wegen eines 'undefined
reference to `init''. Da das jedoch irgendwas mit mit der Schnittstelle
zum Host-Betriebssystem zu tun hat, und ich jedoch bare-metal
programmiere, reichte es aus ihn mit einer Dummy-Definition dieses
Symbols ruhig zu stellen.
1
extern "C" { void _init(void){}; }
Es scheint jetzt wirklich alles zu funktionieren! Die Konstruktoren
werden aufgerufen, und den data membern werden die erwarteten Werte
zugewiesen.
Vielen lieben Dank (an alle!) für die tatkräftige Unterstützung!
Ich sehe schon, wenn ich von C++ wirklich Ahnung haben will werde mich
bei nächster Gelegenheit auf die Interna von C++ fokussieren müssen. Für
mich als Neuling ist es ein Wahnsinn, was da im Hintergrund durch das
Zusammenspiel der diversen Libraries, Compiler-/Linkerflags usw.
geschieht...