Informationen finden zu: BasicLowLevel C++ / Interne Funktionsweise



  • Hallo
    @Moderator: Verzeiung, aber meine Frage befaßt sich ganz allgemein mit C++, darum weiß ich nicht so recht wo ich es eigentlich einordnen soll. Möglicherweise fällt meine Frage sogar in den Bereich Compiler. Also falls nötig bitte entsprechend verschieben. Danke.

    Also ich stelle mir gerade die Frage, wie C++ genau Funktioniert, weiß aber nicht so recht wo ich entsprechende Informationen finde, bzw. wonach genau ich suchen müßte.

    Ein Beispiel:
    Wen ich nach "C++ Operatoren" suche, finde ich Informationen darüber, welche Operatoren es gibt, wofür sie gut sind, wie man Operatoren überlädt, usw.
    Mich würde jedoch Interessieren was ein Operator genau macht, bzw. wie er es macht.

    Sinngmäß sähe also die Information die ich suche folgendermaßen aus:
    "
    Es gibt folgende Arithmetische Operatoren: + - * /
    Der "+"-Operator addiert zwei Zahlen.
    Während dem Compelieren wird der "+"-Operator durch die "+(w1, w2)"-Funktion ersetzt.
    Diese sieht folgendermassen aus:

    int +(int w1, int w2)
    {
    for(i=0; i < w2; i++) {w1++;}
    return w1;
    }
    

    "

    Mir ist natürlich klar, daß das Ganze so nicht gemacht wird.
    Eine alternative Erklärung wäre also:
    "Es gibt folgende Arithmetische Operatoren: + - * /
    Der "+"-Operator addiert zwei Zahlen.
    Addieren von Zahlen ist eine Grundfunktion des Prozessors.
    Steht im QuellCode Beispielsweise "2+3" so ersetzt der Compiler dies durch "2PLS3".
    Der Maschinenbefehl "PLS" sagt dem Prozesser, daß er den Wert vor "PLS" mit dem Wert nach "PLS" addieren soll.
    Der Prozessor arbeitet Binär, kennt also nur 0&1.
    2 als Binärzahl ist 0010
    3 als Binärzahl ist 0011
    Der Prozessor Addiert zahlen indem er von rechts beginnen die einzelnen stellen Addiert. Dabei ersetzt er
    0+0+R0 durch 0 R0;
    0+1+R0 bzw. 1+0+R0 so wie 0+0+R1 durch 1 R0;
    1+1+R0 so wie 1+0+R1 bzw. 0+1+R1 durch 1+R1;
    und 1+1+R1 durch 0+R1;
    R steht dabei für Rest und ist am Anfang immer 0.
    Nach diesem Schema wird "0010" + "0011" zu "0101" was dezimal "5" entspricht."
    Diese Erklärung wäre dann zwar deutlich detaillierter, als für mich notwendig, mir jedoch genau so recht.
    (Anmerkung: Beide Varianten würden zwar das richtige Ergebnis liefern und, ich vermute, daß es so ähnlich wie ich es oben in Variante 2 beschrieben habe auch tatsächlich sein könnte, dies ist jedoch nur eine Vermutung, und ich habe keine Ahnung wie es am Ende wirklich funktioniert.)

    So etwas in der Art wäre es, was ich suche.

    Und eben nicht nur für Operatoren sondern ganz allgemein für C++
    Zum Beispiel wird mit "int" gekennzeichnet, daß es sich um eine Ganzzahlige Dezimalzahl handelt, aber wie wird diese dann in eine Hexadezimalzahl, bzw. in eine Binärzahl umgerechnet.
    Char ist ein Zeichen, es gibt eine Liste die den ASCII-Code enthält, aber wie sieht ein Zeichen für den Prozessor aus? Unter anderem hege ich die Vermutung, daß für den Prozessor die Zahl 1 etwas anderes ist als das "Zeichen" 1.
    Ist das, was zwischen " " steht, intern ein Char Arry oder wird es irgend wie direkt an den Prozessor weitergeleitet?
    (Zwischen CPU und GPU unterscheide ich bei meiner Fragestellung übrigens vorerst noch nicht.)

    Das sind halt so Fragen, die mich beschäftigen.
    Leider kann ich anhand der Information die ich so finde immer nur Vermutungen anstellen, und ich finde auch immer nur "Antworten" auf eine konkrete Fragenstellung.
    Was ich also ideler weise Suche wäre in gewisser Weiße soetwas wie eine Liste: ... das, das, und das sind einge der wichtigsten "Elemente" von C++. Das funktioniert intern so und so, das so und das so. (Das waren jetzt aber viele das. grins)



  • @axam Dezimal, Hexadezimal, ... sind nur Darstellungen eines Wertes. Das ist dem Computer egal.
    Der rechnet mit Werten.

    Wenn du eine ASCII Tabelle genau anschaust, siehst du ganz klar, das 1 etwas anderes als '1' (Zeichen) ist. Bei ASCII wäre das 49 oder 0x31.
    Es sind aber Beides Ganzzahlen (also int)

    "1" (also ein Stringliteral) ist in C++ nochmal etwas ganz anderes.



  • Hallo,

    du stellst dir das vielleicht ein bisschen zu einfach vor. Es gibt so eine Liste nicht, es kann sie auch nicht geben, weil Compiler nicht einfach 1:1 den Quellcode in Maschinencode überführen. Es ist die Aufgabe eines Compilers, ein Maschinenprogramm zu erzeugen, das bei der Ausführung die Effekte hat, die das Quellprogramm anhand der Semantik der Programmiersprache haben müsste.

    Wenn in deinem Programm 2+3 steht, hat das erstmal überhaupt gar keinen Effekt. Vielleicht möchtest du cout << 2+3; schreiben, dann hat es den Effekt 5 auszugeben, also generiert der Compiler ein Programm, das 5 ausgibt. Natürlich darf ein sehr naiver Compiler ein Programm erzeugen, das ungefähr so arbeitet:

    Speichere 2 in Speicherzelle 1
    Speichere 3 in Speicherzelle 2
    Addiere Speicherzelle 2 auf Speicherzelle 1
    Rufe operator<< auf cout mit dem Inhalt von Speicherzelle 1 auf
    

    Muss er aber nicht. Meistens ist es so, dass der Compiler mehrere Phasen hat, in denen der vorliegende Code optimiert wird. Sowas wie 2+3 wird bspw. gleich von vornherein zu 5 zusammengefasst. Später gibt es weitere Optimierungsphasen, die auf anderen Ebenen arbeiten. Beispielsweise werden Maschinenbefehle umgeordnet, um die Möglichkeiten der parallelen Abarbeitung im Prozessor besser auszunutzen.

    @axam sagte in Informationen finden zu: BasicLowLevel C++ / Interne Funktionsweise:

    Steht im QuellCode Beispielsweise "2+3" so ersetzt der Compiler dies durch 2PLS3

    Das hängt vom Prozessor ab, vom Adressierungsmodus, vom Datentyp, ob es vektorisiert ist oder nicht ...

    Der Prozessor Addiert zahlen indem er von rechts beginnen die einzelnen stellen Addiert.

    Das hängt noch viel mehr vom Prozessor ab.

    Zum Beispiel wird mit "int" gekennzeichnet, daß es sich um eine Ganzzahlige Dezimalzahl handelt

    Nein. Nicht dezimal.

    Char ist ein Zeichen, es gibt eine Liste die den ASCII-Code enthält, aber wie sieht ein Zeichen für den Prozessor aus?

    Es ist auch nur eine Zahl. Und es gibt nicht nur ASCII.

    Ist das, was zwischen " " steht, intern ein Char Arry oder wird es irgend wie direkt an den Prozessor weitergeleitet?

    Es ist ein char-Array.

    Was ich also ideler weise Suche wäre in gewisser Weiße soetwas wie eine Liste: ... das, das, und das sind einge der wichtigsten "Elemente" von C++. Das funktioniert intern so und so, das so und das so. (Das waren jetzt aber viele das. grins)

    Dinge, mit denen du dich vielleicht beschäftigen möchtest:

    • Technische Informatik: Wie funktionieren digitale Schaltungen, UND/ODER-Gatter, Halbaddierer, Volladdierer, Flip-Flops, Schieberegister, Zähler, der ganze Kram.
    • Assembler bzw. Maschinensprache eines Prozessors deiner Wahl
    • Compilerbau

    Für den Anfang solltest du auch mal auf https://godbolt.org/ herumspielen und dir die Assembler-Ausgabe des Compilers für einige Programme angucken.



  • @Bashar sagte in Informationen finden zu: BasicLowLevel C++ / Interne Funktionsweise:

    Für den Anfang solltest du auch mal auf https://godbolt.org/ herumspielen und dir die Assembler-Ausgabe des Compilers für einige Programme angucken.

    Genau!
    Schau dir zum Beispiel mal das hier an:
    https://gcc.godbolt.org/z/G97aqz versus https://gcc.godbolt.org/z/EGrE6v
    eine einfache Funktion, die eine Division durch 100 durchführt.

    Obwohl der Prozessor ein idiv kennt, wir es nur in der einen Einstellung (clang mit -O0, also deaktivierten Optimierungen) generiert, in der anderen (gcc, -O beliebig) nicht. Also: es kommt eben in der Regel das raus, was schneller ist, nicht das, was dem Befehl exakt entspricht. Noch dazu ist das, wie schon gesagt, hochgradig abhängig davon, was für Hardware du hast. Vielleicht ist z.B. in zukünftigen Prozessoren das idiv mit einem immediate Divisor schneller als das Multiplizieren und rumshiften. Dann würde der Compiler für diese Prozessoren eben anderen Code erzeugen.

    Oder schau dir dieselbe Funktion für ganz andere Hardware an: https://gcc.godbolt.org/z/8dY39r - wie du siehst, sieht der Maschinencode jetzt völlig anders aus.



  • @Bashar
    Danke für deine Antwort.
    Eine deiner Antworten ist mir bislang jedoch noch etwas unklar.

    @Bashar sagte in Informationen finden zu: BasicLowLevel C++ / Interne Funktionsweise:

    Zum Beispiel wird mit "int" gekennzeichnet, daß es sich um eine Ganzzahlige Dezimalzahl handelt.
    Nein. Nicht dezimal.

    Ich war ja bislang der Meinung zB.:
    int x = 10;
    //wobei 10 eine Dezimalzahl ist.
    Dies funktioniert jedoch nur wenn festgelegt ist, daß der Wert der Varible vom Typ int in Dezimalform vorliegt, den in Hexadezimal müßte ich für den selben Wert A nehmen, während 10 in Binärform den DezimalWert 2 repräsentiert. Oder wo liegt da gerade mein Denkfehler?



  • @axam sagte in Informationen finden zu: BasicLowLevel C++ / Interne Funktionsweise:

    Zum Beispiel wird mit "int" gekennzeichnet, daß es sich um eine Ganzzahlige Dezimalzahl handelt

    Nein. Nicht dezimal.

    Ich war ja bislang der Meinung:
    int x = 10; //wobei 10 eine Dezimalzahl ist.
    Dies funktioniert jedoch nur wenn festgelegt ist, daß der Wert der Varible vom Typ int in Dezimalform vorliegt, den in Hexadezimal müßte ich für den selben Wert A nehmen, während 10 in Binärform den DezimalWert 2 repräsentiert. Oder wo liegt da gerade mein Denkfehler?

    Dass diese Kennzeichnung nichts mit dem "int" zu tun hat. Du kannst ja auch
    int x = 0xA;
    schreiben. Dann hast du auch eine mit "int" gekennzeichnete Variable, aber einen hexadezimalen Wert.

    Für den Inhalt von x ist unerheblich, wie du die Zahl darstellst. Ob binär, oktal, dezimal, hexadezimal.



  • OK
    (Immer das gleiche mit google. Ich wollte meine nächste Frage mal schnell durch googln beantworten, habe aber nur erfahren wie man in c++ dezimal in hexadezimal umrechnet.)

    Aber meine Ursprüngliche Frage bleibt.
    Wenn die Eingabe des Werts einer Varible vom Typ int mit jeder belibige Basis erfolgen kann, woher weiß der Compiler dann welchen Wert die Varible besitzt.
    Als Beispiel:
    int x = 10; //basis2: Wert == Dezmal2
    int y = 10; //diesmal basis3: Wert ist jetzt == Dezmal3
    //z soll Wert == Dezmal5 bekommen:
    int z = 0;
    z=x+y;
    ???



  • 10 ist immer dezimal. Du kannst denselben Wert auch als 012 (oktal), 0xA (hexadezimal) oder 0b1010 (binär) darstellen. Andere Basen gibt es in C++ nicht, und wenn es sie gäbe, müsste man die natürlich auch irgendwie kennzeichnen. Gedankenlesen kann der Compiler nicht.



  • Ah, danke
    (War mir neu)

    Also Merke (Memo an mich):
    Zahl == Zahl (Wie gewohnt Dezimal)
    Zahl mit Vorzeichen 0 == oktalZahl
    Zahl mit Vorzeichen 0x == heXaZahl
    Zahl mit Vorzeichen 0b == binZahl

    Seh mir jetzt dann mal die Links genauer an.
    (Danke auch dafür)



  • Wichtig ist vor allem, dass man sich das mit der führenden 0 für oktal merkt. Man kann leicht reinfallen, wenn man Zahlen ausrichten möchte:

    int a = 0123;
    int b = 1234;
    int c = 2345;
    

    sieht erstmal unverdächtig aus, bis man die erste Zeile (=83) genauer anschaut. Meiner Meinung eine sehr unglückliche Sache, dass man damals die führende 0 für oktal gewählt hat. Ein 0o123 wäre meiner Meinung nach besser gewesen.



  • @axam

    Wenn die Eingabe des Werts einer Varible vom Typ int mit jeder belibige Basis erfolgen kann, woher weiß der Compiler dann welchen Wert die Varible besitzt.

    Du musst unterscheiden zwischen dem was du im Source-Code hinschreibst, und dem was dann in der Variable gespeichert wird.

    z.B.

    int x = 10;
    int y = 0xA;
    int z{};
    z++;
    z = z + z;
    z = z + z;
    z = z + z;
    z++;
    z++;
    // x, y und z haben jetzt den exakt selben Wert
    

    Bzw. wenn du willst kannst du sagen dass der Wert in der Variable immer binär gespeichert wird. Nur du kannst es halt im Source-Code dezimal, hexadezimal, oktal oder binär hinschreiben -- wenn du die passende Schreibweise verwendest. Der Compiler rechnet das dann entsprechend für dich um. Im ausführbaren Code ist alles binär.



  • Zusatzfrage:
    Es wurde hier ja erwähnt, daß der Compiler den Code optimiert.
    Wie weitreichend können diese Optimierungen sein?
    Konkret geht es mir dabei jetzt um das oben angeführte Beispiel einer Zähl-Adition:

    int plus(int w1, int w2)
    {
    for(i=0; i < w2; i++) {w1++;}
    return w1;
    }
    

    Das Ergebnis der Funktion ist eine simple Addition, ich kann mir jedoch Situationen vorstellen, wo mir auch das zählen selbst wichtig wäre. Vereinfacht gesagt, daß die Funktion für das Ergebnis von 100+200 länger braucht als für 1+2, aber gleich lange wie für 1.000+200.

    Besteht überhaupt die Gefahr, daß der Compiler solche Funktionen zB. im obigen Fall durch eine Addition ersetzt?
    Gibt es eine Möglichkeit wie ich Funktionen irgend wie kennzeichnen kann, so daß sie vom Optimierungsprozess ausgenommen sind?

    Oder macht es zumindest im obigen Beispiel ohnehin keinen unterschied, da ein Prozesor ohnehin nur Zählt - also für die Adition ohnehin immer gleichlange brauchen würde, wie für die Ausführung obiger Funktion?

    Fals Adition und Zählen für den Prozessor das selbe wäre, gilt meine Frage übrigens für eine Zähl-Multiplikations-Funktion:

    int mal(int w1, int w2)
    { 
    int w=0;
    for(i=0; i < w2; i++) {w=plus(w, w1);}
    return w;
    }
    

    (PS.: bin mir jetzt nicht ganz sicher ob das so richtig wäre, oder ob es in der Zeile 5 für das richtige Ergebnis "int w=w1; " lauten müßte, da es jedoch nur ein Beispiel sein soll, ist es mehr oder weniger nebensächlich.)



  • Eine Gefahr besteht in dem Sinne nicht, da diese Ersetzungen nur vorgenommen werden, wenn sie nicht zu durch anderen Code beobachtbaren Änderungen führt. Wenn du std::cout << mal(5,7) schreibst kann da durchaus std::cout << 35 im rauskommen. Konkret kann man das aber nicht sagen, was in Einzelfall passiert müsste man immer nachschauen, wenn es einen so dringend interessiert.



  • @axam sagte in Informationen finden zu: BasicLowLevel C++ / Interne Funktionsweise:

    Wie weitreichend können diese Optimierungen sein?

    So klug wie die Compilerentwickler waren - solange du keinen Unterschied in der Ausgabe siehst.

    Sogar sowas wie die Summe der Zahlen von 1 bis n wird von clang durch die Gauß-Summenformel ersetzt.

    Schau dir das doch in godbolt an, wozu haben wir da schon hingelinkt?

    Hier: https://gcc.godbolt.org/z/YEWPKd
    Deine plus-Funktion: Der Parameter w1 wird (unter Linux x86_64) in rdi und w2 im Register rsi übergeben.
    Also

    int plus(int edi, int esi) {
      int eax = 0;               //  xor     eax, eax
      if (esi nicht negativ) {   //  test    esi, esi [1]
        eax = esi;               //  cmovns  eax, esi [1a]
      }
      eax += edi;                //  add     eax, edi
      return eax;                //  ret
    

    [1] und [1a] gehören zusammen - test ist hier "setze sign-Bit" und das cmov (conditional move) ist ein "if + Zuweisung zusammen": move wenn nicht sign-Bit gesetzt"

    Du siehst also: deine Schleife ist weg.

    Und wenn du z.B. noch -march=skylake als Parameter angibst, ist der Algorithmus anders...

    Und wenn du deine mal-Funktion einfach mal in Godbolt reinkopierst, wirst du da ein "imul", also eine Multiplikation, finden...



  • @axam: Noch zur Info für dich: ein Prozessor verwendet intern dafür keine Schleifen, sondern die Hardware-Schaltungen Addierwerk und Multiplizierer.
    Die Performance ist also unabhängig von den verwendeten Summanden (bzw. Faktoren), sondern nur von der Bitbreite.



  • @wob: Danke daß du mich an godbold erinnerst, hab irgend wie gar nicht daran gedacht ... "Brett vorm Kopf". ("grins")

    Also was ich hier so höre gefällt mir irgend wie gar nicht.
    (Liegt nicht an euch, sondern an den Gegebenheiten).

    Dann werd ich mal weiter überlegen, wie ich daß ganze Realisieren könnte.

    Um mein Dilemer vieleicht etwas nachvollziehbarer zu machen und dann eventuell sogar eine gute Anregung für eine Lösung zu bekommen, hier nun wofür ich es brauchen könnte:

    Meine Überlegungen sind zwar vorerst nur theoretischer Natur ist jedoch an eine Reale Situation angelehnt:
    Ein kleines Kind rechnet nicht, es Zählt:
    1-2-3-4Finger + 1-2-3-4Finger = 1-2-3-4-5-6-7-8Finger
    Ein Erwachsener (jemand der schon Rechnen kann) zählt nicht, sondern hat bereits Ergebnisse auswendig gelernt, die er nun nur noch miteinander verknüpfen muß. Wenn jemand sagt 4+4 weis er sofort das es 8 ist, und bei 44+44 zählt er nicht bis 88 sondern rechnet:
    Stelle1: 4+4=8
    Stelle2: 4+4=8
    Stelle1 Stelle2 = 88

    Nun mache ich mir gedanklich ein Objekt "Kind" und ein Objekt "Erwachsener" und weise ihnen verschiedene "Eigenschaften" zu.
    Bei der "Eigenschaft" "Mathematik/Logik" "erbt" "Erwachsener" zwar von "Kind" die "Fähigkeit" "Zählen()" verfügt aber zusätzlich über die "Fähigkeit" "Rechnen()".
    Stelle ich jetzt beiden Objekten("Erwachsener" & "Kind") die Aufgabe eine Liste mit Rechenaufgaben zu lösen, sollte entsprechend der AlltagsLogik "Erwachsener" mit seiner Erfahrung("Rechnen()") schneller sein als "Kind" mit "Zählen()".

    Jetzt weis aber der Compiler nicht worauf es mir ankommt, und wenn er mein Funktionen ändert, zugunsten der Preformenz teilweise sogar unterschiedliche Aufgaben gleich übersetzt, ist am Ende womöglich sogar "Erwachsener" mit seinem "umständlichen" "Rechnen()" langsamer als "Kind" mit "Zählen()".
    (Deswegen "Kind" zählen zu erschweren [zwischen zwei Zahlen immer eine pause einlegen] wäre in dem Fall zwar eine Korrekturmöglichkeit, eine Lösung, die sich aus der Natur der Sache ergibt, wäre mir jedoch lieber. Und wichtiger, ohne Compilerwisen muß man erstmal darauf kommen, daß so ein Händikap notwendig ist.)



  • @axam Irgendwie überlegst du an komischen Sachen 😃

    Normalerweise ist man an der schnellsten Lösung zu einem Problem interessiert. Wenn du eine zeitabhängige Simulation machen möchtest, musst du dich auch selbst um die Zeitabhängigkeit kümmern.

    Ansonsten kannst du dem Compiler natürlich verbieten Optimierungen vorzunehmen. Wenn du dir den Godbolt Link von @wob anguckst, gibt es da das Flag -O2, wenn du daraus -O0 machst, wird nicht mehr optimiert.

    Aber ich möchte dich bitten, dir nicht anzugewöhnen mit -O0 für echten produktiven Code zu kompilieren 😉



  • @Schlangenmensch

    @Schlangenmensch sagte in Informationen finden zu: BasicLowLevel C++ / Interne Funktionsweise:
    Ansonsten kannst du dem Compiler natürlich verbieten Optimierungen vorzunehmen. Wenn du dir den Godbolt Link von @wob anguckst, gibt es da das Flag -O2, wenn du daraus -O0 machst, wird nicht mehr optimiert.

    Genau deshalb war ja auch meine Frage:

    @axam sagte in Informationen finden zu: BasicLowLevel C++ / Interne Funktionsweise:
    Gibt es eine Möglichkeit wie ich Funktionen irgend wie kennzeichnen kann, so daß sie vom Optimierungsprozess ausgenommen sind?

    So wie ich das Verstanden habe deaktiviere ich mit dem Flag -O0 die Optimierung global für den gesamten Compelierungssprozes. Adlerdings soll der Compiler ja seine Aufgabe bestmöglich machen, lediglich ausgewählten Funktionen würde ich gerne mit einer CompilerInformation makieren, damit der Compiler diese Stellen "wortwörtlich" übersetzt, schon allein um sicher sein zu können, daß durch das Compelieren kein unerwartetes "Verhalten" entsteht [wie etwa "Rechnen()" dauert länger als "Zählen()"].



  • @Schlangenmensch sagte in [Informationen finden zu: BasicLowLevel C++ / Interne Funktionsweise]

    wenn du daraus -O0 machst, wird nicht mehr optimiert.

    Niemand garantiert, dass bei -O0 gar keine Optimierungen stattfinden. "Einfachste" Optimierungen passieren trotzdem (was "einfachst" ist, ist abhängig vom Compilerhersteller).

    Aber ich möchte dich bitten, dir nicht anzugewöhnen mit -O0 für echten produktiven Code zu kompilieren 😉

    !



  • Implementiere das Rechnen selber mit Strings statt int.


Log in to reply