Die Suche ergab 238 Treffer

von bbock
03.09.2023, 16:13
Forum: Programmierung
Thema: Fragen und Antworten zum C-Kurs
Antworten: 108
Zugriffe: 518774

Re: Fragen und Antworten zum C-Kurs

Paul hat geschrieben: 03.09.2023, 09:11 Kann es sei das du dich vertippt hast Bernd?
bbock hat geschrieben: 18.08.2023, 10:10

Code: Alles auswählen

zcc +cpm -subtype=pcw80 -compiler=sccz80 -pragma-define:REGISTER_SP=0xF3FF -create-app testprg1.c -o testprg1.com
Die pragma-define-Option stellt sicher, dass der Stackpointer bei FFF0 beginnt. Damit bleibt der Speicherbereich ab F400 sicher vor Überschreibung durch den Stack.
Du wolltest wahrscheinlich schreiben das der Stackpointer bei F3FF nicht bei FFF0?
Richtig erkannt; ich habe den Fehler korrigiert. Danke für den Hinweis.
von bbock
03.09.2023, 16:10
Forum: Programmierung
Thema: Fragen und Antworten zum C-Kurs
Antworten: 108
Zugriffe: 518774

Re: Fragen und Antworten zum C-Kurs

Paul hat geschrieben: 03.09.2023, 08:44 ...
Gibt es eine Liste von sinnvollen Speicheradressen für die Joyce wie z.B. Systemvariablen aus denen man sinnvolles auslesen oder die man sinnvoll manipulieren kann so wie bei den ZX-Maschinen?
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...
von bbock
30.08.2023, 18:23
Forum: Programmierung
Thema: Programmieren in C mit dem z88dk
Antworten: 20
Zugriffe: 104716

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:

Code: Alles auswählen

char *characters = (char *) malloc(200);
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.

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;
}
Wir kompilieren mit

Code: Alles auswählen

zcc +cpm -subtype=pcw80 -compiler=sccz80 -DAMALLOC -create-app testmalloc.c -o malloc.com
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:

Code: Alles auswählen

float *floatVars = (float*) malloc(20 * sizeof(float));
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:

Code: Alles auswählen

floatVars[2] = 5.5;
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.
von bbock
18.08.2023, 10:10
Forum: Programmierung
Thema: Programmieren in C mit dem z88dk
Antworten: 20
Zugriffe: 104716

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:

Code: Alles auswählen

unsigned char *p;
p = (unsigned char *) 0xFF40;
*p = 23;
Um den Wert wieder auszulesen, dereferenzieren wir den Pointer:

Code: Alles auswählen

printf("%d\n", *p);
Das komplette Testprogramm könnte so aussehen:

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;
}
Wir kompilieren es mit

Code: Alles auswählen

zcc +cpm -subtype=pcw80 -compiler=sccz80 -pragma-define:REGISTER_SP=0xF3FF -create-app testprg1.c -o testprg1.com
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.

Code: Alles auswählen

unsigned char ch @ 0xF400;
Das obige Testprogramm kann also wie folgt geändert werden:

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;
}
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:

Code: Alles auswählen

void *gx_scrAcc(void) @ 0xF494;
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.
von bbock
15.08.2023, 20:46
Forum: Programmierung
Thema: HP-GL Interpreter
Antworten: 48
Zugriffe: 251191

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.
von bbock
03.07.2023, 20:48
Forum: Programmierung
Thema: HP-GL Interpreter
Antworten: 48
Zugriffe: 251191

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:
  • 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.
Download
von bbock
30.06.2023, 20:27
Forum: Programmierung
Thema: Übungen zu "Programmieren in C mit dem z88dk"
Antworten: 11
Zugriffe: 56867

Re: Übungen zu "Programmieren in C mit dem z88dk"

Dann schreibe doch mal als kleine Übungsaufgabe das Programm in Java unter Verwendung von BigInteger. ;)

Übrigens: Mein Taschenrechner und meine Tabellenkalkulation kommen auch auf 1085 Jahre.
von bbock
30.06.2023, 20:02
Forum: Programmierung
Thema: Übungen zu "Programmieren in C mit dem z88dk"
Antworten: 11
Zugriffe: 56867

Re: Übungen zu "Programmieren in C mit dem z88dk"

Deswegen float - damit geht's ganz einfach. Die Grundrechenarten mit langen Ganzzahlen zu implementieren wäre zwar auch möglich, ist aber wesentlich aufwändiger.
von bbock
30.06.2023, 19:04
Forum: Programmierung
Thema: Übungen zu "Programmieren in C mit dem z88dk"
Antworten: 11
Zugriffe: 56867

