web-dev-qa-db-de.com

Wechsel von C++ nach C

Nachdem ich einige Jahre in C++ programmiert hatte, wurde mir kürzlich eine Job-Codierung in C, im Embedded-Bereich, angeboten.

Abgesehen von der Frage, ob es richtig oder falsch ist, C++ im eingebetteten Bereich zu entlassen, gibt es einige Features/Idiome in C++, die ich sehr vermissen würde. Nur um ein paar zu nennen:

  • Generische, typsichere Datenstrukturen (unter Verwendung von Vorlagen).
  • RAII. Insbesondere bei Funktionen mit mehreren Rückkehrpunkten, z. Sie müssen nicht daran denken, den Mutex an jedem Rückkehrpunkt freizugeben.
  • Destruktoren im Allgemeinen. Das heißt Wenn Sie eine MyClass-Instanz ein Mitglied von MyOtherClass sind, muss MyOtherClass die MyClass-Instanz nicht explizit desitialisieren. Ihr D'tor wird automatisch aufgerufen.
  • Namensräume.

Welche Erfahrungen haben Sie von C++ zu C gemacht?
Welche C-Substitute haben Sie für Ihre bevorzugten C++ - Funktionen/-Idiome gefunden? Haben Sie irgendwelche C-Funktionen entdeckt, die Sie sich für C++ gewünscht hätten?

79
george

Ich habe an einem Embedded-Projekt gearbeitet und einmal versucht, in C zu arbeiten. Ich konnte es einfach nicht ertragen. Es war einfach so wortreich, dass es schwierig wurde, etwas zu lesen. Ich mochte auch die von mir für die Integration optimierten Container, die #define-Blöcke reparieren mussten.

Code, der in C++ aussah:

if(uart[0]->Send(pktQueue.Top(), sizeof(Packet)))
    pktQueue.Dequeue(1);

verwandelt sich in:

if(UART_uchar_SendBlock(uart[0], Queue_Packet_Top(pktQueue), sizeof(Packet)))
    Queue_Packet_Dequeue(pktQueue, 1);

was viele Leute wahrscheinlich sagen werden, ist in Ordnung, wird aber lächerlich, wenn Sie mehr als ein paar "Methoden" -Aufrufe in einer Zeile durchführen müssen. Aus zwei Zeilen von C++ würden fünf (aufgrund von Zeilengrenzen von 80 Zeichen). Beide würden denselben Code generieren, es ist also nicht so, als würde der Zielprozessor dafür sorgen!

Einmal (1995) habe ich versucht, viel C für ein Multiprozessor-Datenverarbeitungsprogramm zu schreiben. Die Art, bei der jeder Prozessor über einen eigenen Speicher und ein eigenes Programm verfügt. Der vom Hersteller bereitgestellte Compiler war ein C-Compiler (eine Art HighC-Derivat). Ihre Bibliotheken waren Closed Source, sodass ich GCC nicht zum Erstellen verwenden konnte, und ihre APIs wurden mit der Denkweise entwickelt, dass Ihre Programme in erster Linie die Initialisierung/den Prozess darstellen/Vielfalt beenden, so war die Kommunikation zwischen Prozessoren am besten rudimentär.

Ich hatte ungefähr einen Monat Zeit, bevor ich aufgab, eine Kopie von cfront fand und sie in die Makefiles packte, damit ich C++ verwenden konnte. Cfront unterstützte nicht einmal Vorlagen, aber der C++ - Code war viel klarer.

Generische, typsichere Datenstrukturen (unter Verwendung von Vorlagen).

Das, was C den Templates am nächsten kommt, ist die Deklaration einer Header-Datei mit viel Code, der folgendermaßen aussieht:

TYPE * Queue_##TYPE##_Top(Queue_##TYPE##* const this)
{ /* ... */ }

dann ziehen Sie es mit etwas wie:

#define TYPE Packet
#include "Queue.h"
#undef TYPE

Beachten Sie, dass dies nicht für zusammengesetzte Typen funktioniert (z. B. keine Warteschlangen von unsigned char), wenn Sie nicht zuerst eine typedef vornehmen.

Oh, und denken Sie daran, wenn dieser Code nirgendwo verwendet wird, wissen Sie nicht einmal, ob er syntaktisch korrekt ist.

EDIT: Noch eine Sache: Sie müssen manuell die Instantiierung von Code verwalten. Wenn Ihr "Template" -Code nicht all Inline-Funktionen ist, müssen Sie etwas Kontrolle einschalten, um sicherzustellen, dass die Dinge nur einmal instanziiert werden, damit Ihr Linker keinen Haufen spuckt. " mehrere Fälle von "Foo" -Fehlern.

Dazu müssen Sie die nicht eingebetteten Elemente in einem Abschnitt "Implementierung" in Ihrer Header-Datei einfügen:

#ifdef implementation_##TYPE

/* Non-inlines, "static members", global definitions, etc. go here. */

#endif

Und dann in one in Ihrem gesamten Code je Vorlagenvariante müssen Sie:

#define TYPE Packet
#define implementation_Packet
#include "Queue.h"
#undef TYPE

