Hello, Guest the thread was viewed10k times and contains 196 replies

last post from Axellander at the

Würde mich gern mal mit C befassen

  • Sicherlich unterliegt das alles den Regeln des Compilers, natürlich ist es nicht "read only" im Sinne von ROM.

    Man muß sich da damit beschäftigen, welche Speicherbereiche der Compiler für das Programm anlegt:


    https://www.geeksforgeeks.org/memory-layout-of-c-program/


    Da gibt es insbesondere den "Heap" und den "Stack" (wäre auch mal schön, wenn jemand was dazu erklären würde, ich weiß, daß das wichtig ist, aber nicht, was es genau damit auf sich hat).

    Unter "Initialized Data Segment" steht da auch was zu dem Fall 'char *str = "hallo";'. Es mag ja sein, daß man das "string literal" bei manchen Uralt-Compilern zu sagen wir "hello" verändern kann, aber sobald man darüber hinaus schreibt, sagen wir zu "hello world", wird es völlig undefiniert.

    In dem Bereich sind ja auch andere "literals" abgelegt, z.B. die 5 aus "int b = 5;". Würde man die z.B. zu 7 verändern, indem man direkt in den Speicher für diese Literals schreibt? Wohl eher nicht. Man würde einfach "b = 7;" machen.

    Das sind jedenfalls die Fragen, mit denen man sich bei der C-Programmierung beschäftigen muß, und ich finde, die meisten Tutorials und z.B. auch das Buch von K&R bereiten einen nicht so gut darauf vor.


    Z.B. auch, wie bekommt man bessere Fehlermeldungen als nur "Speicherzugriffsfehler". Da gibt es Tools wie das schon genannte "Valgrind", oder auch "gdb" (GNU Debugger von gcc), für den man wahrscheinlich ein Frontend brauchen würde.


    http://sourceware.org/gdb/wiki/GDB%20Front%20Ends


    Solche Tools gibt es ja - für moderne Compiler, auf dem Amiga könnte es schwierig werden. Nur hatte ich immer Probleme, die auch zu benutzen. Man braucht eben viel Grundwissen, um zu verstehen, was die überhaupt machen, und dieses Grundwissen scheint einem kaum jemand zu vermitteln.

    (Man konnte wohl gcc anweisen, Debugginginformationen mit in das Executable zu schreiben, das dadurch größer wurde als sonst. Wenn ich das gemacht hatte, konnte ich mit "kdbg" mein Programm laufen lassen und an beliebiger Stelle anhalten und dann in der GUI von kdbg nachschauen, welche Werte die Variablen in meinem Programm zu der Zeit der Laufzeit hatten. Das wäre schonmal ein Anfang. Aber immer noch sehr unkomfortabel im Vergleich zu den aussagekräftigen Fehlermeldungen von interpretierten Sprachen wie sagen wir Perl. Da weiß man (im Verhältnis) gleich, was los ist, das macht alles sehr viel einfacher.)


    Ich hatte sogar mal kurz in ein Fernstudium Informatik reingeschnuppert, und nichtmal da lernte man das. Im ganzen Studiengang gab es keinen C-Kurs (!). Man konnte also ausgebildeter Informatiker (ist ja nicht mehr Diplom - also dann dieses Bachelor / Master) werden, ohne gelehrt zu bekommen, was Heap und Stack sind, und was gdb ist und wie man den benutzt.

    Was für mich - neben meinem Alter - auch ein Grund war, da nicht weiter zu machen. Na gut, das führt hier wohl zu weit.


    Aber vielleicht lerne ich ja hier im Thema noch was zu diesen Fragen. Wäre schön.

  • Da gibt es insbesondere den "Heap" und den "Stack" (wäre auch mal schön, wenn jemand was dazu erklären würde, ich weiß, daß das wichtig ist, aber nicht, was es genau damit auf sich hat).

    Das sind eigentlich Implementierungsdetails, die beiden Begriffe kommen im C-Standard überhaupt nicht vor.


    Unter "Initialized Data Segment" steht da auch was zu dem Fall 'char *str = "hallo";'.

    Das Statement legt gleich zwei Dinge an: Zum einen ein String-Literal mit dem Inhalt "hallo" und zum anderen einen Pointer namens "str" und Typ "char *", der auf dieses String-Literal zeigt. Den Pointer darf man im Programm verändern, das String-Literal nicht. Auf einem Mikrocontroller könnte der Compiler den String zB im ROM/Flash ablegen, aber der Pointer muss auf jeden Fall ins RAM und würde an geeigneter Stelle (zB beim Programmstart wenn es eine globale Variable ist) automatisch so initialisiert werden, dass er auf das Literal im ROM/Flash zeigt.


    In dem Bereich sind ja auch andere "literals" abgelegt, z.B. die 5 aus "int b = 5;".

    Die 5 in diesem Beispiel ist im C-Standard kein literal sondern eine Konstante. literals sind nur string literals.



    Würde man die z.B. zu 7 verändern, indem man direkt in den Speicher für diese Literals schreibt? Wohl eher nicht.

    Programme, die ihren eigenen Quellcode modifizieren um Werte zu verändern sind ja auch nicht so üblich.


    Ich hatte sogar mal kurz in ein Fernstudium Informatik reingeschnuppert, und nichtmal da lernte man das. Im ganzen Studiengang gab es keinen C-Kurs (!).

    Ein Informatikstudium ist ja auch kein C-Programmierlehrgang. ;) Ich würde allerdings von Informatikstudierenden spätestens ab ungefähr den dritten Semester erwarten, dass sie sich in relativ kurzer Zeit in die Sprache einarbeiten können - C diente ja als Syntaxvorbild für diverse heute verwendete Sprachen und ist relativ klein und feature-arm.


    ohne gelehrt zu bekommen, (...) was gdb ist und wie man den benutzt.

    Sowas konkretes an der Uni? Vielleicht als "Hier ist die Aufgabe, das Tool könnte hilfreich sein, Doku gibts unter <url-des-originalprojekts>" im Rahmen einer Übung oder Praktikums, aber doch nicht in einer Vorlesung...

  • Da gibt es insbesondere den "Heap" und den "Stack" (wäre auch mal schön, wenn jemand was dazu erklären würde, ich weiß, daß das wichtig ist, aber nicht, was es genau damit auf sich hat).

    Die Idee hinter Heap und Stack ist, einem Programm die Möglichkeit zu geben, auch dynamisch, d.h. während der Laufzeit neue Variablen (genauer Speicherobjekte) anlegen zu können. Die Lebensdauer der Heapobjekte unterliegen der Verantwortung des Programmierers (malloc(), free()). Die Lebensdauer der Stackobjekte unterliegen der Verantwortung des Compiler (z.B. lokale auto-Variablen, Funktionsparameter).


    Bildlich gesprochen sitzen Stack und Heap sich gegenüber und bewegen sich aufeinander zu. Damit ist auch eine flexibelere Speichernutzung möglich.


    In der Praxis sind Heaps bei dynamischen Datenstrukturen (z. B. Listen) und Stacks bei rekursiven Funktionen wichtig.

  • Ich hatte sogar mal kurz in ein Fernstudium Informatik reingeschnuppert, und nichtmal da lernte man das. Im ganzen Studiengang gab es keinen C-Kurs (!).

    Ein Informatikstudium ist ja auch kein C-Programmierlehrgang. ;) Ich würde allerdings von Informatikstudierenden spätestens ab ungefähr den dritten Semester erwarten, dass sie sich in relativ kurzer Zeit in die Sprache einarbeiten können - C diente ja als Syntaxvorbild für diverse heute verwendete Sprachen und ist relativ klein und feature-arm.


    ohne gelehrt zu bekommen, (...) was gdb ist und wie man den benutzt.

    Sowas konkretes an der Uni? Vielleicht als "Hier ist die Aufgabe, das Tool könnte hilfreich sein, Doku gibts unter <url-des-originalprojekts>" im Rahmen einer Übung oder Praktikums, aber doch nicht in einer Vorlesung...

    Kann bestätigen, dass das alles an der Uni einfach vorausgesetzt wird. Es gibt zwar optionale Veranstaltungen, in denen man "programmieren lernt", aber keine Scheine (Punkte) dafür.

    "Ein Informatikstudium ist ja auch kein C-Programmierlehrgang." :D Den stibitz ich mir. =)

  • Es liegt im Ermessen des Compilers, wo er Stringliterale ablegt. Können also auch veränderbar sein.

    Dass diese readonly sind hatte ich auch irgendwo gelesen, deshalb hat's mich gewundert, dass ich die in HiSoft C++ trotzdem verändern kannn.

    Aber wie du schriebst, scheint das Compiler-abhängig zu sein.

    Stringliterale werden üblicherweise immer in einem read-only Bereich abgelegt. Das ist IMO aber nicht notwendigerweise Comppilerabhängig, sondern Hardwareabhängig. Auf dem C64, Amiga, etc. gibts keine MMU die einen Speicher schützen könnte. Daher kann man dort IMMER Stringliterale überschreiben. Auf einem Amiga mit 68030/40 und entsprechendem OS würde das aber nicht mehr funktionieren. Aus diesem Grund ist das überschreiben von einem Stringliteral "Undefined Behavior". Es kann funktionieren, muss aber nicht.


    Ein Unterschied ist auch, wie man ein Stringliteral definiert.


    Hier wird ein Stringliteral als Pointer deklariert. Da der Pointer nicht als "const" deklariert ist, "darf" man ihn überschreiben, aber das ist eben "undefinied behavior".

    Code
    1. char *str = "abcde";


    Diesen String zu verändern kann auch unangenehme Seiteneffekte haben.


    z.B.:

    In dem Beispiel könnte es passieren dass nach dem Aufruf von function2 IMMER "hallo" ausgegeben wird statt "Hallo" in function1. Wenn nämlich der Compiler gleiche Stringliterale nur einmal anlegt (was ja sinnvoll wäre), dann wäre dieses Stringliteral für jede weitere Nutzung verändert, ohne dass der Code das bemerken würde.


    Korrekt wäre also

    Code
    1. const char *str = "abcde";


    Wenn man das ganze als Array macht, darf man den String überschreiben:

    Code
    1. char str[] = "abcde";

    In diesem Fall wird ein Array reserviert mit der Länge des Strings(+1 fürs 0-Byte). Das darf man daher auch überschreiben. Ein "const" ist in dem Fall nicht notwendig, ausser man will es.

  • Korrekt wäre also

    Code
    1. const char *str = "abcde";


    Wenn man das ganze als Array macht, darf man den String überschreiben:

    Code
    1. char str[] = "abcde";

    In diesem Fall wird ein Array reserviert mit der Länge des Strings(+1 fürs 0-Byte). Das darf man daher auch überschreiben. Ein "const" ist in dem Fall nicht notwendig, ausser man will es.

    Vom Standpunkt des Computers stimmt das alles.

    Kompliziert ist eher, was der Programmierer dabei alles denken und im Kopf haben muß: Wenn er weiß, daß "Pointer + Literal" besser "const" sein sollte, weil es eben in einem besonderen Speicherbereich abgelegt wird, und er selbst keinen eigenen Speicher dafür reserviert hat, und wenn er weiß, daß das beim Array alles anders ist (aber dafür das Array z.B. nicht direkt aus einer untergeordneten Funktion zurückgegeben werden kann, weil es lokal ist, und der Compiler seinen Speicher beim "return" wieder löscht), dann ist es gut.

    Wenn er das alles aber nicht weiß, sondern sich nur sagt "Ich will einen String", wie man das z.B. in BASIC ja ständig macht ('LET A$="Hallo"'), dann gibt es Probleme. Dann erhält er im besten Fall Compilerfehler, im schlechtesten Fall läuft sein Programm, verhält sich aber unkontrolliert.

    Und das ist es meiner Meinung nach, was C so schwierig macht.


    Die Sache mit den Strings hab' ich jetzt (glaube ich) einigermaßen im Griff. Aber da kann es noch viele andere solcher Minenfelder geben, in denen man dieses zusätzliche Hintergrundwissen braucht, das nicht offensichtlich ist.


    Danke nochmal an "ogd" für die Erklärung von Heap / Stack. Also Heap: Dynamischer Speicher; Stack: Statischer Speicher z.B. für lokale Variablen wie Arrays.

    Das hilft mir weiter!

  • Wenn er das alles aber nicht weiß, sondern sich nur sagt "Ich will einen String", wie man das z.B. in BASIC ja ständig macht ('LET A$="Hallo"'), dann gibt es Probleme. Dann erhält er im besten Fall Compilerfehler, im schlechtesten Fall läuft sein Programm, verhält sich aber unkontrolliert.

    Und das ist es meiner Meinung nach, was C so schwierig macht.

    Aber das ist es eben was man lernen muss wenn man eine Sprache lernt. Je nachdem wie "tief" die Sprache ist, muss man mehr oder weniger berücksichtigen oder wissen. Bei C# oder Java brauche ich mich um Speicherverwaltung in der Regel nicht kümmern, pointer gibts auch keine und auch keinen Stack. Dafür kann ich aber gewisse Dinge nicht tun, oder nicht so effizient. Das ist eben Aufgabe des Programmierers, seine Werkzeuge zu kennen und das passende richtig einzusetzen. :)


    Quote

    Danke nochmal an "ogd" für die Erklärung von Heap / Stack. Also Heap: Dynamischer Speicher; Stack: Statischer Speicher z.B. für lokale Variablen wie Arrays.

    Das hilft mir weiter!

    Stack ist auch dynamischer Speicher, aber der ist Funktionslokal und wird von der CPU (mehr oder weniger) automatisch beim Rücksprung aufgeräumt. Der Heap ist nicht funktionslokal sondern kann beliebig herumgereicht werden nach "unten" und nach "oben". Stackspeicher darf nur nach "unten" weitergereicht werden, aber nie nach "oben". Ausserdem ist der Stack u.U. begrenzt. z.B. auf dem C64 sind das nur 255 Bytes. Auf dem Amiga kann man den mehr oder weniger beliebig gross machen (sofern man genug Speicher hat). Die Grösse des Heaps hängt vom Betriebssystem und vom verbauten RAM ab (sowie von der CPU).

  • Bei C# oder Java brauche ich mich um Speicherverwaltung in der Regel nicht kümmern, pointer gibts auch keine und auch keinen Stack.

    Keinen Stack? Ganz sicher? :)

    Das ist ein Implementierungsdetail der Sprache um das man sich nicht kümmern muss. Das muss kein Stack sein. Da man keine Kontrolle über die darunter liegenden Mechanismen hat,kann man das beliebig implementieren. Ich würde mit ziemlicher Sicherheit sagen dass lokale Variablen in solchen Sprachen keinen CPU Stack verwenden weil das Interpretersprachen sind (oder zumindest pseudocompiliert).

  • Keinen Stack? Ganz sicher? :)

    Das ist ein Implementierungsdetail der Sprache um das man sich nicht kümmern muss. Das muss kein Stack sein. Da man keine Kontrolle über die darunter liegenden Mechanismen hat,kann man das beliebig implementieren. Ich würde mit ziemlicher Sicherheit sagen dass lokale Variablen in solchen Sprachen keinen CPU Stack verwenden weil das Interpretersprachen sind (oder zumindest pseudocompiliert).

    Na ja, alles andere als Stack ergibt für lokale Variablen keinen Sinn. Würde mich stark wundern, wenn sie da irgendwas anderes erfunden hätten - zumal das ja ausgezeichnet auf den CPU-Stack abgebildet werden kann.

  • Na ja, alles andere als Stack ergibt für lokale Variablen keinen Sinn. Würde mich stark wundern, wenn sie da irgendwas anderes erfunden hätten - zumal das ja ausgezeichnet auf den CPU-Stack abgebildet werden kann.

    Das sind Sprachen die interpretiert werden und nicht comüiliert. Daher muss die Laufzeitumgebung alles zur Verfügung stellen was die Sprache benötigt. Funktionen werden also sowas ähnliches haben was einen Stack darstellt, aber das ist nicht der CPU Stack, sondern eine Datenstruktur die etwas ähnliches implementiert.

  • Na ja, alles andere als Stack ergibt für lokale Variablen keinen Sinn. Würde mich stark wundern, wenn sie da irgendwas anderes erfunden hätten - zumal das ja ausgezeichnet auf den CPU-Stack abgebildet werden kann.

    Das sind Sprachen die interpretiert werden und nicht comüiliert. Daher muss die Laufzeitumgebung alles zur Verfügung stellen was die Sprache benötigt. Funktionen werden also sowas ähnliches haben was einen Stack darstellt, aber das ist nicht der CPU Stack, sondern eine Datenstruktur die etwas ähnliches implementiert.

    Jede Sprache kann interpretiert, compiliert oder irgendwas dazwischen werden. Ob der Stack direkt von der CPU implementiert wird oder ein Software-Stack benutzt wird (wie z.B. bei von CC65 compilierten Programmen) spielt keine Rolle. Es bleiben aber Stacks.

  • Jede Sprache kann interpretiert ... werden.

    Daraufhin dachte ich: Gibt es auch einen C-Interpreter?

    Offenbar ja:

    https://stackoverflow.com/ques…here-an-interpreter-for-c

    Ich werde mal "tcc" ausprobieren. Beim Entwickeln könnte ein Interpreter, der einem schnell Programmfehler aufzeigt, ziemlich nützlich sein, würde ich sagen.


    Ich denke, ich mache bald nochmal ein eigenes Thema zu C auf.


    Hab' heute erstmal "MinGW" heruntergeladen, das ist "gcc für Windows" (freier, kostenloser C und C++-Compiler, Standard unter Linux) :

    https://dev.to/gamegods3/how-t…ws-10-the-easier-way-422j


    Anscheinend habe ich gerade Lust, mir noch ein weiteres Mal an C die Zähne auszubeißen. Aber wenn ihr mir helft und Fragen beantwortet, komme ich dieses Mal ja vielleicht ein bißchen weiter.

  • Beim Entwickeln könnte ein Interpreter, der einem schnell Programmfehler aufzeigt, ziemlich nützlich sein, würde ich sagen.

    Das Visualisierungstool Skorbut kann auch sehr hilfreich sein:


    https://github.com/fredoverflow/skorbut-release


    Das dazugehörige YouTube-Kanal:


    https://www.youtube.com/channel/UC9m7D4XKPJqTPCLSBym3BCg

  • MinGW ist so ne zweischneidige Sache. Einerseits ist es toll, dass es ihn gibt, weil ich sonst ein aktuelles Projekt auf der Arbeit niemals in wenigen Wochen unter Win ans Laufen gebracht hätte. Andererseits hab ich schon geflucht, weil die gleiche Library-Funktion unter Windows plötzlich andere Werte zurückgibt.

  • Beim Entwickeln könnte ein Interpreter, der einem schnell Programmfehler aufzeigt, ziemlich nützlich sein, würde ich sagen.

    Die moderne Variante wäre da eher eine IDE, die schon während man das Programm eingibt immer wieder diverse Checks ausführt und Problemstellen markiert.


  • Unter Windows würde man heutzutage am ehesten zu WSL (Windows Subsystem for Linux) greifen, um dann GCC und Co. zu nutzen, glaub ich.

    An die Möglichkeit hatte ich auch gedacht. Aber: Das WSL wollte, daß ich eine Linux-Distribution installiere.

    Quote

    The Windows Subsystem for Linux (WSL) lets developers install a Linux distribution (such as Ubuntu, OpenSUSE, Kali, Debian, Arch Linux, etc) and use Linux applications, utilities, and Bash command-line tools directly on Windows

    Das heißt, man ist dann in einem Linux. Wenn man darin den Linux-gcc verwendet, produziert er ausführbare Dateien für Linux. Man braucht also eine Form von MinGW, um Windows-exes zu produzieren.

    Dann kann man aber auch gleich unter normalem Windows bleiben. Das heißt, das WSL hilft einem da (leider) nicht.