Eingehende Analyse der Inline-Funktion von C (9).

Eingehende Analyse der Inline-Funktion von C (9).

Autor: Once Day Datum: 9. August 2023

Es war ein langer Weg, aber jemand hat dich angelächelt ...

Referenz zitierte Dokumente:

1. Übersicht

Funktions-Inlining ist keine Programmiertechnik, sondern eine Optimierungstechnik. Bei Code auf Programmierebene hoffen wir immer, dass die Abstraktionsebene des Codes höher ist und die gleichen Teile der Logik so weit wie möglich wiederverwendet werden, wodurch die Codemenge und die Wartungsschwierigkeiten verringert werden.

Hochabstrakter Code weist jedoch unweigerlich redundante Beurteilungen, falsches Design, erzwungene Integration oder unzureichende Technologie auf ... Unabhängig vom Grund wird es immer einige Einschränkungen geben, die dazu führen, dass abstrakter wiederverwendeter Code eine schlechte Leistung aufweist. Einige Mängel.

Ein ideales Szenario besteht darin, dass Sie ein Gleichgewicht zwischen Leistung und Wartbarkeit erreichen können, wenn Sie hochabstrakten wiederverwendbaren Code auf der Programmierebene verwalten und spezielle Ausführungsanweisungen auf der Assembly-Ebene generieren .

Aber zu welchen Kosten? Ja, die Codegröße wird stark zunehmen, sodass Sie nur Codesegmente mit Leistungsanforderungen optimieren können. Schließlich liegt der Leistungsengpass in den meisten Fällen nicht in der Ausführungsgeschwindigkeit.

Das in der folgenden Analyse verwendete Kompilierungstool ist gcc (Version 11, verschiedene Versionen funktionieren unterschiedlich). Es besteht kein Grund, sich um die Unterschiede zwischen verschiedenen Compilern zu kümmern. Dieser Artikel soll eine Denkanalysemethode verfolgen und nicht, welche Funktionen erreicht werden können Wenn Sie aus einem Beispiel Schlussfolgerungen ziehen können, verändert der Compiler nur die Suppe, ohne die Medizin zu ändern .

2. Gcc-Inline-Beschreibung

2.1 Wann ist Inline erforderlich?

Hier gibt es zwei Verständnisse von wann . Eines ist aus der Perspektive mehrerer Hauptschritte der Kompilierung, die in drei Schritte unterteilt ist: Vorverarbeitung, Assemblierung und Verknüpfung. Die ersten beiden Schritte beziehen sich auf eine einzelne C-Quelldatei. Im Allgemeinen werden Inline-Funktionen im Montageschritt verarbeitet, sodass Inline-Funktionen alle lokale Funktionen sind und externe Funktionen erst im Verknüpfungsschritt bestimmt werden. Es kann nicht ausgeschlossen werden, dass einige Compiler-Versionen die Inline-Funktionserweiterung während der Verknüpfung unterstützen. In diesem Artikel wird vorübergehend davon ausgegangen, dass Inlining nur während der Assembly-Phase durchgeführt werden kann. Dies ist auch die Leistung des derzeit getesteten gcc-Compilers .

GCC führt kein Funktions-Inlining auf der Standardoptimierungsebene (-O0) durch. Funktions-Inlining ist nur auf höheren Optimierungsebenen aktiviert:

  • -O1: Grundlegende Optimierungsstufe aktivieren. GCC führt auf dieser Ebene einige einfache Optimierungen durch, einschließlich Funktions-Inlining. Es werden jedoch nur die inlineFunktionen inline, die als markiert sind.

  • -O2: Optimierung auf höherer Ebene aktivieren. GCC führt auf dieser Ebene weitere Optimierungen durch, einschließlich Funktions-Inlining. Zusätzlich zu den als gekennzeichneten Funktionen inlineentscheidet GCC auch nach eigenem Ermessen, ob andere Funktionen integriert werden.

  • -O3: Ermöglicht die höchste Optimierungsstufe. GCC führt auf dieser Ebene aggressivere Optimierungen durch, einschließlich Funktions-Inlining. Es wird versucht, weitere Funktionen zu integrieren, um die Leistung zu verbessern. Diese Ebene erzeugt effizienteren Code, kann aber auch zu einer längeren Kompilierungszeit führen.

Zusätzlich zu den oben genannten drei Hauptoptimierungsstufen bietet GCC auch einige andere Optimierungsoptionen, wie z. B. -Os (Codegröße optimieren) und -Og (Debugging-Erfahrung optimieren). Diese Optionen können auch das Inlining von Funktionen ermöglichen, das genaue Inlining-Verhalten hängt jedoch von der Entscheidungsstrategie und der Codestruktur des Compilers ab.

Im Allgemeinen gibt es zwei Möglichkeiten, Inline-Funktionen zu definieren:

static inline int add_one(int ori)
{
    return ori + 1;
}

Diese Methode ist die gebräuchlichste Definitionsmethode. Wenn in add_one keine Nicht-Inline-Referenz des Symbols vorhanden ist, gibt es in der endgültigen Binärdatei kein add_onesolches Symbol . Der zweite Weg ist wie folgt:

extern int add_one(int ori);
inline int add_one(int ori)
{
    return ori + 1;
}

Auf diese Weise wird Funktions-Inlining-Unterstützung für die aktuelle C-Quelldatei bereitgestellt, während Symbole und Codes für die Verwendung in externen Funktionsaufrufen während der Verknüpfungsphase gespeichert werden, was der gleichzeitigen Unterstützung von Funktions-Inlining und Funktionsaufrufen entspricht.

Im Allgemeinen wird nur die erste Definitionsmethode berücksichtigt, da der Schwerpunkt dieses Artikels auf der Erweiterung von Inline-Funktionen liegt und es keinen Unterschied zwischen Inline-Funktionen ohne Erweiterung und anderen Funktionen gibt .

Wenn der Optimierungsgrad hoch genug ist, inlinewerden diese sehr kurzen Funktionen aktiv inline erweitert, auch wenn die Funktion nicht geändert wird. Wenn wir möchten, dass einige Funktionen immer erweitert werden, können wir sie mit den folgenden Attributen ändern, und sie -O0werden inline, auch wenn die Optimierungsstufe gleich ist.

/* Prototype.  */
inline void foo (const char) __attribute__((always_inline))