Außerdem muss dieser Implementierungsabschnitt außerhalb der Standardcode #ifndef/#define/#endif litany sein, da Sie die Kopfdatei der Vorlage in eine andere Headerdatei einfügen können, die jedoch anschließend in einer .c-Datei instanziiert werden muss.

Ja, es wird schnell hässlich. Deshalb versuchen es die meisten C-Programmierer nicht einmal.

RAII.

Insbesondere bei Funktionen mit mehreren Rückkehrpunkten, z. Sie müssen nicht daran denken, den Mutex an jedem Rückkehrpunkt freizugeben.

Vergessen Sie Ihren hübschen Code und gewöhnen Sie sich an alle Ihre Rückgabepunkte (außer dem Ende der Funktion) gotos:

TYPE * Queue_##TYPE##_Top(Queue_##TYPE##* const this)
{
    TYPE * result;
    Mutex_Lock(this->lock);
    if(this->head == this->tail)
    {
        result = 0;
        goto Queue_##TYPE##_Top_exit:;
    }

    /* Figure out `result` for real, then fall through to... */

Queue_##TYPE##_Top_exit:
    Mutex_Lock(this->lock);
    return result;
}

Destruktoren im Allgemeinen.

D.h. Wenn Sie für MyClass ein D'tor schreiben, muss MyOtherClass die MyClass-Instanz nicht explizit desitialisieren, wenn eine MyClass-Instanz Mitglied von MyOtherClass ist. Ihr D'tor wird automatisch aufgerufen.

Die Objektkonstruktion muss explizit auf dieselbe Weise behandelt werden.

Namensräume.

Das ist eigentlich ein einfaches Problem: einfach ein Präfix an das every-Symbol kleben. Dies ist die Hauptursache für die Quellblase, über die ich zuvor gesprochen habe (da Klassen implizite Namespaces sind). Die C-Leute haben das schon ewig gelebt und werden wahrscheinlich nicht sehen, was die große Sache ist.

YMMV

64
Mike DeSimone

Ich bin aus einem anderen Grund von C++ zu C gewechselt (eine Art allergische Reaktion;), und es gibt nur wenige Dinge, die ich vermisse, und einige Dinge, die ich gewonnen habe. Wenn Sie sich an C99 halten, gibt es Konstrukte, mit denen Sie besonders schön und sicher programmieren können

  • benannte Initialisierer (eventuell kombiniert mit Makros) machen die Initialisierung einfacher Klassen als schmerzlos als Konstruktoren
  • zusammengesetzte Literale für temporäre Variablen
  • for- scope-Variable kann Ihnen dabei helfen, scope-gebundenes Ressourcenmanagement durchzuführen, insbesondere um sicherzustellen, dass unlock von Mutexen oder free von Arrays, auch bei vorläufigen Funktionsrückgaben, sichergestellt ist
  • __VA_ARGS__-Makros können verwendet werden, um Standardargumente für Funktionen zu haben und um Code abzurollen 
  • inline-Funktionen und -Makros, die sich gut kombinieren, um überladene Funktionen zu ersetzen
16
Jens Gustedt

Es gibt nichts Vergleichbares für die STL für C.
Es gibt libs mit ähnlicher Funktionalität, die aber nicht mehr integriert ist.

Ich denke, das wäre eines meiner größten Probleme ... Mit dem Werkzeug zu wissen, dass ich das Problem lösen könnte, aber die Werkzeuge nicht in der Sprache verfügbar sind, die ich verwenden muss.

8
MOnsDaR

Der Unterschied zwischen C und C++ ist die Vorhersagbarkeit des Verhaltens des Codes.

Es ist einfacher, mit großer Genauigkeit vorherzusagen, was Ihr Code in C bewirkt. In C++ kann es schwieriger werden, eine genaue Vorhersage zu treffen.

Die Vorhersagbarkeit in C gibt Ihnen eine bessere Kontrolle darüber, was Ihr Code tut, aber das bedeutet auch, dass Sie mehr tun müssen.

In C++ können Sie weniger Code schreiben, um das Gleiche zu erledigen, aber (zumindest für mich) habe ich Schwierigkeiten, gelegentlich zu wissen, wie der Objektcode im Speicher abgelegt wird und wie sich das Verhalten verhält.

8
d.s.

In meiner Arbeitsweise, die übrigens eingebettet ist, wechsle ich ständig zwischen C und C++.

Wenn ich in C bin, vermisse ich C++:

  • vorlagen (einschließlich, aber nicht beschränkt auf STL-Container). Ich benutze sie für spezielle Zähler, Pufferpools usw. (Ich habe meine eigene Bibliothek mit Klassenvorlagen und Funktionsvorlagen aufgebaut, die ich in verschiedenen eingebetteten Projekten verwende).

  • sehr leistungsfähige Standardbibliothek

  • destruktoren, die natürlich RAII ermöglichen (Mutexe, Interrupt-Deaktivierung, Tracing usw.)

  • zugriffsspezifizierer, um besser durchzusetzen, wer was verwenden kann (nicht sehen kann)

