Die unten auf der GCC-Seite für die Funktionsabschnitte und Datenabschnitte aufgeführten Optionen:
-ffunction-sections
-fdata-sections
Platzieren Sie jede Funktion oder jedes Datenelement in einem eigenen Abschnitt in der Ausgabedatei, wenn das Ziel beliebige Abschnitte unterstützt. Der Name der Funktion oder der Name des Datenelements bestimmt den Namen des Abschnitts in der Ausgabedatei. Verwenden Sie diese Optionen auf Systemen, auf denen der Linker Optimierungen durchführen kann, um die Referenzlokalität im Anweisungsbereich zu verbessern. Die meisten Systeme, die das ELF-Objektformat verwenden, und SPARC-Prozessoren, auf denen Solaris 2 ausgeführt wird, verfügen über Linker mit solchen Optimierungen. AIX kann diese Optimierungen in der Zukunft haben.
Verwenden Sie diese Optionen nur, wenn Sie erhebliche Vorteile daraus ziehen. Wenn Sie diese Optionen angeben, erstellen der Assembler und der Linker größere Objekt- und ausführbare Dateien und sind außerdem langsamer. Sie können gprof nicht auf allen Systemen verwenden, wenn Sie diese Option angeben, und Sie könnten Probleme beim Debuggen haben, wenn Sie sowohl diese Option als auch -g angeben.
Ich hatte den Eindruck, dass diese Optionen helfen, die Größe der ausführbaren Datei zu reduzieren. Warum sagt diese Seite, dass sie größere ausführbare Dateien erstellen wird? Übersehe ich etwas?
Interessanterweise mit -fdata-sections
kann die wörtlichen Pools Ihrer Funktionen und damit Ihre Funktionen selbst größer machen. Ich habe dies insbesondere bei ARM bemerkt, aber es ist wahrscheinlich anderswo wahr. Die Binärdatei, die ich getestet habe, wuchs nur um ein Viertel Prozent, aber sie wuchs. Mit Blick auf die Demontage der geänderten Funktionen war klar warum.
Wenn alle BSS- (oder DATA-) Einträge in Ihrer Objektdatei einem einzelnen Abschnitt zugeordnet sind, kann der Compiler die Adresse dieses Abschnitts im Funktionsliteralpool speichern und Ladevorgänge mit bekannten Offsets von dieser Adresse in der Funktion generieren, um auf Ihre zuzugreifen Daten. Aber wenn Sie aktivieren -fdata-sections
Es fügt jedes Stück BSS- (oder DATA-) Daten in einen eigenen Abschnitt ein, und da es nicht weiß, welche dieser Abschnitte später möglicherweise von der Garbage Collection erfasst werden oder in welcher Reihenfolge der Linker alle diese Abschnitte in das endgültige ausführbare Bild einfügt. Es kann keine Daten mehr laden, die Offsets von einer einzelnen Adresse verwenden. Stattdessen muss es einen Eintrag im Literal-Pool pro verwendeten Daten zuweisen, und sobald der Linker herausgefunden hat, was in das endgültige Bild hineingeht und wo, kann er diese Literal-Pool-Einträge mit der tatsächlichen Adresse von reparieren die Daten.
Also ja, sogar mit -Wl,--gc-sections
das resultierende Bild kann größer sein, da der eigentliche Funktionstext größer ist.
Unten habe ich ein Minimalbeispiel hinzugefügt
Der folgende Code reicht aus, um das Verhalten zu sehen, von dem ich spreche. Bitte lassen Sie sich nicht von der flüchtigen Deklaration und Verwendung globaler Variablen abschrecken, die beide in echtem Code fragwürdig sind. Hier sorgen sie für die Erstellung von zwei Datenabschnitten, wenn -fdata-sections verwendet wird.
static volatile int head;
static volatile int tail;
int queue_empty(void)
{
return head == tail;
}
Die für diesen Test verwendete Version von GCC ist:
gcc version 6.1.1 20160526 (Arch Repository)
Zunächst erhalten wir ohne -fdata-sections Folgendes.
> arm-none-eabi-gcc -march=armv6-m \
-mcpu=cortex-m0 \
-mthumb \
-Os \
-c \
-o test.o \
test.c
> arm-none-eabi-objdump -dr test.o
00000000 <queue_empty>:
0: 4b03 ldr r3, [pc, #12] ; (10 <queue_empty+0x10>)
2: 6818 ldr r0, [r3, #0]
4: 685b ldr r3, [r3, #4]
6: 1ac0 subs r0, r0, r3
8: 4243 negs r3, r0
a: 4158 adcs r0, r3
c: 4770 bx lr
e: 46c0 nop ; (mov r8, r8)
10: 00000000 .word 0x00000000
10: R_ARM_ABS32 .bss
> arm-none-eabi-nm -S test.o
00000000 00000004 b head
00000000 00000014 T queue_empty
00000004 00000004 b tail
Aus arm-none-eabi-nm
wir sehen, dass queue_empty 20 Bytes lang ist (14 hex), und die arm-none-eabi-objdump
Die Ausgabe zeigt, dass es am Ende der Funktion ein einzelnes Verschiebungswort gibt, es ist die Adresse des BSS-Abschnitts (der Abschnitt für nicht initialisierte Daten). Die erste Anweisung in der Funktion lädt diesen Wert (die Adresse des BSS) in r3. Die nächsten beiden Befehle werden relativ zu r3 geladen, jeweils um 0 und 4 Bytes versetzt. Diese beiden Lasten sind die Lasten der Werte von Head und Tail. Wir können diese Offsets in der ersten Spalte der Ausgabe von sehen arm-none-eabi-nm
. Das nop
am Ende der Funktion ist die Wortausrichtung der Adresse des Literalpools.
Als nächstes werden wir sehen, was passiert, wenn -fdata-sections hinzugefügt wird.
arm-none-eabi-gcc -march=armv6-m \
-mcpu=cortex-m0 \
-mthumb \
-Os \
-fdata-sections \
-c \
-o test.o \
test.c
arm-none-eabi-objdump -dr test.o
00000000 <queue_empty>:
0: 4b03 ldr r3, [pc, #12] ; (10 <queue_empty+0x10>)
2: 6818 ldr r0, [r3, #0]
4: 4b03 ldr r3, [pc, #12] ; (14 <queue_empty+0x14>)
6: 681b ldr r3, [r3, #0]
8: 1ac0 subs r0, r0, r3
a: 4243 negs r3, r0
c: 4158 adcs r0, r3
e: 4770 bx lr
...
10: R_ARM_ABS32 .bss.head
14: R_ARM_ABS32 .bss.tail
arm-none-eabi-nm -S test.o
00000000 00000004 b head
00000000 00000018 T queue_empty
00000000 00000004 b tail
Wir sehen sofort, dass sich die Länge von queue_empty um vier Bytes auf 24 Bytes (18 Hex) erhöht hat und dass nun zwei Verschiebungen im Literalpool von queue_empty durchgeführt werden müssen. Diese Verschiebungen entsprechen den Adressen der zwei BSS-Abschnitte, die erstellt wurden, einer für jede globale Variable. Hier müssen zwei Adressen vorhanden sein, da der Compiler die relative Position nicht kennen kann, an der der Linker die beiden Abschnitte letztendlich einfügt. Wenn wir uns die Anweisungen am Anfang von queue_empty ansehen, sehen wir, dass es eine zusätzliche Last gibt, den Compiler muss separate Ladepaare generieren, um die Adresse des Abschnitts und dann den Wert der Variablen in diesem Abschnitt zu erhalten. Die zusätzliche Anweisung in dieser Version von queue_empty macht den Hauptteil der Funktion nicht länger, es nimmt nur die Stelle ein, die zuvor ein Nop war, aber das wird im Allgemeinen nicht der Fall sein.
Wenn Sie diese Compileroptionen verwenden, können Sie die Linkeroption hinzufügen -Wl,--gc-sections
Dadurch wird der gesamte unbenutzte Code entfernt.
Sie können verwenden -ffunction-sections
und -fdata-sections
auf statische Bibliotheken, wodurch die Größe der statischen Bibliothek erhöht wird, da jede Funktion und globale Datenvariable in einen separaten Abschnitt gestellt wird.
Und dann verwenden -Wl,--gc-sections
auf das Programm, das mit dieser statischen Bibliothek verknüpft wird, wodurch nicht verwendete Abschnitte entfernt werden.
Daher ist die endgültige Binärdatei kleiner als ohne diese Flags.
Seien Sie jedoch vorsichtig, da -Wl,--gc-sections
kann Dinge kaputt machen.
Ich erhalte bessere Ergebnisse, wenn ich einen zusätzlichen Schritt hinzufüge und eine erstelle .a
Archiv:
- Zuerst werden gcc und g++ verwendet
-ffunction-sections
-fdata-sections
Flaggen
- dann alle
.o
Objekte werden in ein gelegt .a
Archiv mit ar rcs file.a *.o
- schließlich wird der Linker mit aufgerufen
-Wl,-gc-sections,-u,main
Optionen
- für alle ist die optimierung auf eingestellt
-Os
.
Ich habe es vor einiger Zeit ausprobiert und wenn ich mir die Ergebnisse ansehe, scheint es, dass die Größenzunahme von der Reihenfolge der Objekte mit unterschiedlicher Ausrichtung herrührt. Normalerweise sortiert der Linker Objekte, um die Auffüllung zwischen ihnen klein zu halten, aber es sieht so aus, als ob das nur innerhalb eines Abschnitts funktioniert, nicht über die einzelnen Abschnitte hinweg. Daher erhalten Sie häufig zusätzliche Polsterung zwischen den Datenabschnitten für jede Funktion, wodurch der Gesamtraum vergrößert wird.
Bei einer statischen Bibliothek mit -Wl, -gc-Abschnitten wird das Entfernen unbenutzter Abschnitte die kleine Erhöhung jedoch höchstwahrscheinlich mehr als wettmachen.