Standardbibliothek und Stapelspeicher



  • Hallo, ich mal wieder ^^.

    habe gerade in meinem lehrbuch das kapitel über funktionen in c durchgearbeitet, und nun stellt sich mir die frage wie denn so ein maschinencode (oder vereinfacht in assembler) grundsätzlich aufgebaut ist und dann vom rechner ausgeführt wird.
    im internet habe ich schon bisschen gesucht aber ich glaube ich habe nicht das richtige stichwort, denn ständig finde ich solche ratgeber für leute die gerade mal rausgefunden haben, dass es sowas wie pcs gibt...
    ich kann mir nämlich nicht vorstellen, wie der linker die funktionen aus der std-bibliothek in mein programm einbinden kann, wenn die entsprechenden funktionen bereits in maschinensprache vorliegen. ich meine, woher weiß der linker, dass genau ab der zeile x z.B. die funktion printf() anfängt und wo sie wieder aufhört?

    eine weitere frage ist wie das mit dem stapelspeicher funktionieren soll. in meinem buch wurde beschrieben, dass wenn ich eine funktion aufrufe speicher im stack reserviert wird, dann die funktionsparameter (in umgekehrter reihenfolge) und die rücksprungadresse auf dem stack abgelegt werden. dann folgen die lokalen variablen.

    mal angenommen eine funktion meines programms sieht so aus:

    int summe(int a, int b) {
        int c=a+b;
        return c;
    }
    

    dann sieht der stack (laut meinem buch) ja folgendermaßen aus:

    wert von c
    rücksprungadresse
    wert von a
    wert von b

    doch wie kann der pc denn an die parameter überhaupt rankommen (ohne die rücksprungadresse zu verlieren), wenn sie im stack ganz unten liegen?

    außerdem habe ich mich schon immer gefragt, in welcher sprache wohl die standardbibliothek geschrieben wurde. ich meine: ohne die bib wäre c doch nur syntax oder nicht? und mit syntax allein kann man keine bibliotheken schreiben, oder sehe ich das irgendwie falsch?

    eine damit (meiner meinung nach) sehr eng verbundene frage ist: kann ich mit c überhaupt programme schreiben, die mehr können als mir die kombination verschiedener bibliotheksfunktionen ermöglicht?

    meine hauptmotivation c zu lernen war es, genauer zu verstehen, wie ein computer wirklich funktioniert. c soll doch so hardwarenah sein (was ich auch glaube), aber in den büchern aus denen ich bisher gelernt habe wird nur beschrieben, wie ich funktionen wie printf() benutze, dabei ging es mir eher darum zu verstehen, wie man so eine funktion von grund auf schreibt, wie das zusammenspiel von programm, speicher, prozessor und kernel aussieht etc.

    dynamische speicherverwaltung (mit malloc() z.B.) ist meiner meinung nach ein sehr interessantes thema, aber da wurde natürlich auch nur erklärt was malloc() macht und wie man es anwendet. wie es intern funktioniert wird natürlich wieder verschwiegen. (nur als weiteres beispiel meiner verzweiflung ;))

    ich hoffe hier kann irgendjemand ein kleines bisschen licht ins dunkel bringen, oder zumindest irgendeine buch-empfehlung machen, damit ich hinterher schlauer bin 🙂

    vielen dank schonmal im voraus an alle, die mir helfen möchten,

    Dominik



  • Bartfratze schrieb:

    und nun stellt sich mir die frage wie denn so ein maschinencode (oder vereinfacht in assembler) grundsätzlich aufgebaut ist und dann vom rechner ausgeführt wird.

    das ist im Prinzip Architektur abhängig, i.a. kann man das so ausdrücken:
    - die CPU versteht nur Wörter mit 0 und 1. Diese können unterschiedlich lang sein, z.b. 4 Byte breit.
    - Die CPU führt einen Befehl aus, indem der nächste auszuführende Befehl irgendwo aus dem Speicher geladen wird, damit das Wort dekodiert werden kann. Mit Kodierung meint man z.b.:
    stell dir vor ein 'add' Befehl mit Syntax
    add reg1, reg2, reg3 was reg1 := reg2 + reg3 realisiert.
    In den Bytes, die wir für den Befehl zur Verfügung haben, müssen wir mit 0 und 1 diese Information speichern. Dabei könnten wir die ersten 4 Bits für eine interne Kodierung fürs Zielregister verwenden, die nächsten 8 bits für die Operatoren, die letzen Bits stehen dann für die Art vom Befehl zur Verfügung, usw.

    In einem Thread ist es schwer als das zu erklären, wofür man sonst eine Vorlesung bzw. Buch bräuchte. Ich empfehle

    ARM System Developer's Guide | ISBN: 1558608745

    leider recht teuer, aber insgesamt ein wunderschönes Buch, mit dem du ein netten und einfachen Einstieg in die Rechnerarchitektur bekommst (unter anderen lernst du die schöne ARM Achr. kennen).

    Bartfratze schrieb:

    denn ständig finde ich solche ratgeber für leute die gerade mal rausgefunden haben, dass es sowas wie pcs gibt...

    ich verstehe nicht, was du damit meinst.

    Bartfratze schrieb:

    ich kann mir nämlich nicht vorstellen, wie der linker die funktionen aus der std-bibliothek in mein programm einbinden kann, wenn die entsprechenden funktionen bereits in maschinensprache vorliegen. ich meine, woher weiß der linker, dass genau ab der zeile x z.B. die funktion printf() anfängt und wo sie wieder aufhört?

    das ist aber genau die Aufgabe des Linkers 😉

    Eine Binary bzw. Objekt Datei, die vom Kompiler erstellt wird, hat nicht nur den Assembler Code. Je nach Betriebssystem gibt es spezielle Formate für ausführtbare Dateien, Objektdateien, bibliotheken, usw. Da ist zum Beispiel eine Tabelle drin, wo drauf steht, welche öffentliche Symbole es gibt, an welche Stelle sie sich befinden, usw. Der Linker liest diese Information und benutzt sie um offsets zu berechnen, die Stellen zu finden, Objekte miteinander zu verknüpfen, usw.

    C-Code kann nur der C-Compiler verstehen. Der Linker kann nur mit den binären Dateien umgehen.

    Bartfratze schrieb:

    eine weitere frage ist wie das mit dem stapelspeicher funktionieren soll. in meinem buch wurde beschrieben, dass wenn ich eine funktion aufrufe speicher im stack reserviert wird, dann die funktionsparameter (in umgekehrter reihenfolge) und die rücksprungadresse auf dem stack abgelegt werden. dann folgen die lokalen variablen.

    mal angenommen eine funktion meines programms sieht so aus:

    int summe(int a, int b) {
        int c=a+b;
        return c;
    }
    

    dann sieht der stack (laut meinem buch) ja folgendermaßen aus:

    wert von c
    rücksprungadresse
    wert von a
    wert von b

    doch wie kann der pc denn an die parameter überhaupt rankommen (ohne die rücksprungadresse zu verlieren), wenn sie im stack ganz unten liegen?

    das ist auch etwas, was sowohl Compiler als auch arch- abhängig ist. Die Architkturen haben in der Regel spzeielle Register, die nur solche Informationen speichern (stack pointer, return address, instruction pointer, usw).

    Bartfratze schrieb:

    außerdem habe ich mich schon immer gefragt, in welcher sprache wohl die standardbibliothek geschrieben wurde

    Das ist nicht dein ernst, oder? In C natürlich. Gut, viele Teile sind aus performance-Gründen (weil Menschen manchmal besser mit assembler optiemieren als Compiler) in Assembler, aber die Mehrheit des Codes ist C.

    Bartfratze schrieb:

    ohne die bib wäre c doch nur syntax oder nicht? und mit syntax allein kann man keine bibliotheken schreiben, oder sehe ich das irgendwie falsch?

    da hast du schon was falsch verstanden. Eine Sprache ist nur ein Tool, mit dem Menschen der Maschine einfacher mitteilen können, was sie machen soll. Der Compiler ist ein Helfer, der die Programmiersprache in "Maschinensprache" übersetzt.

    Du musst die std-Bib nicht verwenden, du kannst alles selber programmieren, wenn du möchtest. Das muss man manchmal sogar machen, wenn man z.b. einen Kernel schreibt. C ist nur dazu da, damit wir auf einfache Art und Weise dem Computer Befehle geben können.

    Bartfratze schrieb:

    eine damit (meiner meinung nach) sehr eng verbundene frage ist: kann ich mit c überhaupt programme schreiben, die mehr können als mir die kombination verschiedener bibliotheksfunktionen ermöglicht?

    und was genau stellst du dir unter "mehr können als verschiedener bibliotheksfunktionen" vor? 😕

    Bartfratze schrieb:

    meine hauptmotivation c zu lernen war es, genauer zu verstehen, wie ein computer wirklich funktioniert.

    da würde ich eher ein Buch über Rechenarchiktur nehmen oder über eine bestimmte Architktur (da sind wir nochmal an meiner Empfehlung). Wenn dir aber wiederum Betriebssysteme, Kernel usw. interessiert, würde ich dann ein Buch über Betriebssysteme nehmen.

    Diese Information bekommst du nicht nur nicht durchs Programmieren, da musst du schon ein bisschen tiefer einsteiegen.

    Es ist so, als würdest du das Autofahren lernen, um zu verstehen, wie ein 4-Takt Benzin Motor funktioniert. Sicherlich wirst du einiges mehr davon erfahren, nachdem du den Führerschein gemacht hast, aber ohne eine entsprechende Vertiefung wirst du nicht weit kommen. Hier ist es dasselbe.



  • ich kann mir nämlich nicht vorstellen, wie der linker die funktionen aus der std-bibliothek in mein programm einbinden kann, wenn die entsprechenden funktionen bereits in maschinensprache vorliegen.

    der compiler baut platzhalter in den code. der linker findet die und weiss daher, wo er welche funktionsaufrufe (oder die lib-funktion gleich selber) einsetzen soll.

    mal angenommen eine funktion meines programms sieht so aus:
    int summe(int a, int b) {
    int c=a+b;
    return c;
    }
    dann sieht der stack (laut meinem buch) ja folgendermaßen aus:
    wert von c
    rücksprungadresse
    wert von a
    wert von b

    so absolut kannste das nicht sehen. a und b können auch in registern sein und die rücksprungadresse ist oft ganz hinten auf dem stack. das macht jeder compiler anders.

    doch wie kann der pc denn an die parameter überhaupt rankommen (ohne die rücksprungadresse zu verlieren), wenn sie im stack ganz unten liegen?

    der greift z.b. direkt (über stackpointer - offset) drauf zu, ohne die werte mit 'pop' von stack zu holen. erst zum schluss wird der stackpointer restauriert: sp <- sp - sizeof(alle_variablen) und dann führt er das 'return' aus, wobei auch die rücksprungadresse vom stack verschwindet. aber das ist wieder relativ. manche compiler verwenden andere aufrufkonventionen, bei denen z.b. der aufrufer den stack restaurieren muss.

    außerdem habe ich mich schon immer gefragt, in welcher sprache wohl die standardbibliothek geschrieben wurde

    einiges in C und einiges in assembler.

    meine hauptmotivation c zu lernen war es, genauer zu verstehen, wie ein computer wirklich funktioniert. c soll doch so hardwarenah sein (was ich auch glaube), aber in den büchern aus denen ich bisher gelernt habe wird nur beschrieben, wie ich funktionen wie printf() benutze, dabei ging es mir eher darum zu verstehen, wie man so eine funktion von grund auf schreibt, wie das zusammenspiel von programm, speicher, prozessor und kernel aussieht etc.

    dann hättest du besser assembler nehmen sollen. C abstrahiert das gefummel mit registern, stack und speicherzellen in eine 'menschenlesbare' und portable form, die aber trotzdem in effizienten maschinencode übersetzt werden kann. mit den feinheiten der maschine kommste unter 'reinem' C nicht in berührung, deshalb wohl auch deine vielen fragen.

    dynamische speicherverwaltung (mit malloc() z.B.) ist meiner meinung nach ein sehr interessantes thema, aber da wurde natürlich auch nur erklärt was malloc() macht und wie man es anwendet. wie es intern funktioniert wird natürlich wieder verschwiegen. (nur als weiteres beispiel meiner verzweiflung

    malloc/free ist aus C-coders sicht eine black box. schau mal hier: http://en.wikipedia.org/wiki/Dynamic_memory_allocation
    und dann klick dich weiter.
    🙂



  • Hmmm sehr verwirrend alles ... das mit assembler werde ich mir über kurz oder lang mit sicherheit mal antun, ich dachte nur ich könnte es fürs erste vermeiden, weil c ja schon sehr hardware-nah sein soll.

    jetzt aber mal ganz doof gefragt: mal die teile aussen vor gelassen, die in assembler geschrieben wurden, wie kann die bibliothek denn in c geschrieben worden sein?

    ihr müsst es mal aus sicht eines c-anfängers sehen bitte 😃

    mein verständnisproblem liegt genau an folgender stelle: in jedem c-buch, das ich mir bisher angeschaut habe, wird im grunde genommen die syntax der sprache c in verbindung mit den funktionen und möglichkeiten der standardbibliothek vermittelt.
    stelle ich mir nun mein lehrbuch ohne die funktionen der standardbibliothek vor, so habe ich nur variablen, funktionen und unäre, binäre und tertiäre operatoren.
    wie ich daraus überhaupt irgendetwas sinnvolles programmieren soll (ganz zu schweigen von ner dicken bibliothek) ist mir schleierhaft.

    vielleicht konnte ich meine gedanken und probleme diesmal etwas klarer formulieren 🙂

    gibt es vielleicht auch gute bücher auf deutsch zu dem ganzen thema? ich kann zwar gut englisch, doch auf deutsch wärs natürlich trotzdem wesentlich angenehmer.

    vielen dank schonmal für eure hilfe, ist echt ein tolles forum!

    LG,

    Dominik



  • Bartfratze schrieb:

    jetzt aber mal ganz doof gefragt: mal die teile aussen vor gelassen, die in assembler geschrieben wurden, wie kann die bibliothek denn in c geschrieben worden sein?

    Weil C nur ein Werkzeug ist. Mit C sagt du nur

    "CPU mach dies und das
    und dann lade dir 4 Bytes aus Addresse xyz und kopiere sie in
    Addresse zbv, dann addiere 3 und 5 und speichere es in Register so und so...."

    das ist genau die hardware-nahe Eigenschaft von C, du kannst sowas machen

    int *p = (int*) 0xdeadbeef;
    *p = 9;
    

    und wenn du Schreibzugriff auf Addresse 0xdeadbeef hast, dann steht dort nämlich dann eine 9. Du kannst aber nur so viel Funktionalität programmieren, wie dein System es dir erlaubt. Wenn dein Betriebssystem dir eine Schnittstelle anbietet, um damit Zeichen auszugeben, dann kannst du diese verwenden. Wenn es eine solche Schnittstelle nicht gibt, dann wirst du sie eben nicht schreiben können.

    Du kannst dir bei Ikea ein Möbel kaufen. Die Menschen verbinden Ikea Möbel mit Möbeln und Anleitungen. Du kannst sehr wohl dein Möbel bauen, ohne die Anleitung zu benutzen, weil die Anleitung dir nur ein Werkzeug ist und nicht das Möbel an sich.

    Genauso ist es mit der Sprache C: sie ist nur ein Werkzeug, wie man dem Computer Befehle erteilt, mehr nicht. Man kann aber eine grundlegende Bibliothekn damit basteln, damit andere Programmierer das Rad nicht ständig neu erfinden müssen, du kannst es aber es selber tun, wenn du es willst. Du musst den Unterschied zwischen "Sprache C" (als Werkzeug) und Standardbibliothek unterscheiden können.

    Ansonsten scheinst du die Grundlagen in Rechenarchitektur nicht zu wissen, um zu verstehen, wie Code ausgeführt wird. Da empfehle ich nochmal ein Buch über Rechenarchitektur.

    Bsp:

    Die stdlib hat viele Helferfunktionen, die man als Programmierer immer verwendet, z.b. Speichermanipulation, Stringsverarbeitung, usw. Stell dir vor, du willst einen Block von A nach B kopieren

    int *p, *q;
    int len;
    ...
    
    /* kopiere len Bytes aus p in q */
    char *p_b, *q_b;
    
    p_b = (char*) p;
    q_b = (char*) q;
    
    int i;
    for(i=0; i < len; ++i)
      q_b[i] = q_b[i];
    

    Du kannst oder auch die memcpy Funktion verwenden, die so aussehen könnte

    void *memcpy(void *dest, const void *src, size_t n)
    {
        char *src_b = src, *dest_b = dest;
        int i;
        for(i=0; i < n; ++i)
            dest_b[i] = src_b[i];
    
        return dest;
    }
    

    Anstatt, dass ich jedes Mal, wenn ich ein Speicherbklock "per Hand" kopieren muss, kann ich die stdlib-Funktion memcpy verwenden. Meine Implementierung ist in C (für das Bsp eine sehr einfache Implementierung). Mit dem Werkzeug C, habe ich also eine Funktion geschrieben, die einen Speicherblock irgendwo anders kopiert. Genau das macht die stdlib, nur weit performanter als ich im Bsp.

    Also kann ich meinen ersten Code einfach so schreiben

    int *p, *q;
    int len;
    ...
    /* kopiere len Bytes aus p in q */
    memcpy(p, q, len);
    

    und aus meiner Sicht ist dann memcpy uninteressant, ich weiß nur, sie kopiert den Speicher, mehr brauch ich nicht zu wissen. Aber wie du siehst, ich könnte sehr wohl auf die stdlib verzichten.



  • Danke für die wirklich anschauliche erklärung, ich glaube ich habe es jetzt kapiert.
    C ist einfach wiederum eine anschauliche art und weise, dem pc zu sagen was man machen will und dabei sehr hardwarenah.
    Doch welche eigenschaften von C machen denn diese hardwarenähe aus?
    Ich sehe da momentan an erster Stelle die Verwendung von Zeigern und damit einen mehr oder minder direkten Zugriff auf den Speicher. Ist es das, was C so besonders macht?

    dass mir die funktionen der standarbibliothek vieles erleichtern und ich vieles dadurch nicht mehr neu erfinden muss war mir klar, doch wollte ich auch gerne sehen wie diese bibliothek intern realisiert wurde, schade, dass es dazu nicht irgendeinen quelltext gibt, den würde ich gerne sehen 😞

    dann noch eine frage zu deinem quelltext:

    void *memcpy(void *dest, const void *src, size_t n)
    {
        char *src_b = src, *dest_b = dest;
        int i;
        for(i=0; i < n; ++i)
            dest_b[i] = src_b[i];
    
        return dest;
    }
    

    warum steht in deiner schleife nicht:

    dest_b[i] = *src_b[i];
    

    ???
    Das fände ich irgendwie logischer, weil ich doch das tatsächliche Objekt (und nicht seine Adresse) src_b[i] an der Adresse dest_b[i] haben möchte oder nicht?

    Danke schonmal im Voraus und liebe Grüße,

    Dominik



  • ok also meine zweite frage hat sich gerade teilweise geklärt, da ich nen denkfehler bei mir entdeckt habe.

    ich weiß jetzt, dass src_b[i] äquivalent ist zu *(src_b+i), womit ich natürlich das objekt an der adresse src_b+i bekomme.

    doch auf der linken seite des zuweisungsoperators steht dann ja *(dest_b+i), womit ich ja wieder ein objekt habe.

    wie kann es nun sein, dass einem objekt ein objekt zugewiesen wird?
    oder ist es so, dass der compiler intern so vorgeht, dass er die rechte seite des zuweisungsoperators "so wie es ist" in die adresse des objektes auf der linken seite schreibt?

    so würde es für mich am meisten sinn ergeben

    PS: habe mich jetzt endlich registriert, wurde langsam nötig 🙂



  • Bartfratze schrieb:

    Doch welche eigenschaften von C machen denn diese hardwarenähe aus?

    fast alle c-konstrukte lassen sich leicht, ohne viel overhead zu erzeugen, in maschinensprache umwandeln. ein i++ z.b. hat oft 'ne direkte entsprechung in einer assemblersprache (etwa sowas wie inc R0). deshalb wird C gern zum programmieren von mikrocontrollern genutzt. die datentypen von C sind auch maschinennah (bis auf floats und double).

    Bartfratze schrieb:

    doch wollte ich auch gerne sehen wie diese bibliothek intern realisiert wurde, schade, dass es dazu nicht irgendeinen quelltext gibt, den würde ich gerne sehen

    gibts viele, hier z.b.:http://www.uclibc.org/
    🙂



  • Bartfratze schrieb:

    Doch welche eigenschaften von C machen denn diese hardwarenähe aus?
    Ich sehe da momentan an erster Stelle die Verwendung von Zeigern und damit einen mehr oder minder direkten Zugriff auf den Speicher. Ist es das, was C so besonders macht?

    ja, und außerdem, dass man Assembler Code sein einfach in C Code einbetten kann. Auf einem Rechner kannst du auf Geräte nur durch einen Bus zugreifen, auf den Bus selber über eine entsprechende Addresse im Speicher, wo der Memory Controller mappt, usw.

    Z.b. auf einem Intel PXA255 kannst du auf der FFUART (serielle Schnittstelle) über die Addresse 0x40100000 erreichen. Dann kannst du einfach folgendes tun:

    void output_char(char c)
    {
        volatile uint32_t *FFUART = (volatile uint32_t *) 0x40100000;
    
        *FFUART = c;
    }
    
    char read_char(void)
    {
        volatile uint32_t *FFUART = (volatile uint32_t *) 0x40100000;
    
        return *FFUART;
    }
    

    Ich kann mit einem Zeiger auf irgendwelche beliebige Addresse zeigen lassen und lesen und schreiben (sofern der Prozess natürlich Rechte dazu hat, wie in meinem Bsp). In assembler sähe das so in etwa aus

    output_char:
    stmfd    sp!, {r1}
    ldr      r1, FFUART_CONST
    str      r0, [r1]
    ldmfd    sp!, {r1}
    mov      pc, lr
    
    read_char:
    stmfd    sp!, {r1}
    ldr      r1, FFUART_CONST
    ldr      r0, [r1]
    ldmfd    sp!, {r1}
    mov      pc, lr
    
    FFUART_CONST:
    .word 0x40100000
    

    Siehst du, wie stark der C Code las Leben vereinfacht? Und stell dir jetzt vor, alle Intel CPUs würde die serielle Schnittstelle auf Addresse 0x40100000 mappen, dann würde der C Code für alle Intel CPUs universell sein. Wie mehr hardwarenah möchtest du denn noch haben?

    Für deine zweite Frage

    for(i=0; i < n; ++i)
            dest_b[i] = src_b[i];
    

    kannst du so übersetzen:

    mach folgendes solange i kleiner ist als n
      {
          schreib den Wert, der an der Stelle 'src_b' + i sich befindet,
          an der Stelle 'dest_b' + i;
    
          i++;
      }
    

    *src_b[i] ist syntaktisch nicht möglich. src_b ist ein char* . Du kannst entweder mit * oder [] den zeiger dereferenzieren (sprich, auf den Inhalt kommen, worauf der Zeiger zeigt), wobei src_b[0] äquivalent zu *src_b ist.

    src_b[x] (x beleibig) ist aber ein char , kein Zeiger mehr, du kannst also nicht mit * dereferenzieren.

    *src_b ist ebenfalls ein char , kein Zeiger mehr, du kannst also nicht mit [x] (x beliebig) dereferenzieren.


Anmelden zum Antworten