C++ ist machmal echt seltsam



  • Trotz mittlerweile doch recht langer Erfahrung stolpere ich noch hin und wieder über Unintuitivitäten von C++. So habe ich doch letztens einen Bug produziert dessen Essenz ich hier mal zusammengefasst habe:

    #include <iostream>
    #include <string>
    using namespace std;
    
    int main() {
      string result {"Result: "};
      result += "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + '\n';
      cout << result;
    }
    

    Das Programm, sieht harmlos aus (zumindest für mich) kompiliert ohne Warnung, passiert alle statischen Codanalys Tests sowie Valgriend und läuft ohne abzustürzen. Es gibt nur etwas anderes aus, als ich erwartet hätte und mittlerweile weiß ich auch, was da passiert. Wisst ihr es auch?



  • TNA schrieb:

    Wisst ihr es auch?

    Yup.



  • Ich hab am Anfang C mit C++ Compiler programmiert, also eher C mit std::cout/std::cin. So was hat mich dann überzeugt nochmal richtig C++ zu lernen. Deshalb war das recht offensichtlich für mich.



  • Ich hatte bisher immer unbewusst angenommen, dass man mit dem + Operator Strings zusammenfügen kann und das auch mit char* geht, solange der Operand, der am weitesten links ist, ein String ist. Problem mit der Regel ist hier das += mit der veränderten Auswerungsreihenfolge. OK, hätte man drauf kommen können.

    Für C-Programmierer ist es sicherlich nicht verwunderlich, dass man mit

    "ABC" + 'c';
    

    keine C-Strings aneinanderhängen kann. Das wäre mir auch klar. Aber das beide Operanden implizit zu etwas semantisch völlig anderen werden, für die der Operator+ eine völlig andere Bedeutung hat, ist wohl für Benutzer jeder anderen Programmiersprache recht seltsam.



  • was hat das jetzt mit C++ zu tun - das ist doch in C genauso

    int main() { 
      const char* x = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + '\n';
      const char* y = &"ABCDEFGHIJKLMNOPQRSTUVWXYZ"['\n']; 
      const char* z = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "123"; 
      return 0;
    }
    


  • da steht doch nicht anderes als

    std::string x = std::string("ABCDEFGHIJKLMNOPQRSTUVWXYZ" + '\n');
    

    dein Gefühl war

    std::string x = std::string("ABCDEFGHIJKLMNOPQRSTUVWXYZ") + '\n';
    

    aber wer soll den entscheiden das dein "ABC..."-String erst in einen std::string konvertiert werden muss, x kann es nicht

    aber ich geben dir recht das es leicht zu Fehlern führt - und man denkt zu schnell das da alles automatisch konvertiert wird



  • Gast3 schrieb:

    was hat das jetzt mit C++ zu tun - das ist doch in C genauso

    Das ist ja gerade eine "C-Altlast" in C++. Menschen die in C viel mit C-Strings hantieren erwarten so etwas auch. Menschen die in der Regel ideomatisches C++ schreiben oder andere Hochsprachen, dass ein deutlich höheres Abstraktionslevel hat, erwarten eher nicht, dass bei so etwas plötzlich auf Low-Level-Verhalten umgeschaltet wird.



  • die beiden sind schon ganz schön böse

    Array == Pointer
    "xyz" == Pointer



  • Gast3 schrieb:

    aber wer soll den entscheiden das dein "ABC..."-String erst in einen std::string konvertiert werden muss, x kann es nicht

    Natürlich ist es technisch nicht möglich, das so zu realisieren. Um das zu wissen braucht man aber schon recht genaues Detailwissen darüber, wie das ausgewertet wird.

    Gast3 schrieb:

    aber ich geben dir recht das es leicht zu Fehlern führt - und man denkt zu schnell das da alles automatisch konvertiert wird

    Das nicht alles automatisch konvertiert wird ist das eine, das hat man ja hin und wieder schon mal, das andere ist aber, dass es trotzdem kompiliert.

    Wenn man z.B. schreibt

    string x ="ABC" + "DEF"
    

    ;
    Bekommt man zumindest einen Fehler. Das Problem hier ist, das C++ an dieser Stelle die schwache Typisierung von C geerbt hat, die es erlaubt, das Typen implizit in andere Typen mit völlig unterschiedlicher Semantik umgewandelt werden. Das kann hoch gefährlich sein. Daran lässt sich aber wohl nichts mehr ändern. Vielen C-Altlasten kann man gut aus dem weg gehen indem man sie einfach nicht benutzt. An der Doppelbedeutung von char* und char sowie dem Pointer-Decay kommt aber niemand drum rum.



  • Man könnte eventuell auch argumentieren, dass die Operator+ Überladung für std::string ein Fehler war, da sie diesem Operator kontextabhängig eine unterschiedliche Bedeutung gibt. Ein noch größerer Fehler war es wahrscheinlich, diesen Operator auch noch gemischt für char* und char zu überladen, was fälschlicherweise suggeriert man könne std::strings mit C-Strings und Literalkonstanten in solchen Ausdrücken beliebig mischen..



  • Gerade Bjarne Stroustrup will ja gerne den Eindruck erwecken, man könne insbesondere seit C++14 aus C++ eine konsistente, sichere und dennoch vergleichsweise einfach zu erlernende Sprache machen, indem man nur noch eine ideomatische Teilmenge der Sprache lehrt, weil die ganzen unsicheren und fehleranfälligen Low-Level Sachen bräuchten ja eh nur die Library-Implementierer zu verstehen. Ich fürchte jedoch das ist unmöglich, gerade wegen solchen Dingen.



  • TNA schrieb:

    kompiliert ohne Warnung, passiert alle statischen Codanalys Tests

    Clang gibt tatsächlich eine nette Warnung:

    test.cpp:7:42: warning: adding 'char' to a string pointer does not append to the string [-Wstring-plus-char]
      result += "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + '\n';
                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~
    test.cpp:7:42: note: use array indexing to silence this warning
      result += "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + '\n';
                                             ^
                &                            [     ]
    

    Die Lösung für dieses Problem wäre in C++14 wohl so (Man beachte das 's' Suffix):

    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"s + '\n';
    


  • Was macht das s Suffix, Stichwort ?



  • c14_new schrieb:

    Was macht das s Suffix, Stichwort ?

    Es konvertiert den Ausdruck in einen std::string.



  • sebi707 schrieb:

    Clang gibt tatsächlich eine nette Warnung:

    Das ist ne tolle Sache. GCC macht das leider nicht.



  • TNA schrieb:

    Trotz mittlerweile doch recht langer Erfahrung stolpere ich…

    Danke. Den Code kriegen gleich mal die nächsten Bewerber zu sehen. 😃



  • TNA schrieb:

    Gerade Bjarne Stroustrup will ja gerne den Eindruck erwecken, man könne insbesondere seit C++14 aus C++ eine konsistente, sichere und dennoch vergleichsweise einfach zu erlernende Sprache machen, indem man nur noch eine ideomatische Teilmenge der Sprache lehrt, weil die ganzen unsicheren und fehleranfälligen Low-Level Sachen bräuchten ja eh nur die Library-Implementierer zu verstehen. Ich fürchte jedoch das ist unmöglich, gerade wegen solchen Dingen.

    In diese Richtung wurde in der letzten auch ganz gute Arbeit gemacht.
    Allerdings kollidiert der Anspruch der "einfachen Sprache" wie im vorliegenden Fall oft mit dem gleichzitigen Anspruch, dass sich 20 Jahre alter Code immer noch mit aktuellen Compilern bauen lassen soll.

    Ich würde es auch als intuitiver empfinden, wenn "abc" von Haus aus ein String-Literal wäre, anstatt eine Aneinanderreihung von Bytes irgendwo im Speicher.
    In einer modernen Sprache sollte ersteres der Standardfall sein (ist auch sicherlich das, was man in neuem Code am häufigsten braucht) und letzteres der "Sonderfall",
    für den man dann eine etwas andere Syntax verwendet. Was das jedoch alles an altem Code kaputt machen würde, kannst du dir sicher selbst ausmalen 🙂
    ... von den ganzen "String"-Klassen, die man neben std::string sonst noch so in freier Wildbahn antrifft, will ich erst gar nicht anfangen.

    Vielleicht ringt man sich ja mal dazu durch die "einfachere Sprache" per Compiler-Flag aktivierbar zu machen (so wie man es bei vielen Compilern schon für c++11 machen muss).
    "abc" wäre dann automatisch ein std::string -Literal, und wenn man einen const char* haben will, muss man es anders schreiben.
    Oder ein char wäre ein "Zeichen", das man nicht einfach so wie z.B. einen (u)int8_t auf einen Pointer aufaddieren kann.

    Finnegan



  • Finnegan schrieb:

    Vielleicht ringt man sich ja mal dazu durch die "einfachere Sprache" per Compiler-Flag aktivierbar zu machen (so wie man es bei vielen Compilern schon für c++11 machen muss).
    "abc" wäre dann automatisch ein std::string -Literal, und wenn man einen const char* haben will, muss man es anders schreiben.
    Oder ein char wäre ein "Zeichen", das man nicht einfach so wie z.B. einen (u)int8_t auf einen Pointer aufaddieren kann.

    Diese Idee finde ich gefährlich. Erinnert mich an Python 2 und 3 Chaos.
    Nein ich finde sie sogar furchtbar.

    EDIT: Außer vielleicht mit Makros an und abstellbar, aber selbst dann stiftet das eher mehr chaos.

    EDIT 2: Wenn ich schon ein Beitrag in diesem Thread habe lasse ich nochmal das hier liegen:

    char c = '\n'["ABCDEFGHIJKLMNOP"];
    


  • char c = '\n'["ZuKurz"];
    !!warning: array index 10 is past the end of the array (which contains 7 elements) [-Warray-bounds]
    

    wenigstens kann der clang in manchen Situation was komisches erkennen

    '\n' verhält sich hier irgendwie wie ein array aber auch wie ein Wert - oder?



  • Finnegan schrieb:

    Ich würde es auch als intuitiver empfinden, wenn "abc" von Haus aus ein String-Literal wäre, anstatt eine Aneinanderreihung von Bytes irgendwo im Speicher.
    In einer modernen Sprache sollte ersteres der Standardfall sein (ist auch sicherlich das, was man in neuem Code am häufigsten braucht) und letzteres der "Sonderfall",

    Das ist Java-Denke und macht die Sache NICHT einfacher, sondern komplizierter.
    "abc" sollte ein char[3] sein und array-to-pointer sollte nicht automagisch konvertiert werden.


Log in to reply