Wenn eine Funktion sowohl inline als auch statisch ist und alle Aufrufe der Funktion in den Aufrufer integriert sind und die Adresse der Funktion niemals verwendet wird, wird niemals auf den Assemblercode der Funktion selbst verwiesen. In diesem Fall gibt GCC keinen Assemblercode für die Funktion aus, es sei denn, die Option ist angegeben -fkeep-inline-functions. Bei Nicht-Inline-Aufrufen wird die Funktion wie gewohnt in Assembler-Code kompiliert. Wenn das Programm auf seine Adresse verweist, muss die Funktion auch wie gewohnt kompiliert werden, da sie nicht inline möglich ist.

Beachten Sie, dass bestimmte Verwendungen in Funktionsdefinitionen diese möglicherweise für die Inline-Ersetzung ungeeignet machen. Zu diesen Verwendungen gehören: die Verwendung verschiedener Funktionen , die Verwendung von dynamischem goto , die Verwendung von nicht-lokalem goto , die Verwendung verschachtelter Funktionen , die Verwendung von setjmp , die Verwendung von und die Verwendung von or . Wenn eine als Inline markierte Funktion nicht ersetzt werden kann, wird eine Warnung ausgegeben und der Grund für den Fehler angegeben. Daher kann diese Option beim Kompilieren hinzugefügt werden. Darüber hinaus können Sie damit auch Inline-bezogene Optimierungsinformationen anzeigen.alloca__builtin_longjmp__builtin_return__builtin_apply_args-Winline-fopt-info-inline

2.2 Inline-Funktionsanalyse

Hier ist ein einfacher Code:

/* simple-inline.c */
#include <stdio.h>

static inline int add_one(int ori)
{
    return ori + 1;
}

int main(void)
{
    int a = 10;
    a = add_one(a);
    return a;
}

Verwenden Sie den folgenden Befehl, um die tatsächliche Ausgabe zu kompilieren und anzuzeigen (entweder direkt Assembly oder binär ausgeben und dann disassemblieren):

# 二进制输出再反汇编
gcc -o simple-inline.out simple-inline.c -std=c99 -fopt-info-inline -Winline -O[n]
objdump -d simple-inline.out > simple-inline.s
# 直接输出汇编文件
gcc -S -o simple-inline.S simple-inline.c -std=c99 -fopt-info-inline -Winline -O[n]

Tatsächlich können Sie die Inline-Erweiterung im Allgemeinen anhand der Assembly-Ausgabe beurteilen. Sicherheitshalber ist der zerlegte Code jedoch überzeugender und kann auch -fopt-info-inlinedie Inline-Funktionserweiterung ausgeben, ist jedoch in niedrigeren Versionen nicht verfügbar von gcc.

(1) Inlining-Situation auf nicht optimiertem Niveau :

ubuntu->gcc-test:$ gcc -o simple-inline.out simple-inline.c -std=c99 -fopt-info-inline -Winline -O0
ubuntu->gcc-test:$ objdump -d simple-inline.out > simple-inline.s

Die entsprechende Zusammenstellung lautet wie folgt:

0000000000001129 <add_one>:
    1129:	55                   	push   %rbp
    112a:	48 89 e5             	mov    %rsp,%rbp
    112d:	89 7d fc             	mov    %edi,-0x4(%rbp)
    1130:	8b 45 fc             	mov    -0x4(%rbp),%eax
    1133:	83 c0 01             	add    $0x1,%eax
    1136:	5d                   	pop    %rbp
    1137:	c3                   	ret    

0000000000001138 <main>:
    1138:	f3 0f 1e fa          	endbr64 
    113c:	55                   	push   %rbp
    113d:	48 89 e5             	mov    %rsp,%rbp
    1140:	48 83 ec 10          	sub    $0x10,%rsp
    1144:	c7 45 fc 0a 00 00 00 	movl   $0xa,-0x4(%rbp)
    114b:	8b 45 fc             	mov    -0x4(%rbp),%eax
    114e:	89 c7                	mov    %eax,%edi
    1150:	e8 d4 ff ff ff       	call   1129 <add_one>
    1155:	89 45 fc             	mov    %eax,-0x4(%rbp)
    1158:	8b 45 fc             	mov    -0x4(%rbp),%eax
    115b:	c9                   	leave  
    115c:	c3                   	ret    

Offensichtlich wird die Erweiterung standardmäßig nicht ohne Optimierungsstufe durchgeführt . Diese Optimierungsstufe wird im Allgemeinen zum Debuggen verwendet. Auf diese Weise wird der Funktionsaufrufstapel lokalisiert, sodass er immer noch sehr nützlich ist.

add_one(2) Erzwingen Sie die Inline-Einbindung der angegebenen Funktion. Verwenden Sie hier __attribute__((always_inline))Änderungen an der Funktion

ubuntu->gcc-test:$ gcc -o simple-inline.out simple-inline.c -std=c99 -fopt-info-inline -Winline -O0
simple-inline.c:20:9: optimized:   Inlining add_one/0 into main/1 (always_inline).
ubuntu->gcc-test:$ objdump -d simple-inline.out > simple-inline.s

Die Kompilierungsinformationen haben bereits gezeigt, dass die Funktion erfolgreich inline integriert wurde add_one. Schauen wir uns die Hauptfunktionsassembly wie folgt an:

0000000000001129 <main>:
    1129:	f3 0f 1e fa          	endbr64 
    112d:	55                   	push   %rbp
    112e:	48 89 e5             	mov    %rsp,%rbp
    1131:	c7 45 f8 0a 00 00 00 	movl   $0xa,-0x8(%rbp)
    1138:	8b 45 f8             	mov    -0x8(%rbp),%eax
    113b:	89 45 fc             	mov    %eax,-0x4(%rbp)
    113e:	8b 45 fc             	mov    -0x4(%rbp),%eax
    1141:	83 c0 01             	add    $0x1,%eax
    1144:	89 45 f8             	mov    %eax,-0x8(%rbp)
    1147:	8b 45 f8             	mov    -0x8(%rbp),%eax
    114a:	5d                   	pop    %rbp
    114b:	c3                   	ret    

Es ist ersichtlich, dass die Funktion erfolgreich inline ist und die Codelänge reduziert wird. Dies ist auch eine der Funktionen des Funktions-Inlinings, bei dem nutzloser Code und Anweisungen herausgeschnitten werden. Da der Optimierungsgrad nicht hoch genug ist, ist der Effekt vorhanden unklar. Die Binärdatei enthält keine Funktionssymbole add_one, da es keine Nicht-Inline-Aufrufe und Adressverweise darauf gibt, sodass der Compiler keinen entsprechenden Funktionscode generieren muss.

(3) Funktions-Inlining-Situation bei hohem Optimierungsgrad,

ubuntu->gcc-test:$ gcc -o simple-inline.out simple-inline.c -std=c99 -fopt-info-inline -Winline -O1
simple-inline.c:20:9: optimized:   Inlining add_one/13 into main/14 (always_inline).
ubuntu->gcc-test:$ objdump -d simple-inline.out > simple-inline.s

