Wie ist denn der aktuelle Stand? Sind mittlerweile alle gültigen BASICODE-Befehle abgedeckt?
Natürlich erwarte ich nicht, dass schon alles fehlerfrei funktioniert. Ich habe die beiden simplen Testprogramme hello.bas und scroll.bas erfolgreich ausführen können. Dann habe ich mich an etwas Größeres gewagt, nämlich das Programm zur Darstellung von Peano-Kurven, das Thomas hier eingestellt hat. Es bricht mit folgendem Fehler ab:
Exception in thread "main" java.lang.IllegalStateException: line not found: 20
at de.haupz.basicode.ast.ProgramNode.resolveJump(ProgramNode.java:57)
at de.haupz.basicode.ast.ProgramNode.run(ProgramNode.java:37)
at de.haupz.basicode.Main.run(Main.java:33)
at de.haupz.basicode.Main.main(Main.java:49)
Die Suche ergab 232 Treffer
- 26.11.2023, 15:25
- Forum: Programmierung
- Thema: BASICODE in Java
- Antworten: 5
- Zugriffe: 161
- 28.09.2023, 08:45
- Forum: Hardware
- Thema: LibrePCB - Freie Software für PCB-Design erreicht Version 1.0
- Antworten: 0
- Zugriffe: 245
- 03.09.2023, 17:35
- Forum: Programmierung
- Thema: Fragen und Antworten zum C-Kurs
- Antworten: 108
- Zugriffe: 25486
Re: Fragen und Antworten zum C-Kurs
Da hat wohl die Implementierung der printf-Funktion ein Problem mit Strings, die länger als 32.767 Zeichen sind. Das ist wohl ein Bug in der library.
Wenn du den Code folgendermaßen änderst, dann geht's:
Wenn du den Code folgendermaßen änderst, dann geht's:
Code: Alles auswählen
...
char *p;
printf("Wieviele Sterne? ");
scanf("%u", &i);
buffer = (char*) malloc(i + 1); // ein Byte extra für das String-Ende-Zeichen
if (buffer != NULL) { // sonst haben wir keinen Speicher bekommen
buffer[i] = '\0'; // String-Ende-Zeichen setzen
while(i--) {
buffer[i] = '*'; // String mit Sternchen füllen
}
// printf("%s\n", buffer);
p = buffer;
while (*p != '\0') {
putchar(*p++);
}
putchar('\n');
free(buffer); // Wichtig! Speicher wieder freigeben
...
- 03.09.2023, 16:24
- Forum: Programmierung
- Thema: Fragen und Antworten zum C-Kurs
- Antworten: 108
- Zugriffe: 25486
Re: Fragen und Antworten zum C-Kurs
Wenn du eine Variable lokal anlegst, also innerhalb einer Funktion, dann legt sie der Compiler auf dem Stack an (es sei denn, du deklarierst sie als static). Damit bekommt jeder Funktionsaufruf seine eigenen lokalen Variablen, wodurch z.B. Rekursion ermöglicht wird. Der Speicherplatz wird zur Laufzeit beim Funktionsaufruf durch Ändern des Stack Pointers (dem Z80-Register SP) reserviert und am Ende der Funktion wieder freigegeben (abermals durch Ändern des Stack Pointers). Man spricht auch von "automatischen Variablen". Sie haben naturgemäß keine festen Adressen, da sie vom Inhalt des Stack Pointers abhängen. Ergo macht die Zuordnung einer festen Speicheradresse keinen Sinn und der Compiler quittiert den Versuch mit einer Fehlermeldung.Paul hat geschrieben: ↑03.09.2023, 09:33 Und gleich noch eine Frage hinterher:
beim zweiten Beispiel hast die Variable ch global definiert.
Ich wollte sie natürlich gleich lokal definieren was der compiler nicht mag.
Um so größer war meine Verwunderung das der Compiler die unveränderte Zeile als globale Variable akzeptiert.
..,
Globale Variablen werden vom Compiler zur Compile-Zeit festen Speicheradressen zugeordnet (dasselbe gilt für static-Variablen). Während des Programmlaufs ändern diese sich nicht mehr. Hier macht es also Sinn, dem Compiler eine feste Adresse mit '@* vorzugeben. Diese verwendet er dann anstelle der Adresse, die er ohne '@'-Klausel automatisch ermittelt hätte.
- 03.09.2023, 16:13
- Forum: Programmierung
- Thema: Fragen und Antworten zum C-Kurs
- Antworten: 108
- Zugriffe: 25486
Re: Fragen und Antworten zum C-Kurs
Richtig erkannt; ich habe den Fehler korrigiert. Danke für den Hinweis.Paul hat geschrieben: ↑03.09.2023, 09:11 Kann es sei das du dich vertippt hast Bernd?Du wolltest wahrscheinlich schreiben das der Stackpointer bei F3FF nicht bei FFF0?bbock hat geschrieben: ↑18.08.2023, 10:10Die pragma-define-Option stellt sicher, dass der Stackpointer bei FFF0 beginnt. Damit bleibt der Speicherbereich ab F400 sicher vor Überschreibung durch den Stack.Code: Alles auswählen
zcc +cpm -subtype=pcw80 -compiler=sccz80 -pragma-define:REGISTER_SP=0xF3FF -create-app testprg1.c -o testprg1.com
- 03.09.2023, 16:10
- Forum: Programmierung
- Thema: Fragen und Antworten zum C-Kurs
- Antworten: 108
- Zugriffe: 25486
Re: Fragen und Antworten zum C-Kurs
Die ZX-Computer von Sinclair haben ein BASIC im ROM, das gleichzeitig das Betriebssystem ist. Der PCW hat aber kein Betriebssystem im ROM; wenn man eines benötigt, dann wird es wird beim Einschalten von Diskette gebootet. Meist ist das CP/M Plus. Man kann aber auch Programme ohne Betriebssystem booten, wie z.B. LocoScript. Daher gibt es auch keine Systemvariablen wie bei den BASIC-Rechnern. Schon möglich, dass es ein paar interessante Speicheradressen gibt, z.B. für CP/M Plus, aber da fällt mir jetzt nichts ein...
- 30.08.2023, 18:23
- Forum: Programmierung
- Thema: Programmieren in C mit dem z88dk
- Antworten: 20
- Zugriffe: 6787
19. Dynamische Speicherverwaltung
Werden in C Variablen deklariert, dann werden sie auf unterschiedliche Weise im Speicher angelegt. Im Falle von globalen Variablen werden sie zum Zeitpunkt der Kompilierung mit festen Speicheradressen versehen. Für lokale Variablen von Funktionen oder auch deren Parameter wird zur Laufzeit Speicher auf dem Stack reserviert, der beim Beenden der jeweiligen Funktion wieder freigegeben wird.
Allen ist gemeinsam, dass die Größe des zur Verfügung gestellten Speicherplatzes von vornherein festgelegt ist. Oft benötigt man aber Speicherplatz, dessen Größe nicht bereits zur Compile-Zeit bekannt ist, weil er z.B. von Benutzereingaben abhängt. Für diese Fälle braucht man eine dynamische Speicherzuweisung (engl.: dynamic memory allocation).
C bietet dafür Funktionen in der Standardbibliothek, die man mit #include <stdlib.h> in das eigene Programm einbindet. Dort finden wir u.a. die beiden wichtigsten Funktionen malloc() und free(). malloc() hat einen Parameter: die Größe des zu reservierenden Speichers. Die Funktion liefert einen void-Pointer zurück, der auf den Anfang des reservierten Speichers zeigt. Ein void-Pointer ist im Grunde eine Speicheradresse. Von welchem Typ die Daten im reservierten Speicher sein sollen, davon weiß malloc() nichts. Das Ergebnis wird einfach per type cast in den gewünschten Datentyp umgewandelt; dazu schreibt man den Datentyp einfach in Klammern vor den Funktionsaufruf:
Hier wird Platz für 200 Zeichen reserviert; der Pointer characters zeigt anschließend auf das erste der 200 Zeichen. Sollte nicht mehr genügend Speicher verfügbar sein, dann liefert malloc() NULL zurück. Man sollte das Ergebnis immer auf NULL prüfen, sonst sind Abstürze vorprogrammiert.
Sobald der Speicher nicht mehr benötigt wird, muss er mit der Funktion free() wieder freigegeben werden. Tut man das nicht, dann enstehen sog. Speicherlecks, also Speicher, der dauerhaft reserviert bleibt und nicht mehr für andere sinnvolle Aufgaben verwendet werden kann. free() ist komplementär zu malloc(), d.h. zu jedem malloc()-Aufruf muss es einen korrespondierenden free()-Aufruf geben. Zum Freigeben des zuvor angeforderten Speichers muss man free() den Pointer übergeben, den malloc() zurückgeliefert hat. Man muss des Ergebnis des malloc()-Aufrufs also immer in einer Pointer-Variablen speichern, damit man später free() aufrufen kann.
Im folgenden kleinen Beispiel wollen wir eine Zeichenkette mit einer Anzahl Sternen aufbauen, die der Benutzer vorgibt. Wir speichern die Datei als testmalloc.c.
Wir kompilieren mit
Die Präprozessor-Option -DAMALLOC macht die Verwendung von malloc besonders einfach; sie stellt den automatischen Modus der Speicherverwaltung ein.
Wird die ausführbare Datei mit malloc gestartet, dann fragt das Programm nach der gewünschten Anzahl von Sternen. Gibt man z.B. 15 ein, dann wird Speicher für 16 Bytes reserviert, ein String mit 15 Sternen aufgebaut und anschließend per printf() ausgegeben.
Hier binden wir die Header-Datei stdlib.h ein, die ihrerseits malloc.h inkludiert. In diesem Beispiel hätte es genügt, nur malloc.h einzubinden, allerdings hätten dann die Konstanten EXIT_SUCCESS und EXIT_FAILURE nicht zur Verfügung gestanden.
Im Zusammenhang mit malloc() wird oft der Operator sizeof verwendet. Er dient dazu, den Speicherbedarf eines Datentyps zur Compile-Zeit zu bestimmen. Das funktioniert mit jedem in C deklarierten Datentyp, also auch mit komplexen Datentypen wie structs. Möchte man Speicher für zehn float-Variablen reservieren, dann geht das folgendermaßen:
Der Compiler bestimmt also die Größe einer float-Variablen und durch Multiplikation mit 20 bekommen wir den Speicherplatz für 20 float-Variablen. Wie viel Speicher das tatsächlich ist, hängt vom System ab, auf dem das Programm kompiliert wird.
Für den Zugriff auf die einzelnen float-Variablen kann man auch die Array-Schreibweise verwenden. Möchte man die dritte float-Variable, also die mit dem Index 2, auf den Wert 5.3 setzen, dann geht das so:
In der Standardbibliothek findet man weitere Funktionen zur dynamischen Speicherverwaltung. Darunter calloc(), bei der man die Anzahl der gewünschten Elemente und die Elementgröße als Parameter übergibt, und realloc() zur Änderung der Größe eines zuvor allozierten Speicherblocks.
Allen ist gemeinsam, dass die Größe des zur Verfügung gestellten Speicherplatzes von vornherein festgelegt ist. Oft benötigt man aber Speicherplatz, dessen Größe nicht bereits zur Compile-Zeit bekannt ist, weil er z.B. von Benutzereingaben abhängt. Für diese Fälle braucht man eine dynamische Speicherzuweisung (engl.: dynamic memory allocation).
C bietet dafür Funktionen in der Standardbibliothek, die man mit #include <stdlib.h> in das eigene Programm einbindet. Dort finden wir u.a. die beiden wichtigsten Funktionen malloc() und free(). malloc() hat einen Parameter: die Größe des zu reservierenden Speichers. Die Funktion liefert einen void-Pointer zurück, der auf den Anfang des reservierten Speichers zeigt. Ein void-Pointer ist im Grunde eine Speicheradresse. Von welchem Typ die Daten im reservierten Speicher sein sollen, davon weiß malloc() nichts. Das Ergebnis wird einfach per type cast in den gewünschten Datentyp umgewandelt; dazu schreibt man den Datentyp einfach in Klammern vor den Funktionsaufruf:
Code: Alles auswählen
char *characters = (char *) malloc(200);
Sobald der Speicher nicht mehr benötigt wird, muss er mit der Funktion free() wieder freigegeben werden. Tut man das nicht, dann enstehen sog. Speicherlecks, also Speicher, der dauerhaft reserviert bleibt und nicht mehr für andere sinnvolle Aufgaben verwendet werden kann. free() ist komplementär zu malloc(), d.h. zu jedem malloc()-Aufruf muss es einen korrespondierenden free()-Aufruf geben. Zum Freigeben des zuvor angeforderten Speichers muss man free() den Pointer übergeben, den malloc() zurückgeliefert hat. Man muss des Ergebnis des malloc()-Aufrufs also immer in einer Pointer-Variablen speichern, damit man später free() aufrufen kann.
Im folgenden kleinen Beispiel wollen wir eine Zeichenkette mit einer Anzahl Sternen aufbauen, die der Benutzer vorgibt. Wir speichern die Datei als testmalloc.c.
Code: Alles auswählen
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int i, n, result;
char *buffer;
printf("Wieviele Sterne? ");
scanf("%d", &i);
buffer = (char*) malloc(i + 1); // ein Byte extra für das String-Ende-Zeichen
if (buffer != NULL) { // sonst haben wir keinen Speicher bekommen
buffer[i] = '\0'; // String-Ende-Zeichen setzen
while(i--) {
buffer[i] = '*'; // String mit Sternchen füllen
}
printf("%s\n", buffer);
free(buffer); // Wichtig! Speicher wieder freigeben
result = EXIT_SUCCESS;
}
else {
result = EXIT_FAILURE;
}
return result;
}
Code: Alles auswählen
zcc +cpm -subtype=pcw80 -compiler=sccz80 -DAMALLOC -create-app testmalloc.c -o malloc.com
Wird die ausführbare Datei mit malloc gestartet, dann fragt das Programm nach der gewünschten Anzahl von Sternen. Gibt man z.B. 15 ein, dann wird Speicher für 16 Bytes reserviert, ein String mit 15 Sternen aufgebaut und anschließend per printf() ausgegeben.
Hier binden wir die Header-Datei stdlib.h ein, die ihrerseits malloc.h inkludiert. In diesem Beispiel hätte es genügt, nur malloc.h einzubinden, allerdings hätten dann die Konstanten EXIT_SUCCESS und EXIT_FAILURE nicht zur Verfügung gestanden.
Im Zusammenhang mit malloc() wird oft der Operator sizeof verwendet. Er dient dazu, den Speicherbedarf eines Datentyps zur Compile-Zeit zu bestimmen. Das funktioniert mit jedem in C deklarierten Datentyp, also auch mit komplexen Datentypen wie structs. Möchte man Speicher für zehn float-Variablen reservieren, dann geht das folgendermaßen:
Code: Alles auswählen
float *floatVars = (float*) malloc(20 * sizeof(float));
Für den Zugriff auf die einzelnen float-Variablen kann man auch die Array-Schreibweise verwenden. Möchte man die dritte float-Variable, also die mit dem Index 2, auf den Wert 5.3 setzen, dann geht das so:
Code: Alles auswählen
floatVars[2] = 5.5;
- 18.08.2023, 10:10
- Forum: Programmierung
- Thema: Programmieren in C mit dem z88dk
- Antworten: 20
- Zugriffe: 6787
18. Absolute Adressen
Manchmal ist es erforderlich auf bestimmte Speicheradressen zuzugreifen. Hierfür bietet das z88dk verschiedene Möglichkeiten. Eine Möglichkeit ist die Verwendung von Pointer-Variablen. So kann man z.B. den Wert 23 folgendermaßen in die Speicherzelle FF40 schreiben:
Um den Wert wieder auszulesen, dereferenzieren wir den Pointer:
Das komplette Testprogramm könnte so aussehen:
Wir kompilieren es mit
Die pragma-define-Option stellt sicher, dass der Stackpointer bei F3FF beginnt. Damit bleibt der Speicherbereich ab F400 sicher vor Überschreibung durch den Stack.
Es gibt noch einen einfacheren Weg um auf Werte an absoluten Adressen zuzugreifen: Variablen können so deklariert werden, dass sie an einer bestimmten Adresse im Speicher liegen.
Das obige Testprogramm kann also wie folgt geändert werden:
Das zweite printf dient zur Überprüfung, dass der Wert tatsächlich in der Speicherzelle F400 angekommen ist.
Hat man Maschinencode an einer bestimmten Speicheradresse abgelegt, dann kann man die Einsprungstellen als C-Funktionen zugänglich machen. Die Syntax ist wie folgt:
Ruft man im C-Programm die Funktion mit gx_scrAcc() auf, dann erzeugt der Compiler einen CALL-Befehl zur Speicheradresse F494. Für weitere Details zur Zusammenarbeit von C mit Maschinencode sei auf die z88dk-Dokumentation unter "Mixing C and Z80 Assembler" verwiesen. Dort werden Details zur Aufrufkonvention, die Parameter-Übergabe, das Erstellen von Bibliotheken und die Verwendung von Inline Assembler erläutert.
Code: Alles auswählen
unsigned char *p;
p = (unsigned char *) 0xFF40;
*p = 23;
Code: Alles auswählen
printf("%d\n", *p);
Code: Alles auswählen
#include <stdio.h>
int main(void) {
unsigned char *p;
p = (unsigned char *) 0xF400;
*p = 23;
printf("%d\n", *p);
return 0;
}
Code: Alles auswählen
zcc +cpm -subtype=pcw80 -compiler=sccz80 -pragma-define:REGISTER_SP=0xF3FF -create-app testprg1.c -o testprg1.com
Es gibt noch einen einfacheren Weg um auf Werte an absoluten Adressen zuzugreifen: Variablen können so deklariert werden, dass sie an einer bestimmten Adresse im Speicher liegen.
Code: Alles auswählen
unsigned char ch @ 0xF400;
Code: Alles auswählen
#include <stdio.h>
unsigned char ch @ 0xF400;
int main(void) {
ch = 23;
printf("%d\n", ch);
printf("%d\n", *((unsigned char *) 0xF400));
return 0;
}
Hat man Maschinencode an einer bestimmten Speicheradresse abgelegt, dann kann man die Einsprungstellen als C-Funktionen zugänglich machen. Die Syntax ist wie folgt:
Code: Alles auswählen
void *gx_scrAcc(void) @ 0xF494;
- 15.08.2023, 20:46
- Forum: Programmierung
- Thema: HP-GL Interpreter
- Antworten: 48
- Zugriffe: 94725
HP-GL Interpreter C-Version
Es war eine Herausforderung, den Maschinencode-Anteil im z88dk-C zum Laufen zu bringen, aber schließlich hat es funktioniert. Der Maschinencode wird beim HPGL-Interpreter und bei pbmread für das Lesen und Schreiben von PBM-Dateien benötigt, genauer gesagt für den Zugriff auf den Bildspeicher im "Roller RAM".
Damit der Maschinencode in Sicherheit ist, muss dem C-Compiler mitgeteilt werden, dass der Stack unterhalb des gesicherten Speicherbereichs beginnen soll. Das wird mit der Option -pragma-define:REGISTER_SP=0xF3FF erreicht (siehe make.cmd).
Die Quellcode-Dateien und die ausführbaren COM-Dateien sind wie üblich im Download-Bereich zu finden.
Damit der Maschinencode in Sicherheit ist, muss dem C-Compiler mitgeteilt werden, dass der Stack unterhalb des gesicherten Speicherbereichs beginnen soll. Das wird mit der Option -pragma-define:REGISTER_SP=0xF3FF erreicht (siehe make.cmd).
Die Quellcode-Dateien und die ausführbaren COM-Dateien sind wie üblich im Download-Bereich zu finden.
- 03.07.2023, 20:48
- Forum: Programmierung
- Thema: HP-GL Interpreter
- Antworten: 48
- Zugriffe: 94725
Re: HP-GL Interpreter
pbmread V3.2 kann jetzt auch eine Diashow anzeigen. Dazu gibt es die neue Kommandozeilenoption -diashow, die mit einer Laufwerksangabe ergänzt werden kann. Wird kein Laufwerk angegeben, dann wird das aktuelle Laufwerk verwendet.
Die Option zum Invertieren des Bildes beim Laden hat nun auch ein Minus-Zeichen vorangestellt bekommen: -invert.
Beispiele:
pbmread
Zeigt das Menü zur Eingabe eines Dateinamens an. Dem Dateinamen kann auch ein Laufwerksbuchstabe - z.B. B: - vorangestellt werden.
pbmread dodo1.pbm
Lädt die Datei vom aktuellen Laufwerk und zeigt sie an.
pbmread dodo1.pbm -invert
Lädt die Datei vom aktuellen Laufwerk und zeigt sie invertiert an.
pbmread -diashow
Zeigt eine Diashow aller PBM-Dateien des aktuellen Laufwerks an.
pbmread -diashow b:
Zeigt eine Diashow aller PBM-Dateien des Laufwerks B: an.
Hinweise:
Die Option zum Invertieren des Bildes beim Laden hat nun auch ein Minus-Zeichen vorangestellt bekommen: -invert.
Beispiele:
pbmread
Zeigt das Menü zur Eingabe eines Dateinamens an. Dem Dateinamen kann auch ein Laufwerksbuchstabe - z.B. B: - vorangestellt werden.
pbmread dodo1.pbm
Lädt die Datei vom aktuellen Laufwerk und zeigt sie an.
pbmread dodo1.pbm -invert
Lädt die Datei vom aktuellen Laufwerk und zeigt sie invertiert an.
pbmread -diashow
Zeigt eine Diashow aller PBM-Dateien des aktuellen Laufwerks an.
pbmread -diashow b:
Zeigt eine Diashow aller PBM-Dateien des Laufwerks B: an.
Hinweise:
- Keine Diashow: Mit der Taste "I" wird das Bild invertiert. Eine beliebige andere Taste beendet das Programm.
- Die Diashow kann mit einer beliebigen Taste abgebrochen werden; das begonnene Bild wird noch zuende gezeichnet.