C zu C++ Projektmigration



  • Hallo, Ich plane eine mittelgroßes C Projekt auf C++ zu migrieren. Und als ersten Schritt erstmal .c Dateien in .cpp (und .h zu .hpp) Dateien umzubennen, damit der C++ Compiler diese baut und dann alle casts, compiler errors und warnings zu fixen.

    Ich mache mir aber Sorgen vor unsichtbaren Regressions, weil wie es so will ist das ganze natürlich mit 0 Unittests abgedeckt.

    Hat das schonmal jemand gemacht und Erfahrungen zu teilen oder tiefen Einblick in irgendwelche C zu C++ Edge cases, die ich nicht mit -Wall -Wextra -pedantic erwische?



  • Du willst aber dann auch die C++ Features (bzw. Typen) einbauen?
    Ich weiß nicht, ob du den aktuellen Thread Klassenfrage verfolgt hast - dort sind einige wichtige Punkte diesbzgl. angesprochen worden.



  • @5cript

    Keine leichte Aufgabe.

    Ich selbst hatte ein mittelgroßes Projekt von C nach C++ umgestellt und es war nicht so einfach wie es sich anhört. Manchmal hat C Code gerade wg. der Verwendung von Pointern heftige Seiteneffekte und diese schießen dir bei der Portierung in der Quere.

    Wichtig sind Testreihen und ein gutes Verständis von C++. Ein Beispiel, ich hatte memset durch die folgende Funktion ersetzt:

    template <typename T>
    void my_memset(T& Value)
    {
    	static_assert(std::is_standard_layout<T>::value, "T muss ein Standardlayout besitzen!");	// keine Klassenmember, keine private Member
    	static_assert(std::is_trivially_copyable<T>::value, "T muss trivial kopierbar sein!");		// keine virtual Funktionen
    	memset(&Value, 0, sizeof(T));
    }
    

    Dadurch vermied ich memset Aufrufe auf Nicht POD Klassen. Aber auch delete Funktionen sind da manchmal hilfreich:

    bool operator!=(std::string&, wchar_t* s) = delete;

    Eine Tücke in C ist die Allokation von Speicher und die Interpretation dieser als Struct. Dann hängt unter anderem die Lebenzeit des Struct von der Lebenszeit des allokierten Speichers ab.

    Was auch dir auffalllen wird, ist die vermehrte Verwendung von globalen Variabeln. Und nicht wenige von diesen sind nicht global, sondern Teil von XYZ. Ein geeignetes Design wird da wichtig.

    Und natürlich ist es wichtig die manuelle Speicherverwaltung zu minimieren.

    Und einmal ist mir eine Portierung fehlgeschlagen. Es dürfte sich um eine Variante der DirectX Klasse CArrayList gehandelt haben.



  • @Th69 Ja alles mit der Zeit, aber alles in einem rutsch neu machen ist nicht machbar im Zeitrahmen. Deswegen erstmal die Basis schaffen dass man C++ verwenden kann, ohne dass man sich auf C schnittstellen zwischen den Headern beschränkt, das wäre ja bescheuert. Und man dann refactored over time.

    @Quiche-Lorraine Danke. Ich hatte das einschleichen von non-POD in alten C code zb noch nicht bedacht.



  • @5cript
    Kennst du auch solche C Gemeinheiten? Ein Array der Größe 1?

    https://learn.microsoft.com/de-de/windows/win32/api/wingdi/ns-wingdi-bitmapinfo



  • @5cript sagte in C zu C++ Projektmigration:

    weil wie es so will ist das ganze natürlich mit 0 Unittests abgedeckt.

    Das ist die Stelle, an der ich ansetzen würde erstmal. Wenn man eine halbwegs ordentliche Testabdeckung hat, kann man mal riskieren was zu ändern. Dann sieht man wenigstens direkt, wenn man dabei ist, sich in den Fuß zu schießen.



  • @5cript sagte in C zu C++ Projektmigration:

    Hallo, Ich plane eine mittelgroßes C Projekt auf C++ zu migrieren. Und als ersten Schritt erstmal .c Dateien in .cpp (und .h zu .hpp) Dateien umzubennen, damit der C++ Compiler diese baut und dann alle casts, compiler errors und warnings zu fixen.

    Macht es nicht mehr Sinn, einzelne Teile nacheinander umstellen als alles auf einmal? Der C-Code läuft doch, hat aber keine Tests, wenn ich dich richtig verstehe. Ist es denn wichtig, dass alles C++ wird? Vielleicht willst du lieber einzelne Aspekte raussuchen, z.B. eine Klasse erstellen und dann den entsprechenden C-Code dazu löschen?

    Ich bin generell kein großer Fan von Rewrites. Da ergeben sich immer mehr Probleme als man denkt. Und nur durch das Umbenennen gewinnst du ja nicht viel. Der Code ist immer noch in C (das muss ja nicht schlecht sein). Gibt es denn Stellen, wo der Schuh besonders drückt? Macht es nicht vielleicht Sinn, sich da den Bereich rauszusuchen und dort zu beginnen?



  • Es gibt diversen Bedarf für refactoring, und C++ Mittel zu haben öffnet die Optionen weiter.

    Und wenn man nur partiell anfängt, dann ist man sowieso dabei architekturell es durchzuziehen und macht es dann doch. So nur teilweise ist schwierig, wenn es nicht schon schön modularisiert ist.



  • Hallo @5cript ,
    Ich war auch einmal an einer "Projektmigration" -> C++ beteiligt. Gewünscht war eine Umstellung auf ein objektorientiertes Design, weil die Wartbarkeit des prozedural Programmierten Koloss nicht mehr gewährleistet war.
    C++ mit seinem objektorientierten Ansatz versprach mit der fachlichen Modularisierung in Klassen eine vielversprechenden Weg. Zumal mit UML eine sehr gute Dokumentationsform gegeben war.
    Schnell merkten wir, dass das "Umschreiben" der Funktionen mit C++ Sprachmitteln nur Arbeit verursacht, ohne dem Ziel nur einen Schritt näher zu kommen. Es war uns schlicht unmöglich, ohne das gesamtem Verfahren, welches mit der vorhandenen Software abgebildet wurde, zu verstehen, einzelne Funktionen umzustellen.
    Wie schon von @Quiche-Lorraine erwähnt, sind Seiteneffekte (Wert von globalen Variablen wurde an vielen Stellen abgefragt), die teils sinnvoll eingebaut wurden, kaum zu handhaben.
    Also haben wir, um das Verfahren zu verstehen, mittels UML ein OO-Model gebaut (hat eigentlich schon das Bouquet gesprengt). Raus kam ein recht gutes Klassenmodell mit fachlichen Ablaufdiagrammen.
    Zu guter Letzt haben wir das Klassenmodell auch gebaut und im alten C Code nur noch geräubert.

    Was richtig teuer und frustrierend war, war die Zeit, wo wir versuchten, die einzelnen Funktionen umzustellen.
    Aber es brauchte auch erst einen externen Berater, der uns (jetzt mal stark vereinfacht) erzählte, "Nur std::cout anstelle von printf zu nutzen, macht ein C-Programm nicht zu einem C++ Programm. C++ wurde entwickelt, um objektorientierte Programmierung zu unterstützen."

    Edit: Orthographie



  • Genau darum hatte ich auch gefragt, ob denn auch dann C++ Features genutzt werden, denn einfach nur den C-Code mit einem C++ Compiler zu kompilieren, macht es noch nicht zu wirklichem C++ (geschweige denn von der Inkompatibilität einzelner Features: Compatibility of C and C++).

    Ich würde nach und nach die Portierung vornehmen (und den alten Code noch mit einem C-Compiler kompilieren lassen).

    Um von C++ auf C-Funktionen zuzugreifen (d.h. das Name-Mangling auszustellen), einfach

    #ifdef __cplusplus
    extern "C" {
    #endif
    
    // C-Function prototypes
    
    #ifdef __cplusplus
    }
    #endif
    

    in die benötigten C-Header einbauen.



  • @Th69 Das will ich explizit nicht machen, weil ich bei neuen Features dann auf RAII und OOP setzen will und ich nicht künstlich die C Schnittstellen weiterziehen will für die C Code Teile.

    Das Projekt ist schon von offensichtlichen Klassen durchzogen, wo die eigentlichen member globale variablen sind.

    Deswegen einmal Pflaster abreißen und alles "umfixen" zu C++ casts etc. Und dann von da aus jedes modul einzeln per PR durch Klassen ersetzen und die ganzen code smells fixen.

    Und wir haben dann darauf aufsetzend Bedarf an deklarativem Design usw. Wenn ich nicht die ganzen Möglichkeiten sehen würde den code besser zu designen mit C++, würde ich den Schritt ja gar nicht unbedingt machen.

    So groß ist die codebase jetzt auch nicht, wenn das nicht jemand reviewen müsste, könnte ich das auch in 3 Wochen komplett in OOP umschreiben, aber kein mensch will 20k+ Zeilen reviewn (Und systemtester nicht alles neu testen). Sonst lassen die mich verschwinden.



  • Ein grosses Problem bei der schrittweisen Umstellung auf C++ ist, dass der bestehende C-Code nicht mit Exceptions klarkommt.
    D.h. du musst aufpassen dass sich nicht Situationen ergeben wo eine noch nicht angepasste Funktion foo eine angepasste Funktion bar aufruft.
    Wenn bar dann eine Exception wirft, dann ist foo nicht darauf vorbereitet beim Stack-Unwinding alles rückgängig zu machen was rückgängig zu machen ist.
    Und gerade wenn es dein Ziel ist viel (oder alles) auf RAII umzustellen, kann es dazu sehr schnell kommen.


Anmelden zum Antworten