Wann nehmt ihr lieber C und wann lieber C++?



  • Nathan schrieb:

    Wenn man Exceptions und RTTI deaktiviert hat, gibts eigentlich kaum einen Unterschied zwischen C und C++, was Perfomance und Co angeht.

    Templates erzeugen schwer trackbaren Binary-Bloat, das kann auf dem µC stören. Die Standardbibliothek wird deshalb auch deaktiviert.

    Dieses eingeschränkte C++ hat dann keinerlei Vorteile gegenüber C, so dass man gleich C verwenden sollte, das ist viel einfacher und bringt den Programmierer nicht in Versuchung, doch ineffiziente C++-Features zu nutzen.



  • Verwende C, wenn du das Gefühl hast, eine auf Retro-Style machen zu müssen.
    Bei allen anderen, "normalen und anständigen" Programmen nimmst du C++. 😉



  • Ja genau mach wie die Leute die Mikrocontroller, GTK+ oder Linux benutzen, immer schon Retro-Style^



  • c=µc schrieb:

    Nathan schrieb:

    Wenn man Exceptions und RTTI deaktiviert hat, gibts eigentlich kaum einen Unterschied zwischen C und C++, was Perfomance und Co angeht.

    Templates erzeugen schwer trackbaren Binary-Bloat, das kann auf dem µC stören. Die Standardbibliothek wird deshalb auch deaktiviert.

    Dieses eingeschränkte C++ hat dann keinerlei Vorteile gegenüber C, so dass man gleich C verwenden sollte, das ist viel einfacher und bringt den Programmierer nicht in Versuchung, doch ineffiziente C++-Features zu nutzen.

    Es gibt so viele kostenlose C++-Features, auf die ich nicht verzichten möchte.

    Zum Beispiel Typsicherheits-Wrapper um mache void*-Fumnktionen.
    Auf den meisten µC würde ich auch die Standardbibliothek meiden, aber trotzdem C++ bevorzugen.



  • 1. Wenn man auf kleinen µC programmieren muss.
    2. Wenn man am Linux Kernel mitarbeiten will.
    3. Wenn man einen Kernel selber schreibt.
    4. Wenn man eine Lib schreibt und dafür möglichst viele Bindings zur Verfügung stellen möchte.
    5. Wenn man so klug wie ich ist.



  • ich würde alleine schon C++ nehmen nur um destruktoren zu bekommen. der rest von c++ ist nicht soo wichtig. aber ohne destruktoren wird es übelst masochistisch.



  • volkard schrieb:

    Es gibt so viele kostenlose C++-Features, auf die ich nicht verzichten möchte.

    Vor allem kann man auch Performance berücksichtigen.

    Templates sind zum Beispiel einfach unverzichtbar.

    Dieses eingeschränkte C++ hat dann keinerlei Vorteile gegenüber C

    Doch, es hat welche. Aber vor allem keine Nachteile.



  • Würde C nur nutzen wenn ich muss.

    Ich sehe auch nicht warum man kein C++ für die Kernelentwicklung verwenden sollte. Kein new, keine Exceptions, kein RTTI und gut.



  • Kein new

    Das entspricht "kein malloc" in C.

    keine Exceptions

    Das entspricht kein SJLJ in C.

    Oder?



  • Ethon schrieb:

    Ich sehe auch nicht warum man kein C++ für die Kernelentwicklung verwenden sollte. Kein new, keine Exceptions, kein RTTI und gut.

    Dazu: Keine Vererbung1, keine Template-Übernutzung, kein RAII2

    1: Das Problem ist der vptr. Der nimmt einen wichtigen Platz in der Cacheline weg. virtuelle Funktionen aufrufen ist langsam, erfordert 2 Indirektionen. Den vptr erstellen ist langsam, weil er mehrmals umgebogen wird, destructen genauso. Dann will man manchmal eine Instanz der Klasse ohne den vptr-Overhead und es geht nicht. Man erhält schnellere Klassen mit Devirtualisierung. Vererbung muss von Hand gecoded werden.

    2: Ja, kein RAII. Begründung: RAII in manchen Klassen und und manchen Klassen nicht, ist schädlich. Entweder ganz oder gar nicht. RAII hat aber ein riesiges Problem und das ist Besitzübertragung. Ohne C++11 hat man verloren. Einen std::string zurückgeben ist nicht performant möglich bzw. nur mit NRVO/URVO, aber darauf zu vertrauen ist fragil. Mit C++11 hat man zwar move, aber der Destruktor wird trotzdem aufgerufen, und auch wenn der nichts tut, das ist unnötiger Code. Den Destruktor eines moved-from-Objektes auszulassen können moderne Compiler in vielen Fällen nicht. Also bleibt eine close()-Funktion, die man von Hand aufruft. Selbst BOOST_SCOPE_EXIT hat Overhead. Deshalb von RAII fernbleiben.



  • Sone schrieb:

    Kein new

    Das entspricht "kein malloc" in C.

    Trotzdem ist new in C++ eingebaut (im Gegensatz zu malloc in C), deswegen sollte man schon explizit darauf verzichten. 😉

    Sone schrieb:

    keine Exceptions

    Das entspricht kein SJLJ in C.

    Oder?

    Nö, Longjumps sind okay. Die erfordern ja auch keine Compiler-Magie.
    Im Prinzip werden da ja nur alle Register gesichert und wiederhergestellt.
    Exceptionhandling erfordert einiges an Krempel um zu funktionieren.

    1: Das Problem ist der vptr. Der nimmt einen wichtigen Platz in der Cacheline weg. virtuelle Funktionen aufrufen ist langsam, erfordert 2 Indirektionen. Den vptr erstellen ist langsam, weil er mehrmals umgebogen wird, destructen genauso. Dann will man manchmal eine Instanz der Klasse ohne den vptr-Overhead und es geht nicht. Man erhält schnellere Klassen mit Devirtualisierung. Vererbung muss von Hand gecoded werden.

    Baut nicht der Linux-Kernel an 100 verschiedenen Stellen virtuelle Funktionen nach?
    Es muss doch nicht jede Zeile Code im Kernel 100% performant sein?
    Das Problem ist doch eher die Wartbarkeit der monolithischen Monster. Da hilft Polymorphie garantiert.

    Ja, kein RAII. Begründung: RAII in manchen Klassen und und manchen Klassen nicht, ist schädlich. Entweder ganz oder gar nicht. RAII hat aber ein riesiges Problem und das ist Besitzübertragung. Ohne C++11 hat man verloren. Einen std::string zurückgeben ist nicht performant möglich bzw. nur mit NRVO/URVO, aber darauf zu vertrauen ist fragil. Mit C++11 hat man zwar move, aber der Destruktor wird trotzdem aufgerufen, und auch wenn der nichts tut, das ist unnötiger Code. Den Destruktor eines moved-from-Objektes auszulassen können moderne Compiler in vielen Fällen nicht. Also bleibt eine close()-Funktion, die man von Hand aufruft. Selbst BOOST_SCOPE_EXIT hat Overhead. Deshalb von RAII fernbleiben.

    Mach mal ein paar Tests. Moderne Compiler hauen praktisch alles unnötige raus.
    Ansonsten bleibt das Argument davor: Hin und wieder mal nen Takt zu verschwenden ist vermutlich insgesamt besser wenn dafür der Code um 50% schrumpft und deutlich bugresistenter wird.



  • Ethon schrieb:

    Baut nicht der Linux-Kernel an 100 verschiedenen Stellen virtuelle Funktionen nach?
    Es muss doch nicht jede Zeile Code im Kernel 100% performant sein?
    Das Problem ist doch eher die Wartbarkeit der monolithischen Monster. Da hilft Polymorphie garantiert.

    Ja, der Kernel baut virtuelle Funktionen nach. Dagegen spricht auch nichts.

    fs.h:
    /*
     * Keep mostly read-only and often accessed (especially for
     * the RCU path lookup and 'stat' data) fields at the beginning
     * of the 'struct inode'
     */
    struct inode {
     umode_t i_mode;
     unsigned short i_opflags;
     kuid_t i_uid;
     kgid_t i_gid;
     unsigned int i_flags;
    
    #ifdef CONFIG_FS_POSIX_ACL
     struct posix_acl *i_acl;
     struct posix_acl *i_default_acl;
    #endif
    
     const struct inode_operations *i_op;
     struct super_block *i_sb;
     struct address_space *i_mapping;
    

    Unter C++ hätte das so ausgesehen:

    struct inode {
      const struct inode_operations *i_op; // das ist der vptr
    
      umode_t i_mode;
      unsigned short i_opflags;
      kuid_t i_uid;
      kgid_t i_gid;
      unsigned int i_flags;
      ...
    

    Reihenfolge ist wichtig. Nur die ersten 64 Bytes liegen in der Cacheline. Deshalb wichtige Daten oben und unwichtige unten.

    Und der Konstruktor von inode hätte i_op auf abstract_i_op gesetzt, der konkrete Konstruktor von concrete_inode hätte den Zeiger dann nochmals neu auf concrete_i_op gesetzt. Zu beweisen, dass der vptr nicht benutzt wird, können die Compiler nicht.

    Mach mal ein paar Tests.

    Ok. Weder g++ 4.8 (COW) noch clang++ 3.4 (SSO) schaffen es

    std::string f(int i, const char *c)
    {
      if (i < 0)
        return std::string("nope");
      std::string s;
      if (i--)
        s += c;
      return s;
    }
    

    dahingehend zu optimieren, dass kein Destruktor von s aufgerufen wird. s wird gemoved, aber dann wird der nullpointer deleted.

    Moderne Compiler hauen praktisch alles unnötige raus.

    Manchmal ist man erstaunt, wie viel sie raushauen, aber manchmal ist man erstaunt, wie "dumm" sie sind.

    Ansonsten bleibt das Argument davor: Hin und wieder mal nen Takt zu verschwenden ist vermutlich insgesamt besser wenn dafür der Code um 50% schrumpft und deutlich bugresistenter wird.

    Ja, oft ist C++ ausreichend. Aber wenn es wie im Kernel praktisch überall um volle Geschwindigkeit geht, dann ist RAII halt nicht geeignet.



  • c=µc schrieb:

    Den Destruktor eines moved-from-Objektes auszulassen können moderne Compiler in vielen Fällen nicht.

    Hmm, wann denn genau? Ich muss sagen mich stört auch etwas dass sie ein neues Sprachkonzept mehr oder weniger hinter binding-rules versteckt haben, aber bisher scheinen Compiler da erstaunlich gut zu sein was das Optimieren angeht.

    http://gcc.godbolt.org/#{"version"%3A3%2C"filterAsm"%3A{"labels"%3Atrue%2C"directives"%3Atrue%2C"commentOnly"%3Atrue%2C"intel"%3Atrue}%2C"compilers"%3A[{"source"%3A"\n%23include <memory>\n\nstruct foo\n{\n int* ptr%3B\n \n foo()\n %3A ptr(new int)\n {}\n \n foo(foo%26%26 other)\n %3A ptr(other.ptr)\n {\n other.ptr %3D nullptr%3B\n }\n \n ~foo()\n {\n delete ptr%3B\n }\n}%3B\n\n\nint main()\n{\n std%3A%3Aunique_ptr<int> p1(new int)%3B\n auto p2 %3D std%3A%3Amove(p1)%3B\n auto p3 %3D std%3A%3Amove(p2)%3B\n}\n"%2C"compiler"%3A"%2Fusr%2Fbin%2Fg%2B%2B-4.8"%2C"options"%3A"-O3 -std%3Dc%2B%2B11"}]}

    @Ethon Fix mal deinen Beitrag und mach den c++ zu einem quote Block.

    Edit @c: Da godbolt irgendwie nicht will kann ich's nur mit MSVC testen, aber

    std::string f(int i, const char *c)
    {
    000007F7DE031270  push        rbx  
    000007F7DE031272  sub         rsp,50h  
    000007F7DE031276  mov         qword ptr [rsp+28h],0FFFFFFFFFFFFFFFEh  
    000007F7DE03127F  mov         rbx,rcx  
    000007F7DE031282  mov         dword ptr [rsp+20h],0  
    	if (i < 0)
    		return std::string("nope");
    
    	std::string s;
    000007F7DE03128A  mov         qword ptr [rsp+48h],0Fh  
    000007F7DE031293  mov         qword ptr [rsp+40h],0  
    000007F7DE03129C  mov         byte ptr [s],0  
    
    	if (i--)
    		s += c;
    000007F7DE0312A1  mov         r8d,0Dh  
    
    	if (i--)
    		s += c;
    000007F7DE0312A7  lea         rcx,[s]  
    000007F7DE0312AC  call        std::basic_string<char,std::char_traits<char>,std::allocator<char> >::append (07F7DE0314B0h)  
    
    	return s;
    000007F7DE0312B1  mov         qword ptr [rbx+18h],0Fh  
    000007F7DE0312B9  mov         qword ptr [rbx+10h],0  
    000007F7DE0312C1  mov         byte ptr [rbx],0  
    000007F7DE0312C4  mov         rcx,qword ptr [rsp+48h]  
    000007F7DE0312C9  cmp         rcx,10h  
    000007F7DE0312CD  jae         f+7Dh (07F7DE0312EDh)  
    000007F7DE0312CF  mov         r8,qword ptr [rsp+40h]  
    000007F7DE0312D4  inc         r8  
    000007F7DE0312D7  je          f+85h (07F7DE0312F5h)  
    000007F7DE0312D9  lea         rdx,[s]  
    000007F7DE0312DE  mov         rcx,rbx  
    000007F7DE0312E1  call        memcpy (07F7DE031C70h)  
    000007F7DE0312E6  mov         rcx,qword ptr [rsp+48h]  
    000007F7DE0312EB  jmp         f+85h (07F7DE0312F5h)  
    000007F7DE0312ED  mov         rax,qword ptr [s]  
    000007F7DE0312F2  mov         qword ptr [rbx],rax  
    000007F7DE0312F5  mov         rax,qword ptr [rsp+40h]  
    000007F7DE0312FA  mov         qword ptr [rbx+10h],rax  
    000007F7DE0312FE  mov         qword ptr [rbx+18h],rcx  
    000007F7DE031302  mov         rax,rbx  
    }
    000007F7DE031305  add         rsp,50h  
    000007F7DE031309  pop         rbx  
    000007F7DE03130A  ret
    

    Kein call zu operator delete. ?



  • @cooky451:

    http://gcc.godbolt.org/#{%22version%22%3A3%2C%22filterAsm%22%3A{%22labels%22%3Atrue%2C%22directives%22%3Atrue%2C%22commentOnly%22%3Atrue%2C%22intel%22%3Atrue}%2C%22compilers%22%3A[{%22source%22%3A%22%23include%20%3Clist%3E\nvoid%20feddisch%28%29%3B\nstd%3A%3Alist%3Cint%3E%20f%28int%20i%2C%20int%20x%29\n{\n%20%20if%20%28i%20%3C%200%29\n%20%20%20%20return%20std%3A%3Alist%3Cint%3E%281%2C1%29%3B\n%20%20std%3A%3Alist%3Cint%3E%20v%3B\n%20%20if%20%28i--%29\n%20%20%20%20v.push_back%28x%29%3B\n%20%20feddisch%28%29%3B\n%20%20return%20v%3B\n}\n%22%2C%22compiler%22%3A%22%2Fusr%2Fbin%2Fg%2B%2B-4.8%22%2C%22options%22%3A%22-O3%20-std%3Dc%2B%2B11%22}]}

    Oder auch das std::move löschen, dann passiert das gleiche. Der ganze delete-Schrott ist völlig unnötig.



  • c=µc schrieb:

    Reihenfolge ist wichtig. Nur die ersten 64 Bytes liegen in der Cacheline. Deshalb wichtige Daten oben und unwichtige unten.

    Also wenn ich mich nicht verzählt habe, sollte der Pointer in beiden Fällen innerhalb der ersten 64 Byte sein. Und selbst wenn nicht: ich würde in der Regel den vptr als wichtiges Datum zählen. Es nützt ja nix, wenn man auf die Daten fix zugreifen kann, aber auf die Operationen auf diesen Daten nicht.
    Sollte das dennoch ein Problem sein, kann man es immernoch mit einer solchen Struktur und einer Wrapper-Klasse lösen.



  • @c Ui, der Code den std::list generiert ist tatsächlich beängstigent. Zumindest std::forward_list sieht aber mehr oder weniger sauber aus:

    std::forward_list<int> f(int i, int x)
    {
    000007F617A51270  push        rbx  
    000007F617A51272  sub         rsp,40h  
    000007F617A51276  mov         qword ptr [rsp+30h],0FFFFFFFFFFFFFFFEh  
    000007F617A5127F  mov         rbx,rcx  
    000007F617A51282  xor         eax,eax  
    000007F617A51284  mov         dword ptr [rsp+28h],eax  
    000007F617A51288  mov         dword ptr [x],7  
    	if (i < 0)
    		return std::forward_list<int>(1, 1);
    
    	std::forward_list<int> v;
    000007F617A51290  mov         qword ptr [v],rax  
    
    	if (i--)
    		v.push_front(x);
    000007F617A51295  lea         r8,[x]  
    000007F617A5129A  xor         edx,edx  
    000007F617A5129C  call        std::_Flist_buy<int,std::allocator<int> >::_Buynode<int const & __ptr64> (07F617A51790h)  
    
    	return v;
    000007F617A512A1  mov         qword ptr [rbx],rax  
    000007F617A512A4  mov         rax,rbx  
    }
    000007F617A512A7  add         rsp,40h  
    000007F617A512AB  pop         rbx  
    000007F617A512AC  ret
    

    Also RAII nicht zu verwenden als Grundregel scheint mir auch im Kernel keine gute Idee. Allerdings gibt es da wohl leider noch einige Fallstricke, da muss ich dir recht geben.



  • Templates erzeugen schwer trackbaren Binary-Bloat, das kann auf dem µC stören. Die Standardbibliothek wird deshalb auch deaktiviert.

    Dieses eingeschränkte C++ hat dann keinerlei Vorteile gegenüber C, so dass man gleich C verwenden sollte, das ist viel einfacher und bringt den Programmierer nicht in Versuchung, doch ineffiziente C++-Features zu nutzen.

    Weist du, bei C bekomme ich regelmäßig die Krätze weil ich keine STL habe und so programmiere ich mir regelmäßig einen Wolf ab. X Implementierungen von std::list, X Fehlerquellen, X Testreihen... Nicht schön...

    Klar, wenn man Low Level programmieren muss (Interrupt Funktion,...) sind so manche Dinge an C++ nicht praktikabel. Aber deswegen die Standardbibliothek verbieten finde ich suspekt, sehr suspekt...
    Du scheinst deinen Anwendungsgebiet von C einfach mal so standardisieren wollen, und das geht nicht.

    Ich könnte genausogut argumentieren das die C Standardbibliothek nicht empfehlenswert ist da diese regelmäßig Buffer Overflows produziert, die Mutter aller Undefined Behaviors printf ist und Pointer öfters
    ins Nirvana zeigen als man denkt,... 😞

    Übrigens. Auch bei kleinere Prozessoren ändern sich die Aufgabenfeldern. Und so mancher Controller besitzt auch schon so einige MHz, auf denen man wunderbar dynamische Probleme wie kleine
    Datenbanken, einfache Simulationen implementieren kann. Und da wird es mit C schnell unschön...

    Nur mal eine Frage: Was ist ein schwer trackbaren Binary-Bloat?



  • Das entspricht kein SJLJ in C.

    SJLJ?

    HIV!

    SPD!!!



  • Bitte ein Bit schrieb:

    Nur mal eine Frage: Was ist ein schwer trackbaren Binary-Bloat?

    Das, was man bekommt, wenn man mit templates nicht umzugehen weiß...



  • Bitte ein Bit schrieb:

    Nur mal eine Frage: Was ist ein schwer trackbaren Binary-Bloat?

    Vermutlich wenn die resultierende Binärdatei dick ist weil der Compiler sehr viel Code aus Templates generiert hat.
    Kann in C nicht passieren, da hat man nur schrecklich zu benutzende Interfaces die void* für den tollsten Datentyp überhaupt halten.

    Bitte ein Bit schrieb:

    Das entspricht kein SJLJ in C.

    SJLJ?

    HIV!

    SPD!!!

    setjmp / longjmp


Anmelden zum Antworten