Ich benutze Vererbung für größere Projekte, und die integrierte Unterstützung von C++ ist wesentlich sauberer und netter als der "Hack" der Einbettung der Basisklasse als erstes Mitglied (ganz zu schweigen von dem automatischen Aufruf von Konstruktoren, Initialisierungslisten usw.). ) aber die oben aufgeführten Dinge sind die, die ich am meisten vermisse.

Wahrscheinlich ist nur etwa ein Drittel der eingebetteten C++ - Projekte, an denen ich arbeite, Ausnahmen verwendet. Daher habe ich mich daran gewöhnt, ohne sie zu leben, und ich vermisse sie nicht allzu sehr, wenn ich zu C gehe.

Auf der anderen Seite, wenn ich zurück zu einem C-Projekt mit einer beträchtlichen Anzahl von Entwicklern gehe, gibt es ganze Klassen von C++ - Problemen, die ich gewöhnt bin, um Leuten zu erklären, die weggehen. Meistens Probleme aufgrund der Komplexität von C++ und von Leuten, die glauben zu wissen, was los ist, aber sie sind wirklich im "C mit Klassen" -Teil der C++ - Konfidenzkurve .

Wenn ich die Wahl habe, würde ich C++ lieber für ein Projekt verwenden, aber nur, wenn das Team die Sprache ziemlich gut beherrscht. Vorausgesetzt, es ist kein 8K-µC-Projekt, bei dem ich sowieso effektiv "C" schreibe.

7
Dan

Ein paar Beobachtungen

  • Wenn Sie nicht vorhaben, Ihren C++ - Compiler zum Erstellen Ihres C zu verwenden (was möglich ist, wenn Sie sich an eine gut definierte Teilmenge von C++ halten), werden Sie bald Dinge entdecken, die Ihr Compiler in C zulässt.
  • Keine kryptischen Vorlagenfehler mehr (yay!)
  • Keine (sprachgestützte) objektorientierte Programmierung
3
hhafez

Die gleichen Gründe, die ich für die Verwendung von C++ oder einer Mischung aus C/C++ anstelle von reinem C habe. Ich kann ohne Namespaces leben, aber ich benutze sie ständig, wenn der Codestandard es erlaubt. Das liegt daran, dass Sie in C++ wesentlich kompakteren Code schreiben können. Das ist sehr nützlich für mich, ich schreibe Server in C++, die ab und zu zum Absturz neigen. An diesem Punkt ist es sehr hilfreich, wenn der Code, den Sie betrachten, kurz und konsistent ist. Betrachten Sie zum Beispiel den folgenden Code: 

uint32_t 
ScoreList::FindHighScore(
  uint32_t p_PlayerId)
{
  MutexLock lock(m_Lock); 

  uint32_t highScore = 0; 
  for(int i = 0; i < m_Players.Size(); i++)
  {
    Player& player = m_Players[i]; 
    if(player.m_Score > highScore)
      highScore = player.m_Score; 
  }

  return highScore; 
}

In C sieht das so aus: 

uint32_t 
ScoreList_getHighScore(
  ScoreList* p_ScoreList)
{
  uint32_t highScore = 0; 

  Mutex_Lock(p_ScoreList->m_Lock); 

  for(int i = 0; i < Array_GetSize(p_ScoreList->m_Players); i++)
  {
    Player* player = p_ScoreList->m_Players[i]; 
    if(player->m_Score > highScore)
      highScore = player->m_Score; 
  }

  Mutex_UnLock(p_ScoreList->m_Lock);

  return highScore; 
}

Keine Welt der Unterschiede. Eine weitere Codezeile, aber das summiert sich. Normalerweise versuchen Sie Ihr Bestes, um es sauber und schlank zu halten, aber manchmal müssen Sie etwas komplexeres tun. In diesen Situationen schätzen Sie Ihre Zeilenzahl. Eine weitere Zeile ist eine weitere Sache, die Sie beachten sollten, wenn Sie herausfinden möchten, warum Ihr Broadcast-Netzwerk plötzlich keine Nachrichten mehr sendet. 

Wie auch immer, ich finde, dass C++ mir erlaubt, komplexere Dinge auf sichere Art und Weise zu erledigen. 

2
Antihero

Ich denke, das Hauptproblem, warum C++ in eingebetteter Umgebung schwieriger akzeptiert werden kann, ist der Mangel an Ingenieuren, die wissen, wie C++ richtig verwendet wird.

Ja, die gleichen Überlegungen lassen sich auch auf C anwenden, aber zum Glück gibt es nicht so viele Fallgruben in C, die sich selbst in den Fuß schießen können. Andererseits müssen Sie wissen, wann Sie bestimmte Funktionen in C++ nicht verwenden.

Alles in allem mag ich C++. Ich verwende das für die O/S-Services-Schicht, den Treiber, den Verwaltungscode usw. Wenn jedoch Ihr Team nicht genügend Erfahrung damit hat, wird dies eine schwierige Herausforderung.

Ich hatte Erfahrung mit beiden. Als der Rest des Teams nicht bereit war, war es eine totale Katastrophe. Auf der anderen Seite war es eine gute Erfahrung.

0
KOkon