Es ist auch inline und die Montageanweisungen der Hauptfunktion sind stark reduziert:

0000000000001129 <main>:
    1129:	f3 0f 1e fa          	endbr64 
    112d:	b8 0b 00 00 00       	mov    $0xb,%eax
    1132:	c3                   	ret    

Das Berechnungsergebnis im eigentlichen Code ist eine feste Konstante, sodass das endgültige konstante Ergebnis direkt nach der Optimierung ausgegeben wird. Dies ist auch einer der erwarteten Optimierungseffekte von Inline-Funktionen, die einige nutzlose Situationen, die zur Kompilierungszeit ermittelt werden, ausschließen können.

2.3 Inline-Instanzanalyse

Unter normalen Umständen ist die Inline-Funktion möglicherweise nicht inline, da sie zu groß ist, und der Compiler überspringt sie wie folgt direkt und ohne Fehler:

#include <stdio.h>

static inline int add_one(int ori) { return ori + 1;}

/* clang-format off */

#define VPP_REPEAT_1(op)        op(1)
#define VPP_REPEAT_2(op)        VPP_REPEAT_1(op); op(2)
#define VPP_REPEAT_3(op)        VPP_REPEAT_2(op); op(3)
#define VPP_REPEAT_4(op)        VPP_REPEAT_3(op); op(4)
#define VPP_REPEAT_5(op)        VPP_REPEAT_4(op); op(5)
#define VPP_REPEAT_6(op)        VPP_REPEAT_5(op); op(6)
#define VPP_REPEAT_7(op)        VPP_REPEAT_6(op); op(7)
#define VPP_REPEAT_8(op)        VPP_REPEAT_7(op); op(8)
#define VPP_REPEAT_9(op)        VPP_REPEAT_8(op); op(9)
#define VPP_REPEAT_10(op)       VPP_REPEAT_9(op); op(10)
#define VPP_REPEAT_11(op)       VPP_REPEAT_10(op); op(11)
#define VPP_REPEAT_12(op)       VPP_REPEAT_11(op); op(12)
#define VPP_REPEAT_13(op)       VPP_REPEAT_12(op); op(13)
#define VPP_REPEAT_14(op)       VPP_REPEAT_13(op); op(14)
#define VPP_REPEAT_15(op)       VPP_REPEAT_14(op); op(15)
#define VPP_REPEAT_16(op)       VPP_REPEAT_15(op); op(16)
#define VPP_REPEAT(op, times)   VPP_REPEAT_##times(op)

/* clang-format on */

static inline int add_many(int a, int num)
{
#define DO_ONE(i)                              \
    {                                          \
        if (i > num) {                         \
            break;                             \
        }                                      \
        a = add_one(a);                        \
        printf("[%i]add one -> %d .\n", i, a); \
    }

    do {
        VPP_REPEAT(DO_ONE, 16);
    } while (0);
    printf("Add many over.\n");

    return a;
}

static inline int add_much(int a)
{
    a = add_many(a, a);
    a = add_many(a, a);
    return a;
}

int main(void)
{
    int a = 10;
    a     = add_many(a, 16);
    return add_much(a);
}

Dieser Code verwendet Makros, um Funktionen zu wiederholen. Wenn keine Optimierungsstufe vorhanden ist, werden Makros standardmäßig erweitert, 16 add_oneFunktionen in add_manydie Funktion eingefügt und dann einzeln aufgerufen. Nachdem die Optimierung der Kompilierung aktiviert wurde, add_onewird alles inline und add_manyauch in die Hauptfunktion eingebunden. Die endgültige Binärdatei enthält keine add_onekaufmännischen Und-Zeichen add_many. wie folgt:

ubuntu->gcc-test:$ gcc -o simple-inline.out simple-inline.c -std=c99 -fopt-info-inline -Winline -O1
simple-inline.c:50:9: optimized:  Inlining add_one/13 into add_many/14.
simple-inline.c:50:9: optimized:  Inlining add_one/13 into add_many/14.
simple-inline.c:50:9: optimized:  Inlining add_one/13 into add_many/14.
simple-inline.c:50:9: optimized:  Inlining add_one/13 into add_many/14.
simple-inline.c:50:9: optimized:  Inlining add_one/13 into add_many/14.
simple-inline.c:50:9: optimized:  Inlining add_one/13 into add_many/14.
simple-inline.c:50:9: optimized:  Inlining add_one/13 into add_many/14.
simple-inline.c:50:9: optimized:  Inlining add_one/13 into add_many/14.
simple-inline.c:50:9: optimized:  Inlining add_one/13 into add_many/14.
simple-inline.c:50:9: optimized:  Inlining add_one/13 into add_many/14.
simple-inline.c:50:9: optimized:  Inlining add_one/13 into add_many/14.
simple-inline.c:50:9: optimized:  Inlining add_one/13 into add_many/14.
simple-inline.c:50:9: optimized:  Inlining add_one/13 into add_many/14.
simple-inline.c:50:9: optimized:  Inlining add_one/13 into add_many/14.
simple-inline.c:50:9: optimized:  Inlining add_one/13 into add_many/14.
simple-inline.c:50:9: optimized:  Inlining add_one/13 into add_many/14.
simple-inline.c:60:13: optimized:  Inlined add_many/14 into main/15 which now has time 23.273261 and size 26, net change of -115.
simple-inline.c: In function ‘main’:
simple-inline.c:38:19: warning: inlining failed in call to ‘add_many’: call is unlikely and code size would grow [-Winline]
   38 | static inline int add_many(int a, int num)
      |                   ^~~~~~~~
simple-inline.c:67:13: note: called from here
   67 |     a     = add_many(a, 16);
      |             ^~~~~~~~~~~~~~~
simple-inline.c:38:19: warning: inlining failed in call to ‘add_many’: --param max-inline-insns-single limit reached [-Winline]
   38 | static inline int add_many(int a, int num)
      |                   ^~~~~~~~
simple-inline.c:60:9: note: called from here
   60 |     a = add_many(a, a);
      |         ^~~~~~~~~~~~~~
simple-inline.c:38:19: warning: inlining failed in call to ‘add_many’: --param max-inline-insns-single limit reached [-Winline]
   38 | static inline int add_many(int a, int num)
      |                   ^~~~~~~~
simple-inline.c:59:9: note: called from here
   59 |     a = add_many(a, a);

