Notes 4
Sie können diese Seite ausdrucken oder mit einem PDF-Drucker in ein PDF umwandeln, um Ihre eigenen Notizen hinzuzufügen.
Willkommen!
- In den vergangenen Wochen haben wir darüber gesprochen, dass Bilder aus kleineren Bausteinen bestehen, die Pixel genannt werden.
- Heute werden wir uns näher mit den Nullen und Einsen beschäftigen, aus denen diese Bilder bestehen. Insbesondere werden wir uns mit den grundlegenden Bausteinen befassen, aus denen Dateien, einschließlich Bilder, bestehen.
- Außerdem werden wir besprechen, wie man auf die zugrunde liegenden Daten im Computerspeicher zugreifen kann.
Pixel Art
-
Pixel sind Quadrate, einzelne Farbpunkte, die in einem Raster von oben nach unten und von links nach rechts angeordnet sind.
-
Sie können sich ein Bild wie eine Karte aus Bits vorstellen, bei der Nullen für Schwarz und Einsen für Weiß stehen.
-
RGB, oder rot, grün, blau, sind Zahlen, die den Anteil jeder dieser Farben darstellen. In Adobe Photoshop können Sie diese Einstellungen wie folgt sehen:
Beachten Sie, wie die Menge an Rot, Blau und Grün die ausgewählte Farbe verändert.
-
Anhand des obigen Bildes können Sie sehen, dass die Farbe nicht nur in drei Werten dargestellt wird. Am unteren Rand des Fensters befindet sich ein spezieller Wert, der sich aus Zahlen und Buchstaben zusammensetzt. Der Wert “255” wird als “FF” dargestellt. Warum ist das so?
Hexadezimal
-
Das Hexadezimalsystem ist ein Stellenwertsystem mit 16 Zählwerten. Sie sind wie folgt:
0 1 2 3 4 5 6 7 8 9 a b c d e f
Beachten Sie, dass “F” für “15” steht.
-
Hexadezimal ist auch als Base-16 bekannt.
-
Beim Zählen in Hexadezimal ist jede Spalte eine Potenz von 16.
-
Die Zahl “0” wird als “00” dargestellt.
-
Die Zahl “1” wird als “01” dargestellt.
-
Die Zahl “9” wird durch “09” dargestellt.
-
Die Zahl “10” wird dargestellt als “0A”.
-
Die Zahl “15” wird als “0F” dargestellt.
-
Die Zahl “16” wird als “10” dargestellt.
-
Die Zahl “255” wird als “FF” dargestellt, denn 16 x 15 (oder “F”) ist 240. Addiere weitere 15 und du erhältst 255. Dies ist die höchste Zahl, die man mit einem zweistelligen Hexadezimalsystem zählen kann.
-
Hexadezimal ist nützlich, weil es mit weniger Ziffern dargestellt werden kann. Mit dem Hexadezimalsystem können wir Informationen prägnanter darstellen.
Speicher
-
Vielleicht erinnern Sie sich an unsere Darstellung der Speicherblöcke in den vergangenen Wochen. Wenn Sie jeden dieser Speicherblöcke mit einer hexadezimalen Nummerierung versehen, können Sie sich diese wie folgt vorstellen:
-
Sie können sich vorstellen, dass es Verwirrung darüber geben kann, ob der obige “10”-Block eine Stelle im Speicher oder den Wert “10” darstellt. Dementsprechend werden die Positionen im Speicher oft als hexadezimale Zahlen mit dem Präfix “0x” dargestellt:
-
Geben Sie in Ihrem Terminalfenster “code addresses.c” ein und schreiben Sie Ihren Code wie folgt:
#include <stdio.h>
int main(void)
{
int n = 50;
printf("%i\n", n);
}
Beachten Sie, dass “n” nun im Speicher mit dem Wert “50” gespeichert ist.
-
Wie dieses Programm diesen Wert speichert, können Sie sich wie folgt vorstellen:
-
Die Sprache “C” verfügt über zwei Operatoren, die sich auf den Speicher-Ort, also die Adresse von Variablen beziehen:
&
liefert die Adresse von etwas, das im Speicher gespeichert ist.*
weist den Compiler an, eine Stelle im Speicher aufzusuchen.
-
Wir können unseren Code wie folgt abändern:
#include <stdio.h>
int main(void)
{
int n = 50;
printf("%p\n", &n);
}
Beachten Sie das “%p”, mit dem wir die Adresse einer Speicherstelle anzeigen können. &n
kann wörtlich übersetzt werden als “die Adresse von n
”. Die Ausführung dieses Codes gibt eine Speicheradresse aus, die mit 0x
beginnt.
Pointer
-
Ein Pointer (deutsch: Zeiger) ist eine Variable, die die Adresse eines bestimmten Wertes enthält. Kurz gesagt, ein Pointer repräsentiert eine bestimmte Adresse im Speicher Ihres Computers.
-
Betrachten Sie den folgenden Code:
int n = 50; int *p = &n;
Beachten Sie, dass “p” ein Pointer ist, der die Adresse einer Ganzzahl “n” enthält.
-
Ändern Sie Ihren Code wie folgt:
#include <stdio.h>
int main(void)
{
int n = 50;
int *p = &n;
printf("%p\n", p);
}
Beachten Sie, dass dieser Code die gleiche Wirkung hat wie unser vorheriger Code. Wir haben einfach unser neues Wissen über die Operatoren &
und *
genutzt.
- Um die Verwendung des
*
-Operators zu veranschaulichen, betrachten Sie das Folgende:
#include <stdio.h>
int main(void)
{
int n = 50;
int *p = &n;
printf("%i\n", *p);
}
Beachten Sie, dass die “printf”-Zeile die Ganzzahl an der Stelle “p” ausgibt. int *p
erzeugt einen Pointer, der die Aufgabe hat, die Speicheradresse einer Ganzzahl zu speichern.
-
Sie können sich unseren Code wie folgt vorstellen:
Beachten Sie, dass der Pointer recht groß erscheint. In der Tat wird ein Pointer normalerweise als 8-Byte-Wert gespeichert.
p
speichert die Adresse von “50”. -
Sie können sich einen Pointer als einen Wert an einer Adresse im Speicher vorstellen, der auf eine andere Adresse zeigt:
Strings
-
Da wir nun ein mentales Modell für Pointer haben, können wir eine Ebene der Vereinfachung zurücknehmen, die zuvor in diesem Kurs angeboten wurde.
-
Erinnern Sie sich, dass eine Zeichenkette einfach eine Reihe von Zeichen ist. Zum Beispiel kann
String s = "HI!"
wie folgt dargestellt werden: -
Aber was ist das “s” wirklich? Wo wird das “s” im Speicher abgelegt? Wie Sie sich vorstellen können, muss “s” irgendwo gespeichert werden. Sie können sich die Beziehung von “s” zur Zeichenkette wie folgt vorstellen:
Beachten Sie, dass ein Pointer namens “s” dem Compiler mitteilt, wo sich das erste Byte der Zeichenkette im Speicher befindet.
-
Ändern Sie Ihren Code wie folgt:
#include <cs50.h>
#include <stdio.h>
int main(void)
{
string s = "HI!";
printf("%p\n", s);
printf("%p\n", &s[0]);
printf("%p\n", &s[1]);
printf("%p\n", &s[2]);
printf("%p\n", &s[3]);
}
Beachten Sie, dass oben die Speicherplätze jedes Zeichens in der Zeichenkette “s” ausgegeben werden. Das Symbol “&” wird verwendet, um die Adresse jedes Elements der Zeichenkette anzuzeigen. Wenn Sie diesen Code ausführen, sehen Sie, dass die Elemente 0
, 1
, 2
und 3
im Speicher nebeneinander liegen.
- Ebenso können Sie Ihren Code wie folgt ändern:
#include <stdio.h>
int main(void)
{
char *s = "HI!";
printf("%s\n", s);
}
Beachten Sie, dass dieser Code die Zeichenkette darstellt, die an der Stelle von s
beginnt. Dieser Code entfernt effektiv die Stützräder des string
Datentyps, der von cs50.h
angeboten wird. Dies ist roher C-Code, ohne das Gerüst der CS50-Bibliothek.
- Sie können sich vorstellen, wie ein String als Datentyp erstellt wird.
- Letzte Woche haben wir gelernt, wie man mit
typedef
einen eigenen Datentyp alsstruct
erstellt. - Die CS50-Bibliothek enthält folgenden Code:
typedef char *string
- Dadurch kann man bei der Verwendung der cs50-Bibliothek einen eigenen Datentyp namens
string
verwenden.
Pointerarithmetik
- Sie können Ihren vorherigen Code wie folgt ändern, um das Gleiche in einer längeren Form zu erreichen, also den String auszugeben:
#include <stdio.h>
int main(void)
{
char *s = "HI!";
printf("%c\n", s[0]);
printf("%c\n", s[1]);
printf("%c\n", s[2]);
}
Beachten Sie, dass wir jedes Zeichen an der Stelle von “s” drucken.
- Außerdem können Sie Ihren Code wie folgt ändern:
#include <stdio.h>
int main(void)
{
char *s = "HI!";
printf("%c\n", *s);
printf("%c\n", *(s + 1));
printf("%c\n", *(s + 2));
}
Beachten Sie, dass das erste Zeichen an der Stelle von “s” gedruckt wird. Dann wird das Zeichen an der Stelle s + 1
gedruckt, und so weiter.
String-Vergleich
- Eine Zeichenkette ist einfach eine Anordnung von Zeichen, die durch ihr erstes Byte identifiziert wird.
- Zu Beginn des Kurses haben wir uns mit dem Vergleich von ganzen Zahlen beschäftigt. Wir könnten dies in Code darstellen, indem wir “code compare.c” in das Terminalfenster eingeben und den Code wie folgt schreiben:
#include <cs50.h>
#include <stdio.h>
int main(void)
{
// Zwei Ganzzahlen holen
int i = get_int("i: ");
int j = get_int("j: ");
// Ganzzahlen vergleichen
if (i == j)
{
printf("Same\n");
}
sonst
{
printf("Anders\n");
}
}
Beachten Sie, dass dieser Code zwei Ganzzahlen vom Benutzer entgegennimmt und sie vergleicht.
- Im Falle von Zeichenketten kann man jedoch nicht zwei Zeichenketten mit dem Operator “=” vergleichen.
- Wenn man mit dem Operator
==
versucht, Zeichenketten zu vergleichen, wird versucht, die Speicherstellen der Zeichenketten zu vergleichen und nicht die darin enthaltenen Zeichen. Wir müssen daherstrcmp
verwenden. - Um dies zu veranschaulichen, ändern Sie Ihren Code wie folgt:
#include <cs50.h>
#include <stdio.h>
int main(void)
{
// Zwei Zeichenketten holen
char *s = get_string("s: ");
char *t = get_string("t: ");
// Adressen der Zeichenketten vergleichen
if (s == t)
{
printf("Same\n");
}
sonst
{
printf("Anders\n");
}
}
Es fällt auf, dass die Eingabe von HI!
für beide Zeichenketten immer noch die Ausgabe von Different
ergibt.
-
Warum sind diese Zeichenfolgen scheinbar unterschiedlich? Sie können die folgende Grafik verwenden, um sich das zu verdeutlichen:
-
Daher prüft der obige Code für
compare.c
, ob die Speicheradressen unterschiedlich sind, nicht die Zeichenketten selbst. -
Mit
strcmp
können wir unseren Code korrigieren:
#include <cs50.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
// Zwei Zeichenketten holen
char *s = get_string("s: ");
char *t = get_string("t: ");
// Zeichenketten vergleichen
if (strcmp(s, t) == 0)
{
printf("Same\n");
}
sonst
{
printf("Anders\n");
}
}
Beachten Sie, dass strcmp
0
zurückgibt, wenn die Zeichenketten gleich sind.
- Um weiter zu veranschaulichen, wie diese beiden Zeichenfolgen an zwei Orten leben, ändern Sie Ihren Code wie folgt:
#include <cs50.h>
#include <stdio.h>
int main(void)
{
// Zwei Zeichenketten holen
char *s = get_string("s: ");
char *t = get_string("t: ");
// Zeichenketten drucken
printf("%s\n", s);
printf("%s\n", t);
}
Beachten Sie, dass wir jetzt zwei getrennte Zeichenketten haben, die wahrscheinlich an zwei verschiedenen Orten gespeichert sind.
- Mit einer kleinen Änderung können Sie die Positionen dieser beiden gespeicherten Zeichenfolgen sehen:
#include <cs50.h>
#include <stdio.h>
int main(void)
{
// Zwei Zeichenketten holen
char *s = get_string("s: ");
char *t = get_string("t: ");
// Adressen der Zeichenketten ausgeben
printf("%p\n", s);
printf("%p\n", t);
}
Beachten Sie, dass das “%s” in der Druckanweisung in “%p” geändert wurde.
Kopieren (malloc, free)
- In der Programmierung ist es häufig erforderlich, eine Zeichenkette in eine andere zu kopieren.
- Geben Sie in Ihrem Terminalfenster “code copy.c” ein und schreiben Sie den folgenden Code:
#include <cs50.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
// Abrufen einer Zeichenkette
string s = get_string("s: ");
// Kopieren der Adresse der Zeichenkette
string t = s;
// Ersten Buchstaben in der Zeichenkette großschreiben
t[0] = toupper(t[0]);
// String zweimal drucken
printf("s: %s\n", s);
printf("t: %s\n", t);
}
Beachten Sie, dass string t = s
die Adresse von s
nach t
kopiert. Dies führt nicht zu dem, was wir wollen. Die Zeichenkette wird nicht kopiert - nur die Adresse.
-
Sie können sich den obigen Code wie folgt vorstellen:
Beachten Sie, dass “s” und “t” immer noch auf dieselben Speicherblöcke verweisen. Es handelt sich nicht um eine echte Kopie einer Zeichenkette. Stattdessen handelt es sich um zwei Pointer, die auf dieselbe Zeichenkette zeigen.
-
Bevor wir dieses Problem angehen, müssen wir sicherstellen, dass wir in unserem Code keinen Segmentierungsfehler erleben, bei dem wir versuchen,
string s
nachstring t
zu kopieren, obwohlstring t
nicht existiert. Wir können die Funktionstrlen
wie folgt verwenden, um dabei zu helfen:#include <cs50.h> #include <ctype.h> #include <stdio.h> #include <string.h> int main(void) { // Abrufen einer Zeichenkette string s = get_string("s: "); // Kopieren der Adresse der Zeichenkette string t = s; // Großschreibung des ersten Buchstabens in der Zeichenkette if (strlen(t) > 0) { t[0] = toupper(t[0]); } // String zweimal drucken printf("s: %s\n", s); printf("t: %s\n", t); }
Beachten Sie, dass
strlen
verwendet wird, um sicherzustellen, dassstring t
existiert. Ist dies nicht der Fall, wird nichts kopiert. -
Um eine richtige Kopie der Zeichenkette erstellen zu können, müssen wir zwei neue Bausteine einführen. Erstens:
malloc
erlaubt es Ihnen, einen Block im Speicher mit einer bestimmten Größe anzufordern und dort Daten abzulegen. Zweitens können Sie mitfree
dem Compiler mitteilen, dass er den zuvor zugewiesenen Speicherblock freigeben soll. -
Wir können unseren Code wie folgt ändern, um eine richtige Kopie unserer Zeichenkette zu erstellen:
#include <cs50.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
// Abrufen einer Zeichenkette
char *s = get_string("s: ");
// Speicher für eine weitere Zeichenkette zuweisen
char *t = malloc(strlen(s) + 1);
// Zeichenkette in den Speicher kopieren, einschließlich '\0'
for (int i = 0; i <= strlen(s); i++)
{
t[i] = s[i];
}
// Kopie großschreiben
t[0] = toupper(t[0]);
// Zeichenketten drucken
printf("s: %s\n", s);
printf("t: %s\n", t);
}
Beachten Sie, dass malloc(strlen(s) + 1)
einen Speicherblock erzeugt, der der Länge der Zeichenkette s
plus eins entspricht. Dies ermöglicht die Aufnahme des Null-Terminators \0
in unsere endgültige, kopierte Zeichenkette. Dann geht die “for”-Schleife durch die Zeichenkette “s” und weist jeden Wert der gleichen Stelle in der Zeichenkette “t” zu.
-
Es stellt sich heraus, dass unser Code eine Ineffizienz aufweist. Ändern Sie Ihren Code wie folgt:
#include <cs50.h> #include <ctype.h> #include <stdio.h> #include <stdlib.h> #include <string.h> int main(void) { // Abrufen einer Zeichenkette char *s = get_string("s: "); // Speicher für eine weitere Zeichenkette zuweisen char *t = malloc(strlen(s) + 1); // Zeichenfolge in den Speicher kopieren, einschließlich '\0' for (int i = 0, n = strlen(s); i <= n; i++) { t[i] = s[i]; } // Kopie großschreiben t[0] = toupper(t[0]); // Zeichenketten drucken printf("s: %s\n", s); printf("t: %s\n", t); }
Beachten Sie, dass “n = strlen(s)” jetzt auf der linken Seite der “for”-Schleife definiert ist. Es ist ratsam, nicht benötigte Funktionen nicht in der mittleren Bedingung der “for”-Schleife aufzurufen, da diese immer wieder durchlaufen wird. Wenn Sie
n = strlen(s)
auf die linke Seite verschieben, wird die Funktionstrlen
nur einmal ausgeführt. -
Die Sprache
C
hat eine eingebaute Funktion zum Kopieren von Zeichenketten namensstrcpy
. Sie kann wie folgt implementiert werden:
#include <cs50.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
// Abrufen einer Zeichenkette
char *s = get_string("s: ");
// Speicher für eine weitere Zeichenkette zuweisen
char *t = malloc(strlen(s) + 1);
// Zeichenkette in den Speicher kopieren
strcpy(t, s);
// Kopie großschreiben
t[0] = toupper(t[0]);
// Zeichenketten drucken
printf("s: %s\n", s);
printf("t: %s\n", t);
}
Beachten Sie, dass strcpy
die gleiche Arbeit leistet wie unsere for
-Schleife zuvor.
- Sowohl
get_string
als auchmalloc
gebenNULL
zurück, einen speziellen Wert im Speicher, für den Fall, dass etwas schief geht. Sie können Code schreiben, der auf dieseNULL
-Bedingung wie folgt prüft:
#include <cs50.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
// Abrufen einer Zeichenkette
char *s = get_string("s: ");
if (s == NULL)
{
return 1;
}
// Speicher für eine weitere Zeichenkette zuweisen
char *t = malloc(strlen(s) + 1);
if (t == NULL)
{
return 1;
}
// Zeichenkette in den Speicher kopieren
strcpy(t, s);
// Kopie großschreiben
if (strlen(t) > 0)
{
t[0] = toupper(t[0]);
}
// Zeichenketten drucken
printf("s: %s\n", s);
printf("t: %s\n", t);
// Speicher freigeben
free(t);
return 0;
}
Beachten Sie, dass NULL
zurückgegeben wird, wenn die erhaltene Zeichenkette die Länge 0
hat oder malloc
fehlschlägt. Beachten Sie auch, dass free
den Computer wissen lässt, dass Sie mit diesem Speicherblock, den Sie mit malloc
erstellt haben, fertig sind.
malloc und Valgrind
- Valgrind ist ein Werkzeug, das überprüfen kann, ob es speicherbezogene Probleme in Ihren Programmen gibt, bei denen Sie
malloc
verwendet haben. Insbesondere prüft es, ob Sie den gesamten zugewiesenen Speicher “freigeben”. - Betrachten Sie den folgenden Code für
memory.c
:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int *x = malloc(3 * sizeof(int));
x[1] = 72;
x[2] = 73;
x[3] = 33;
}
Beachten Sie, dass die Ausführung dieses Programms keine Fehler verursacht. Während malloc
verwendet wird, um genügend Speicher für ein Array zuzuweisen, kümmert sich der Code nicht darum, den zugewiesenen Speicher wieder freizugeben.
-
Wenn Sie
make memory
gefolgt vonvalgrind ./memory
eingeben, erhalten Sie einen Bericht von valgrind, der aufzeigt, wo durch Ihr Programm Speicher verloren gegangen ist. Ein Fehler, den valgrind aufdeckt, ist, dass wir versucht haben, den Wert33
an der 4. Position des Arrays zuzuweisen, obwohl wir nur ein Array der Größe3
zugewiesen haben. Ein weiterer Fehler ist, dass wirx
nie freigegeben haben. -
Sie können Ihren Code wie folgt ändern:
#include <stdio.h> #include <stdlib.h> int main(void) { int *x = malloc(3 * sizeof(int)); x[0] = 72; x[1] = 73; x[2] = 33; free(x); }
Beachten Sie, dass die erneute Ausführung von valgrind nun keine Speicherlecks mehr aufweist.
Garbage Values („Müllwerte“)
- Wenn Sie den Compiler nach einem Speicherblock fragen, gibt es keine Garantie, dass dieser Speicher leer ist.
- Es ist sehr gut möglich, dass der von Ihnen zugewiesene Speicher bereits vorher vom Computer verwendet wurde. Dementsprechend können Sie Junk- oder Garbage-Werte sehen. Dies ist darauf zurückzuführen, dass Sie einen Speicherblock erhalten haben, ihn aber nicht initialisiert haben. Betrachten Sie zum Beispiel den folgenden Code für
garbage.c
:
#include <stdio.h>
int main(void)
{
int scores[1024];
for (int i = 0; i < 1024; i++)
{
printf("%i\n", punkte[i]);
}
}
Beachten Sie, dass die Ausführung dieses Codes 1024
Speicherplätze für Ihr Array zuweist, aber die for
-Schleife wird wahrscheinlich zeigen, dass nicht alle Werte darin 0
sind. Es ist immer die beste Praxis, sich des Potentials für Garbage-Werte bewusst zu sein, wenn Sie Speicherblöcke nicht auf einen anderen Wert wie Null oder anders initialisieren.
Swap
- In der realen Welt besteht eine häufige Notwendigkeit in der Programmierung darin, zwei Werte zu vertauschen. Natürlich ist es schwierig, zwei Variablen ohne einen temporären Speicherplatz zu vertauschen. In der Praxis können Sie
code swap.c
eingeben und den folgenden Code schreiben, um dies in Aktion zu sehen:
#include <stdio.h>
void swap(int a, int b);
int main(void)
{
int x = 1;
int y = 2;
printf("x ist %i, y ist %i\n", x, y);
swap(x, y);
printf("x ist %i, y ist %i\n", x, y);
}
void swap(int a, int b)
{
int tmp = a;
a = b;
b = tmp;
}
Beachten Sie, dass dieser Code zwar ausgeführt wird, aber nicht funktioniert. Die Werte werden, auch nachdem sie an die Funktion “swap” gesendet wurden, nicht getauscht. Warum?
-
Wenn Sie Werte an eine Funktion übergeben, stellen Sie nur Kopien zur Verfügung. In den vergangenen Wochen haben wir bereits das Konzept des Scope besprochen. Die Werte von
x
undy
, die in den geschweiften{}
Klammern der Funktionmain
erzeugt werden, haben nur den Geltungsbereich der Funktionmain
. Betrachten Sie das folgende Bild:Beachten Sie, dass globale Variablen, die wir in diesem Kurs nicht verwendet haben, an einem bestimmten Ort im Speicher liegen. Verschiedene Funktionen und die dort genutzten Variablen werden in einem anderen Bereich des Speichers abgelegt, dem “Stack”.
-
Betrachten Sie nun das folgende Bild:
Beachten Sie, dass
main
undswap
zwei getrennte Frames oder Speicherbereiche haben. Daher können wir die Werte nicht einfach von einer Funktion an die andere übergeben, um sie zu ändern. -
Ändern Sie Ihren Code wie folgt:
#include <stdio.h>
void swap(int *a, int *b);
int main(void)
{
int x = 1;
int y = 2;
printf("x ist %i, y ist %i\n", x, y);
swap(&x, &y);
printf("x ist %i, y ist %i\n", x, y);
}
void swap(int *a, int *b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
Beachten Sie, dass Variablen nicht als Wert, sondern als Referenz übergeben werden. Das heißt, die Adressen von a
und b
werden an die Funktion übergeben. Daher erfährt die Funktion swap
wissen, wo sie Änderungen an den tatsächlichen a
und b
von der Hauptfunktion vornehmen muss.
-
Sie können sich das folgendermaßen vorstellen:
Überlauf
- Ein Heap Overflow liegt vor, wenn der Heap überläuft und Bereiche des Speichers berührt werden, die nicht dafür vorgesehen sind.
- Von einem Stack Overflow spricht man, wenn zu viele Funktionen aufgerufen werden und dadurch der verfügbare Speicher überläuft.
- Weiterhin gibt es den Pufferüberlauf, wenn man einen Buffer vorbereitet hat, der zu klein ist für das was eingelesen werden soll.
scanf
- Für CS50 wurden Funktionen wie
get_int
entwickelt, um die Abfrage von Eingaben durch den Benutzer zu vereinfachen. scanf
ist eine eingebaute Funktion, die Benutzereingaben abrufen kann.- Wir können
get_int
ganz einfach mitscanf
wie folgt neu implementieren:
#include <stdio.h>
int main(void)
{
int x;
printf("x: ");
scanf("%i", &x);
printf("x: %i\n", x);
}
Beachten Sie, dass der Wert von x
in der Zeile scanf("%i", &x)
an der Adresse von x
gespeichert wird.
- Der Versuch,
get_string
neu zu implementieren, ist jedoch nicht einfach. Betrachten Sie das Folgende:
#include <stdio.h>
int main(void)
{
char *s;
printf("s: ");
scanf("%s", s);
printf("s: %s\n", s);
}
Beachten Sie, dass kein “&” erforderlich ist, weil Zeichenketten etwas Besonderes sind. Trotzdem wird dieses Programm nicht funktionieren. Nirgendwo in diesem Programm weisen wir die Menge an Speicher zu, die für unsere Zeichenkette benötigt wird. In der Tat wissen wir nicht, wie lang eine Zeichenkette sein darf, die der Benutzer eingibt!
-
Ihr Code könnte nun wie folgt geändert werden. Für eine Zeichenkette müssen wir eine bestimmte Menge an Speicher vorab reservieren:
#include <stdio.h> #include <stdlib.h> int main(void) { char *s = malloc(4); if (s == NULL) { return 1; } printf("s: "); scanf("%s", s); printf("s: %s\n", s); free(s); return 0; }
Beachten Sie, dass Sie bei einer Zeichenkette von sechs Bytes eine Fehlermeldung erhalten könnten (aber nicht unbedingt erhalten werden).
-
Wenn wir unseren Code wie folgt vereinfachen, können wir dieses wesentliche Problem der Vorabzuweisung besser verstehen:
#include <stdio.h>
int main(void)
{
char s[4];
printf("s: ");
scanf("%s", s);
printf("s: %s\n", s);
}
Beachten Sie, dass wir, wenn wir ein Array der Größe 4
vorab zuweisen, cat
eingeben können und das Programm funktioniert. Eine Zeichenkette, die größer ist als diese, könnte jedoch einen Fehler verursachen.
- Manchmal kann der Compiler oder das System, auf dem er läuft, mehr Speicher zuweisen, als wir angeben. Im Grunde genommen ist der obige Code jedoch unsicher. Wir können nicht darauf vertrauen, dass der Benutzer eine Zeichenkette eingibt, die in den von uns zugewiesenen Speicher passt.
File I/O
- Sie können aus Dateien lesen und diese manipulieren. Betrachten Sie den folgenden Code für
phonebook.c
:
#include <cs50.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
// CSV-Datei öffnen
FILE *file = fopen("telefonbuch.csv", "a");
// Name und Nummer abrufen
char *name = get_string("Name: ");
char *Nummer = get_string("Nummer: ");
// Drucken in Datei - neu!
fprintf(file, "%s,%s\n", name, number);
// Datei schließen
fclose(file);
}
Beachten Sie, dass dieser Code Pointer für den Zugriff auf die Datei verwendet.
- Sie können eine Datei mit dem Namen “phonebook.csv” erstellen, bevor Sie den obigen Code ausführen. Nachdem Sie das obige Programm ausgeführt und einen Namen und eine Telefonnummer eingegeben haben, werden Sie feststellen, dass diese Daten in Ihrer CSV-Datei bestehen bleiben.
- Wenn wir sicherstellen wollen, dass die Datei “phonebook.csv” bereits vor der Ausführung des Programms existiert, können wir unseren Code wie folgt ändern:
#include <cs50.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
// CSV-Datei öffnen
FILE *file = fopen("telefonbuch.csv", "a");
if (!file)
{
return 1;
}
// Name und Nummer abrufen
char *name = get_string("Name: ");
char *Nummer = get_string("Nummer: ");
// Drucken in Datei
fprintf(file, "%s,%s\n", name, number);
// Datei schließen
fclose(file);
}
Beachten Sie, dass dieses Programm darauf prüft, ob fopen
einen NULL
-Pointer zurückgegeben habt. Wenn ja, ruft es return 1
auf.
- Wir können unser eigenes Kopierprogramm implementieren, indem wir
code cp.c
eintippen und den Code wie folgt schreiben:
#include <stdio.h>
#include <stdint.h>
typedef uint8_t BYTE;
int main(int argc, char *argv[])
{
FILE *src = fopen(argv[1], "rb");
FILE *dst = fopen(argv[2], "wb");
BYTE b;
while (fread(&b, sizeof(b), 1, src) !=0)
{
fwrite(&b, sizeof(b), 1, dst);
}
fclose(dst);
fclose(src);
}
Beachten Sie, dass diese Datei unseren eigenen Datentyp namens BYTE
erzeugt, der die Größe eines uint8_t
hat. Dann liest die Datei ein BYTE
und schreibt es in eine Datei.
- BMPs sind auch Datensammlungen, die wir untersuchen und manipulieren können. Diese Woche werden Sie genau das in der Übung tun.
Zusammenfassung
In dieser Vorlesung haben Sie etwas über Pointer gelernt, mit denen Sie auf Daten an bestimmten Speicherplätzen zugreifen und diese manipulieren können. Behandelt haben wir insbesondere…
- Pixelbilder
- Hexadezimalzahlen
- Speicher
- Pointer
- Zeichenketten
- Pointer-Arithmetik
- String-Vergleiche
- Kopieren
- malloc und Valgrind
- Garbage-Werte
- Vertauschen von Variablen
- Overflow-Fehlern
- scanf
- Datei-E/A
Bis zum nächsten Mal!