Hallo,
ich schreibe ein etwas großeres Programm für den AVR32. Der compiliert
mit dem GCC für diese Architektur.
Ich habe vor kurzem das Projekt geteilt, sodass das Compilieren nicht
mehr so ewig dauert. Fertige Module habe ich in statische Libs
ausgelagert.
Nun habe ich folgendes Problem:
In den statischen Libs befinden sich globale Klassen. Auf wundersame
wiese werden deren Konstruktoren vor der Main nicht mehr aufgerufen.
Woran kann sowas liegen?
Ich habe mal eine globale Klasse die mit "extern" im Header deklariert
ist, in die Main.cpp reinkopiert sodass nur noch die -extern-Deklaration
in der ausgelagerten Headerdatei ist. Die Deklaration ohne extern ist in
der Main.cpp.
Wird die globale Klasse nun im Hauptprogram "definiert", anstatt in
einer Datei aus einer static Lib, dann wird der Konstruktor normal
aufgerufen.
Was genau haut mir das auslagern kaput?
Ich hoffe auf fachkundige Nicht-Spam-Antworten :-)
MFG
Hi,
hab mal etwas gegrübelt....
Normaler weise wird im Startup-Code des MC die Daten von Klassen ins RAM
kopiert. Danach erfolgt der Aufruf der Konstruktoren.
Kann es nun bei dir möglich sein, dass die Daten von der Lib in einer
anderen Section gelandet sind und so nicht mehr ausgeführt werden???
Ich kann dir leider nicht sagen, wo man da schauen könnte. :-(
(Aufbau der Lib (MAP), Linker .....)
ein Versuch fällt mir ein:
Programm einmal mit Lib und einmal ohne Lib erstellen und dann die
Grösse von der Section berechnen, sollte sie gleich sein, sind deine
Daten von der Lib woanders gelandet! (_ctors_end__ - __ctors_start_ =
size)
Vielleicht bringt dich das auf eine Idee oder ein anderer hier im Forum
kann noch mehr dazu sagen.
Stephan
Zwei Sachen fallen mir da ein:
Wird mit -nostdlib oder Linkerscript gelinkt, muss meistens trotzdem die
libgcc.a mit -lgcc reingelinkt werden. Die kümmert sich auch um
Konstruktoren.
Wir wissen ja nicht um welche Klassen es sich handelt, aber wenn es um
so etwas wie als globale Variablen deklarierte Objekte geht, die sich
durch den Konstruktor selber irgendwo einklinken aber sonst nicht direkt
referenziert werden, dann erklärt sich das auch. Aus einer statischen
Library werden nur die Objektdateien reingelinkt, die auch tatsächlich
Referenzen auflösen.
Danke erstmal für euren Einsatz.
Die Sections sind ok. Soweit hab ich am Linkerfile nichts geändert und
außerdem sollte dann auch das Hauptprogramm keine Konstruktoren
aufgerufen bekommen.
Soweit ich sehe, hat Andreas B. recht. Es ist das leidige Problem, dass
>Aus einer statischen Library werden nur die Objektdateien reingelinkt, die >auch
tatsächlich Referenzen auflösen.
Da Konstruktoren nie wirklich aufgerufen werden (aus dem Code), werden
sie nicht mit in die Lib reingenommen. Das nervt.
Es gibt das Linker flag --whole-archive, aber dann kommen wilde fehler,
da ja jede Static lib für sich selbst diverse Grundfunktionen
implementiert. Irgendwie ist das komisch.
Fehlermeldungen sind alle wie:
In function `__emutls_register_common': multiple definition of
`__emutls_register_common'
n function `__emutls_get_address': multiple definition of
`__emutls_get_address'
blahh usw.
ICh verstehe den Grund der Fehlermeldungen. Aber ich sehe dann keinen
Ausweg mehr, wie ich das Konstruktorproblem lößen soll.
Es muss dafür ja eine schöne Lösung geben.
> Da Konstruktoren nie wirklich aufgerufen werden (aus dem Code), werden> sie nicht mit in die Lib reingenommen. Das nervt.
Nur um es noch mal zu präzisieren: Wenn die Objektdatei aus einer
statischen Lib irgendeine Referenz auflöst wird sie reingelinkt und wenn
sie gelinkt wird, werden auch sonstige Konstruktoren in derselben Datei
aufgerufen.
Aber insgesamt ist wohl das Problem, dass du statische Libs überhaupt
verwendest. Libs sind dazu da, um Code für andere Programme zur
Verfügung zu stellen, aus diesem Grund wird auch das ausgelassen was
nicht referenziert wird.
Die Compile-Zeit bei größeren Projekten zu reduzieren ist die Aufgabe
von make, der nur die Dateien übersetzt, bei denen es nötig ist. Wenn
das nicht funktioniert, sollte das Makefile korrigiert werden anstatt
auf Libs auszuweichen.
Du hast schon recht, die CompileZeit ist bei geringen veränderungen
nicht lang. Aber ändere mal die Optimierungsstufe um zu tersten dass
alles bei =O0, O1, O2, O3 und Os geauso funktioniert. Da sitzte dann da
und sagst dir: Ahhhja. Schade dass ich kein Fernsehr habe ;-)
Und dazu kommt auch noch die Linkzeit, die aber JEDES mal fällig ist.
und für 120 .o Dateien rattert mein Rechner schon ganz schön. Das geht
mit den Libs schon wesentlich schneller.
Die einzelnen .o dateien in den Libs.. hmmh.
Problem: die Klassen werden praktisch über ihren Konstruktor an das GUI
system angemeldet, dass dann nur noch Funktionspointer und Namen (zur
Laufzeit) kennt.
Wenn eine GUI Komponente einen Handler hat, wird dann in der Liste der
registrierten Funktionen gesucht und der Funktionspointer zugewiesen.
Nur so werden die Klassenfunktionen zum schluss aufgerufen. Die
Anmeldung der Klassenfunktionen passiert relativ automatisiert über
Vererbungsmechanismen und einem kleinen Macro.
So. Nun wird der anmeldende Konstruktor aber nicht ausgeführt, die
Funktionen werden nicht registriert. Dadurch wiederrum ist es sinnlos
für den Linker die .o noch einzubinden.
Teufelskreis. So ein Dreck. Das is ja schon fast Bug-Haftes Verhalten
des Linkers.
Sinnlose funktionen zu schreiben, nur um jede Datei mit sicherheit
einzubinden ist einfach keine sinnvolle Lösung.
Es kann doch nicht sein, dass aufgetrennter Code sich anders verhält als
Code aus einer einzelnen Compilierung.
> Und dazu kommt auch noch die Linkzeit, die aber JEDES mal fällig ist.> und für 120 .o Dateien rattert mein Rechner schon ganz schön. Das geht> mit den Libs schon wesentlich schneller.
Vielleicht weil das Dateisystem fragmentiert ist? Eine statische Lib mit
den GNU binutils ist ja nichts anderes als alle Objekt-Dateien in eine
Archiv-Datei gepackt. Rein von der Linkerei (Anzahl der .o) bleibt sichs
also gleich.
> Sinnlose funktionen zu schreiben, nur um jede Datei mit sicherheit> einzubinden ist einfach keine sinnvolle Lösung.
Nö. Um eine Datei mit Sicherheit einzubinden, muss man sie nur beim
Linken angeben.
> Es kann doch nicht sein, dass aufgetrennter Code sich anders verhält als> Code aus einer einzelnen Compilierung.
Tut er auch nicht. Libraries sind aber was anderes als aufgetrennter
Code.
Aber es gibt noch eine Möglichkeit: Man kann mehrere .o zu einer
größeren .o zusammenlinken, vielleicht hilft das. Option -r beim GNU ld.
--warn-constructors Warn if global constructors/destructors are seen
Das sind binutils-2.19
Was die Build-Zeit angeht, hilft evtl. ne Übersetzung mit gcc -pipe ...
Ebenso make -j<n>
Zudem muss nach einem build für O0, O1, ... kein clean gemacht werden,
wenn mehrer Projekte angelegt sind die die gleiche Quelle verwenden und
in unterschiedliche Verzeichnisse generieren --> build parallelisierbar,
overhead durch clean fällt weg, besser diff'bar
Johann L. schrieb:
> ld --help | grep -i constr> -Ur Build global constructor/destructor tables> --warn-constructors Warn if global constructors/destructors are seen
Vor globalen Konstruktoren mit --warn-constructors zu warnen wird nicht
helfen, wenn er globale Konstruktoren mit Absicht einsetzt.
Aber trotzdem gut, dass du das geschrieben hast. Um aus anderen Objekten
wieder ein linkbares Objekt zu linken, muss man die Option -Ur statt -r
für C++ angeben. Zumindest, wenn man nicht in mehr als zwei Stufen
linkt:
1
`-Ur'
2
For anything other than C++ programs, this option is equivalent to
3
`-r': it generates relocatable output--i.e., an output file that
4
can in turn serve as input to `ld'. When linking C++ programs,
5
`-Ur' _does_ resolve references to constructors, unlike `-r'. It
6
does not work to use `-Ur' on files that were themselves linked
7
with `-Ur'; once the constructor table has been built, it cannot
8
be added to. Use `-Ur' only for the last partial link, and `-r'
Blöde Frage aber:
wo setze ich diese Linkeroption ein?
a) in den LinkerCommand des Hauptprogrammes
b) in den LinkerCommand der Libs (AVR32 Studio nennt das AVR32 Archiver
- der wird mit -r gefüttert zZ)
Im Hauptprogramm hat es zu misteriösen fehlern geführt, deren Wortlaut
ich gerade nicht mehr wiedergeben kann.
Btw: Linkerspeed ist wirklich vn der Anzahl der Dateien abhängig. Mit
Fragmentierung hat das nichts zu tun. Die Dateien müssen alle einzeln
geöffnet werden und das ist das was Zeit frist. (Wäre auch auf meinem
Mp3-Player so ;-) ) Eine Einzelne datei zu öffnen ist hingegen rasend
schnell.
Ich kannte die Option bislang auch nicht, hab nur ein grep auf ld --help
gemacht.
Auch weiß ich nicht, inwieweit das nur relokatierbaren Code betrifft und
ob du relokatierbaren Code brauchst und auch dafür compilierst (zB
-fpic, -fPIC, -mpic oder so).
In den seltensten Fällen ist es git, ld direkt aufzurufen. Stattdessen
rugt man gcc auf und passt dio Opt an ld per gcc -Wl,Ur
Wissenswert für dich ist bestimmt auch
http://gcc.gnu.org/onlinedocs/gccint/Collect2.html
Um die Option einzusetzen muss man das Makefile anpassen. Es geht ja
darum, in zwei Stufen zu linken.
Normalerweise sieht der letzte Schritt ja so ähnlich aus:
1
output.elf: a.o b.o c.o d.o
2
$(LD) $(LDFLAGS) -o $@ $^
Das könnte man dann beispielsweise so machen:
1
output.elf: ab.o cd.o
2
$(LD) $(LDFLAGS) -o $@ $^
3
4
ab.o: a.o b.o
5
$(LD) $(LDFLAGS) -Ur -o $@ $^
6
7
cd.o: c.o d.o
8
$(LD) $(LDFLAGS) -Ur -o $@ $^
Wenn man in diesem Fall schon einmal komplett übersetzt hat und dann was
an b.c ändert, wird es zu b.o übersetzt, zu ab.o gelinkt und dann ab.o
und (die schon existierende) cd.o gelinkt. Was jetzt noch keine so große
Ersparnis ist, aber bei vielen Objekten und guter Aufteilung sicher was
bringt.