Was die Aktivitäten in C immer wieder brauchen: Typen und Rechnen, Arrays und Structs, Funktionen aus math.h, Bits und Bytes, Zeiger, Dateien und was die Fehlermeldungen des Compilers sagen. Die Beispiele sind so geschrieben wie in PixelRAM, Raytracing, GIF und JPEG. Was PixelRAM selbst kann, steht im Handbuch PixelRAM, was raylib kann, unter raylib.
#include <stdio.h> #include <math.h> #define W 320 // eine Funktion: Rückgabetyp, Name, Parameter mit Typ double half(double x) { return x / 2; } int main(void) { int n = 7; printf("%d %f\n", n, half(n)); return 0; }
;, Blöcke stehen in { … }. Kommentare: // bis zum Zeilenende oder /* … */.#include <…> holt die Erklärungen einer Bibliothek: stdio.h für printf und Dateien, stdlib.h für malloc, free und exit, math.h für sqrt und Co., stdint.h für uint8_t und seine Verwandten. pixelram.h und raylib.h bringen stdbool.h, stdint.h und ihre eigenen Funktionen schon mit.#define W 320 ersetzt vor dem Übersetzen jedes W durch 320. So stehen feste Größen an einer Stelle.double half(double x);, mit Semikolon statt Rumpf.main ist der Anfang. Mit int main(int argc, char **argv) bekommt das Programm, was beim Start dahinter steht: argv[1] ist das erste Wort, argc die Zahl der Wörter einschließlich des Programmnamens. In den PixelRAM-Vorlagen gibst du es mit make ARGS=diskette.gif mit.Dennis Ritchie (1941–2011), amerikanischer Informatiker bei den Bell Labs. Anfang der 1970er Jahre erfand er C, und damit schrieben er und Ken Thompson ihr Betriebssystem Unix neu, das es bis dahin nur in Assembler gab. Das Terminal im Workspace läuft unter Linux, und auch dessen Kern ist in C geschrieben.
1978 schrieb er mit Brian Kernighan das Buch The C Programming Language. Sein erstes Programm gibt hello, world aus, und seitdem beginnt fast jede Einführung in eine Programmiersprache so.
| Typ | Was darin steht | Beispiel |
|---|---|---|
int |
ganze Zahl, im Workspace 32 Bit: etwa ±2,1 Milliarden | int x = 160; |
long |
ganze Zahl, mindestens 32 Bit; Dateigrößen | long pos = 0; |
double |
Kommazahl mit etwa 16 Stellen | double t = 0.5; |
float |
Kommazahl mit etwa 7 Stellen; raylib rechnet damit | float speed = 2.5f; |
uint8_t |
ein Byte ohne Vorzeichen, 0 bis 255 | uint8_t red = 255; |
uint16_t, uint32_t
|
16 und 32 Bit ohne Vorzeichen; int16_t, int32_t mit |
uint32_t color = 0xff8800; |
bool |
true oder false
|
bool hit = false; |
char |
ein Zeichen, eigentlich ein Byte; Text ist ein Array davon | char *name = "Kugel"; |
Eine Variable, die in einer Funktion steht, gilt nur bis zum Ende ihres Blocks, und ihr Wert ist unbestimmt, bis du ihr einen gibst. Eine Variable außerhalb aller Funktionen gilt überall in der Datei und beginnt bei 0.
| Ausdruck | Ergebnis | Warum |
|---|---|---|
7 / 2 |
3 |
zwei ganze Zahlen: ganzzahlige Division, der Rest fällt weg |
7 / 2.0 |
3.5 |
sobald eine Kommazahl beteiligt ist, wird mit Kommazahlen gerechnet |
(double)x / W |
zwischen 0 und 1 | der Cast macht aus x vorher eine Kommazahl |
(int)(2.9) |
2 |
der Cast zu int schneidet ab, er rundet nicht; (int)(-2.9) ist -2
|
7 % 3 |
1 |
der Rest der ganzzahligen Division |
-7 % 3 |
-1 |
der Rest hat das Vorzeichen der linken Zahl; für einen Index, der nach vorn umläuft: ((i % n) + n) % n
|
x += 5, x++
|
kurz für x = x + 5 und x = x + 1
|
|
a > b ? a : b |
die größere | Bedingung, dann der Wert für wahr, dann der für falsch |
Wird ein Wert in einen kleineren Typ geschrieben, geht der Rest verloren: uint8_t c = 300; ergibt 44, denn nur die unteren 8 Bit passen hinein (300 − 256). Wo ein Farbwert über 255 kommen kann, begrenze ihn vorher, etwa mit v > 255 ? 255 : v.
math.h| Funktion | Was sie liefert |
|---|---|
sqrt(x) |
die Wurzel |
fabs(x) |
den Betrag einer Kommazahl; abs(n) aus stdlib.h für ganze Zahlen |
floor(x), ceil(x), round(x)
|
ab-, auf- und gerundet, als Kommazahl |
fmod(x, y) |
den Rest einer Division von Kommazahlen |
sin(a), cos(a), tan(a)
|
Winkel im Bogenmaß: ein Vollkreis ist 2π, nicht 360 |
atan2(y, x) |
den Winkel des Punkts (x, y), von −π bis π; y kommt zuerst |
pow(x, y) |
x hoch y |
fmin(a, b), fmax(a, b)
|
die kleinere, die größere Kommazahl |
INFINITY |
unendlich: größer als jede Zahl, gut als »noch nichts getroffen« |
π steht nicht in C selbst: #define PI 3.14159265358979 oben in die Datei; raylib bringt PI schon mit.
if (x < 0) x = 0; else if (x >= W) x = W - 1; for (int y = 0; y < H; y++) // y von 0 bis H - 1 for (int x = 0; x < W; x++) set_pixel(x, y, color(x, y)); while (!should_close()) { // … ein Bild pro Durchgang … }
==, !=, <, <=, >, >=; verknüpft mit && (und), || (oder), ! (nicht). = ist eine Zuweisung: if (x = 0) setzt x auf 0, und die Bedingung ist nie wahr.if, for oder while. Sobald es zwei sind, braucht es { … }.break verlässt die Schleife, continue springt zum nächsten Durchgang.switch (key) { case 1: …; break; default: …; } wählt nach einer ganzen Zahl; ohne break läuft es in den nächsten case weiter.int counts[16] = {0}; // 16 ganze Zahlen, alle 0 uint8_t palette[4][3] = { // 4 Farben mit je Rot, Grün, Blau {0, 0, 0}, {255, 255, 255}, {255, 0, 0}, {0, 0, 255}, }; int map[H][W]; // zweidimensional: erst die Zeile, dann die Spalte counts[3] += 1; palette[2][0] = 200; map[y][x] = 1;
counts[16] liest oder überschreibt Speicher, der jemand anderem gehört. Das Programm rechnet dann falsch oder stürzt ab, oft weit entfernt von der Stelle des Fehlers.void draw(int *values, int count).memcpy aus string.h.typedef struct { double x, y, z; } vec; vec add(vec a, vec b) { return (vec){a.x + b.x, a.y + b.y, a.z + b.z}; } vec eye = {0, 1.2, 5.5}; eye.y += 0.1;
Ein struct fasst Werte unter einem Namen zusammen, mit typedef bekommt er einen kurzen Typnamen. (vec){…} schreibt einen Wert dieses Typs direkt hin. Structs werden als Ganzes zugewiesen, übergeben und zurückgegeben, als Kopie: Ändert eine Funktion ihren Parameter a, bleibt das Original, wie es war.
| Platzhalter | für | Beispiel und Ausgabe |
|---|---|---|
%d |
int |
printf("%d x %d\n", w, h); → 320 x 180
|
%ld |
long |
printf("%ld Bytes\n", size); |
%f, %.2f
|
double, mit 2 Nachkommastellen |
printf("%.2f\n", 3.14159); → 3.14
|
%x, %02x, %08x
|
hexadezimal, mit führenden Nullen auf 2 oder 8 Stellen |
printf("%02x\n", 10); → 0a
|
%s |
Text | printf("Kann %s nicht öffnen.\n", path); |
%c |
ein Zeichen | printf("%c\n", 'G'); |
\n beendet die Zeile. Passt ein Platzhalter nicht zum Typ, druckt printf Unsinn; der Compiler warnt davor. In PixelRAM erscheint die Ausgabe in der Konsole der Seite (Handbuch PixelRAM).
Text ist ein Array von char, dessen letztes Zeichen eine 0 ist: "GIF" belegt vier Bytes. Vergleichen geht nicht mit ==, sondern mit strcmp(a, b) == 0 aus string.h.
| Ausdruck | Was er tut | Beispiel |
|---|---|---|
a & b |
Und, Bit für Bit: lässt nur die Bits stehen, die in beiden gesetzt sind |
0xb6 & 0x0f → 0x06, die unteren vier Bits |
a | b |
Oder, Bit für Bit: setzt zusammen | low | (high << 8) |
a ^ b |
exklusives Oder: kippt die Bits, die in b gesetzt sind |
x ^ 1 wechselt zwischen gerade und ungerade |
~a |
kippt alle Bits |
~0x0f & 0xff → 0xf0
|
a << n |
schiebt um n Bits nach links: mal 2n
|
1 << 3 → 8
|
a >> n |
schiebt nach rechts: ganzzahlig durch 2n |
0xb6 >> 4 → 0x0b, die oberen vier Bits |
(a >> n) & 1 |
das Bit Nummer n, von rechts ab 0 gezählt |
(0xb6 >> 7) & 1 → 1
|
(1 << n) - 1 |
eine Maske mit den unteren n Bits |
(1 << 5) - 1 → 0x1f
|
0x schreibt eine Zahl hexadezimal: 0xff ist 255, jede Ziffer steht für vier Bits. Eine Zahl aus mehreren Bytes steht in GIF im Little Endian (das niedrige Byte zuerst, low | (high << 8)), in JPEG im Big Endian (das hohe zuerst, (high << 8) | low). Klammern lohnen sich: << bindet schwächer als +, & sogar schwächer als ==. 1 << n + 1 heißt 1 << (n + 1), und x & 1 == 0 heißt x & (1 == 0).
int x = 5; int *p = &x; // p ist die Adresse von x *p = 7; // schreibt 7 dorthin: x ist jetzt 7 uint8_t *file = malloc(size); // size Bytes, die das Programm behalten darf if (file == NULL) exit(1); // kein Speicher mehr frei file[0] = 0x47; // ein Zeiger wird wie ein Array benutzt free(file); // zurückgeben, wenn er nicht mehr gebraucht wird
&x ist die Adresse von x, *p der Wert an der Adresse p. Ein Array, das einer Funktion übergeben wird, kommt als Zeiger auf sein erstes Element an: void fill(uint8_t *data).void swap(int *a, int *b), aufgerufen als swap(&x, &y).malloc(n) aus stdlib.h holt n Bytes, die bleiben, bis free sie zurückgibt. Für ein Array von Werten: malloc(count * sizeof(double)). Was nach free noch benutzt wird, gehört niemandem mehr.NULL ist ein Zeiger, der nirgendwohin zeigt. Wer über ihn liest oder schreibt, bringt ein Programm im Terminal zum Absturz; in einer PixelRAM-Seite geht es oft unbemerkt weiter, mit Unsinn.So liest der GIF-Decoder eine ganze Datei in den Speicher:
FILE *f = fopen(path, "rb"); // "rb": lesen, Byte für Byte, wie es dasteht if (!f) { printf("Kann %s nicht öffnen.\n", path); exit(1); } fseek(f, 0, SEEK_END); // ans Ende ... long size = ftell(f); // ... dort steht die Größe fseek(f, 0, SEEK_SET); // ... und zurück an den Anfang uint8_t *data = malloc(size); fread(data, 1, size, f); // size Stücke zu je 1 Byte fclose(f);
fopen gibt NULL zurück, wenn die Datei fehlt; deshalb die Prüfung. Zum Schreiben öffnest du mit "wb" und schreibst mit fwrite(data, 1, size, f) oder fprintf(f, …) wie mit printf.
Der Compiler nennt Datei, Zeile und Spalte: main.c:12:5: error: …. Lies die erste Meldung zuerst; die folgenden sind oft nur Folgen von ihr. Warnungen (warning:) verhindern das Übersetzen nicht, zeigen aber fast immer einen echten Fehler.
expected ';' at end of declaration, expected ';' after expression – An der genannten Stelle fehlt ein Semikolon. Zeigt sie auf den Anfang einer Zeile, fehlt es am Ende der Zeile davor.use of undeclared identifier 'x' – Ein Tippfehler im Namen, oder x ist in einem anderen Block deklariert.call to undeclared library function 'sqrt', call to undeclared function – Das #include fehlt, oder die Funktion steht weiter unten als ihr Aufruf.expected '}', expected expression – Eine Klammer fehlt oder ist zu viel. Such von der genannten Zeile aus nach oben.format specifies type 'int' but the argument has type 'double' – Der Platzhalter in printf passt nicht zum Wert, hier %f statt %d.unused variable – Eine Warnung: Die Variable wird nie benutzt, oft wegen eines Tippfehlers an der Stelle, an der sie gebraucht wird.Segmentation fault im Terminal, memory access out of bounds in der Konsole einer PixelRAM-Seite – Kein Fehler des Compilers, sondern des laufenden Programms: ein Index außerhalb eines Arrays, ein NULL-Zeiger oder Speicher nach free. In einer Webseite stürzt so ein Fehler oft gar nicht ab, sondern rechnet still mit falschen Werten weiter.Rechnet das Programm falsch, ohne abzustürzen, hilft ein printf an der verdächtigen Stelle mehr als langes Hinsehen: Gib die Werte aus, die du dort erwartest, und vergleiche.
stdio.h, math.h, string.h …), und die Sprache selbst, auf Englisch.