Re: Übungen zu "Programmieren in C mit dem z88dk"

Übung 2

Der Kaiser von China und der Reis

Der Kaiser von China spielt mit einem Bauern Schach. Nachdem er das Spiel verloren hat, ist der Kaiser großzügig und will dem Bauern jeden Wunsch erfüllen. Der Bauer gibt sich bescheiden und verlangt für das erste Schachfeld ein Reiskorn, für das zweite zwei Reiskörner, für das dritte vier Reiskörner usw. Er verlangt also für jedes Schachfeld doppelt so viele Reiskörner wie für das vorhergehende.

Das C-Programm soll das Schachfeld als zweidimensionalen float-Array abbilden. Jedes Element, also jeder float-Wert, soll die Anzahl der Reiskörner enthalten. Am Ende sollen die Reiskörner aller Felder aufsummiert und die Summe ausgegeben werden.

Nehmen wir ferner an, dass ein Reiskorn ca. 30 Milligramm wiegt, also 0,03 Gramm. Das Programm soll das Gesamtgewicht aller Reiskörner in Tonnen ausgeben.

Die Erntemenge an Reis weltweit beträgt heutzutage ca. 510 Millionen Tonnen. Das Programm soll auch die Anzahl Jahre ausgeben, die auf der ganzen Welt Reis geerntet werden muss, um den Wunsch des Bauern zu erfüllen.

Hinweis: Da wir float-Werte verwenden, benötigen wir die zcc-Option -lm.
von bbock
30.06.2023, 12:16
Forum: Programmierung
Thema: Programmieren in C mit dem z88dk
Antworten: 20
Zugriffe: 104716

17. Mehrdimensionale Arrays

Wir sind bereits in Kapitel 10.2 mit Arrays in Berührung gekommen. Die dort behandelten Beispiele waren eindimensionale Arrays, auch Vektoren genannt. Deren Elemente werden durch einen Index adressiert. Es gibt in C auch mehrdimensionale Arrays, deren Elemente mit zwei oder mehr Indizes adressiert werden; dementsprechend spricht man von zwei-, drei- oder mehrdimensionalen Arrays.
Ein zweidimensionaler Array mit zwei Zeilen und vier Spalten könnte z.B. so deklariert werden:

Code: Alles auswählen

int zweidim[2][4];
Möchte man ihn bei der Deklaration bereits initialisieren, dann geht das wie folgt:

Code: Alles auswählen

int zweidim[2][4] = {
                      {1, 2, 3, 4},
                      {5, 6, 7, 8}
                    };
Das veranschaulicht die zweidimensionale Struktur. Die Initialisierung ist aber auch folgendermaßen möglich:

Code: Alles auswählen

int zweidim[2][4] = {1, 2, 3, 4, 5, 6, 7, 8};
Werden nicht für alle Elemente Werte bei der Initialisierung angegeben, dann werden die übrigen Elemente automatisch mit 0 initialisiert. Fehlt die Initialisierung, dann sind die Elemente mit zufälligen Werten belegt, es sei denn, es handelt sich bei dem Array um eine globale oder um eine static-Variable: dann werden alle Elemente mit 0 initialisiert.

Wie man oben sieht, ist die Anzahl der Elemente eines mehrdimensionalen Arrays gleich dem Produkt der Dimensionen, in obigem Beispiel also 2 * 4 = 8 Elemente. Man beachte, dass mehrdimensionale Arrays schnell sehr groß werden können. So hat der vierdimensionale Array int vierdim[14][14][14][14] bereits 14*14*14*14 = 38.416 Elemente und belegt auf einem 8-Bit-System (int = 2 Bytes) 76.832 Bytes. Das sprengt bereits das 64k-Limit.

Als praktisches Beispiel wollen wir eine Quartalsabrechnung erstellen, ähnlich wie in einer Tabellenkalkulation:

Code: Alles auswählen

      Artikel1   Artikel2   Artikel3   Gesamt
Q1       15         98         35        148
Q2       23         47         52        122
Q3       48         32         14         94
Q4       47         56         76        179
Gesamt  133        233        177        543
Dazu verwenden wir einen zweidimensionalen Array:

Code: Alles auswählen

    int matrix[5][4] = {
        // Zeile: Artikel 1, Artikel 2, Artikel 3, Gesamt
        {15, 98, 35, 0}, // Q1
        {23, 47, 52, 0}, // Q2
        {48, 32, 14, 0}, // Q3
        {47, 56, 76, 0}, // Q4
        { 0,  0,  0, 0}  // Gesamt
    };