In der obigen Ausgabe der Kompilierungsinformationen weist die Warnmeldung darauf hin, dass das Inlining der Funktion fehlgeschlagen ist, weil die Codegröße nach dem Inlining der Funktion zu stark angestiegen ist und den Grenzwert überschritten hat, was zu einem Inlining-Fehler geführt hat. Wenn diese Situation auftritt __attribute__((always_inline)), können Sie Inlining erzwingen, indem Sie ein Makro definieren, um Inlining bei Bedarf zu erreichen (kein Inlining bei niedriger Priorität, Inlining bei Bedarf bei hoher Priorität) .

#ifdef XXX
#define __myapp_inline __attribute__((always_inline))
#else
#define __myapp_inline
#endif

Diese Kompilierungsausgabeinformationen geben auch den Inlining-Prozess an. Für add_manyFunktionen gibt es 16 add_oneFunktionsaufrufe, sie werden also 16 Mal inline. Aber tatsächlich sind , numund ibeide Konstanten. Wenn die Inline- Funktion zur Summenfunktion add_manyhinzugefügt wird , werden die redundanten Funktionen automatisch gelöscht. Das Folgende sind die Ausgabe der Kompilierungsinformationen und die entsprechenden Demontageanweisungen nach Änderung einiger Codes:mainadd_much

Ändern Sie einige konstante Werte. Zu diesem Zeitpunkt kann der Compiler den toten Code selbst ausschneiden und dadurch die Codegröße reduzieren.

static inline int add_much(int a)
{
    a = add_many(a, 1);
    a = add_many(a, 1);
    return a;
}

int main(void)
{
    int a = 10;
    a     = add_many(a, 4);
    return add_much(a);
}

Ausgabe der Zusammenstellungsinformationen:

ubuntu->gcc-test:$ gcc -o simple-inline.out simple-inline.c -std=c99 -fopt-info-inline -Winline -O1
simple-inline.c:50:9: optimized:  Inlining add_one/13 into add_many/14.
simple-inline.c:50:9: optimized:  Inlining add_one/13 into add_many/14.
simple-inline.c:50:9: optimized:  Inlining add_one/13 into add_many/14.
simple-inline.c:50:9: optimized:  Inlining add_one/13 into add_many/14.
simple-inline.c:50:9: optimized:  Inlining add_one/13 into add_many/14.
simple-inline.c:50:9: optimized:  Inlining add_one/13 into add_many/14.
simple-inline.c:50:9: optimized:  Inlining add_one/13 into add_many/14.
simple-inline.c:50:9: optimized:  Inlining add_one/13 into add_many/14.
simple-inline.c:50:9: optimized:  Inlining add_one/13 into add_many/14.
simple-inline.c:50:9: optimized:  Inlining add_one/13 into add_many/14.
simple-inline.c:50:9: optimized:  Inlining add_one/13 into add_many/14.
simple-inline.c:50:9: optimized:  Inlining add_one/13 into add_many/14.
simple-inline.c:50:9: optimized:  Inlining add_one/13 into add_many/14.
simple-inline.c:50:9: optimized:  Inlining add_one/13 into add_many/14.
simple-inline.c:50:9: optimized:  Inlining add_one/13 into add_many/14.
simple-inline.c:50:9: optimized:  Inlining add_one/13 into add_many/14.
simple-inline.c:60:9: optimized:  Inlined add_many/49 into add_much/15 which now has time 33.222000 and size 16, net change of +5.
simple-inline.c:59:9: optimized:  Inlined add_many/51 into add_much/15 which now has time 38.444000 and size 21, net change of +5.
simple-inline.c:68:12: optimized:  Inlined add_much/15 into main/16 which now has time 51.444000 and size 25, net change of -6.
simple-inline.c:67:13: optimized:  Inlined add_many/14 into main/16 which now has time 59.717261 and size 44, net change of -115.

Der endgültige Hauptfunktionscode (bitte beachten Sie, dass andere Funktionen (add_one/add_many/add_much alle erfolgreich eingebunden wurden)):

0000000000001149 <main>:
    1149:	f3 0f 1e fa          	endbr64 
    114d:	55                   	push   %rbp
    114e:	53                   	push   %rbx
    114f:	48 83 ec 08          	sub    $0x8,%rsp
    1153:	b9 0b 00 00 00       	mov    $0xb,%ecx
    1158:	ba 01 00 00 00       	mov    $0x1,%edx
    115d:	48 8d 1d a0 0e 00 00 	lea    0xea0(%rip),%rbx        # 2004 <_IO_stdin_used+0x4>
    1164:	48 89 de             	mov    %rbx,%rsi
    1167:	bf 01 00 00 00       	mov    $0x1,%edi
    116c:	b8 00 00 00 00       	mov    $0x0,%eax
    1171:	e8 da fe ff ff       	call   1050 <__printf_chk@plt>
    1176:	b9 0c 00 00 00       	mov    $0xc,%ecx
    117b:	ba 02 00 00 00       	mov    $0x2,%edx
    1180:	48 89 de             	mov    %rbx,%rsi
    1183:	bf 01 00 00 00       	mov    $0x1,%edi
    1188:	b8 00 00 00 00       	mov    $0x0,%eax
    118d:	e8 be fe ff ff       	call   1050 <__printf_chk@plt>
    1192:	b9 0d 00 00 00       	mov    $0xd,%ecx
    1197:	ba 03 00 00 00       	mov    $0x3,%edx
    119c:	48 89 de             	mov    %rbx,%rsi
    119f:	bf 01 00 00 00       	mov    $0x1,%edi
    11a4:	b8 00 00 00 00       	mov    $0x0,%eax
    11a9:	e8 a2 fe ff ff       	call   1050 <__printf_chk@plt>
    11ae:	b9 0e 00 00 00       	mov    $0xe,%ecx
    11b3:	ba 04 00 00 00       	mov    $0x4,%edx
    11b8:	48 89 de             	mov    %rbx,%rsi
    11bb:	bf 01 00 00 00       	mov    $0x1,%edi
    11c0:	b8 00 00 00 00       	mov    $0x0,%eax
    11c5:	e8 86 fe ff ff       	call   1050 <__printf_chk@plt>
    11ca:	48 8d 2d 48 0e 00 00 	lea    0xe48(%rip),%rbp        # 2019 <_IO_stdin_used+0x19>
    11d1:	48 89 ee             	mov    %rbp,%rsi
    11d4:	bf 01 00 00 00       	mov    $0x1,%edi
    11d9:	b8 00 00 00 00       	mov    $0x0,%eax
    11de:	e8 6d fe ff ff       	call   1050 <__printf_chk@plt>
    11e3:	b9 0f 00 00 00       	mov    $0xf,%ecx
    11e8:	ba 01 00 00 00       	mov    $0x1,%edx
    11ed:	48 89 de             	mov    %rbx,%rsi
    11f0:	bf 01 00 00 00       	mov    $0x1,%edi
    11f5:	b8 00 00 00 00       	mov    $0x0,%eax
    11fa:	e8 51 fe ff ff       	call   1050 <__printf_chk@plt>
    11ff:	48 89 ee             	mov    %rbp,%rsi
    1202:	bf 01 00 00 00       	mov    $0x1,%edi
    1207:	b8 00 00 00 00       	mov    $0x0,%eax
    120c:	e8 3f fe ff ff       	call   1050 <__printf_chk@plt>
    1211:	b9 10 00 00 00       	mov    $0x10,%ecx
    1216:	ba 01 00 00 00       	mov    $0x1,%edx
    121b:	48 89 de             	mov    %rbx,%rsi
    121e:	bf 01 00 00 00       	mov    $0x1,%edi
    1223:	b8 00 00 00 00       	mov    $0x0,%eax
    1228:	e8 23 fe ff ff       	call   1050 <__printf_chk@plt>
    122d:	48 89 ee             	mov    %rbp,%rsi
    1230:	bf 01 00 00 00       	mov    $0x1,%edi
    1235:	b8 00 00 00 00       	mov    $0x0,%eax
    123a:	e8 11 fe ff ff       	call   1050 <__printf_chk@plt>
    123f:	b8 10 00 00 00       	mov    $0x10,%eax
    1244:	48 83 c4 08          	add    $0x8,%rsp
    1248:	5b                   	pop    %rbx
    1249:	5d                   	pop    %rbp
    124a:	c3                   	ret    

