Heim > Artikel > Web-Frontend > Erweiterungsproblem durch die Verwendung von Generika in C
Vor ein paar Tagen las ein Blogger einen Artikel, in dem er die Generika von C dafür kritisierte, dass sie aufgeblähten Code in der generierten ausführbaren Datei verursachten.
Der Blogger beschäftigt sich seit vielen Jahren mit der Entwicklung von C-Software. Da die vorherige Entwicklungsumgebung ein Server mit ausreichenden Ressourcen war, musste man sich keine Gedanken über den Speicherplatz machen. Kürzlich habe ich geplant, C für die Entwicklung auf der eingebetteten Plattform des Smart-Home-Hosts zu verwenden. Der FLASH-Speicherplatz ist begrenzt, dies ist ein Faktor, der berücksichtigt und beachtet werden muss.
Definieren Sie zwei Listen wie folgt mit unterschiedlichen Elementtypen:
list<int> l1; list<string> l2;
Was sollten Sie tun, wenn Sie die Sprache C verwenden? Es wird ein Codesatz für listbd43222e33876353aff11e13a7dc75f6 und ein weiterer Codesatz für list98c455a79ddfebb79781bff588e7b37e geschrieben. Jeder Satz hat die gleichen Mitgliedsfunktionen, nur unterschiedliche Variablentypen.
Das Folgende ist die C-Sprachimplementierung von listbd43222e33876353aff11e13a7dc75f6:
//! code-1struct list_int_item { int value; struct list_int_item *next; };struct list_int { struct list_int_item *head; size_t size; }; void list_int_insert(struct list_int *p, int value);int list_int_sort(struct list_int *p);bool list_int_empty(struct list_int *p); ...
Das Folgende ist die C-Sprachimplementierung von list98c455a79ddfebb79781bff588e7b37e:
//! code-2 struct list_string_item { string value; struct list_string_item *next; }; struct list_string { struct list_string_item *head; size_t size; }; void list_string_insert(struct list_int *p, string value); int list_string_sort(struct list_int *p); bool list_string_empty(struct list_int *p); ...
Beide Der Unterschied liegt in der Art. In der C-Sprache verwenden wir häufig Makros, um die Typen wie folgt zu ersetzen:
//! code-3 #define LIST_DECLARE(TYPE) \ struct list_##TYPE##_item { \ TYPE## value; \ struct list_##TYPE##_item *next; \ }; \ \ struct list_##TYPE { \ struct list_##TYPE##_item *head; \ size_t size; \ }; \ \ void list_##TYPE##_insert(struct list_##TYPE *p, ##TYPE## value); \ int list_##TYPE##_sort(struct list_##TYPE *p); \ bool list_##TYPE##_empty(struct list_##TYPE *p); \ ...
Dann ist list229a20c20174f89abe8fab2ad31639d8 in der Header-Datei wie folgt definiert:
//! code-4 LIST_DECLARE(double)
Daher ist es unvermeidlich, dass Generika redundanten Code erzeugen, zumindest ist die Verwendung von C zur Erstellung solcher Generika unvermeidlich.
Da es unvermeidbar ist, wollen wir sehen, wie wir die oben genannten Probleme so weit wie möglich vermeiden können. In „Effective C“ gibt es ein Kapitel, in dem ausdrücklich erwähnt wird: Verwenden Sie keine unnötigen Parameter in Vorlagen. Weil der Compiler für jeden Parameter einen entsprechenden Codesatz generiert.
Wenn der Code nur einen Datentyp enthält, generiert der Compiler dann nur einen Satz verwandter Codes, selbst wenn mehrere Variablen mit diesem Typ definiert sind? (So sollte es sein).
Schreiben Sie ein Beispiel zum Vergleich: (ohne unnötigen Code)
test1.cpp, das nur mapd0cb00eb53176e0fb76e0d413b9941f1 enthält, aber m1, m2, m3 definiert sind.
//! code-5 map<int, string> m1; map<int, string> m2; map<int, string> m3; m1.insert(std::make_pair(1, "hello")); m2.insert(std::make_pair(1, "hi")); m3.insert(std::make_pair(1, "lichunjun"));
test2.cpp, verglichen mit test1.cpp, gibt es drei Typen:
//! code-6 map<int, string> m1; map<int, double> m2; map<int, int> m3; m1.insert(std::make_pair(1, "hello")); m2.insert(std::make_pair(1, 1.2)); m3.insert(std::make_pair(1, 44));
Als Ergebnis wird die Größe der kompilierten ausführbaren Datei verglichen:
[hevake_lcj@Hevake tmp]$ ll test1 test2 -rwxrwxr-x. 1 18784 Mar 19 22:01 test1 -rwxrwxr-x. 1 35184 Mar 19 22:03 test2
Test2 ist doppelt so groß wie Test1. Der Grund dafür muss natürlich nicht genannt werden.
Noch eine Frage: Werden Zeiger als Typ betrachtet?
Die obige Listebd43222e33876353aff11e13a7dc75f6 und list98c455a79ddfebb79781bff588e7b37e Daher müssen zwei Codesätze generiert werden, um dies zu erreichen.
Und Hinweise, egal was sie sind, sie sind gleich. Wir können void* verwenden, um alle Zeigertypen darzustellen.
Also haben wir den obigen Code geändert und erneut getestet:
//! code-7 map<int, string*> m1; map<int, string*> m2; map<int, string*> m3; m1.insert(std::make_pair(1, new string("hello"))); m2.insert(std::make_pair(1, new string("hi"))); m3.insert(std::make_pair(1, new string("lichunjun")));
und
//! code-8 map<int, string*> m1; map<int, double*> m2; map<int, int*> m3; m1.insert(std::make_pair(1, new string("hello"))); m2.insert(std::make_pair(1, new double(1.2))); m3.insert(std::make_pair(1, new int(44)));
Das Ergebnis sieht so aus:
-rwxrwxr-x. 1 18736 Mar 19 23:05 test1 -rwxrwxr-x. 1 35136 Mar 19 23:05 test2
Die erwarteten Ergebnisse von Test1 und Test2 sind ähnlich, aber es gibt keine Optimierung in Bezug auf die Ergebnisse~
Denke: Gibt es beliebige C Welche Parameter können dies optimieren?
Wenn nicht, können wir aus Platzgründen nur alle Zeiger als void*-Typ definieren und sie dann bei Verwendung umwandeln.
//! code-9 map<int, void*> m1; map<int, void*> m2; map<int, void*> m3; m1.insert(std::make_pair(1, new string("hello"))); m2.insert(std::make_pair(1, new double(1.2))); m3.insert(std::make_pair(1, new int(44))); cout << *static_cast<string*>(m1[1]) << endl; cout << *static_cast<double*>(m2[1]) << endl; cout << *static_cast<int*>(m3[1]) << endl;
Der obige Code basiert auf Code-8, definiert alle Spezifikationen als void* und verwendet static_cast, um bei Verwendung die Konvertierung in den entsprechenden Zeigertyp zu erzwingen.
Im Vergleich zu Code-7 beträgt die auf diese Weise erhaltene Codegröße nur 16 Byte mehr.
Aber dieser Ansatz ist sehr unerwünscht. Nachdem void*-Zeiger verwendet werden müssen, überprüft der Compiler den Typ nicht mehr und es ist leicht, den Typ zu verwechseln.