Die Zeilen- und Spaltensummen sind mit 0 initialisiert; wir werden die Werte im Programm berechnen.

Code: Alles auswählen

#include <stdio.h>

int main(void)
{
    int matrix[5][4] = {
        // Zeile: Artikel 1, Artikel 2, Artikel 3, Gesamt
        {15, 98, 35, 0}, // Q1
        {23, 47, 52, 0}, // Q2
        {48, 32, 14, 0}, // Q3
        {47, 56, 76, 0}, // Q4
        { 0,  0,  0, 0}  // Gesamt
    };
    unsigned char zeile, spalte;
    int sum;

    // Spaltensummen
    for (spalte = 0; spalte < 3; spalte++) {
        sum = 0;
        for (zeile = 0; zeile < 4; zeile++) {
            sum += matrix[zeile][spalte];
        }
        matrix[4][spalte] = sum;
    }

    // Zeilensummen
    for (zeile = 0; zeile < 5; zeile++) {
        sum = 0;
        for (spalte = 0; spalte < 3; spalte++) {
            sum += matrix[zeile][spalte];
        }
        matrix[zeile][3] = sum;
    }
    
    // Ausgabe
    printf("\tArt1\tArt2\tArt3\tGesamt\n");
    
    for (zeile = 0; zeile < 5; zeile++) {
        if (zeile < 4) {
            printf("Q%d\t", zeile + 1);
        }
        else {
            printf("Gesamt\t");
        }
        
        for (spalte = 0; spalte < 4; spalte++) {
            printf("%d\t", matrix[zeile][spalte]);
        }
        printf("\n");
    }
    
    return 0;
}
Hier ist das ganze Programm in der main-Funktion realisiert, was nicht besonders schön ist. Besser wäre es, das Programm in meherere Funktionen aufzuteilen; dabei lernen wir, wie man ein Array an eine Funktion übergibt:

Code: Alles auswählen

#include <stdio.h>

/* Calculates the sum for each column of the matrix. */
void calcColumnSums(int m[5][4]) {
    unsigned char zeile, spalte;
    int sum;

    for (spalte = 0; spalte < 3; spalte++) {
        sum = 0;
        for (zeile = 0; zeile < 4; zeile++) {
            sum += m[zeile][spalte];
        }
        m[4][spalte] = sum;
    }
}

/* Calculates the sum for each row of the matrix. */
void calcRowSums(int m[5][4]) {
    unsigned char zeile, spalte;
    int sum;

    for (zeile = 0; zeile < 5; zeile++) {
        sum = 0;
        for (spalte = 0; spalte < 3; spalte++) {
            sum += m[zeile][spalte];
        }
        m[zeile][3] = sum;
    }
}

/* Prints the quarterly statement. */
void printQuarterlyStatement(int m[5][4]) {
    unsigned char zeile, spalte;
    
    printf("\tArt1\tArt2\tArt3\tGesamt\n");
    
    for (zeile = 0; zeile < 5; zeile++) {
        if (zeile < 4) {
            printf("Q%d\t", zeile + 1);
        }
        else {
            printf("Gesamt\t");
        }
        
        for (spalte = 0; spalte < 4; spalte++) {
            printf("%d\t", m[zeile][spalte]);
        }
        printf("\n");
    }
}

/* The main function. */
int main(void)
{
    int matrix[5][4] = {
        // Zeile: Artikel 1, Artikel 2, Artikel 3, Gesamt
        {15, 98, 35, 0}, // Q1
        {23, 47, 52, 0}, // Q2
        {48, 32, 14, 0}, // Q3
        {47, 56, 76, 0}, // Q4
        { 0,  0,  0, 0}  // Gesamt
    };

    calcColumnSums(matrix);
    calcRowSums(matrix);
    printQuarterlyStatement(matrix);
    
    return 0;
}
Normalerweise verwenden alle Funktionsaufrufe call-by-value bei der Parameterübergabe. Die Ausnahme sind Arrays: hier wird nicht etwa eine Kopie des gesamten Arrays an die Funktion übergeben, sondern lediglich die Adresse des ersten Elements des Arrays. So kann eine Funktion wie calcColumnSums() die Inhalte der Variablen matrix in main() verändern ohne dabei besonders mit Pointern arbeiten zu müssen. Wir erinnern uns: Der Name eines Arrays ist ein Zeiger auf das erste Element.