Sie können printfdies einfach anhand der Anzahl der Aufrufe der Funktion beurteilen. Insgesamt gibt es 9 Aufrufe, was mit der tatsächlichen Anzahl der Aufrufe im Code übereinstimmt (2 + 2 + 5 = 9) . Im Vergleich zum Abrollen von Nicht-Inline-Funktionen reduziert entpackter Code die tatsächliche Codepfadlänge auf jedem Aufrufpfad und macht den Code effizienter. In Bezug auf den Ausdruck der oberen Ebene (C-Sprachcodierungsebene) spiegelt es auch die abstrakte Einheit wider (es gibt nur eine add_one-Funktionsdefinition, aber entsprechende spezifische Ausführungscodes können entsprechend unterschiedlichen Aufrufumgebungen generiert werden) .

3. Konstante Modifikation und Inline-Funktionen (const&inline)

Basierend auf der obigen Diskussion kann festgestellt werden, dass der Schlüsselfaktor für Inline-Funktionen zur Reduzierung von totem Code und zur Implementierung von Spezialcode darin besteht, dass die relevanten Code-Operationsdaten konstant sind . Wenn es sich jedoch nur um eine Literalkonstante handelt, ist ihre Wirkung tatsächlich sehr begrenzt. Es ist unmöglich, Makrooperationen zum Schreiben komplexer Codes zu verwenden. In diesem Fall ist es besser, Makrosprachen (M4/Bison/Flex, usw.), um sie umzusetzen.

In diesem Kapitel wird hauptsächlich constdie Beziehung zwischen Konstantenmodifikatoren und der Inline-Erweiterung von Funktionen analysiert. Dabei werden die Techniken zur Verwendung von Konstantenkonstanten zur Implementierung von Compilerberechnungen und zur Inline-Erweiterung von Funktionen zusammengefasst.

3.1 Berechnung der Kompilierungszeit

Die Berechnung zur Kompilierungszeit ist eine allgemeine und effiziente Methode zur Codeoptimierung. Auf der C-Codeebene müssen wir ein möglichst hohes Maß an Abstraktion und Einheit aufrechterhalten, sodass der tatsächliche Wert häufig nicht berechnet wird (wir müssen die Berechnung abstrahieren). Prozess und nicht Ergebnis, weil Das Ergebnis ist ein spezialisierter Wert, aber der Prozess ist abstrakt einheitlich. Hier ist ein typisches Beispiel:

int multiple(int a, int b) { return a * b; }
static __always_inline multiple2(int a, int b) { return a * b; }
#define multiple3(a, b) ((a) * (b))

Es gibt drei verschiedene Multiplikationsprozessdefinitionen. Die erste ist eine normale Funktionsdefinition, die ein bestimmtes Abstraktionsniveau erreichen kann, aber auch die Mängel liegen auf der Hand: Es gibt Funktionsaufrufe, und im Fall von Konstanten ist die Berechnung zur Kompilierungszeit nicht möglich durchgeführt werden. Wenn jedoch die Kompilierung mit hoher Optimierungsstufe aktiviert ist, sind die Funktionen von multiple und multiple2 gleich. Der Wert wird während der Kompilierung bestimmt und der Zwischenprozess wird direkt weggelassen.

Interessant ist hier der Unterschied zwischen multiple3 und multiple2. Die Form der Makrodefinition weist den höchsten Abstraktionsgrad auf, da keine Berücksichtigung der Typdefinition erforderlich ist (Variablentyp ist ebenfalls eine Art Abstraktion und von hoher Ordnung). Abstraktion) .

Manche Leute denken vielleicht, dass die C-Sprache als typische prozessorientierte Programmiersprache häufig abstrakte Prozesse hervorheben muss ? Tatsächlich bestimmt der Abstraktionsprozess aus logischer Sicht die Attribute und Methoden eines Objekts. Wenn wir von den Teildaten des Objekts ausgehen, können wir über die Attribute und Methoden die gewünschten Ergebnisse erzielen. Bei der prozessorientierten Programmierung wird der Prozess der Problemlösung direkt durch Attribute und Methoden beschrieben, so dass es sich oft nur um eine Lösung unter bestimmten Umständen handelt.

Schauen wir uns multiple3den Prozess noch einmal an. Dieser Teil des Codes implementiert die Abschirmung von Variablentypen auf einer höheren Ebene. Wenn die Fehlerbehandlung wie verschiedene Arten von Überläufen nicht berücksichtigt wird, ist die Definition natürlich multiple3besser. multiple2Der Vorteil von Funktionen besteht darin, dass sie typbewusst sind, sodass einige Fehlerbeurteilungen direkt für ganzzahlige Typen vorgenommen werden können.

Werfen wir einen Blick auf die folgenden Aufrufe (es gibt 5 Kategorien, Variablen können ignoriert werden, Zeiger können unendlich verschachtelt werden, aber die rekursive Logik basiert nur auf diesen drei grundlegenden Zeigern):

// (1) 直接填字面量常数(包括宏常量/枚举)
multiple(1, 2);
// (2) 填入const常量
const int k = 2;
multiple(1, k);
// (3) 填入const常量指针(指向常量的指针)
const int *kp1 = &k;
multiple(1, *kp1);
// (4) 填入const指针常量(指向变量的指针常量)
int * const kp2 = &k;
multiple(1, *kp2);
// (5) 填入const常量的const指针常量(指向常量的指针常量)
const int * const kp3 = &k;
multiple(1, *kp3);

Es besteht keine Notwendigkeit, die Variablensituation zu berücksichtigen. **„Definitiv“** kann zur Kompilierungszeit nicht berechnet werden (ein fester Wert bedeutet nicht, dass er nicht geändert werden kann. Sie können den Wert direkt über die Adresse ändern und die Kompilierungszeit ausführen Berechnungsoptimierung für die Variable, was zu Problemen führen wird. Darüber hinaus werden in diesem Artikel nur Konstanten behandelt .

Die obige Definition hat auch ein Schlüsselattribut: Umfang . Im Allgemeinen gibt es drei Typen: global (global), lokal (statisch) und funktionslokal (Stapelvariable). Die Geltungsbereiche sind unterschiedlich, und auch die oben dargestellten Regeln sind unterschiedlich. Die globale und lokale Leistung sind konsistent, das heißt, von den oben genannten fünf Beispielen kann nur das dritte Beispiel (Ausfüllen eines konstanten Zeigers (Zeiger auf eine Konstante)) zur Kompilierungszeit nicht berechnet werden. Für den lokalen Funktionsbereich können alle fünf Beispiele zur Kompilierungszeit berechnet werden. Zusammengefasst wie folgt:

  • Die Berechnung von Konstanten zur Kompilierungszeit ist grundsätzlich dieselbe wie die von Literalkonstanten. Der Berechnungsprozess zur Kompilierungszeit kann durch die Verwendung komplexerer konstanter Datenstrukturen realisiert werden, um den Berechnungsaufwand während der Ausführung des Codes zu reduzieren.
  • Im vierten Beispiel gibt es tatsächlich ein Problem mit der Definition der Initialisierung, da der Zeigertyp nicht mit dem Typ des Initialisierungswerts übereinstimmt. Unter normalen Umständen kann er nicht auf diese Weise definiert werden und es wird ein Fehler gemeldet.
  • Das fünfte Beispiel ist die am besten geeignete Definition. Nicht nur der Zeiger muss eine Konstante sein, sondern auch der Wert, auf den gezeigt wird, sollte eine Konstante sein, um eine konstante Übertragung zu erreichen. Hier ist eine einfache Konstante. Wenn es um mehrdimensionale Array-Definitionen geht, es wird komplizierter sein.
  • (In einigen Compilern niedrigerer Versionen) können const-Konstanten nicht auf alle Dateninitialisierungsszenarien angewendet werden, daher müssen Daten in einigen Fällen über Zeiger übergeben werden.
3.2 Ständiges Zweigurteil

Oben wurde gezeigt, dass Konstanten direkt zur Kompilierungszeit berechnet werden können, ohne dass der Inhalt immer neu berechnet werden muss. Im Folgenden erfahren Sie, wie Sie mithilfe der konstanten Verzweigungsbeurteilung nutzlosen toten Code reduzieren. Der folgende Code:

static inline int test_const_judge(const int condition)
{
    if (condition < 1) {
        return 0x2222;
    } else if (condition < 128) {
        return 0x3333;
    } else {
        return 0x4444;
    }
}

int main(void)
{
    int a = 10;
    return test_const_judge(a);
}

Verwenden Sie die folgenden Kompilierungsoptionen:

 gcc -o simple-inline.out simple-inline.c -std=c99 -fopt-info-inline -Winline -O1

Im endgültig generierten Code ist test_const_judge in die Hauptfunktion eingebunden und diese nutzlosen Beurteilungscodes und Anweisungen wurden entfernt:

00000000000011e8 <main>:
    11e8:	f3 0f 1e fa          	endbr64 
    11ec:	b8 33 33 00 00       	mov    $0x3333,%eax
    11f1:	c3                   	ret    

Wie Sie sehen, ist der endgültige Assemblercode sehr prägnant und gibt das Ergebnis direkt zurück . Eine effektivere Möglichkeit besteht darin, es mit einem konstanten Array zu verwenden, um einen erweiterten Datenberechnungsprozess zur Kompilierungszeit zu implementieren.

const int test_data[] = { 0x1111, 0x2222, 0x3333, 0x4444 };

static inline int test_const_judge(const int condition)
{
    if (condition >= sizeof(test_data)/sizeof(test_data[0])) {
        return 0xffff;
    }
    return test_data[condition];
}

int main(void)
{
    int a = 10;
    return test_const_judge(a);
}

Das Beispiel hier ist sehr einfach und zeigt die Auswirkung der Verwendung von const- und Inline-Funktionen zur Implementierung einer Berechnung zur Kompilierungszeit. Der entsprechende Wert wird direkt aus dem tatsächlichen Code abgerufen, ohne dass die Adresse beurteilt und abgerufen werden muss. Die Demontageanweisungen lauten wie folgt:

00000000000011e8 <main>:
    11e8:	f3 0f 1e fa          	endbr64 
    11ec:	b8 ff ff 00 00       	mov    $0xffff,%eax
    11f1:	c3                   	ret    
3.3 Konstantes Funktions-Inlining

Hier ist ein erweitertes Beispiel, das zunächst add_oneüber die Eigenschafteneinstellungen des Compilers einen weiteren Alias ​​für die Funktion generiert:

static inline int add_one(int ori)
{
    return ori + 1;
}

/* 如果没有static声明, 会生成外部符号 */
static __typeof__(add_one) add_one_2 __attribute__((alias("add_one")));

int main(void)
{
    int a = 10;
    return add_one_2(a);
}

Der Aufruf einer Funktion über einen Alias ​​wird ebenfalls wie folgt inline:

00000000000011e8 <main>:
    11e8:	f3 0f 1e fa          	endbr64 
    11ec:	b8 0b 00 00 00       	mov    $0xb,%eax
    11f1:	c3                   	ret    

Daher kann davon ausgegangen werden, dass der Compiler eine Inline-Optimierung für Funktionsaufrufe durchführen kann, die zur Kompilierungszeit bestimmt werden können, selbst wenn die Funktion über eine Funktionsvariable angegeben wird . Hier ist ein komplexes Beispiel:

#include <stdio.h>

static inline int add_one(int ori)
{
    return ori + 1;
}

/* clang-format off */

static const struct sss {
    __typeof__(&add_one) aaa;
} aaa_ccc[] = {
    add_one, add_one, add_one, add_one, add_one, add_one, add_one, add_one,
};

static const struct sss aaa_ccc2[] = {
    add_one, add_one, add_one, add_one,
};

static const struct sss *const aaa_ccc_xxx[] = { aaa_ccc2, aaa_ccc, NULL};
#define ss(v) sizeof(v) / sizeof(v[0])
static const int aaa_ccc_sss[] = {ss(aaa_ccc2), ss(aaa_ccc), 0};

#define VPP_REPEAT_1(op)        op(1)
#define VPP_REPEAT_2(op)        VPP_REPEAT_1(op); op(2)
#define VPP_REPEAT_3(op)        VPP_REPEAT_2(op); op(3)
#define VPP_REPEAT_4(op)        VPP_REPEAT_3(op); op(4)
#define VPP_REPEAT_5(op)        VPP_REPEAT_4(op); op(5)
#define VPP_REPEAT_6(op)        VPP_REPEAT_5(op); op(6)
#define VPP_REPEAT_7(op)        VPP_REPEAT_6(op); op(7)
#define VPP_REPEAT_8(op)        VPP_REPEAT_7(op); op(8)
#define VPP_REPEAT_9(op)        VPP_REPEAT_8(op); op(9)
#define VPP_REPEAT_10(op)       VPP_REPEAT_9(op); op(10)
#define VPP_REPEAT_11(op)       VPP_REPEAT_10(op); op(11)
#define VPP_REPEAT_12(op)       VPP_REPEAT_11(op); op(12)
#define VPP_REPEAT_13(op)       VPP_REPEAT_12(op); op(13)
#define VPP_REPEAT_14(op)       VPP_REPEAT_13(op); op(14)
#define VPP_REPEAT_15(op)       VPP_REPEAT_14(op); op(15)
#define VPP_REPEAT_16(op)       VPP_REPEAT_15(op); op(16)
#define VPP_REPEAT(op, times)   VPP_REPEAT_##times(op)

/* clang-format on */

__always_inline static inline int add_many(int a, int b)
{
#define DO_ONE(i)                              \
    {                                          \
        if (i > aaa_ccc_sss[b]) {              \
            break;                             \
        }                                      \
        a = aaa_ccc_xxx[b][i].aaa(a);          \
        printf("[%i]add one -> %d .\n", i, a); \
    }

    do {
        VPP_REPEAT(DO_ONE, 16);
    } while (0);
    printf("Add many over.\n");

    return a;
}

static inline int add_much(int a)
{
    return add_many(a, 1);
}

int main(void)
{
    int a = 10;
    a = add_much(a);
    return add_many(a, 2);
}

In diesem Code ist die tatsächliche Anzahl add_manyder eingefügten Funktionen eine feste Zahl, sodass der endgültig generierte Code in die Hauptfunktion eingefügt wird und es keine redundanten Funktionsaufrufe gibt. Die Ausgabe der Kompilierungsinformationen lautet wie folgt:

ubuntu->gcc-test:$ gcc -o simple-inline.out simple-inline.c -std=c99 -fopt-info-inline -Winline -O1
simple-inline.c:66:5: optimized:   Inlining printf/7 into add_many/18 (always_inline).
simple-inline.c:64:9: optimized:   Inlining printf/7 into add_many/18 (always_inline).
simple-inline.c:64:9: optimized:   Inlining printf/7 into add_many/18 (always_inline).
simple-inline.c:64:9: optimized:   Inlining printf/7 into add_many/18 (always_inline).
simple-inline.c:64:9: optimized:   Inlining printf/7 into add_many/18 (always_inline).
simple-inline.c:64:9: optimized:   Inlining printf/7 into add_many/18 (always_inline).
simple-inline.c:64:9: optimized:   Inlining printf/7 into add_many/18 (always_inline).
simple-inline.c:64:9: optimized:   Inlining printf/7 into add_many/18 (always_inline).
simple-inline.c:64:9: optimized:   Inlining printf/7 into add_many/18 (always_inline).
simple-inline.c:64:9: optimized:   Inlining printf/7 into add_many/18 (always_inline).
simple-inline.c:64:9: optimized:   Inlining printf/7 into add_many/18 (always_inline).
simple-inline.c:64:9: optimized:   Inlining printf/7 into add_many/18 (always_inline).
simple-inline.c:64:9: optimized:   Inlining printf/7 into add_many/18 (always_inline).
simple-inline.c:64:9: optimized:   Inlining printf/7 into add_many/18 (always_inline).
simple-inline.c:64:9: optimized:   Inlining printf/7 into add_many/18 (always_inline).
simple-inline.c:64:9: optimized:   Inlining printf/7 into add_many/18 (always_inline).
simple-inline.c:64:9: optimized:   Inlining printf/7 into add_many/18 (always_inline).
simple-inline.c:73:12: optimized:   Inlining add_many/18 into add_much/19 (always_inline).
simple-inline.c:136:12: optimized:   Inlining add_many/18 into main/20 (always_inline).
simple-inline.c:64:9: optimized:  Inlined add_one/39 into add_much/19 which now has time 216.000000 and size 70, net change of -2.
simple-inline.c:64:9: optimized:  Inlined add_one/40 into add_much/19 which now has time 205.000000 and size 68, net change of -2.
simple-inline.c:64:9: optimized:  Inlined add_one/41 into add_much/19 which now has time 194.000000 and size 66, net change of -2.
simple-inline.c:64:9: optimized:  Inlined add_one/42 into add_much/19 which now has time 183.000000 and size 64, net change of -2.
simple-inline.c:64:9: optimized:  Inlined add_one/43 into add_much/19 which now has time 172.000000 and size 62, net change of -2.
simple-inline.c:64:9: optimized:  Inlined add_one/44 into add_much/19 which now has time 161.000000 and size 60, net change of -2.
simple-inline.c:64:9: optimized:  Inlined add_one/13 into add_much/19 which now has time 150.000000 and size 58, net change of -6.
simple-inline.c:135:9: optimized:  Inlined add_much/19 into main/20 which now has time 161.000000 and size 60, net change of -7.

Die eigentliche Assembler-Code-Ausgabe lautet wie folgt (nur Hauptfunktion):

0000000000001149 <main>:
    1149:	f3 0f 1e fa          	endbr64 
    114d:	55                   	push   %rbp
    114e:	53                   	push   %rbx
    114f:	48 83 ec 08          	sub    $0x8,%rsp
    1153:	b9 0b 00 00 00       	mov    $0xb,%ecx
    1158:	ba 01 00 00 00       	mov    $0x1,%edx
    115d:	48 8d 1d a0 0e 00 00 	lea    0xea0(%rip),%rbx        # 2004 <_IO_stdin_used+0x4>
    1164:	48 89 de             	mov    %rbx,%rsi
    1167:	bf 01 00 00 00       	mov    $0x1,%edi
    116c:	b8 00 00 00 00       	mov    $0x0,%eax
    1171:	e8 da fe ff ff       	call   1050 <__printf_chk@plt>
    1176:	b9 0c 00 00 00       	mov    $0xc,%ecx
    117b:	ba 02 00 00 00       	mov    $0x2,%edx
    1180:	48 89 de             	mov    %rbx,%rsi
    1183:	bf 01 00 00 00       	mov    $0x1,%edi
    1188:	b8 00 00 00 00       	mov    $0x0,%eax
    118d:	e8 be fe ff ff       	call   1050 <__printf_chk@plt>
    1192:	b9 0d 00 00 00       	mov    $0xd,%ecx
    1197:	ba 03 00 00 00       	mov    $0x3,%edx
    119c:	48 89 de             	mov    %rbx,%rsi
    119f:	bf 01 00 00 00       	mov    $0x1,%edi
    11a4:	b8 00 00 00 00       	mov    $0x0,%eax
    11a9:	e8 a2 fe ff ff       	call   1050 <__printf_chk@plt>
    11ae:	b9 0e 00 00 00       	mov    $0xe,%ecx
    11b3:	ba 04 00 00 00       	mov    $0x4,%edx
    11b8:	48 89 de             	mov    %rbx,%rsi
    11bb:	bf 01 00 00 00       	mov    $0x1,%edi
    11c0:	b8 00 00 00 00       	mov    $0x0,%eax
    11c5:	e8 86 fe ff ff       	call   1050 <__printf_chk@plt>
    11ca:	b9 0f 00 00 00       	mov    $0xf,%ecx
    11cf:	ba 05 00 00 00       	mov    $0x5,%edx
    11d4:	48 89 de             	mov    %rbx,%rsi
    11d7:	bf 01 00 00 00       	mov    $0x1,%edi
    11dc:	b8 00 00 00 00       	mov    $0x0,%eax
    11e1:	e8 6a fe ff ff       	call   1050 <__printf_chk@plt>
    11e6:	b9 10 00 00 00       	mov    $0x10,%ecx
    11eb:	ba 06 00 00 00       	mov    $0x6,%edx
    11f0:	48 89 de             	mov    %rbx,%rsi
    11f3:	bf 01 00 00 00       	mov    $0x1,%edi
    11f8:	b8 00 00 00 00       	mov    $0x0,%eax
    11fd:	e8 4e fe ff ff       	call   1050 <__printf_chk@plt>
    1202:	b9 11 00 00 00       	mov    $0x11,%ecx
    1207:	ba 07 00 00 00       	mov    $0x7,%edx
    120c:	48 89 de             	mov    %rbx,%rsi
    120f:	bf 01 00 00 00       	mov    $0x1,%edi
    1214:	b8 00 00 00 00       	mov    $0x0,%eax
    1219:	e8 32 fe ff ff       	call   1050 <__printf_chk@plt>
    121e:	bf 11 00 00 00       	mov    $0x11,%edi
    1223:	b8 00 00 00 00       	mov    $0x0,%eax
    1228:	ff d0                	call   *%rax
    122a:	89 c5                	mov    %eax,%ebp
    122c:	89 c1                	mov    %eax,%ecx
    122e:	ba 08 00 00 00       	mov    $0x8,%edx
    1233:	48 89 de             	mov    %rbx,%rsi
    1236:	bf 01 00 00 00       	mov    $0x1,%edi
    123b:	b8 00 00 00 00       	mov    $0x0,%eax
    1240:	e8 0b fe ff ff       	call   1050 <__printf_chk@plt>
    1245:	48 8d 1d cd 0d 00 00 	lea    0xdcd(%rip),%rbx        # 2019 <_IO_stdin_used+0x19>
    124c:	48 89 de             	mov    %rbx,%rsi
    124f:	bf 01 00 00 00       	mov    $0x1,%edi
    1254:	b8 00 00 00 00       	mov    $0x0,%eax
    1259:	e8 f2 fd ff ff       	call   1050 <__printf_chk@plt>
    125e:	48 89 de             	mov    %rbx,%rsi
    1261:	bf 01 00 00 00       	mov    $0x1,%edi
    1266:	b8 00 00 00 00       	mov    $0x0,%eax
    126b:	e8 e0 fd ff ff       	call   1050 <__printf_chk@plt>
    1270:	89 e8                	mov    %ebp,%eax
    1272:	48 83 c4 08          	add    $0x8,%rsp
    1276:	5b                   	pop    %rbx
    1277:	5d                   	pop    %rbp
    1278:	c3                   	ret    

Hier gibt es 10 printf-Funktionen, die der tatsächlichen Anzahl der Funktionen im Array und der entsprechenden Indexbeziehung entsprechen. Der redundante nutzlose Funktionscode wurde herausgeschnitten . Es gibt viele ähnliche Operationen, daher werde ich sie hier nicht alle auflisten. Der beste Weg, um festzustellen, ob eine Funktion inline ist, ist der Assembler-Code.

4. Zusammenfassung

Beim Funktions-Inlining kann die erwartete konstante Erweiterung/Berechnung/Schneiden manchmal nicht abgeschlossen werden, selbst wenn Konstanten verwendet werden. In diesem Fall kann die Funktionsparameterdefinition wie folgt geändert werden:

int func (const int k) { aaa[k](...) };

Wenn die obige aaa[k]Funktion zur Kompilierungszeit nicht erweitert oder bestimmt werden kann, möchten Sie möglicherweise aaadie Definition in eine Konstante ändern und die Definition wie folgt ändern:

int func (const __typeof__(aaa[0]) func) { func(...) };

Durch die oben beschriebene Änderung können Sie das Problem, dass einige Konstantenarrays nicht erweitert werden können, effektiv lösen. Bei Inline-Funktionen spielt es keine Rolle, ob mehr Parameter vorhanden sind, da nach dem Inlining kein Overhead für das Ein- und Austreten von Parametern in den Stapel entsteht .

Häufiger müssen Probleme flexibel gehandhabt werden, beispielsweise durch die Verwendung von Makros und Inline-Assembly. Weitere Details zu Inline-Funktionen müssen im täglichen Gebrauch weiter zusammengefasst werden.

Supongo que te gusta

Origin blog.csdn.net/Once_day/article/details/132252377
Recomendado
Clasificación