Arrays und Zeichenketten in C

Lerne, wie C viele Werte in einem Array speichert und Text als Zeichen darstellt, die mit einem Nullbyte enden. Durchlaufe Arrays und Tabellen, übergib sie an Funktionen, verarbeite Zeichenketten mit eigenen Schleifen und mit den Standardfunktionen für Zeichenketten, sieh dir an, wie UTF-8-Text in Bytes gespeichert wird, und lies die Argumente, die ein Programm über die Kommandozeile erhält. Dabei lernst du, warum C Arraygrenzen nie prüft und wie du Daten so liest und kopierst, dass sie immer in den vorgesehenen Speicher passen.

AnfängerEinheit 3 von 8 in Kurs: C-Programmierung – Zeiger, Speicherverwaltung und sicheres C

Was du lernst

  1. Deklariere Arrays mit fester Größe, greife über den Index auf jedes Element zu, durchlaufe sie, ohne ihr Ende zu überschreiten, und übergib sie an Funktionen.
    • Ein Array deklarieren, initialisieren und über Indizes darauf zugreifen
      Erstelle Arrays mit einer festen Anzahl von Elementen, gib ihnen Anfangswerte und lies oder ändere einzelne Elemente über ihren Index.
    • Arrays mit Schleifen durchlaufen, ohne ihre Grenzen zu überschreiten
      Greife mit einer Zählschleife auf jedes Element zu und verwende dieses Muster, um zu summieren, den größten Wert zu finden, zu suchen, zu zählen und die Reihenfolge umzukehren.
    • Arrays an Funktionen übergeben
      Schreibe Funktionen, die Arrays als Parameter entgegennehmen, übergib die Länge separat und erkenne, wann die Funktion die Daten des Aufrufers ändern kann.
  2. Behandle Zeichen als kleine ganzzahlige Codes, speichere Text in char-Arrays, die mit einem Nullzeichen enden, verarbeite ihn Zeichen für Zeichen und sieh dir an, wie UTF-8-Text in Bytes gespeichert wird.
    • Zeichen als numerische Codes behandeln
      Verwende den Typ char und Zeichenkonstanten als Zahlen, wandle Ziffern und Buchstaben rechnerisch um und klassifiziere Zeichen mit <ctype.h>.
    • Text als nullterminierte Zeichenketten speichern
      Erkläre, wie eine C-Zeichenkette in einem char-Array aufgebaut ist, warum sie ein abschließendes Nullzeichen benötigt und wie sich Zeichenkettenliterale und Arrays unterscheiden.
    • Zeichenketten zeichenweise verarbeiten
      Durchlaufe eine Zeichenkette bis zu ihrem abschließenden Nullzeichen, um Text zu zählen, umzuwandeln, umzukehren, zu filtern und zu verschlüsseln, und schließe jede neue Zeichenkette mit '\0' ab.
    • Erklären, wie UTF-8-Text in char-Arrays gespeichert wird
      Setze Zeichen, Unicode-Codepunkte und UTF-8-Bytes zueinander in Beziehung und sage voraus, was bytebasierte C-Funktionen mit Text außerhalb von ASCII tun.
  3. Bestimme mit <string.h> die Länge von Zeichenketten, kopiere, verknüpfe, vergleiche und durchsuche sie und stelle sicher, dass jedes Ziel Platz für das Ergebnis und dessen abschließendes Nullzeichen hat.
    • Die Länge von Zeichenketten bestimmen, Zeichenketten kopieren und verketten
      Verwende strlen, strcpy und strcat korrekt und ermittle, wie viel Platz jedes Ziel benötigt.
    • Zeichenketten vergleichen und durchsuchen
      Vergleiche Zeichenketten mit strcmp und strncmp, finde Zeichen und Teilzeichenketten und zerlege Text in Token.
    • Beim Kopieren Speichergrenzen einhalten und mit rohem Speicher arbeiten
      Verwende die größenbegrenzten Funktionen strncpy, strncat und snprintf korrekt, erkenne abgeschnittenen Text und kopiere, fülle und vergleiche ganze Arrays mit den Speicherfunktionen.
  4. Speichere Tabellen und Zeichenkettenlisten in Arrays mit zwei oder mehr Dimensionen, durchlaufe sie mit verschachtelten Schleifen und übergib sie an Funktionen.
    • Zweidimensionale Arrays deklarieren und durchlaufen
      Deklariere und initialisiere Wertetabellen, erkläre die Anordnung ihrer Zeilen im Speicher und verarbeite Zeilen und Spalten mit verschachtelten Schleifen.
    • Tabellen an Funktionen übergeben und Zeichenkettenlisten speichern
      Schreibe Funktionen, die zweidimensionale Arrays entgegennehmen, und speichere mehrere Zeichenketten entweder in einem 2D-char-Array oder in einem Array von Zeichenkettenzeigern.
  5. Lies die nach einem Programmnamen eingegebenen Wörter über argc und argv, prüfe ihre Anzahl und wandle sie sicher in Zahlen und Optionen um.
    • argc und argv auslesen
      Deklariere main mit seinen beiden Parametern, beschreibe den Inhalt jedes Elements von argv und prüfe die Anzahl der Argumente, bevor du ein Argument verwendest.
    • Numerische Argumente konvertieren und auf Zulässigkeit prüfen
      Wandle Argumentzeichenketten mit Fehlerprüfung in Zahlen um, weise unzulässige Eingaben zurück und prüfe Werte vor ihrer Verwendung auf den zulässigen Wertebereich.
  6. Erkenne, wie C-Programme außerhalb ihrer Arrays lesen und schreiben, warum das gefährlich ist und wie du Text so liest und kopierst, dass er immer in den vorgesehenen Speicher passt.
    • Zugriffe außerhalb der Speichergrenzen und ihre Folgen erkennen
      Erkläre, warum C Zugriffe außerhalb der Speichergrenzen nicht verhindert, was dabei geschehen kann und welche Fehler dazu führen.
    • Text unter Einhaltung der Puffergrenzen lesen und kopieren
      Lies Eingaben mit Größenbegrenzung, erkenne zu lange Zeilen und kopiere Text nur, wenn er in das Ziel passt.

Arrays und Zeichenketten in C einfach erklärt

6 Kapitel

1

Wertesammlungen in Arrays speichern und verarbeiten

Mit Arrays kann eine Schleife viele zusammengehörige Werte verarbeiten. Das Array legt fest, wie viel Speicher vorhanden ist, während Indizes einzelne Elemente auswählen und eine Anzahl festhält, wie viele belegt sind. Lerne, wie Deklarationen Anfangswerte festlegen, wie Schleifen Ergebnisse berechnen, ohne eine Grenze zu überschreiten, und wie Funktionen auf die Elemente des Aufrufers zugreifen. Kleine Beispiele zeigen, welche Positionen der Code genau liest oder beschreibt.

1.1 Ein Array deklarieren, initialisieren und über Indizes darauf zugreifen

Ein Array gibt einer festen Anzahl von Werten desselben Typs einen Namen. Seine Elemente liegen im Speicher nebeneinander. Verwende ein Array, wenn dieselbe Operation auf viele Werte angewendet wird, etwa beim Aktualisieren einer Reihe von Punktzahlen.

Die Elemente erstellen
int scores[5] = {90, 72, 85, 64, 99};
int primes[] = {2, 3, 5, 7};
int counts[6] = {0};

Die erste Deklaration erstellt fünf Ganzzahlen. Der Compiler zählt vier Werte im zweiten Initialisierer, sodass primes vier Elemente hat. Im dritten ist das erste Element ausdrücklich null und jeder fehlende Initialisierungswert wird ebenfalls null. Ebenso enthält int a[5] = {1, 2}; die Werte 1, 2, 0, 0, 0. Ein seit C99 verfügbarer designierter Initialisierer wählt eine Position: int b[5] = {[2] = 7}; setzt das dritte Element auf 7 und die anderen auf null.

Eine lokale Deklaration mit automatischer Speicherdauer wie int data[5]; reserviert Platz, ohne Anfangswerte bereitzustellen. Beschreibe jedes Element, bevor du es liest. Arrays, die außerhalb aller Funktionen deklariert werden, und Arrays, die als static deklariert werden, werden mit null initialisiert, wenn kein expliziter Initialisierer angegeben ist.

Über den Index auf einen Wert zugreifen

Indizes beginnen bei null. Bei fünf Elementen sind die Positionen 0 bis 4 zulässig. scores[1] bezeichnet die zweite Punktzahl, nicht die erste. Ein Element kann überall dort verwendet werden, wo eine gewöhnliche Variable seines Typs verwendet werden kann:

scores[1] = 75;
scores[1] += 5;
int last = scores[4];

Die zweite Punktzahl ist jetzt 80. Die letzte ist weiterhin 99. Ein Index kann eine Variable oder ein Ausdruck sein. Deshalb greift scores[n - 1] auf das letzte Element zu, wenn n die Anzahl der Elemente und positiv ist. Bei scores[5] gibt es kein Element, auf das zugegriffen werden könnte.

Elemente und Bytes zählen

sizeof scores misst das gesamte Array in Bytes. Auf einem System mit 4-Byte-Ganzzahlen belegen die fünf Elemente 20 Bytes. Durch Division durch die Größe eines Elements entfällt die Abhängigkeit von der Ganzzahlgröße:

size_t n = sizeof scores / sizeof scores[0];
printf("%zu\n", n);

Dies gibt 5 aus. size_t ist der vorzeichenlose Ganzzahltyp für Größen; %zu gibt ihn aus. Diese Berechnung funktioniert dort, wo scores das tatsächliche Array bezeichnet. Ein Funktionsparameter enthält nicht die vollständige Größe des Arrays.

Größe und Werte trennen

Ein Name wie #define NUM_SCORES 5 ermöglicht es, in der Deklaration und ihren Schleifen dieselbe Größe zu verwenden. In C17 erzeugt ein lokales const int n = 5; int a[n]; dagegen ein Array variabler Länge: const macht n nicht zu einem ganzzahligen konstanten Ausdruck. Seine Größe wird bei der Ausführung der Deklaration festgelegt und bleibt während der Lebensdauer dieses Arrays unverändert. Es kann keine Initialisierungsliste verwenden. Die Unterstützung für Arrays variabler Länge ist in C11 und C17 optional; C23 ergänzt constexpr für Konstanten zur Übersetzungszeit.

Arrays können nicht als Ganzes zugewiesen werden: b = a; ist ungültig. Kopiere ihre Elemente mit einer Schleife oder einer geeigneten Speicherfunktion. Bei zwei getrennten Arrays vergleicht a == b Adressen, nicht Elementwerte. Vergleiche einander entsprechende Elemente, um die Gleichheit der Werte zu prüfen. Ein byteweiser Vergleich mit memcmp prüft dagegen ihre gespeicherten Darstellungen; gleiche Gleitkommawerte wie positive und negative null können unterschiedliche Darstellungen haben.

Wichtige Erkenntnisse

  • Arrayindizes reichen von 0 bis eins weniger als die Anzahl der Elemente.
  • Fehlende Initialisierungswerte werden null; nicht initialisierte lokale Elemente müssen vor ihrer Verwendung beschrieben werden.
  • Verwende sizeof a / sizeof a[0] nur dort, wo a ein tatsächliches Array ist.
  • Ein Array behält seine Größe während seiner Lebensdauer.
  • Kopiere und vergleiche Arraywerte Element für Element.

1.2 Arrays mit Schleifen durchlaufen, ohne ihre Grenzen zu überschreiten

Eine Zählschleife verbindet die Positionen eines Arrays mit einer Operation. Entscheide, welche Elemente nutzbare Daten enthalten, und greife dann genau auf diese Positionen zu. Dasselbe Muster eignet sich für Summen, Suchvorgänge und Aktualisierungen.

Auf die belegten Elemente zugreifen
int t[5] = {12, 15, 9, 14, 10};
int sum = 0;
for (int i = 0; i < 5; i++) {
    sum += t[i];
}
double average = sum / 5.0;

Die Schleife verwendet die Indizes 0, 1, 2, 3 und 4. Die Summe nimmt nacheinander die Werte 12, 27, 36, 50 und 60 an, der Mittelwert ist also 12,0. Mit i <= 5 käme ein unzulässiger Zugriff auf Index 5 hinzu. Dieser zusätzliche Durchlauf ist ein Off-by-one-Fehler.

Dividiere für einen Mittelwert mit Gleitkommaarithmetik. Bei einer Summe von 61 ergibt 61 / 5 die Ganzzahl 12; 61 / 5.0 ergibt 12,2. Wird das erste Ergebnis anschließend in einem double gespeichert, lässt sich der bereits abgeschnittene Nachkommateil nicht wiederherstellen.

Ein Maximum oder einen Treffer finden

Initialisiere bei einem nicht leeren Array das Maximum mit seinem ersten Wert:

int max = t[0];
for (int i = 1; i < 5; i++) {
    if (t[i] > max) max = t[i];
}

Der erste Vergleich ersetzt 12 durch 15. Die übrigen Werte sind kleiner, sodass 15 bleibt. Ein Startwert von null würde bei einer Liste wie {-8, -3, -11} scheitern: null ist größer als jeder gespeicherte Wert, steht aber nicht in der Liste.

Eine lineare Suche prüft Werte der Reihe nach:

int found = -1;
for (int i = 0; i < 5; i++) {
    if (t[i] == 14) {
        found = i;
        break;
    }
}

Hier wird found zu 3. Der Wert -1 markiert, dass es keinen Treffer gibt, weil er kein zulässiger Index sein kann. Melde erst dann einen Misserfolg, wenn alle Kandidaten geprüft wurden. Wird -1 nach dem ersten abweichenden Element zurückgegeben, werden spätere Treffer im Array übersehen.

Zählen und umordnen

Ein Häufigkeitsarray speichert einen Zähler pro Kategorie. Für Ziffern stellt int counts[10] = {0}; die Zähler 0 bis 9 bereit. Nach der Prüfung von d >= 0 && d < 10 erhöht counts[d]++ den Zähler für die Ziffer d. Die Prüfung schützt das Array, wenn der Ziffernwert aus einer Eingabe stammt.

Um ein Array umzukehren, tausche Paare von seinen Enden her:

for (int i = 0; i < 5 / 2; i++) {
    int temp = t[i];
    t[i] = t[5 - 1 - i];
    t[5 - 1 - i] = temp;
}

Der erste Tausch vertauscht 12 und 10. Der zweite vertauscht 15 und 14. Die 9 in der Mitte bleibt stehen, sodass {10, 14, 9, 15, 12} entsteht. Höre nach der Hälfte auf: Würdest du alle fünf Positionen durchlaufen, würde jedes Paar zweimal getauscht.

Anzahl und Kapazität getrennt festhalten

Ein Array mit Platz für 100 Werte kann derzeit nur 37 nutzbare Einträge enthalten. Seine Kapazität ist 100; die Anzahl belegter Elemente ist 37. Verarbeite Positionen unterhalb von count. Um einen Wert anzuhängen, prüfe zuerst count < CAPACITY, speichere ihn bei a[count] und erhöhe dann count. Eine Schleife bis zur Kapazität kann Positionen lesen, die noch nie nutzbare Werte erhalten haben, obwohl diese Positionen physisch innerhalb des Arrays liegen.

Wichtige Erkenntnisse

  • Verwende eine strikte Schleifenschranke mit <.
  • Verwende Gleitkommadivision, wenn ein Mittelwert einen Nachkommateil haben kann.
  • Initialisiere das Maximum eines nicht leeren Arrays mit einem Element, auch wenn alle Daten negativ sind.
  • Eine Suche ist erst erfolglos, nachdem alle Kandidaten geprüft wurden.
  • Prüfe einen Wert auf Zulässigkeit, bevor du ihn als Index verwendest.
  • Kehre die Reihenfolge um, indem du nur die erste Hälfte der Paare tauschst.
  • Die Anzahl belegter Elemente gibt an, wie viele Elemente genutzt werden; die Kapazität ist der verfügbare Platz.

1.3 Arrays an Funktionen übergeben

Eine Funktion kann ein Array verarbeiten, ohne eine separate Kopie jedes Elements zu erhalten. Der Aufruf ermöglicht den gemeinsamen Zugriff auf den Speicher des Aufrufers. Übergib die Anzahl der Elemente zusätzlich, damit die Funktion weiß, wo sie aufhören muss.

Daten und Länge bereitstellen
#include <stddef.h>

double average(const int a[], size_t n) {
    if (n == 0) return 0.0;
    int sum = 0;
    for (size_t i = 0; i < n; i++) {
        sum += a[i];
    }
    return (double) sum / n;
}

Der Aufrufer kann int values[] = {7, 8, 10}; deklarieren und average(values, sizeof values / sizeof values[0]) aufrufen. Die drei Additionen ergeben 25; die Umwandlung von sum in double vor der Division ergibt ungefähr 8,33. Die Rückgabe von 0,0 bei keinen Werten ist das für diese Funktion gewählte Verhalten bei leerer Eingabe. Die kleinen Werte in diesem Beispiel sorgen dafür, dass die ganzzahlige Summe im Wertebereich bleibt.

In einer Parameterdeklaration wird int a[] zu int *a angepasst. Der Parameter erhält die Adresse des ersten Elements. Selbst int a[10] prüft nicht, ob zehn Elemente vorhanden sind, und erzwingt dies auch nicht. Der Aufrufer muss eine Länge angeben, die zu den verfügbaren Daten passt.

Warum sizeof die Länge verliert

An der Aufrufstelle misst sizeof values das Array. Innerhalb der Funktion misst sizeof a den Zeigerparameter. Wenn Zeiger 8 Bytes und Ganzzahlen 4 Bytes belegen, ergibt die Division dieser Größen unabhängig von der Elementanzahl des Aufrufers den Wert 2. Dieses Ergebnis beschreibt weder eine Kopie mit zwei Elementen noch das ursprüngliche Array. Behalte die Länge als expliziten Parameter bei.

size_t, deklariert durch Headerdateien wie <stddef.h>, ist der von sizeof erzeugte vorzeichenlose Typ. Er kann Objektgrößen darstellen und %zu gibt ihn aus. Wird er für den Schleifenindex verwendet, vermeidet das den Vergleich eines vorzeichenbehafteten Index mit einer vorzeichenlosen Länge.

Entscheiden, ob Elemente verändert werden dürfen

Das const in const int a[] verhindert Zuweisungen an Elemente über diesen Parameter. Es eignet sich für eine Funktion, die aus den Daten ein Ergebnis berechnet. Eine Funktion, die Elemente aktualisieren soll, lässt es weg:

void zero(int a[], size_t n) {
    for (size_t i = 0; i < n; i++) {
        a[i] = 0;
    }
}

Nach zero(values, 3) enthalten die drei Elemente des Aufrufers alle null. Das Array hat weiterhin drei Elemente. Ein skalarer Parameter wie n ist eine separate Kopie; eine Änderung dieser Kopie würde die Zählvariable des Aufrufers nicht verändern.

Um mehrere Ergebnisse zu erzeugen, lass den Aufrufer ein Ausgabearray und dessen Länge bereitstellen. Eine Funktion kann out[i] mit jedem Ergebnis befüllen. C-Funktionen können ein Array nicht als Wert zurückgeben, und die Rückgabe der Adresse eines lokalen Arrays schlägt fehl, weil dieses Array nach der Rückkehr aus der Funktion nicht mehr existiert.

Mit einem vorzeichenlosen Index rückwärts zählen

Ein vorzeichenloser Index kann nicht negativ werden. In for (size_t i = n - 1; i >= 0; i--) ist die Bedingung immer wahr; nach null springt der Wert durch Wraparound auf eine große positive Zahl. Bei n == 0 führt bereits die anfängliche Subtraktion zum Wraparound. Beginne stattdessen bei der Anzahl:

for (size_t i = n; i > 0; i--) {
    printf("%d\n", a[i - 1]);
}

Bei drei Elementen sind die aufgerufenen Indizes 2, 1 und 0. Bei null Elementen wird der Schleifenrumpf nie ausgeführt. Binde <stdio.h> für printf ein.

Wichtige Erkenntnisse

  • Übergib eine Arraylänge separat; aus einem Parameter lässt sich die Anzahl der Elemente nicht ermitteln.
  • Schreibzugriffe über einen Arrayparameter verändern die Elemente des Aufrufers.
  • Verwende const für einen Parameter, der Elemente nur liest.
  • Der Aufrufer stellt Speicher für mehrere Ausgabewerte bereit.
  • Zähle bei Verwendung von size_t von n aus rückwärts und greife auf i - 1 zu.
2

Text mit Zeichen und nullterminierten Zeichenketten darstellen

Text beginnt mit Zeichencodes und endet mit einer Speicherregel: Eine C-Zeichenkette endet bei ihrem ersten Nullbyte. Lerne, wie sich Zeichen, Literale und beschreibbare Arrays unterscheiden, und verwende dann Schleifen, um Text zu zählen und umzuwandeln. UTF-8 bringt eine wichtige Unterscheidung zwischen einem Byte und einem vollständigen Codepunkt hinzu. Wenn du sowohl den Text als auch seine Speicherung nachvollziehst, verstehst du, warum ein sichtbares Wort mehr Arraypositionen benötigen kann, als seine Buchstaben vermuten lassen.

2.1 Zeichen als numerische Codes behandeln

Ein C-Zeichen wird als numerischer Code gespeichert. So kann ein Programm ein Symbol ausgeben, es mit einem anderen vergleichen oder mit seinem Code rechnen. Der Zeichensatz legt fest, welche Zahl welches Symbol darstellt.

Code und Zeichen auslesen

Ein char belegt ein Byte: sizeof(char) ist definitionsgemäß 1. Eine Zeichenkonstante wie 'A' hat den Typ int. Im hier verwendeten ASCII-basierten Zeichensatz ist ihr Code 65:

printf("%c %d\n", 'A', 'A');

Die Ausgabe ist „A 65“. %c zeigt das Zeichen an; %d zeigt seine Zahl an. ASCII vergibt die Codes 0 bis 127. Ziffern reichen von 48 bis 57, Großbuchstaben von 65 bis 90, Kleinbuchstaben von 97 bis 122, und das Leerzeichen hat den Code 32.

Einfache und doppelte Anführungszeichen erzeugen unterschiedliche Dinge. 'A' ist ein einzelner Zeichencode. "A" ist ein Array mit 'A' und einem abschließenden Nullbyte. Speichere ein einzelnes Zeichen mit char c = 'A'; und vergleiche es mit c == 'A'.

Zeichen mit Escape-Sequenzen schreiben
C-SchreibweiseZeichen
'\n'Zeilenumbruch, ASCII-Code 10
'\t'Tabulator, ASCII-Code 9
'\\'Umgekehrter Schrägstrich
'\''Einfaches Anführungszeichen
'\0'Nullzeichen, Code 0

Das Nullzeichen unterscheidet sich von der sichtbaren Ziffer '0', deren ASCII-Code 48 ist. Eine Zeichenkette verwendet das Nullzeichen, um ihr Ende zu markieren.

Codes in brauchbare Werte umwandeln

Der C-Standard garantiert aufeinanderfolgende Zifferncodes. Nachdem geprüft wurde, dass c eine Ziffer ist, liefert c - '0' ihren Wert. So ist '7' - '0' gleich 7, nicht 55. Das Abziehen des Anfangscodes entfernt die Position der Ziffer innerhalb des Zeichensatzes.

Auch die ASCII-Buchstaben folgen aufeinander. Durch das Abziehen von 'A' wird ein Großbuchstabe daher in eine Position von 0 bis 25 umgewandelt. Eine Caesar-Verschiebung um drei verwendet:

char shifted = (c - 'A' + 3) % 26 + 'A';

Für 'X' ergibt das Abziehen von 65 den Wert 23; das Addieren von drei ergibt 26; der Rest nach Division durch 26 ist null; das Addieren von 65 ergibt wieder 'A'. Einfaches 'X' + 3 ergibt den Code 91, also '['. Buchstabenarithmetik hängt von einer Buchstabenreihenfolge wie in ASCII ab; C garantiert aufeinanderfolgende Ziffern, verlangt aber keine aufeinanderfolgenden Buchstaben.

Mit ctype.h klassifizieren und konvertieren

Binde <ctype.h> für isdigit, isalpha, isalnum, isspace, isupper, islower, toupper und tolower ein. Die Tests geben bei einem Treffer einen Wert ungleich null und andernfalls null zurück. toupper('q') gibt 'Q' zurück; toupper('3') lässt die Ziffer unverändert. isspace erkennt Trennzeichen wie Leerzeichen, Tabulator und Zeilenumbruch.

Ein einfaches char kann vorzeichenbehaftet sein. Ein Byte über 127 kann dann zu einem negativen Wert werden, der kein zulässiges Argument für diese Funktionen ist, es sei denn, er ist der besondere Eingabemarker EOF. Wandle bei der Verarbeitung von Bytes aus einer Zeichenkette zuerst um:

if (isalpha((unsigned char) c)) {
    c = (char) toupper((unsigned char) c);
}

In der Standard-Locale „C“ erkennen diese Funktionen die grundlegenden Buchstaben, keine vollständigen mehrbyteigen UTF-8-Zeichen. Die explizite Typumwandlung macht das Byte zu einem zulässigen Argument; sie dekodiert kein UTF-8.

Wichtige Erkenntnisse

  • Zeichenkonstanten verwenden einfache Anführungszeichen; Zeichenkettenliterale verwenden doppelte.
  • \0 ist ein Zeichen mit dem Wert null, nicht die Ziffer 0.
  • Ziehe '0' von einem Ziffernzeichen ab, um seinen Wert zu erhalten.
  • Die Berechnung von Buchstabenpositionen verwendet hier die ASCII-Reihenfolge.
  • Wandle ein Byte einer Zeichenkette vor einem ctype-Aufruf in unsigned char um.
  • Ein ctype-Test gibt bei einem Treffer einen Wert ungleich null zurück, nicht unbedingt 1.

2.2 Text als nullterminierte Zeichenketten speichern

Eine C-Zeichenkette ist eine Folge von Zeichen, die mit '\0' endet. Das abschließende Nullzeichen ist Teil ihrer Speicherung, zählt aber nicht zu ihrer Textlänge. C hat keinen eigenen eingebauten Zeichenkettentyp: Die Zeichen belegen ein Array.

Die Bytes im Array betrachten
char word[] = "cat";
char name[20] = "Ada";

word enthält vier Bytes: 'c', 'a', 't', '\0'. Seine Arraygröße ist 4 und strlen(word) ist 3. Binde für strlen <string.h> ein. Das größere Array name enthält die drei Buchstaben, ihr abschließendes Nullzeichen und sechzehn weitere Nullbytes. Seine Kapazität ist 20, seine aktuelle Länge 3.

Ein zeichenweiser Initialisierer kann dieselbe Zeichenkette erzeugen:

char word[] = {'c', 'a', 't', '\0'};

Wird das letzte Element weggelassen, entstehen drei Zeichen ohne abschließendes Nullzeichen. C erlaubt auch char word[3] = "cat";. Das füllt alle drei Positionen und lässt das Nullbyte weg. Ein solches Array ist keine C-Zeichenkette. Eine Funktion wie strlen oder printf mit %s liest weiter, bis sie auf ein Nullbyte trifft. Sie kann dieses drei Byte große Array daher nicht sicher als Text verarbeiten.

Ein veränderbares Array oder ein Literal wählen
DeklarationSpeicherort des TextesKönnen seine Zeichen geändert werden?
char a[] = "hello";Eine Kopie im Array aJa
const char *p = "hello";Ein Zeichenkettenliteral, auf das p verweistNein

a[0] = 'H'; ändert die Kopie zu „Hello“. Ein Literal darf nicht verändert werden. Selbst wenn es einem einfachen char * zugewiesen wird, ist ein Schreibzugriff über diesen Zeiger undefiniertes Verhalten. Die Schreibweise const char * macht diese Einschränkung für den Compiler sichtbar.

Der Compiler verbindet benachbarte Zeichenkettenliterale: "Hello, " "world" ergibt denselben Text wie "Hello, world". Die entstehende Zeichenkette hat weiterhin ein abschließendes Nullzeichen. Das ist nützlich, um ein langes Literal auf mehrere Quelltextzeilen aufzuteilen.

Text ausgeben und ändern
printf("%s\n", word);
printf("%c\n", word[0]);

Der erste Aufruf gibt die gesamte Zeichenkette aus. Der zweite gibt ihr erstes Zeichen aus. Ein Zeichenkettenliteral in printf steht in doppelten Anführungszeichen, weil es Text mit einem Format enthält; %c erhält einen einzelnen Zeichencode.

Eine Elementzuweisung ändert ein gespeichertes Zeichen. Bei char name[20] = "Ada"; ergibt name[0] = 'E'; den Text „Eda“. Setzt du danach name[1] = '\0';, entsteht die Zeichenkette „E“. Die Bytes für 'a' und das alte abschließende Nullzeichen stehen später im Array noch zur Verfügung, aber Zeichenkettenoperationen stoppen bei der ersten null.

Ein Array kann nach der Deklaration keinen neuen Text durch name = "Bob"; erhalten. Kopiere Zeichen mit einem geprüften strcpy-Aufruf oder einer Schleife hinein und schließe das Nullzeichen ein. Die Länge des neuen Textes muss kleiner als die Kapazität sein.

Keinen Text darstellen

Die leere Zeichenkette "" belegt dennoch ein Byte für '\0'. Sie hat die Länge null und kann mit name[0] == '\0' geprüft werden. Eine leere Zeichenkette ist eine echte Zeichenkette mit einem abschließenden Nullzeichen; ein Nullzeiger verweist dagegen auf keinen Zeichenkettenspeicher.

Wichtige Erkenntnisse

  • Eine C-Zeichenkette endet bei ihrem ersten '\0'.
  • Die Kapazität schließt das abschließende Nullzeichen ein; die Zeichenkettenlänge nicht.
  • Ein Zeichenarray ohne abschließendes Nullzeichen kann nicht als C-Zeichenkette übergeben werden.
  • Arraykopien von Literalen sind veränderbar; die Literale selbst nicht.
  • Ein früher geschriebenes abschließendes Nullzeichen kürzt den Text, ohne die Größe seines Arrays zu ändern.

2.3 Zeichenketten zeichenweise verarbeiten

Das abschließende Nullzeichen einer Zeichenkette gibt einer Schleife ihren Endpunkt vor. Greife auf die Bytes davor zu, um Text zu zählen, umzuwandeln oder zu filtern. Beim Erstellen einer neuen Zeichenkette muss das Programm selbst ein neues abschließendes Nullzeichen schreiben.

Einmal bis zum Ende laufen
for (size_t i = 0; s[i] != '\0'; i++) {
    /* Process s[i]. */
}

Bei jedem Durchlauf prüft die Bedingung, ob das aktuelle Byte zum Text gehört. Die Schleife stoppt vor dem abschließenden Nullzeichen. Ein Aufruf von strlen(s) in dieser Bedingung kann bei jedem Durchlauf die ganze Zeichenkette erneut durchsuchen. Prüfe entweder das aktuelle Byte direkt oder berechne size_t len = strlen(s); einmal, wenn die Länge unverändert bleibt.

Verwende für die Umwandlung in Großbuchstaben ein veränderbares Array und <ctype.h>:

for (size_t i = 0; s[i] != '\0'; i++) {
    s[i] = (char) toupper((unsigned char) s[i]);
}

Aus „Hello, World 42“ wird „HELLO, WORLD 42“. Die Umwandlung erhält Komma, Leerzeichen und Ziffern. Die Umwandlung in den vorzeichenlosen Typ macht jedes gespeicherte Byte zu einem zulässigen ctype-Argument.

Kategorien und Wortanfänge zählen

Das ursprüngliche „Hello, World 42“ hat 15 Textbytes: 10 Buchstaben, zwei Ziffern, zwei Leerzeichen und ein Komma. Separate Zähler können isalpha, isdigit und isspace verwenden. Entscheide vor dem Zählen, was ein Wort ist. Für eine Folge von Buchstaben beginnt ein neues Wort, wenn ein Buchstabe auf ein anderes Zeichen folgt oder das erste Zeichen ist:

int in_word = 0;
size_t words = 0;
for (size_t i = 0; s[i] != '\0'; i++) {
    int letter = isalpha((unsigned char) s[i]) != 0;
    if (letter && !in_word) words++;
    in_word = letter;
}

Das Beispiel hat zwei Buchstabenfolgen, „Hello“ und „World“. Die Ziffern bilden nach dieser Definition kein Wort. Wird in_word bei jedem Durchlauf aktualisiert, kann ein Satzzeichen eine Folge beenden.

Nur den Text umkehren

Ermittle für eine Zeichenkette aus Einzelbytezeichen len einmal und tausche dann s[i] mit s[len - 1 - i], solange i < len / 2 gilt. Beim Umkehren von „stressed“ werden zuerst der erste und der letzte Buchstabe vertauscht, dann das nächste Paar, bis „desserts“ entsteht. Das Nullbyte bleibt bei Index len. Würde ein Tausch bei s[len] beginnen, gelangte diese null an den Anfang. Das Umkehren von Bytes auf diese Weise eignet sich für ASCII-Text; mehrbyteige Zeichen müssen vor dem Umordnen dekodiert werden.

Ein gefiltertes Ergebnis erstellen

Verwende unterschiedliche Indizes zum Lesen und Schreiben, weil einige Eingabebytes übersprungen werden:

char in[] = "a1b2c3";
char out[sizeof in];
size_t j = 0;
for (size_t i = 0; in[i] != '\0'; i++) {
    if (isalpha((unsigned char) in[i])) {
        out[j++] = in[i];
    }
}
out[j] = '\0';

Das Ergebnis ist „abc“. Das Ziel hat genug Platz, weil das Filtern diese Eingabe nicht verlängern kann. Ohne out[j] = '\0'; hat die Ausgabe kein definiertes Zeichenkettenende.

Verschiebe für eine Caesar-Verschlüsselung Großbuchstaben relativ zu 'A' und Kleinbuchstaben relativ zu 'a' und kopiere dann andere Bytes unverändert. Mit dem Schlüssel 13 wird aus „Hello, World!“ der Text „Uryyb, Jbeyq!“. Eine Wiederholung dieser Verschiebung stellt das Original wieder her, weil zwei Verschiebungen zusammen 26 ergeben. Ein Vergleich wie s[i] == 'a' prüft ein einzelnes Zeichen; verwende strcmp, um vollständigen Text zu vergleichen.

Wichtige Erkenntnisse

  • Prüfe das aktuelle Byte oder speichere die Länge zwischen, um wiederholte vollständige Durchläufe zu vermeiden.
  • Halte beim Filtern Lese- und Schreibindex getrennt.
  • Schreibe nach dem Erstellen einer neuen Zeichenkette ein abschließendes Nullzeichen.
  • Kehre die Textbytes um, ohne das abschließende Nullzeichen zu verschieben.
  • Definiere eine Wortgrenze, bevor du einen Wortzähler schreibst.
  • Wende ASCII-Buchstabenverschiebungen getrennt auf Groß- und Kleinbuchstaben an.

2.4 Erklären, wie UTF-8-Text in char-Arrays gespeichert wird

Textlänge kann die Anzahl der Bytes oder die Anzahl der Unicode-Codepunkte bedeuten. Diese Anzahlen unterscheiden sich oft. Ein C-char-Array macht einzelne Bytes zugänglich, sodass seine Indizes und Zeichenkettenfunktionen nicht automatisch vollständigen Zeichen außerhalb von ASCII folgen.

Zeichen, Codepunkte und Bytes unterscheiden

Eine Zeichenkodierung beschreibt, wie Text gespeichert wird. ASCII verwendet die Codes 0 bis 127 für grundlegende Buchstaben, Ziffern, Satz- und Steuerzeichen. Unicode weist Zeichen aus vielen Schriftsystemen Codepunkte zu: „é“ hat den Codepunkt U+00E9 und „€“ den Codepunkt U+20AC. UTF-8 kodiert Unicode-Skalarwerte mit einem bis vier Bytes.

ASCII-Codepunkte behalten ihre ein Byte langen Kodierungen. Das vorkombinierte „é“ benötigt zwei Bytes, 0xC3 und 0xA9; „€“ benötigt drei. Viele einzelne Emoji-Codepunkte benötigen vier Bytes. Ein angezeigtes Symbol kann mehrere Codepunkte enthalten, sodass selbst die Anzahl der Codepunkte nicht immer der Anzahl sichtbarer Symbole entspricht.

Eine UTF-8-Zeichenkette messen
char s[] = "café";
printf("%zu %zu\n", strlen(s), sizeof s);

Wenn der Ausführungstext in UTF-8 vorliegt, gibt dies „5 6“ aus. Die ersten drei Buchstaben benötigen drei Bytes und „é“ zwei, also insgesamt fünf Textbytes. Das abschließende Nullzeichen fügt das sechste Speicherbyte hinzu. s[3] ist das erste Byte von „é“, nicht der ganze Buchstabe.

Ein Schnitt nach vier Bytes würde 0xC3 erhalten, aber sein Fortsetzungsbyte 0xA9 entfernen. Das Ergebnis wäre kein gültiges UTF-8 mehr. Eine in Bytes angegebene Puffergrenze erfordert daher eine Prüfung der Zeichengrenze, wenn das Programm UTF-8-Text abschneidet.

Anfangsbytes in gültigem UTF-8 zählen

Ein Fortsetzungsbyte beginnt mit den Bits 10. Sein Wert liegt zwischen 0x80 und 0xBF. Ein führendes Byte oder ASCII-Byte hat dieses Muster nicht:

size_t points = 0;
for (size_t i = 0; s[i] != '\0'; i++) {
    unsigned char c = (unsigned char) s[i];
    if ((c & 0xC0) != 0x80) points++;
}

Die Maske 0xC0 behält nur die beiden höchstwertigen Bits; 0x80 stellt das Muster 10 an diesen Positionen dar. Der Test zählt „café“ als vier Codepunkte, indem er seine vier Anfangsbytes zählt. Er setzt gültiges UTF-8 voraus und prüft weder fehlerhafte Sequenzen noch fasst er Codepunkte zu sichtbaren Symbolen zusammen.

Operationen wählen, die die Kodierung erhalten

Das Kopieren einer vollständigen UTF-8-Zeichenkette erhält ihre Bytes. Auch das Verketten vollständiger Zeichenketten, der Vergleich ihrer Bytefolgen auf Gleichheit und die Suche nach einem ASCII-Zeichen oder einer vollständigen UTF-8-Teilzeichenkette funktionieren. UTF-8-Fortsetzungsbytes können nicht mit ASCII-Zeichen oder führenden Bytes verwechselt werden.

strcmp sortiert vorzeichenlose Bytewerte, daher unterscheidet sich seine Reihenfolge von der sprachspezifischen alphabetischen Reihenfolge. In UTF-8 haben gewöhnliche lateinische Buchstaben mit Akzent führende Bytes oberhalb von ASCII-'z'. Ein byteweiser Vergleich ordnet diese Buchstaben daher unabhängig von Wörterbuchregeln nach 'z' ein.

In der Standard-Locale „C“ verarbeiten toupper und isalpha jeweils ein Byte und dekodieren „é“ nicht als Buchstaben. Die explizite Umwandlung in unsigned char macht den Aufruf zulässig, kann ihn aber nicht zu einer Unicode-Umwandlung der Groß- und Kleinschreibung machen. Dafür werden Wide-Character-Funktionen oder eine Unicode-Bibliothek benötigt. Eine Caesar-Schleife nur für ASCII kann Buchstaben mit Akzent erhalten, indem sie nur Bytes von A–Z oder a–z ändert.

Das Nullbyte ist die UTF-8-Kodierung von U+0000; andere kodierte Codepunkte enthalten kein Nullbyte. Das passt zur Konvention des abschließenden Nullzeichens in C. UTF-16 kann Nullbytes innerhalb gewöhnlichen kodierten Textes enthalten, sodass ein UTF-16-Bytearray nicht als gewöhnliche char-Zeichenkette verarbeitet werden kann.

Wichtige Erkenntnisse

  • strlen zählt Bytes, auch in UTF-8-Text.
  • Ein Unicode-Codepunkt kann mehrere Bytes belegen.
  • Ein char-Index kann innerhalb eines kodierten Zeichens landen.
  • Das Zählen von Bytes, die keine Fortsetzungsbytes sind, zählt nur bei gültigem UTF-8 die Codepunkte.
  • Byteweiser Gleichheitsvergleich funktioniert für identisch kodierten Text; die Byte-Reihenfolge ist nicht die Wörterbuchreihenfolge.
  • ctype-Aufrufe für einzelne Bytes dekodieren kein UTF-8.
3

Die Standardfunktionen für Zeichenketten verwenden

Die Standardbibliothek stellt gängige Textoperationen bereit, aber jede Funktion erwartet eine bestimmte Art von Eingabe und eine bestimmte Bedeutung ihres Größenarguments. Lerne, die Länge von Zeichenketten zu bestimmen, sie zu kopieren, anzuhängen, zu vergleichen und zu durchsuchen, und wähle dann größenbegrenzte Formatierung oder Byteoperationen, wo sie passen. Durch das Prüfen der Kapazität und das Deuten der Rückgabewerte wird aus einem kurzen Funktionsaufruf eine vorhersehbare Operation auf bekanntem Speicher.

3.1 Die Länge von Zeichenketten bestimmen, Zeichenketten kopieren und verketten

Die Funktionen in <string.h> arbeiten mit nullterminiertem Text. Sie können seine Länge bestimmen, ihn kopieren oder weiteren Text anhängen, aber die Kopierfunktionen ermitteln nicht die Kapazität des Ziels. Berechne vor dem Schreiben den benötigten Speicher.

Die Länge ohne das abschließende Nullzeichen bestimmen

strlen(s) gibt einen size_t-Wert zurück, der die Anzahl der Bytes vor dem ersten Nullbyte angibt. Daher ist strlen("hello") gleich 5 und strlen("") gleich null. Gib das Ergebnis mit %zu aus.

char buf[64] = "hi";
printf("%zu %zu\n", strlen(buf), sizeof buf);

Dies gibt „2 64“ aus. Das erste Ergebnis misst den aktuellen Text; das zweite den verfügbaren Arrayspeicher. Ein späteres Kopieren kann das erste ändern, ohne das zweite zu ändern. Beide Aufrufe sind hier nützlich, weil buf ein tatsächliches Array ist und bereits eine gültige Zeichenkette enthält.

Eine vollständige Zeichenkette kopieren

strcpy(dest, src) kopiert den Text und sein abschließendes Nullbyte. Die benötigte Kapazität ist die Länge der Quelle plus eins:

char dest[12];
const char *src = "Maple";
if (strlen(src) < sizeof dest) {
    strcpy(dest, src);
}

„Maple“ belegt fünf Textbytes und ein abschließendes Nullzeichen und passt daher hinein. Das strikte < ist entscheidend: Eine Quelle, deren Länge der Kapazität entspricht, lässt keinen Platz für ihr Nullbyte. Schlägt die Prüfung fehl, muss das Programm den Fehler behandeln, bevor es das Ziel als neue Zeichenkette verwendet.

Eine Arrayzuweisung wie dest = src; kann den Inhalt nicht ersetzen. Die Zuweisung einer char *-Variablen an eine andere ist zulässig, kopiert aber nur einen Verweis auf denselben Speicher. Sie erstellt keine weitere Kopie der Zeichen.

An eine vorhandene Zeichenkette anhängen

strcat(dest, src) sucht zuerst das bereits in dest vorhandene abschließende Nullzeichen. Es ersetzt dieses durch das erste Byte der Quelle und schreibt ein neues abschließendes Nullzeichen hinter den angehängten Text. Die Zielkapazität muss beide aktuellen Längen plus eins abdecken.

char full[16];
strcpy(full, "Ada");
strcat(full, " ");
strcat(full, "Lovelace");

Die Längen wachsen von 3 über 4 auf 12. Das fertige „Ada Lovelace“ benötigt 13 Bytes Speicher, sodass drei Bytes ungenutzt bleiben. Ein Ziel mit 12 Bytes wäre um ein Byte zu klein, obwohl es der sichtbaren Textlänge entspricht.

Hänge nie an einen nicht initialisierten lokalen Puffer an. Bei char out[20]; strcat(out, "hi"); kann strcat kein festgelegtes Textende finden. Beginne mit char out[20] = "";, schreibe out[0] = '\0'; oder führe als erste Operation eine geprüfte Kopie aus.

Speicherbereiche und Aufwand beim Durchsuchen beachten

Quelle und Ziel dürfen sich bei strcpy oder strcat nicht überlappen. Das Verschieben von Text innerhalb desselben Puffers kann eine Überlappung erzeugen, selbst wenn beide Stellen für sich genommen zulässig sind. memmove ist die für überlappende Bytebereiche vorgesehene Funktion.

strlen muss bis zum abschließenden Nullzeichen suchen. strcat muss das aktuelle Ziel durchsuchen, bevor es anhängt. Wiederholtes Anhängen in einer Schleife durchsucht daher bereits erstellten Text erneut. Wenn du viele Teile zusammensetzt, halte die aktuelle Ausgabelänge fest und schreibe jeden Teil an diesem Index. Prüfe dabei die verbleibende Kapazität und erhalte das abschließende Nullzeichen. Eine bekannte Länge ist nur nützlich, solange sie dem tatsächlichen Inhalt entspricht.

Wichtige Erkenntnisse

  • strlen misst den aktuellen Text; sizeof misst bei einem Array dessen Kapazität.
  • Eine Kopie benötigt die Länge der Quelle plus ein Byte.
  • Zum Anhängen werden beide Zeichenkettenlängen plus ein Byte benötigt.
  • Initialisiere das Ziel als Zeichenkette, bevor du etwas anhängst.
  • Kopieren oder Anhängen mit überlappenden Zeichenkettenbereichen ist unzulässig.

3.2 Zeichenketten vergleichen und durchsuchen

Der Vergleich von Zeichenketten beantwortet Fragen zum Text, während die Suche eine Stelle innerhalb dieses Textes findet. Keine der beiden Aufgaben wird durch den Vergleich von Arraynamen mit == erledigt. Verwende das Ergebnis der Bibliotheksfunktion entsprechend seiner Bedeutung: als Vergleichsvorzeichen, Länge oder Fundstelle.

Text und Präfixe vergleichen

strcmp(a, b) vergleicht einander entsprechende Bytes als unsigned char-Werte bis zu einem Unterschied oder dem Zeichenkettenende. Das Ergebnis ist null bei gleichem Text, negativ, wenn a zuerst kommt, und positiv, wenn es später kommt. Nur das Vorzeichen ist festgelegt; erwarte nicht genau -1 oder 1.

if (strcmp(command, "quit") == 0) {
    puts("Goodbye");
}

Der ausdrückliche Vergleich mit null bedeutet Gleichheit. Ein einfaches if (strcmp(command, "quit")) führt den Zweig bei unterschiedlichem Text aus, weil ein Wert ungleich null wahr ist. command == "quit" würde Speicheradressen vergleichen.

In der ASCII-Reihenfolge kommt „apple“ beim ersten Buchstaben vor „banana“. „Zebra“ kommt vor „apple“, weil der Großbuchstabe 'Z' den Code 90 und der Kleinbuchstabe 'a' den Code 97 hat. „app“ kommt vor „apple“, weil sein Nullbyte vor dem 'l' der anderen Zeichenkette erreicht wird. Dies ist eine Reihenfolge nach Bytes, kein Wörterbuchvergleich.

strncmp(a, b, n) vergleicht höchstens n Bytes. strncmp(s, "--", 2) == 0 erkennt ein Präfix aus zwei Bindestrichen, bedeutet aber nicht, dass die gesamte Zeichenkette nur aus „--“ besteht. Standard-C hat keine Funktion zum Vergleich ganzer Zeichenketten ohne Beachtung der Groß- und Kleinschreibung. Ein Programm kann konvertierte Bytes selbst vergleichen; POSIX stellt strcasecmp in <strings.h> bereit.

Eine Stelle suchen

Verwende strchr für das erste Vorkommen eines Zeichens, strrchr für das letzte und strstr für die erste vollständige Teilzeichenkette. Die Funktionen geben einen Zeiger auf die Fundstelle zurück oder NULL, wenn es keinen Treffer gibt.

char name[] = "report.final.txt";
char *extension = strrchr(name, '.');
if (extension != NULL) {
    printf("%s\n", extension);
}

Dies gibt „.txt“ einschließlich des gefundenen Punkts aus. Ein Start am ersten Punkt mit strchr würde „.final.txt“ ausgeben. strstr(name, "final") findet den Rest der Zeichenkette ab „final.txt“; eine Suche nach „draft“ ergibt NULL. Prüfe auf NULL, bevor du ein Suchergebnis ausgibst oder liest.

Ein Trennzeichen finden oder Text zerlegen

strcspn(s, set) zählt die anfänglichen Bytes, die zu keinem der Zeichen in set gehören. Bei "oak:pine,birch" und der Zeichenmenge ":," stoppt die Funktion am Doppelpunkt. Bei einer gültigen Eingabezeichenkette entfernt dieses Muster den ersten Zeilenumbruch:

buf[strcspn(buf, "\n")] = '\0';

Gibt es keinen Zeilenumbruch, ist der zurückgegebene Index die Länge der Zeichenkette, sodass die Zuweisung das bereits vorhandene abschließende Nullzeichen überschreibt.

strtok zerlegt eine veränderbare Zeichenkette anhand beliebiger Zeichen aus einer Menge von Trennzeichen:

char colors[] = "red, green,blue";
char *token = strtok(colors, " ,");
while (token != NULL) {
    puts(token);
    token = strtok(NULL, " ,");
}

Die Token sind „red“, „green“ und „blue“. Der erste Aufruf übergibt das Array; spätere Aufrufe übergeben NULL, um fortzufahren. Die Funktion schreibt Nullbytes in die Eingabe und speichert zwischen den Aufrufen einen versteckten Zustand. Verwende beschreibbaren Speicher und erwarte nicht, dass die ursprüngliche Zeichenkette mit ihren Trennzeichen erhalten bleibt. Aufeinanderfolgende Trennzeichen erzeugen keine leeren Token.

Wichtige Erkenntnisse

  • Verwende strcmp(a, b) == 0 für gleichen Text.
  • Deute ein Vergleichsergebnis anhand seines Vorzeichens, nicht seines genauen Betrags.
  • Ein übereinstimmendes Präfix belegt nicht, dass die ganzen Zeichenketten gleich sind.
  • Suchergebnisse müssen auf einen Nullzeiger geprüft werden.
  • strcspn gibt die Länge eines Präfixes zurück; strtok verändert seine Eingabe und merkt sich seine Position.
  • Die Reihenfolge von Zeichenketten folgt den vorzeichenlosen Bytecodes.

3.3 Beim Kopieren Speichergrenzen einhalten und mit rohem Speicher arbeiten

Eine Längenbegrenzung ist nur nützlich, wenn sie die richtige Größe beschreibt. Manche Funktionen begrenzen die Quellbytes, andere die Zielkapazität; Speicherfunktionen verarbeiten rohe Bytes ohne Zeichenkettenenden. Lies die Vorgaben jeder Funktion, bevor du ihre Schranke festlegst.

Wissen, was n bedeutet

strncpy(dest, src, n) schreibt genau n Positionen, wenn die Quelle kürzer ist, und füllt mit Nullbytes auf. Hat die Quelle mindestens n Textbytes, kopiert die Funktion diese Bytes ohne abschließendes Nullzeichen. Für ein Array mit einer Kapazität größer als null ergibt sich so eine abgeschlossene, gegebenenfalls abgeschnittene Kopie:

strncpy(dest, src, sizeof dest - 1);
dest[sizeof dest - 1] = '\0';

Die letzte Zuweisung ist unerlässlich, wenn die Quelle die Grenze ausschöpft. Das Abschneiden eines Namens oder Befehls kann dennoch seine Bedeutung ändern. Weise daher zu lange Eingaben zurück, wenn eine vollständige Kopie erforderlich ist.

strncat(dest, src, n) hängt höchstens n Textbytes und dann ein abschließendes Nullzeichen an. Die Funktion erhält nicht die Gesamtkapazität. Für eine gültige Zeichenkette, die bereits in einem Zielarray steht, ist der verbleibende Platz für Text sizeof dest - strlen(dest) - 1. sizeof dest als n zu übergeben, kann zu einem Überlauf führen.

Formatieren und Abschneiden erkennen

snprintf, deklariert in <stdio.h>, erhält die gesamte Zielkapazität. Ist die Kapazität größer als null, schreibt die Funktion höchstens ein Textbyte weniger und danach ein abschließendes Nullzeichen:

char buf[8];
int needed = snprintf(buf, sizeof buf, "%s-%d", "item", 2026);
if (needed < 0) {
    puts("Formatting error");
} else if ((size_t) needed >= sizeof buf) {
    puts("Text did not fit");
}

Das vollständige Ergebnis „item-2026“ hat neun Textbytes. Der Puffer speichert „item-20“ und dessen Nullbyte, während needed gleich 9 ist. Der Rückgabewert zählt den Text, der erzeugt worden wäre, ohne das abschließende Nullzeichen. Prüfe ein negatives Ergebnis, bevor du es in size_t umwandelst.

Bytes kopieren und füllen

memcpy(dest, src, n) kopiert genau n Bytes, einschließlich möglicher Nullbytes, ohne nach einem abschließenden Nullzeichen zu suchen. Bei zwei gleich großen Ganzzahlarrays kopiert memcpy(b, a, sizeof a) deren gesamten Speicherinhalt, wenn die Bereiche nicht überlappen. Belegt jede von fünf Ganzzahlen vier Bytes, beträgt die Anzahl 20 Bytes. memmove kopiert Bytes mit definiertem Verhalten bei überlappenden Bereichen.

memset(a, 0, sizeof a) füllt jedes Byte mit null und setzt ein Ganzzahlarray auf null. Das zweite Argument beschreibt jedes Byte, nicht jedes Element. Bei Ganzzahlen mit vier Bytes setzt das Füllen mit 1 die Bytes auf 01 01 01 01, was bei der üblichen Darstellung 16843009 ergibt, nicht die Ganzzahl 1. Verwende eine Schleife, um Ganzzahlwerte ungleich null zuzuweisen.

memcmp(a, b, n) vergleicht gespeicherte Bytes. Null bedeutet, dass diese Bytefolgen übereinstimmen. Vergleiche für die Gleichheit der Werte die Elemente: Die Gleitkommawerte 0.0 und -0.0 werden als gleich verglichen, obwohl sich ihre Darstellungen unterscheiden können.

Andere Bibliotheksfamilien erkennen

strlcpy und strlcat aus BSD wurden 2024 in POSIX aufgenommen, sind aber keine Kernfunktionen von C17. Sie geben eine Länge zurück, die sie zu erzeugen versucht haben; ein Ergebnis mindestens so groß wie die angegebene Größe zeigt ein Abschneiden an. strlcpy schreibt ein abschließendes Nullzeichen, wenn die Größe positiv ist. strlcat tut dies nur, wenn der vorhandene Text innerhalb dieser Größe endet. Die Funktionen sind ab glibc 2.38 verfügbar. Optionale Funktionen aus Anhang K wie strcpy_s fehlen in vielen Bibliotheken, darunter glibc.

Wichtige Erkenntnisse

  • strncpy kann das abschließende Nullzeichen weglassen; strncat benötigt Platz für ein neues.
  • snprintf gibt die benötigte Länge des vollständigen Textes oder bei einem Fehler einen negativen Wert zurück.
  • Ein nicht negatives Formatierungsergebnis mindestens so groß wie die Kapazität bedeutet, dass Text abgeschnitten wurde.
  • Speicherfunktionen zählen Bytes und stoppen nicht bei null.
  • Verwende memmove für überlappende Bereiche.
  • memset füllt Bytes; memcmp vergleicht Darstellungen.
4

Daten in mehrdimensionalen Arrays organisieren

Tabellen ordnen Werte nach mehr als einer Position. Eine C-Tabelle speichert gleich breite Zeilen nacheinander, und verschachtelte Schleifen ermöglichen einem Programm, zeilen- oder spaltenweise zu arbeiten. Lerne, wie du diese Form deklarierst, an eine Funktion übergibst und beide Indizes im zulässigen Bereich hältst. Bei Textlisten stellt sich eine nützliche Frage: Beschreibbare Zeichenzeilen reservieren festen Platz, während Zeigerarrays auf Zeichenketten verweisen, die anderswo gespeichert sind.

4.1 Zweidimensionale Arrays deklarieren und durchlaufen

Eine Tabelle benötigt einen Zeilen- und einen Spaltenindex. In C ist ein zweidimensionales Array ein Array, dessen Elemente selbst Arrays sind. Jede Zeile hat dieselbe Anzahl an Elementen, und jeder Index hat seine eigene Schranke.

Zeilen und Spalten deklarieren
int grid[3][4];
int m[2][3] = {{1, 2, 3}, {4, 5, 6}};

grid enthält drei Zeilen mit jeweils vier Ganzzahlen: insgesamt zwölf Ganzzahlen. Seine Zeilenindizes reichen von 0 bis 2, seine Spaltenindizes von 0 bis 3. grid[1][2] greift auf den dritten Wert in der zweiten Zeile zu.

Verschachtelte geschweifte Initialisierungsklammern zeigen die Einteilung in Zeilen. Fehlende Initialisierungswerte werden null. Du kannst den Compiler in int m[][3] = {{1, 2, 3}, {4, 5, 6}}; die Zeilen zählen lassen, musst aber die Spaltenanzahl angeben. Ohne sie könnte der Compiler nicht bestimmen, wo jede Zeile endet.

Die zeilenweise Speicherung nachvollziehen

C speichert eine vollständige Zeile, bevor die nächste beginnt. In grid bilden die ersten vier gespeicherten Ganzzahlen Zeile 0, die nächsten vier Zeile 1 und die letzten vier Zeile 2. grid[1][2] ist vom Anfang aus die siebte Ganzzahl, an der bei null beginnenden Elementposition 6: eine vollständige Zeile mit vier Elementen, dann zwei weitere Elemente.

Wenn Ganzzahlen vier Bytes belegen, benötigt eine Zeile sechzehn Bytes und die gesamte Tabelle achtundvierzig. An der Deklarationsstelle:

size_t rows = sizeof grid / sizeof grid[0];
size_t cols = sizeof grid[0] / sizeof grid[0][0];

Die erste Division ergibt 3 und die zweite 4. Eine Zeile ist selbst ein Array, weshalb sizeof grid[0] mehr als eine Ganzzahl misst.

Die Tabelle durchlaufen und summieren
int sales[2][3] = {{4, 0, 7}, {5, 2, 1}};
int total = 0;
for (int r = 0; r < 2; r++) {
    int row_sum = 0;
    for (int c = 0; c < 3; c++) {
        row_sum += sales[r][c];
    }
    printf("%d\n", row_sum);
    total += row_sum;
}

Die erste Zeile ergibt 11 und die zweite 8. Die Summe dieser Zeilensummen ist 19. Das Zurücksetzen von row_sum innerhalb der äußeren Schleife beginnt für jede Zeile eine neue Summe. Würde die Variable nur einmal vor beiden Schleifen initialisiert, enthielte der zweite ausgegebene Wert auch die erste Zeile.

Setze für Spaltensummen die Spaltenschleife nach außen und die Zeilenschleife nach innen und greife weiterhin auf sales[r][c] zu. Die drei Summen sind 9, 2 und 8 und ergeben zusammen wieder 19. Die Änderung der Durchlaufreihenfolge erfordert keine Änderung der Bedeutung der beiden Indizes. Ein zeilenweiser Durchlauf folgt der zusammenhängenden Speicherung und verbessert bei großen Tabellen oft die Cache-Nutzung.

Jede Dimension innerhalb ihrer Grenzen halten

grid[c][r] vertauscht die Bedeutung der Indizes. Erreicht c den Wert 3, wird eine vierte Zeile angefordert, die nicht existiert. Die Tatsache, dass das gesamte Objekt zwölf Ganzzahlen enthält, erlaubt es nicht, mit einem Spaltenindex die Grenze einer Zeile zu überschreiten.

Schreibe zwei getrennte Indexzugriffe: grid[1][2]. In C17 wertet grid[1, 2] den Kommaausdruck aus und wählt grid[2], eine Zeile, statt eines einzelnen Elements. Bei mehr Dimensionen wird dieselbe Idee wiederholt angewendet: int cube[2][3][4] hat 24 Ganzzahlelemente, die über drei Indizes ausgewählt werden.

Wichtige Erkenntnisse

  • Ein zweidimensionales Array ist ein Array gleich breiter Zeilen.
  • Bei der zeilenweisen Speicherung werden Zeilen nacheinander gespeichert.
  • Halte die Grenzen für Zeilen und Spalten getrennt.
  • Setze einen Zeilen- oder Spaltenakkumulator beim Beginn einer neuen Gruppe zurück.
  • Nur die erste Dimension darf aus einem Initialisierer abgeleitet werden.

4.2 Tabellen an Funktionen übergeben und Zeichenkettenlisten speichern

Eine Funktion, die eine Tabelle erhält, benötigt genügend Informationen, um jede Zeile zu bestimmen. Bei einer Zeichenkettenliste kommt eine weitere Entscheidung hinzu: Speichere die Zeichen in gleich breiten Zeilen oder speichere Verweise auf Zeichenketten unterschiedlicher Länge.

Die Zeilenbreite eines Tabellenparameters angeben
void double_grid(int rows, int g[][4]) {
    for (int r = 0; r < rows; r++) {
        for (int c = 0; c < 4; c++) {
            g[r][c] *= 2;
        }
    }
}

Die erste Dimension kann entfallen, weil die Zeilenanzahl separat übergeben wird. Die Breite 4 definiert den Typ jeder Zeile. Ohne sie wäre der Abstand zwischen den Zeilen für g[r][c] nicht bekannt. Eine Deklaration mit int g[][] ist ungültig. Änderungen über diesen Parameter bleiben in der Tabelle des Aufrufers erhalten; ein Tabellenparameter, der nur gelesen wird, kann const int g[][4] verwenden.

Seit C99 akzeptieren Compiler, die Parameter mit variabel modifiziertem Typ unterstützen, Dimensionen, die zuvor in der Parameterliste deklariert wurden:

void print_grid(int rows, int cols, int g[rows][cols]);

GCC und Clang unterstützen diese C17-Form. Sie ermöglicht einer Funktion, verschiedene Zeilenbreiten entgegenzunehmen; übergib positive Dimensionen, die zur tatsächlichen Tabelle passen. C23 verlangt Unterstützung für diese Parametertypen, während automatische Arrayobjekte variabler Länge ein separates optionales Merkmal bleiben. Parameter mit fester Breite sind nützlich, wenn die Compilerunterstützung begrenzt ist.

Zeichenketten in Zeilen speichern
char days[7][10] = {
    "Monday", "Tuesday", "Wednesday", "Thursday",
    "Friday", "Saturday", "Sunday"
};

Jede Zeile hat unabhängig von ihrer aktuellen Textlänge Speicher für zehn Zeichen. „Wednesday“ benötigt alle zehn Positionen: neun Buchstaben und ein abschließendes Nullzeichen. days[2] wählt diese ganze Zeile als Zeichenkette; days[2][0] wählt 'W'.

Eine Zeile kann mit %s ausgegeben, mit strlen gemessen oder mit strcmp verglichen werden. Sie kann auch eine geprüfte Kopie aufnehmen. strcpy(days[0], "Mon"); passt in zehn Bytes und ändert den Text von Zeile 0. Die Zeile bleibt zehn Bytes breit und die nächste Zeile bleibt an derselben Stelle. Ein Ersatztext mit zehn Textbytes würde elf Bytes benötigen und nicht hineinpassen.

Verweise auf Literale speichern
const char *names[] = {"Ann", "Bartholomew", "Li"};

Dies deklariert ein Zeigerarray. Jedes Element verweist auf ein Literal mit seiner eigenen Länge plus einem abschließenden Nullzeichen. names[1] verweist auf „Bartholomew“ und names[1][0] ist 'B'. Die Literale dürfen nicht verändert werden.

Die Zeigerelemente können sich ändern, obwohl die Zeichen der Literale nicht verändert werden dürfen. names[0] = "Anna"; ändert, auf welches Literal das erste Element verweist; es verlängert „Ann“ nicht an Ort und Stelle. In der Form mit 2D-Zeilen ist ein Element ein Zeichenarray mit fester Breite und kann nicht auf diese Weise neu zugewiesen werden. Wähle beschreibbare Zeilen für Text, der direkt bearbeitet wird, und Verweise auf Literale für eine Liste fester Textoptionen. Der Kommandozeilenparameter argv hat die Form eines Zeigerarrays, obwohl die Zeichen der übergebenen Argumente veränderbar sind.

Wichtige Erkenntnisse

  • Tabellenparameter benötigen jede Dimension nach der ersten.
  • Eine separat übergebene Dimension muss zur tatsächlichen Tabelle passen.
  • Jede Zeichenkettenzeile benötigt innerhalb ihrer festen Breite ein eigenes abschließendes Nullzeichen.
  • Ein Zeigerarray speichert Verweise, keine gleich breiten Zeichenzeilen.
5

Kommandozeilenargumente verarbeiten

Eine Kommandozeile stellt einem Programm beim Start Text bereit. Mit der Argumentanzahl, der Gruppierung durch die Shell und Zeichenkettenoperationen kann das Programm diesen Text deuten, ohne vorauszusetzen, dass ein Argument existiert oder bereits eine Zahl enthält. Verfolge einen geprüften Weg von argv zu einem nutzbaren Wert: Prüfe, ob das Argument vorhanden ist, konvertiere es vollständig, erkenne Konvertierungsfehler und setze den Wertebereich durch, den das Programm tatsächlich akzeptiert.

5.1 argc und argv auslesen

Kommandozeilenargumente ermöglichen es dem Aufrufer, beim Start eines Programms Text bereitzustellen. Die Laufzeitumgebung stellt diese Zeichenketten main zur Verfügung. Prüfe ihre Anzahl, bevor du über Indizes darauf zugreifst.

Die Argumentliste lesen
int main(int argc, char *argv[]) {
    for (int i = 1; i < argc; i++) {
        printf("%d: %s\n", i, argv[i]);
    }
    return 0;
}

Binde <stdio.h> für die Ausgabe ein. argc ist die Argumentanzahl und argv die Liste der Zeichenketten. Die gleichwertige Parameterschreibweise char **argv beschreibt dieselbe Schnittstelle. Die Schleife beginnt bei 1, weil Index 0 normalerweise den Namen enthält, mit dem das Programm gestartet wurde.

In einer gewöhnlichen Shell liefert der Aufruf ./greet Ada Lovelace drei Zeichenketten: „./greet“, „Ada“ und „Lovelace“. Somit ist argc gleich 3 und das letzte Benutzerargument ist argv[2]. Bei argv[argc], hier argv[3], steht ein Nullzeiger; er ist ein Wächterwert nach den eigentlichen Argumentzeichenketten.

Text durch die Shell gruppieren lassen

Anführungszeichen beeinflussen, wie die Shell Argumente bildet. Der Aufruf ./greet "Ada Lovelace" liefert ein Benutzerargument, das ein Leerzeichen enthält. Die Anführungszeichen gruppieren die Wörter und werden nicht Teil des Argumenttextes. argc ist jetzt 2.

Unix-artige Shells können auch Muster erweitern, bevor das Programm startet. Bei ./count *.txt erhält das Programm üblicherweise die von der Shell gefundenen Namen als separate Argumente. Es erhält die resultierenden Zeichenketten, keinen Auftrag, das Muster selbst zu erweitern. Das genaue Verhalten bei der Erweiterung hängt von der Shell und ihren Einstellungen ab.

Ein fehlendes oder zusätzliches Argument zurückweisen

Wenn ein Programm genau ein Benutzerargument benötigt, verlange argc == 2, bevor du es verwendest:

if (argc != 2) {
    printf("Usage: %s name\n", argv[0]);
    return 1;
}
printf("Hello, %s\n", argv[1]);

Wenn argc gleich 1 ist, ist argv[1] der Nullzeiger als Wächterwert und kann weder an strlen übergeben noch mit %s ausgegeben werden. Indizes hinter dem Wächterwert liegen außerhalb des Argumentarrays.

Die Rückgabe von null aus main signalisiert Erfolg. Ein Status ungleich null, wie die 1 oben, teilt der Shell mit, dass das Programm nicht erfolgreich abgeschlossen wurde. <stdlib.h> stellt auch EXIT_SUCCESS und EXIT_FAILURE bereit.

Jedes Argument als Zeichenkette behandeln

argv[i][0] greift auf das erste Zeichen des Arguments zu; strlen(argv[i]) misst seine Textbytes; strcmp(argv[i], "-v") == 0 prüft eine vollständige Option. Ein Argument, das wie eine Zahl aussieht, ist weiterhin Text und muss vor einer Berechnung konvertiert werden.

argv[0] hält normalerweise fest, wie das Programm gestartet wurde, kann aber leer sein, wenn diese Information nicht verfügbar ist. Es ist für Meldungen nützlich, nicht für eine feste Identitätsprüfung. Die Zeichen der Argumente dürfen während des Programms verändert werden, aber der bereitgestellte Speicher lässt nicht darauf schließen, dass ein längerer Ersatz hineinpasst. Um eine Erweiterung anzuhängen oder Text anderweitig zu verlängern, kopiere ihn zuerst in einen separat bemessenen Puffer.

Wichtige Erkenntnisse

  • argc zählt die übergebenen Argumentzeichenketten einschließlich des üblichen Eintrags für den Startnamen.
  • Prüfe die Argumentanzahl, bevor du ein Benutzerargument liest.
  • argv[argc] ist ein Nullzeiger, keine leere Argumentzeichenkette.
  • Anführungszeichen in der Shell können Leerzeichen innerhalb eines Arguments erhalten.
  • Verwende Zeichenkettenoperationen für Argumenttext und Konvertierung für Zahlen.

5.2 Numerische Argumente konvertieren und auf Zulässigkeit prüfen

Ein numerisches Argument kommt als Zeichenfolge an, nicht als Ganzzahl. Eine zuverlässige Konvertierung prüft drei getrennte Fragen: Haben Ziffern eine Zahl gebildet, hat die Konvertierung den erforderlichen Text verwendet und ist der resultierende Wert zulässig?

Den Verlust von Fehlerinformationen vermeiden

atoi, deklariert in <stdlib.h>, führt eine einfache Konvertierung durch. Es wandelt „13“ in 13 um, aber auch „12abc“ in 12. Sowohl für „0“ als auch für Text wie „abc“, der nicht mit einer Zahl beginnt, gibt die Funktion null zurück. Ein Ergebnis außerhalb des Wertebereichs führt zu undefiniertem Verhalten. Daher ist ein Test auf ausschließlich Ziffern mit anschließendem atoi bei sehr langen Eingaben weiterhin unsicher.

Eine Formatregel wie „nur eine oder mehrere Ziffern“ erfordert eine separate Prüfung. Weise eine leere Zeichenkette zurück und verlange, dass jedes Byte isdigit((unsigned char) c) erfüllt. Diese Regel akzeptiert „13“ und weist „13a“, „-5“ und „“ zurück. Sie prüft die Schreibweise, nicht ob der Wert in eine Maschinenganzzahl passt.

strtol verwenden und die Endposition untersuchen
#include <errno.h>
#include <stdlib.h>

char *end;
errno = 0;
long count = strtol(argv[1], &end, 10);
if (end == argv[1] || *end != '\0' || errno == ERANGE) {
    puts("Enter a whole number");
    return 1;
}
if (count < 1 || count > 100) {
    puts("Count must be from 1 to 100");
    return 1;
}

Dieses Fragment folgt auf eine Prüfung der Argumentanzahl und verwendet <stdio.h> für puts. Die 10 wählt die Dezimalkonvertierung. end erhält die Stelle, an der die Konvertierung endete; &end ermöglicht der Funktion, diese Stelle zu speichern. *end liest das dortige Zeichen. Zeigerarithmetik ist nicht nötig.

Für „13a“ ist der Rückgabewert 13, aber *end ist 'a', sodass der Text zurückgewiesen wird. Bei einer leeren Zeichenkette oder „abc“ entspricht end dem Anfang des Arguments und zeigt damit, dass keine Zahl eingelesen wurde. Beide Prüfungen sind nötig: Würde nur das abschließende Nullzeichen geprüft, würde eine leere Zeichenkette fälschlich akzeptiert.

Setze errno vor jeder Konvertierung auf null zurück. Ein Wert außerhalb des long-Bereichs veranlasst strtol, einen Endwert zurückzugeben und ERANGE zu setzen. Ohne das Zurücksetzen könnte ein alter Fehler dieser Konvertierung zugeschrieben werden. Prüfe das Ergebnis, bevor du es in den engeren Typ int umwandelst; verwende INT_MIN und INT_MAX aus <limits.h>, wenn der gesamte int-Bereich zulässig ist.

Die eigene Regel des Programms anwenden

strtol akzeptiert führende Leerraumzeichen und ein optionales Vorzeichen. Entscheide, ob diese Formen zulässig sind. Verlangt die Schnittstelle ausschließlich Ziffern, prüfe zusätzlich zu den Konvertierungsprüfungen, dass die Eingabe nicht leer ist und nur Ziffern enthält. Prüfe nach einer erfolgreichen Konvertierung den anwendungsspezifischen Wertebereich, bevor du einen Wert als Anzahl oder Index verwendest. „0“ kann ein vollkommen gültiger numerischer Text und dennoch für eine Anzahl unzulässig sein, die positiv sein muss.

Ein Caesar-Programm, das einen nicht negativen Schlüssel akzeptiert, prüft zuerst argc, dann, ob der Schlüssel ausschließlich aus Ziffern besteht, und anschließend das vollständige strtol-Ergebnis und den Wertebereich. Die Reduktion eines zulässigen Schlüssels mit % 26 ergibt vor der Buchstabenarithmetik eine Verschiebung von 0 bis 25. Schlüssel 27 hat die Wirkung von Schlüssel 1. Führe die Wertebereichsprüfung vor einer expliziten Umwandlung in einen engeren Typ oder einer möglicherweise überlaufenden Berechnung durch. Für Dezimalwerte bietet strtod die entsprechende Schnittstelle für Endposition und Wertebereichsfehler.

Wichtige Erkenntnisse

  • Argumente, die wie Zahlen aussehen, müssen dennoch konvertiert werden.
  • atoi kann eine gültige null nicht von einem Konvertierungsfehler unterscheiden.
  • Prüfe, ob strtol etwas gelesen und den gesamten erforderlichen Text verarbeitet hat.
  • Setze errno zurück, prüfe ERANGE und prüfe dann den anwendungsspezifischen Wertebereich.
  • Eine Prüfung auf ausschließlich Ziffern schließt einen Überlauf nicht aus.
  • Prüfe einen Wert auf Zulässigkeit, bevor du ihn in einen engeren Typ umwandelst oder als Index verwendest.
6

Zugriffe außerhalb der Speichergrenzen und Pufferüberläufe verhindern

Fehler an Speichergrenzen verbinden mehrere frühere Ideen: Eine Schleife kann einen zusätzlichen Durchlauf ausführen, Text kann ein zusätzliches Byte benötigen, und eine Funktion kann die Kapazität eines Arrays aus dem Blick verlieren. Lerne, den unzulässigen Zugriff zu erkennen, bevor du seine Folgen vorhersagst. Lies und kopiere dann Eingaben innerhalb bekannter Grenzen und prüfe Erfolg und Vollständigkeit getrennt, damit ein ausreichend großer Puffer auch die Daten enthält, die das Programm annehmen sollte.

6.1 Zugriffe außerhalb der Speichergrenzen und ihre Folgen erkennen

Jeder Arrayzugriff benötigt einen zulässigen Index. C schreibt zur Laufzeit keine Prüfung der Arraygrenzen vor, daher muss der Programmierer den Index innerhalb des Objekts halten. Ein Zugriff außerhalb seines Bereichs hat undefiniertes Verhalten: Die Sprache garantiert kein bestimmtes Ergebnis.

Einen unzulässigen Zugriff erkennen

Für int a[5] sind die Indizes 0 bis 4 zulässig. Index 5 ist bereits zu groß und -1 ist negativ. Ein Compiler kann einen sichtbaren Fehler melden, aber viele unzulässige Indizes hängen von Eingaben zur Laufzeit ab und werden beim Kompilieren nicht zurückgewiesen.

Ein fehlerhaftes Programm kann scheinbar funktionieren, abstürzen oder unerwartete Werte erzeugen. Ein Schreibzugriff kann benachbarte Daten beschädigen; das Symptom kann später bei einer unabhängigen Berechnung auftreten. Ein Lesezugriff kann auf Bytes außerhalb des vorgesehenen Objekts zugreifen. Keine der beiden Operationen hat ein verlässliches Ergebnis. Auch die Wiederholung eines erfolgreichen Laufs kann nicht belegen, dass der Zugriff zulässig ist.

Jeden Schreibzugriff einschließlich des abschließenden Nullzeichens zählen
char name[8];
strcpy(name, "Christopher");

„Christopher“ hat elf Textbytes. Beim Kopieren wird auch das abschließende Nullzeichen geschrieben, sodass dieser Aufruf versucht, zwölf Bytes in acht Positionen zu schreiben. Das ist ein Pufferüberlauf. Auf realen Systemen kann ein Überlauf andere Daten oder Steuerinformationen beschädigen. In der Vergangenheit nutzten Angriffe wie der Morris-Wurm von 1988 solche Fehler bei der Eingabeverarbeitung aus.

Ein Überlauf kann nur ein Byte umfassen:

char s[5];
for (int i = 0; i <= 5; i++) {
    s[i] = 'x';
}

Der Schleifenrumpf läuft sechsmal und beschreibt Index 5. Eine Änderung der Bedingung zu i < 5 hält fünf Zeichenschreibzugriffe innerhalb des Arrays, erzeugt aber weiterhin keine Zeichenkette. Schreibe für eine Zeichenkette aus vier 'x'-Zeichen nur die Indizes 0 bis 3 und speichere dann '\0' bei Index 4.

Berechnete Indizes auf Zulässigkeit prüfen

Eine Häufigkeitstabelle kann ein Zeichen in einen Index umwandeln, aber die Subtraktion allein garantiert keinen zulässigen Bereich:

int counts[26] = {0};
if (c >= 'a' && c <= 'z') {
    counts[c - 'a']++;
}

Bei ASCII-Reihenfolge begrenzt dieser Test das Ergebnis auf 0 bis 25. Für 'A' oder ein Satzzeichenbyte könnte c - 'a' ohne den Test einen negativen Index erzeugen. Prüfe den ursprünglichen Wert vor dem Indexzugriff; eine Prüfung nach dem Schreiben kommt zu spät.

Prüfe bei einer Tabelle Zeile und Spalte getrennt. In int g[2][3] ist ein Spaltenindex von 3 unzulässig, selbst wenn die nächste Zeile im Speicher angrenzt. Der Speicher der gesamten Tabelle erlaubt es nicht, die Grenze einer Zeile zu überschreiten.

Zur Operation passende Prüfungen verwenden

Eine Arrayschleife verwendet eine strikte obere Schranke; beim Anhängen wird die Anzahl mit der Kapazität verglichen; beim Kopieren einer Zeichenkette wird die Textlänge plus eins geprüft. Funktionen erhalten tatsächliche Längen oder Kapazitäten von ihren Aufrufern. Diese Prüfungen betreffen unterschiedliche Größen, sodass eine fest im Code hinterlegte Größe nicht alle ersetzen kann.

Kompiliere mit Warnungen wie -Wall -Wextra. -fsanitize=address -g von GCC und Clang kann viele unzulässige Zugriffe auf ausgeführten Pfaden melden und die Quelltextstelle anzeigen. Solche Tests helfen, Fehler zu finden, aber bei nicht ausgeführten Pfaden müssen die Grenzen weiterhin durch Nachdenken geprüft werden. Überprüfe jeden indizierten Lese- und Schreibzugriff, indem du sein Objekt, dessen Grenze und die Bedingung bestimmst, die den Index zulässig hält.

Wichtige Erkenntnisse

  • Lese- und Schreibzugriffe außerhalb der Speichergrenzen haben undefiniertes Verhalten.
  • Ein erfolgreicher Lauf ist kein Beleg dafür, dass ein Index zulässig ist.
  • Beim Kopieren einer Zeichenkette zählt das abschließende Nullzeichen zu den geschriebenen Bytes.
  • Prüfe Daten auf Zulässigkeit, bevor du daraus einen Arrayindex ableitest.
  • Prüfe jede Tabellendimension unabhängig.
  • Warnungen und Sanitizer helfen, Programmfehler auf getesteten Pfaden zu erkennen.

6.2 Text unter Einhaltung der Puffergrenzen lesen und kopieren

Die Länge einer Eingabe wird von der Person bestimmt, die sie bereitstellt. Gib Lesefunktionen eine tatsächliche Puffergrenze und prüfe ihre Rückgabewerte. Entscheide dann, ob die Daten eine vollständige Zeile bilden und ob sie zu den eigenen Regeln des Programms passen.

Ein Wort mit einer Feldbreite lesen

Für char word[16]; liest scanf("%15s", word) höchstens fünfzehn Zeichen, die keine Leerraumzeichen sind, und hängt '\0' an. Die Feldbreite lässt eine der sechzehn Positionen für das abschließende Nullzeichen frei. %s ohne Feldbreite hat keine Längenbegrenzung; gets hatte für Zeilen dasselbe grundlegende Problem und wurde in C11 aus C entfernt.

Prüfe, ob scanf 1 zurückgibt, bevor du ein Wort verwendest. Die Funktion liest ein Wort, keine ganze Zeile mit Leerzeichen. Überzählige Bytes des Wortes können ungelesen bleiben. Eine Feldbreite schützt also den Speicher, entscheidet aber nicht, ob die gesamte Eingabe zulässig ist.

Eine Zeile mit fgets lesen
char buf[8];
if (fgets(buf, sizeof buf, stdin) == NULL) {
    return 1;
}

fgets liest hier höchstens sieben Zeichen und schreibt ein abschließendes Nullbyte. Die Funktion behält einen Zeilenumbruch, wenn sie ihn vor der Grenze erreicht. Eine Zeile mit „hello“ und einem Zeilenumbruch wird zu "hello\n" plus dem abschließenden Nullzeichen. Auch das Lesen einer letzten Zeile, die am Dateiende ohne Zeilenumbruch endet, kann erfolgreich sein. Wurden vor dem Dateiende keine Zeichen gelesen oder trat ein Lesefehler auf, ist das Ergebnis NULL; verarbeite diesen Puffer nicht als neue Eingabe.

Einen gespeicherten Zeilenumbruch von einer unvollständigen Zeile unterscheiden

Finde einen Zeilenumbruch, bevor du ihn entfernst:

size_t end = strcspn(buf, "\n");
int had_newline = buf[end] == '\n';
buf[end] = '\0';

Binde <string.h> ein. Wurde kein Zeilenumbruch gespeichert, ist end die Textlänge und die Zuweisung überschreibt das vorhandene abschließende Nullzeichen. Ein fehlender Zeilenumbruch kann eine unvollständige Zeile, einen genau vor einem Zeilenumbruch gefüllten Puffer oder eine letzte Zeile bedeuten, die am Dateiende endete. Er allein belegt nicht, dass weitere Zeichen verbleiben.

Bei der Eingabezeile „abcdefghij“ und einem Puffer mit acht Bytes speichert der erste Lesezugriff „abcdefg“ und der nächste kann „hij\n“ lesen. Würde der zweite Abschnitt als eigene Antwort behandelt, würde eine zusammengehörige Eingabe geteilt. Ein Programm kann eine Zeile ohne erforderlichen Zeilenumbruch zurückweisen oder ihren Rest lesen und verwerfen, bevor es erneut fragt. Lege die Eingaberegel fest, bevor du sie implementierst.

Für bis zu dreißig ASCII-Zeichen eines Namens plus einen Zeilenumbruch und ein abschließendes Nullzeichen verwende 32 Bytes. Bei UTF-8 ist dies eine Bytegrenze: Dreißig angezeigte Zeichen können mehr Speicher benötigen.

Erst nach Prüfung aller Anforderungen kopieren
char user[10];
if (argc != 2) return 1;
if (strlen(argv[1]) >= sizeof user) {
    puts("Name too long");
    return 1;
}
strcpy(user, argv[1]);

Die Prüfung der Argumentanzahl kommt zuerst. Die Längenprüfung lässt dann höchstens neun Textbytes zu, sodass die zehnte Position für null frei bleibt. Eine schreibende Funktion sollte die Kapazität explizit erhalten, etwa als read_name(out, capacity), weil sizeof out innerhalb der Funktion einen Zeigerparameter misst.

Lies bei numerischer Eingabe eine vollständige Zeile und konvertiere sie mit strtol. Prüfe dabei die Endposition, einen Wertebereichsfehler und den zulässigen Wertebereich. Ein fehlgeschlagener Aufruf von scanf("%d", &n) kann den problematischen Text ungelesen zurücklassen; eine blinde Wiederholung kann an denselben Bytes endlos scheitern. Prüfe mindestens die Zuweisungsanzahl und behandle die unzulässige Eingabe, bevor du es erneut versuchst.

Wichtige Erkenntnisse

  • Setze die Feldbreite beim Lesen eines Wortes auf eins weniger als die Pufferkapazität.
  • Prüfe ein Leseergebnis, bevor du seinen Puffer verwendest.
  • fgets behält einen Zeilenumbruch nur, wenn die Funktion ihn innerhalb der Grenze erreicht.
  • Ein fehlender Zeilenumbruch belegt für sich allein nicht, dass Eingabe übrig ist.
  • Plane Platz für Textbytes, einen gegebenenfalls beibehaltenen Zeilenumbruch und das abschließende Nullzeichen ein.
  • Prüfe, ob ein Argument vorhanden ist, bevor du seinen Text misst und kopierst.
  • Übergib die Pufferkapazität an eine Funktion, die in den Puffer schreibt.

Arrays und Zeichenketten in C: Beispiele

6 Interaktive Lektionen

1

Ein Array von der Deklaration bis zum Funktionsaufruf verfolgen

13 Schritte
Verfolge initialisierte Elemente, berechne einen Mittelwert und gib einer Funktion Zugriff auf Arraydaten.
  1. 1
    EinführungMehreren Werten einen Namen geben

    Ein Array enthält eine feste Anzahl von Elementen eines Typs. Eine Schleife wählt Positionen aus, während eine Funktion über einen Arrayparameter mit demselben Speicher arbeiten kann.

    Verfolge vier Sensormesswerte von ihrer Deklaration über eine Summe bis zu einem Mittelwert. Halte bei jedem Schritt drei Größen auseinander: die Anzahl der Elemente, den letzten zulässigen Index und die Anzahl der belegten Speicherbytes.

  2. 2
    BeispielDie Anfangspositionen lesen
    int readings[4] = {11, 16, 13};
    

    Es gibt vier Elemente mit den Indizes 0 bis 3. Der Initialisierer setzt 11, 16 und 13 an die ersten drei Positionen. Der ausgelassene Wert wird null, sodass das Array {11, 16, 13, 0} ist.

    Die null ist eine Initialisierungsregel, kein Zeichen dafür, dass das Array nur drei Elemente hat. Bei einem lokalen Array ohne Initialisierer müssten die Werte vor der Verwendung geschrieben werden.

  3. 3
    ErklärungElemente an der Deklaration zählen
    size_t n = sizeof readings / sizeof readings[0];
    

    Der Zähler misst alle vier Elemente und der Nenner eines. Wenn int vier Bytes belegt, ist dies sechzehn geteilt durch vier und ergibt 4. Derselbe Ausdruck funktioniert auch bei einer anderen Ganzzahlgröße.

    readings[3] ist das letzte Element. readings[4] liegt hinter dem Ende, obwohl die Anzahl der Arrayelemente 4 ist. size_t ist der vorzeichenlose Typ für dieses Größenergebnis.

  4. 4
    HerausforderungDen letzten Messwert ändern

    Die Deklaration lautet weiterhin int readings[4] = {11, 16, 13};. Welche Zuweisung ersetzt die null durch 20 und welche Anzahl ergibt sich?

    ---reveal---

    Verwende readings[3] = 20;. Die resultierenden Werte sind {11, 16, 13, 20} und die Anzahl bleibt 4. Eine Elementzuweisung ändert einen Wert, nicht die Anzahl der Positionen. Index 4 wäre eine Position zu weit und kann diesem Array keinen Speicher hinzufügen.

  5. 5
    BeispielDie Summe Durchlauf für Durchlauf bilden
    int sum = 0;
    for (size_t i = 0; i < n; i++) {
        sum += readings[i];
    }
    

    Bei {11, 16, 13, 20} nimmt die laufende Summe die Werte 11, 27, 40 und 60 an. Nach Index 3 wird i zu 4 und die Bedingung schlägt vor einem weiteren Zugriff fehl.

    Der Startwert null gibt der Summe einen Ausgangspunkt für den ersten Wert. Durch die strikte obere Schranke greift die Schleife auf genau die vier vorhandenen Elemente zu.

  6. 6
    KernaussageDen Nachkommateil eines Mittelwerts erhalten

    Für diese vier Messwerte ergibt (double) sum / n den Wert 15,0. Die explizite Typumwandlung erfolgt vor der Division, sodass die Operation bei einer anderen Summe ein Ergebnis mit Nachkommateil erzeugen kann.

    Wäre die Summe 61, ergäbe die Ganzzahldivision sum / 4 den Wert 15, während sum / 4.0 den Wert 15,25 ergäbe. Die Zuweisung des bereits abgeschnittenen ganzzahligen Ergebnisses an einen double-Wert ergäbe nur 15,0; der abgeschnittene Nachkommateil würde nicht wiederhergestellt.

  7. 7
    TippEinen zulässigen Startwert wählen

    Eine Maximumsuche benötigt einen Startwert aus einem nicht leeren Array. Initialisiere für {-6, -2, -9} den Wert max mit -6 und ersetze ihn durch -2, wenn dieser größere Wert auftritt. Der letzte Vergleich lässt -2 unverändert.

    Eine Initialisierung von max mit null würde null zurückgeben, obwohl dieser Wert nicht vorhanden ist. Auch bei einer linearen Suche ist die passende Abbruchregel wichtig: Eine Abweichung belegt keinen Misserfolg; spätere Elemente müssen weiterhin geprüft werden.

  8. 8
    HerausforderungEine Suche fortsetzen

    Eine Suche nach 13 prüft {11, 16, 13, 20}. Nach der Prüfung von Index 0 gibt sie -1 zurück, weil 11 von 13 abweicht. Was sollte sich ändern und wo liegt der Treffer?

    ---reveal---

    Prüfe nach einer Abweichung weiter und gib -1 erst zurück, wenn alle Kandidaten nicht übereinstimmen. Hier weicht auch Index 1 ab, aber Index 2 stimmt überein. Eine erfolgreiche Suche kann dort stoppen. Der erste fehlgeschlagene Vergleich beschreibt ein Element, nicht das gesamte Array.

  9. 9
    VerbindungDen Mittelwert in eine Funktion verlagern
    double mean(const int a[], size_t n);
    

    Der Arrayparameter ermöglicht den gemeinsamen Zugriff auf die Elemente des Aufrufers. Er enthält nicht deren Anzahl, daher liefert der zweite Parameter sie. const verhindert bei dieser nur lesenden Operation versehentliche Schreibzugriffe über a.

    Berechne die Anzahl dort, wo readings deklariert ist, und rufe mean(readings, n) auf. Innerhalb von mean misst sizeof a einen Zeigerparameter statt des Arrays mit vier Elementen des Aufrufers.

  10. 10
    BeispielDen leeren Bereich vor der Division behandeln
    double mean(const int a[], size_t n) {
        if (n == 0) return 0.0;
        int sum = 0;
        for (size_t i = 0; i < n; i++) sum += a[i];
        return (double) sum / n;
    }
    

    Für die vier kleinen Messwerte bleibt die Summe 60 und das Ergebnis ist 15,0. Für n gleich null ist das gewählte Ergebnis 0,0 und es findet weder ein Arrayzugriff noch eine Division statt. Der Aufrufer muss eine korrekte, verwendbare Anzahl übergeben.

  11. 11
    HerausforderungEin verändertes Array des Aufrufers erklären

    Eine andere Funktion erhält int a[], size_t n und setzt jedes besuchte a[i] auf null. Was geschieht mit den Messwerten des Aufrufers und wie könnte eine Funktion, die nur eine Summe berechnet, solche Schreibzugriffe verhindern?

    ---reveal---

    Die Elemente des Aufrufers werden null, weil die Funktion in den gemeinsam genutzten Arrayspeicher schreibt. Die vier Positionen existieren weiterhin. Eine Funktion, die nur eine Summe berechnet, sollte const int a[] verwenden, wodurch Elementzuweisungen über a unzulässig werden. Eine Änderung einer skalaren Kopie von n würde die Zählvariable des Aufrufers nicht verändern.

  12. 12
    HerausforderungNur die vorhandenen Messwerte verwenden

    Ein Puffer hat die Kapazität 10, aber nur vier Messwerte sind belegt. Vor der Berechnung eines Mittelwerts muss ein neuer Messwert angehängt werden. Welche Prüfungen und welche Anzahl sollte das Programm verwenden?

    ---reveal---

    Prüfe, ob die Anzahl belegter Elemente, 4, kleiner als die Kapazität 10 ist. Schreibe bei Index 4 und aktualisiere dann die Anzahl auf 5. Übergib diese Anzahl belegter Elemente an mean, sodass die Funktion genau die fünf Werte verarbeitet. Die Übergabe der Kapazität würde Positionen einschließen, für die keine Messwerte bereitgestellt wurden.

  13. 13
    ZusammenfassungJede Anzahl ihrer Aufgabe zuordnen

    Eine Deklaration erstellt die Positionen, eine Schleife durchläuft die belegten und eine Funktion erhält die nutzbare Anzahl ausdrücklich. Ein Parameter, über den nur gelesen wird, verhindert, dass Berechnungen die Eingabe überschreiben.

    Verfolge bei der Überprüfung von Arraycode den ersten und den letzten besuchten Index sowie den Zustand jedes Akkumulators. Beginne bei rückwärts laufenden size_t-Schleifen bei n, fahre fort, solange i positiv ist, und greife auf a[i - 1] zu, sodass ein leerer Bereich keine Subtraktion von null erfordert.

2

Gespeicherte Bytes für Textoperationen nutzen

16 Schritte
Erstelle und verändere eine nullterminierte Zeichenkette und verfolge dann ein mehrbyteiges UTF-8-Zeichen.

Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.

  1. 1
    EinführungText und Speicherung betrachten
  2. 2
    ErklärungEin Zeichen als Code lesen
  3. 3
    HerausforderungInnerhalb des Alphabets wieder von vorn beginnen
  4. 4
    BeispielEine beschreibbare Zeichenkette erstellen
  5. 5
    KernaussageDas Ende verschieben, ohne den Speicher zu verschieben
  6. 6
    HerausforderungEine Position für das Ende freilassen
  7. 7
    BeispielLesen und Schreiben trennen
  8. 8
    BeispielDie neue Zeichenkette abschließen
  9. 9
    HerausforderungBuchstaben ohne das abschließende Nullzeichen umkehren
  10. 10
    TippBytes mit zulässigen Argumenten klassifizieren
  11. 11
    BeispielEin UTF-8-Zeichen mit Akzent darstellen
  12. 12
    ErklärungFortsetzungsbytes erkennen
  13. 13
    HerausforderungEin weiteres kodiertes Wort messen
  14. 14
    VerbindungOperationen wählen, die Zeichen ganz lassen
  15. 15
    HerausforderungEine vorgeschlagene Abkürzung für Text prüfen
  16. 16
    ZusammenfassungDas Ende und die Zeichengrenzen finden
3

Text zusammensetzen, durchsuchen und sicher formatieren

14 Schritte
Prüfe Kapazitäten, deute Suchergebnisse und behandle abgeschnittenen Text und Überlappungen.

Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.

  1. 1
    EinführungDie Vorgaben jeder Funktion bestimmen
  2. 2
    BeispielMit einer vollständigen Kopie beginnen
  3. 3
    BeispielAm vorhandenen Ende anhängen
  4. 4
    HerausforderungEntscheiden, ob ein weiterer Anhang hineinpasst
  5. 5
    ErklärungGleichen Text anhand von null erkennen
  6. 6
    BeispielAb einer Fundstelle lesen
  7. 7
    HerausforderungEine präfixbasierte Befehlsprüfung untersuchen
  8. 8
    TippBei der Tokenisierung eine Textänderung erwarten
  9. 9
    BeispielInnerhalb einer bekannten Kapazität formatieren
  10. 10
    HerausforderungDasselbe Format bei weniger Platz deuten
  11. 11
    KernaussageEine Schranke kann Quellbytes zählen
  12. 12
    BeispielEinen überlappenden Bereich verschieben
  13. 13
    HerausforderungNach dem Verschieben ein vollständiges Ergebnis erhalten
  14. 14
    ZusammenfassungBytes von Textergebnissen unterscheiden
4

Eine Tabelle lesen und eine Speicherform für Zeichenkettenlisten wählen

10 Schritte
Verfolge Zeilen- und Spaltensummen, übergib eine Tabelle und reserviere Platz für jede Zeichenkette.

Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.

  1. 1
    EinführungJeder Position zwei Indizes geben
  2. 2
    BeispielWerte in Zeilen einordnen
  3. 3
    BeispielEine Zeilensumme am Zeilenanfang zurücksetzen
  4. 4
    HerausforderungDie Gruppierung ändern, die Indizes beibehalten
  5. 5
    ErklärungEiner Funktion die Zeilenbreite mitteilen
  6. 6
    BeispielPro Zeile eine abgeschlossene Zeichenkette vorsehen
  7. 7
    KernaussageVerweise erlauben unterschiedliche Zeichenkettenlängen
  8. 8
    HerausforderungText im richtigen Speicher ersetzen
  9. 9
    HerausforderungBeide Dimensionen in einer Funktion zulässig halten
  10. 10
    ZusammenfassungDie Tabellenform sichtbar halten
5

Eine Auswahl über die Kommandozeile auf Zulässigkeit prüfen

11 Schritte
Deute gruppierte Argumente und konvertiere eine geprüfte Zahl in eine nutzbare Auswahl.

Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.

  1. 1
    EinführungText vor seiner Deutung empfangen
  2. 2
    BeispielEinen Shell-Befehl Einträgen zuordnen
  3. 3
    ErklärungDie Anzahl vor der Konvertierung prüfen
  4. 4
    HerausforderungEinen Namen richtig gruppieren
  5. 5
    TippEine zurückgegebene Ganzzahl genügt nicht
  6. 6
    BeispielEine vollständige Konvertierung untersuchen
  7. 7
    ErklärungDen eigenen Wertebereich der Auswahl prüfen
  8. 8
    HerausforderungTeilweise numerisch aussehenden Text erkennen
  9. 9
    KernaussageSchreibweise und Wert sind getrennte Regeln
  10. 10
    HerausforderungGruppierung und numerische Prüfung verbinden
  11. 11
    ZusammenfassungVorhandensein, Konvertierung und Zweck prüfen
6

Eine größenbegrenzte Eingaberoutine korrigieren

13 Schritte
Verfolge Zeilenabschnitte, halte Platz für das abschließende Nullzeichen frei und kopiere nur ein vollständiges akzeptiertes Ergebnis.

Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.

  1. 1
    EinführungDen Schreibzugriff begrenzen und das Ergebnis prüfen
  2. 2
    BeispielDen zusätzlichen Schreibzugriff finden
  3. 3
    KernaussageEine zulässige letzte Position kann das Ende aufnehmen
  4. 4
    HerausforderungEinen aus einem Byte abgeleiteten Index prüfen
  5. 5
    ErklärungEin Wort mit Platz für das abschließende Nullzeichen lesen
  6. 6
    BeispielEinen größenbegrenzten Zeilenabschnitt lesen
  7. 7
    BeispielBeobachten, wo eine lange Zeile geteilt wird
  8. 8
    TippEinen Zeilenumbruch vor dem Entfernen untersuchen
  9. 9
    HerausforderungPlatz für eine vollständige Zeile einplanen
  10. 10
    BeispielEine Kopie vor dem Schreiben prüfen
  11. 11
    HerausforderungEin sicheres Präfix von einer akzeptierten Antwort unterscheiden
  12. 12
    VerbindungDiagnosewerkzeuge und Überlegungen zu Grenzen verwenden
  13. 13
    ZusammenfassungVier Prüfungen auseinanderhalten

Arrays und Zeichenketten in C: Übungsaufgaben

60 Übungsfragen in 6 Sets

Set 1: Arraywerte, Schleifen und Funktionsaufrufe10 Übungsfragen

Frage 1 von 10

Welcher Wert ist nach int a[5] = {6, 9}; bei a[3] gespeichert?
  1. 6
  2. 9
  3. 0
  4. Ein unbestimmter Wert
Antwort zeigenAntwort ausblenden
Antwort: C
Der Initialisierer liefert die ersten beiden Werte und C initialisiert die übrigen Elemente mit null. Index 3 enthält daher 0. Unbestimmte Werte wären bei einem lokalen Array ohne Initialisierer relevant, was sich von diesem teilweise initialisierten Array unterscheidet.

Frage 2 von 10

Ein Array wird als int a[7]; deklariert, wobei int 4 Bytes belegt. Welches Paar gibt sizeof a und den letzten zulässigen Index an?
  1. 28 Bytes; Index 7
  2. 28 Bytes; Index 6
  3. 7 Bytes; Index 6
  4. 24 Bytes; Index 6
Antwort zeigenAntwort ausblenden
Antwort: B
Sieben Elemente mit jeweils vier Bytes belegen 28 Bytes, und die bei null beginnende Indizierung endet bei 6. Der letzte Index ist um eins kleiner als die Anzahl. Wer Index 7 verwendet, verwechselt die Anzahl der Elemente mit der Position des letzten Elements und würde hinter diesem Array zugreifen.

Frage 3 von 10

Welchen Wert sollte eine Maximumsuche für int a[] = {-12, -4, -9}; zurückgeben?
  1. -4
  2. 0
  3. -12
  4. -9
Antwort zeigenAntwort ausblenden
Antwort: A
Der größte gespeicherte Wert ist -4, weil er größer als -12 und -9 ist. Eine Maximumsuche sollte mit einem tatsächlichen Element beginnen und es aktualisieren, wenn ein größerer Wert erscheint. Ein Startwert von null würde einen Wert zurückgeben, den dieses ausschließlich negative Array nicht enthält.

Frage 4 von 10

Was gibt dieser Code aus?

int a[] = {3, 8, 5, 2};
int sum = 0;
for (int i = 1; i < 4; i++) sum += a[i];
printf("%d", sum);
  1. 18
  2. 13
  3. 16
  4. 15
Antwort zeigenAntwort ausblenden
Antwort: D
Die Schleife beginnt bei Index 1 und addiert daher 8, 5 und 2 zum Ergebnis 15. Index 0 wird nie besucht. Die Summe 18 schließt die anfängliche 3 ein und wäre nur bei einer Schleife richtig, die bei null beginnt, was dieser Code nicht tut.

Frage 5 von 10

Eine Suche nach 7 in {2, 9, 7, 4} gibt beim ersten abweichenden Element -1 zurück. Wo sollte die Rückgabe bei Misserfolg erfolgen?
  1. Innerhalb der Schleife nach jedem abweichenden Kandidaten.
  2. Nach der Schleife, wenn sie alle Kandidaten geprüft hat.
  3. Vor der Prüfung des letzten Kandidatenelements.
  4. Unmittelbar nach dem ersten Erhöhen des Schleifenindex.
Antwort zeigenAntwort ausblenden
Antwort: B
Der erste Wert, 2, stimmt nicht überein, aber Index 2 enthält 7. Ein Misserfolg ist erst gerechtfertigt, wenn die Schleife ohne Treffer endet. Die Rückgabe nach einer Abweichung behandelt einen Befund zu einem Element wie einen Befund zum gesamten Array.

Frage 6 von 10

Welches Array entsteht, wenn ausgehend von {4, 7, 1, 8, 6} für i = 0 und i = 1 jeweils a[i] mit a[5 - 1 - i] getauscht wird?
  1. {6, 7, 1, 8, 4}
  2. {4, 7, 1, 8, 6}
  3. {6, 8, 1, 7, 4}
  4. {8, 6, 1, 4, 7}
Antwort zeigenAntwort ausblenden
Antwort: C
Der erste Tausch vertauscht 4 und 6; der zweite vertauscht 7 und 8. Der Wert 1 in der Mitte bleibt stehen, sodass das umgekehrte Array entsteht. Das teilweise umgekehrte Ergebnis mit unveränderten 7 und 8 berücksichtigt nur den ersten Tausch, obwohl zwei Tauschvorgänge angegeben sind.

Frage 7 von 10

Eine Funktion void set_first(int a[]) führt a[0] = 11; aus. Was enthält das erste Element des Arrays des Aufrufers nach dem Aufruf?
  1. Es enthält 11.
  2. Es ist unverändert.
  3. Es ist undefiniert.
  4. Es enthält 0.
Antwort zeigenAntwort ausblenden
Antwort: A
Der Parameter gibt der Funktion Zugriff auf die Elemente des Aufrufers, sodass diese Zuweisung das ursprüngliche erste Element auf 11 ändert. Es gibt keine separate Elementkopie, die bei der Rückkehr aus der Funktion verworfen wird. Die Übergabe eines Arrays unterscheidet sich hier von der Übergabe eines einzelnen skalaren Werts.

Frage 8 von 10

Innerhalb von void f(int a[], size_t n) belegen Zeiger 8 Bytes und Ganzzahlen 4 Bytes. Was ergibt sizeof a / sizeof a[0]?
  1. n, bei jedem Aufruf
  2. 8, unabhängig von n
  3. 4, bei jedem Aufruf
  4. 2, unabhängig von n
Antwort zeigenAntwort ausblenden
Antwort: D
Der Arrayparameter wird zu einem Zeiger angepasst, sodass seine Größe in dieser Umgebung 8 Bytes beträgt. Ein Element belegt 4 Bytes, was 2 ergibt. Dieses Ergebnis liefert nicht die Arraylänge des Aufrufers; die Funktion muss für ihre Elementanzahl das separat übergebene n verwenden.

Frage 9 von 10

Eine nur lesende Funktion verwendet size_t für n und i. Sie muss ein Array rückwärts ausgeben und für n = 0 funktionieren. Welcher Parameter und welche Schleife erfüllen beide Anforderungen?
  1. const int a[]; beginne i bei n - 1, prüfe i >= 0, greife auf a[i] zu.
  2. int a[]; beginne i bei n, prüfe i > 0, greife auf a[i] zu.
  3. const int a[]; beginne i bei n, prüfe i > 0, greife auf a[i - 1] zu.
  4. int a[]; beginne i bei n - 1, prüfe i > 0, greife auf a[i - 1] zu.
Antwort zeigenAntwort ausblenden
Antwort: C
Der const-Parameter verhindert Schreibzugriffe über a, während der Start bei n eine vorzeichenlose Subtraktion bei leerem Bereich vermeidet. Der Zugriff auf i - 1 besucht dann die Elemente vom letzten zulässigen Element bis zum Index null. Ein Start bei n - 1 mit einem vorzeichenlosen i und der Prüfung i >= 0 führt zum Wraparound und liefert nie eine Abbruchbedingung.

Frage 10 von 10

Was garantiert C, nachdem ein Programm a[4] aus int a[4] liest?
  1. Kein Ergebnis ist garantiert.
  2. Der Lesezugriff ergibt null.
  3. Der Lesezugriff ergibt a[3].
  4. Der Lesezugriff stürzt immer ab.
Antwort zeigenAntwort ausblenden
Antwort: A
Die zulässigen Indizes des Arrays sind 0 bis 3. Dieser Lesezugriff liegt daher außerhalb der Grenzen und hat undefiniertes Verhalten. C schreibt für diese Operation kein Ergebnis vor. Ein Absturz ist möglich, aber wer ihn jedes Mal erwartet, behandelt ein mögliches Symptom fälschlich als Garantie der Sprache.
Set 2: Zeichen, Zeichenkettenenden und UTF-810 Übungsfragen

Speichere diese Einheit, um jede Antwort zu sehen und jedes Set zu üben.

Frage 1 von 10

Welchen numerischen Wert ergibt '6' - '0'?
  1. 54
  2. 48
  3. 0
  4. 6
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 2 von 10

Welches Zeichen ergibt in ASCII ('W' - 'A' + 5) % 26 + 'A'?
  1. 'B'
  2. 'A'
  3. 'C'
  4. '\\'
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 3 von 10

Wie sollte ein char-Byte c an isalpha übergeben werden, ohne ein negatives Byteargument zu riskieren?
  1. isalpha((signed char) c)
  2. isalpha(c - '0')
  3. isalpha((unsigned char) c)
  4. isalpha((int) c)
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 4 von 10

Wie lauten Textlänge und Arraygröße von char color[] = "amber";?
  1. Länge 6; Größe 6
  2. Länge 5; Größe 6
  3. Länge 5; Größe 5
  4. Länge 6; Größe 5
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 5 von 10

Welche Zeichenkette bleibt nach char s[8] = "planet"; s[3] = '\0'; s[0] = 'P'; übrig?
  1. "Pla"
  2. "Planet"
  3. "Pla0et"
  4. "Plan"
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 6 von 10

Warum ist char tag[4] = "east"; printf("%s", tag); unsicher?
  1. Der Initialisierer hat zu wenige Zeichen.
  2. Das Format gibt nur ein einzelnes Byte aus.
  3. Das Array hat kein abschließendes Nullbyte.
  4. Die Zeichen des Arrays sind schreibgeschützt.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 7 von 10

Ein Filter behält nur Buchstaben aus "m8n2p" und schreibt sie in ein ausreichend großes Ausgabearray. Was muss er nach dem letzten p schreiben?
  1. Ein Nullbyte bei Ausgabeindex 2
  2. Eine Ziffer null bei Ausgabeindex 3
  3. Ein Nullbyte bei Ausgabeindex 5
  4. Ein Nullbyte bei Ausgabeindex 3
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 8 von 10

Ein Programm kehrt char s[] = "lamp"; um, indem es s[i] mit s[len - i] tauscht, solange i < len / 2 gilt. Dabei ist len gleich strlen(s). Wie lauten das abschließende strlen(s) und die unveränderte Kapazität?
  1. Länge 4; Kapazität 5
  2. Länge 0; Kapazität 5
  3. Länge 0; Kapazität 4
  4. Länge 5; Kapazität 5
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 9 von 10

In UTF-8 benötigt das vorkombinierte „é“ zwei Bytes. Wie groß sind strlen und der Mindestspeicher für die Zeichenkette „écho“?
  1. 5 Textbytes; 6 Speicherbytes
  2. 4 Textbytes; 5 Speicherbytes
  3. 5 Textbytes; 5 Speicherbytes
  4. 6 Textbytes; 7 Speicherbytes
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 10 von 10

Eine gültige UTF-8-Zeichenkette hat die Bytes 41 C3 B1 42 00. Ein Programm schreibt bei Byteindex 2 eine null. Wie lauten das neue strlen und die UTF-8-Gültigkeit des Textes?
  1. Länge 2; gültiges UTF-8
  2. Länge 2; ungültiges UTF-8
  3. Länge 3; gültiges UTF-8
  4. Länge 1; ungültiges UTF-8
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Set 3: Bibliotheksoperationen auswählen und prüfen10 Übungsfragen

Speichere diese Einheit, um jede Antwort zu sehen und jedes Set zu üben.

Frage 1 von 10

Was ist die kleinste char-Arraykapazität für eine vollständige Kopie von "silver"?
  1. 7 Bytes
  2. 6 Bytes
  3. 8 Bytes
  4. 5 Bytes
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 2 von 10

Kann char label[10] = "blue"; strcat(label, "berry"); sein vollständiges Ergebnis speichern?
  1. Nein; das Ergebnis benötigt 11 Bytes.
  2. Ja; das Ergebnis benötigt 9 Bytes.
  3. Ja; das Ergebnis benötigt 10 Bytes.
  4. Nein; das Ergebnis benötigt 12 Bytes.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 3 von 10

Innerhalb einer Funktion wird ein automatisches lokales Array so verwendet: char out[20]; strcat(out, "go");. Warum ist dies kein zulässiger Anfang für den Aufbau einer Zeichenkette?
  1. out ist für den angehängten Text zu klein.
  2. out hat kein festgelegtes Zeichenkettenende.
  3. strcat benötigt ein Zeigerarray für out.
  4. strcat kann kein Zeichenkettenliteral kopieren.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 4 von 10

Welche Bedingung erkennt gleichen Text in zwei gültigen Zeichenketten a und b?
  1. a == b
  2. strcmp(a, b) > 0
  3. strlen(a) == strlen(b)
  4. strcmp(a, b) == 0
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 5 von 10

Welcher Text beginnt nach einem erfolgreichen char *p = strrchr("archive.backup.bin", '.'); bei p?
  1. ".bin"
  2. "bin"
  3. ".backup.bin"
  4. "archive.backup"
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 6 von 10

Ein beschreibbares Array enthält "on,,off;wait". Die Aufrufe verwenden strtok(s, ",;") und wiederholen dann strtok(NULL, ",;") bis NULL. Welche Token entstehen und bleibt s erhalten?
  1. „on“, „“, „off“, „wait“; s wird verändert.
  2. „on“, „off;wait“; s wird verändert.
  3. „on“, „off“, „wait“; s wird verändert.
  4. „on“, „off“, „wait“; s bleibt erhalten.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 7 von 10

Was speichert char b[6]; int r = snprintf(b, sizeof b, "%s:%d", "job", 84); und was gibt der Aufruf zurück?
  1. Er speichert „job:8“ und gibt 5 zurück.
  2. Er speichert „job:84“ und gibt 6 zurück.
  3. Er speichert „job:“ und gibt 4 zurück.
  4. Er speichert „job:8“ und gibt 6 zurück.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 8 von 10

Ein Array mit sieben Bytes beginnt mit "AB". Das Programm führt strncat(b, "123456", sizeof b - strlen(b) - 1) aus. Wie lauten der resultierende Text und seine gespeicherte Byteanzahl einschließlich null?
  1. „AB12345“; 8 Bytes
  2. „AB1234“; 7 Bytes
  3. „AB123“; 6 Bytes
  4. „AB123456“; 9 Bytes
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 9 von 10

Was enthält s nach char s[] = "wxyz"; memmove(s, &s[1], 4); als Zeichenkette?
  1. "xyzz"
  2. "wxy"
  3. "xyz"
  4. "yz"
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 10 von 10

Zwei double-Arrays sind beim Vergleich Element für Element gleich, aber eines speichert positive null, wo das andere negative null speichert. Was kann ein byteweiser Vergleich feststellen?
  1. Ob ihre gespeicherten Darstellungen übereinstimmen.
  2. Ob sich ihre numerischen Werte unterscheiden.
  3. Ob eines eine größere Elementanzahl hat.
  4. Ob beide Werte positive null sind.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Set 4: Tabellen, Textlisten und Eingaben beim Start10 Übungsfragen

Speichere diese Einheit, um jede Antwort zu sehen und jedes Set zu üben.

Frage 1 von 10

Welches ist das letzte zulässige Zeilen- und Spaltenpaar für int table[4][6]?
  1. Zeile 4, Spalte 6
  2. Zeile 3, Spalte 5
  3. Zeile 3, Spalte 6
  4. Zeile 4, Spalte 5
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 2 von 10

Wenn int 4 Bytes belegt, was ist sizeof table[0] für int table[3][5]?
  1. 12 Bytes
  2. 15 Bytes
  3. 60 Bytes
  4. 20 Bytes
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 3 von 10

Eine Tabelle lautet {{2, 7, 4}, {6, 1, 5}}. Eine Schleife initialisiert sum einmal vor beiden Schleifen mit null, addiert jeweils eine Zeile und gibt nach jeder Zeile aus, ohne sum zurückzusetzen. Welche Werte werden ausgegeben und wie wird das korrigiert?
  1. 13, dann 25; sum vor jeder Zeile zurücksetzen.
  2. 13, dann 12; sum vor jeder Zeile zurücksetzen.
  3. 13, dann 25; sum vor jeder Zelle zurücksetzen.
  4. 8, dann 16; sum vor jeder Spalte zurücksetzen.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 4 von 10

Welcher Arrayparameter mit fester Breite nimmt Zeilen mit jeweils sieben Ganzzahlen entgegen?
  1. int g[7][]
  2. int g[][]
  3. int g[][7]
  4. int g[][6]
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 5 von 10

Was ist die kleinste Zeilenbreite für char names[3][width], um „Iris“, „Magnolia“ und „Ash“ als vollständige ASCII-Zeichenketten zu speichern?
  1. 8 Bytes
  2. 10 Bytes
  3. 16 Bytes
  4. 9 Bytes
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 6 von 10

Ein Programm hat char rows[2][5] = {"oak", "elm"}; und const char *refs[] = {"oak", "elm"};. Es muss das erste aufgeführte Wort durch den vollständigen Text „cedar“ ersetzen. Welches Vorgehen ist für die jeweilige Speicherform zulässig?
  1. „cedar“ nach rows[0] kopieren; refs[0] neu zuweisen.
  2. Die Zeilenkopie ablehnen; refs[0] „cedar“ zuweisen.
  3. Die Zeilenkopie ablehnen; „cedar“ nach refs[0] kopieren.
  4. rows[0] = „cedar“ zuweisen; refs[0] neu zuweisen.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 7 von 10

In einer normalen Shell wird ein Programm als ./mark "West Room" 9 ausgeführt. Was sind argc und argv[2]?
  1. argc ist 3; argv[2] ist „9“.
  2. argc ist 4; argv[2] ist „Room“.
  3. argc ist 2; argv[2] ist NULL.
  4. argc ist 3; argv[2] ist „West Room“.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 8 von 10

Ein Programm benötigt zwei Benutzerargumente. Es prüft argc == 2 und ruft dann strlen(argv[2]) auf. Welche Anzahlprüfung und welcher letzte Index passen zu seiner tatsächlichen Schnittstelle?
  1. argc == 2 prüfen; Benutzerargumente bis Index 2 lesen.
  2. argc == 3 prüfen; Benutzerargumente bis Index 3 lesen.
  3. argc == 3 prüfen; Benutzerargumente bis Index 2 lesen.
  4. argc == 2 prüfen; Benutzerargumente bis Index 1 lesen.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 9 von 10

Was zeigen das Ergebnis und der Endzeiger, nachdem strtol „42rest“ dezimal konvertiert hat?
  1. Wert 0; kein Zeichen wurde verarbeitet.
  2. Wert 42; unverarbeiteter Text beginnt mit r.
  3. Wert 42; die gesamte Eingabe wurde verarbeitet.
  4. Wert 42; ein Wertebereichsfehler muss aufgetreten sein.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 10 von 10

Ein Schlüssel aus ausschließlich Ziffern passt in long, aber nicht in int. Das Programm wandelt ihn explizit in int um, bevor es key % 26 berechnet. Wie kann es die beabsichtigte Verschiebung erhalten, ohne zuerst in einen engeren Typ umzuwandeln?
  1. Zuerst in int umwandeln, dann den Rest berechnen.
  2. atoi verwenden, dann den Rest als int berechnen.
  3. Den Rest als long berechnen, dann das Ergebnis 0–25 umwandeln.
  4. Ziffern erneut prüfen, dann den ursprünglichen Wert umwandeln.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Set 5: Eingabegrenzen und vollständige Ergebnisse10 Übungsfragen

Speichere diese Einheit, um jede Antwort zu sehen und jedes Set zu üben.

Frage 1 von 10

Welche größte %s-Feldbreite lässt in char word[18] eine Position für das abschließende Nullzeichen frei?
  1. %18s
  2. %16s
  3. %17s
  4. %19s
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 2 von 10

Ein erfolgreicher fgets-Aufruf liest aus der Eingabe "pine\n" in ein Array mit 10 Bytes. Wie lauten strlen des Ergebnisses und der Index seines abschließenden Nullzeichens?
  1. Länge 4; Endzeichen bei Index 4
  2. Länge 5; Endzeichen bei Index 6
  3. Länge 6; Endzeichen bei Index 5
  4. Länge 5; Endzeichen bei Index 5
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 3 von 10

Die Eingabezeile lautet „qrstuvwxy“, gefolgt von einem Zeilenumbruch. Zwei fgets-Aufrufe verwenden dasselbe char b[6], ohne weitere Lesezugriffe dazwischen. Welchen Text speichert der zweite Aufruf?
  1. "uvwxy"
  2. "vwxy\n"
  3. "vwxy"
  4. "qrst\n"
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 4 von 10

Ein Programm erlaubt 12 ASCII-Textzeichen in einem Namen, der als vollständige Zeile mit abschließendem Zeilenumbruch eingegeben wird. Es muss den Zeilenumbruch prüfen und dann nur den Namen in einem separaten Array speichern. Welche Mindestkapazitäten werden für Eingabe und Endergebnis benötigt?
  1. Eingabe 14 Bytes; Endergebnis 13 Bytes
  2. Eingabe 13 Bytes; Endergebnis 13 Bytes
  3. Eingabe 14 Bytes; Endergebnis 12 Bytes
  4. Eingabe 13 Bytes; Endergebnis 12 Bytes
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 5 von 10

Ein Puffer mit 9 Bytes erhält ein erfolgreiches fgets-Ergebnis der Länge 8 ohne Zeilenumbruch. Es wurde keine weitere Eingabe untersucht. Was lässt sich über die zusammengehörige Zeile schließen?
  1. Sie ist sicher länger als acht Zeichen.
  2. Sie kann dort enden oder weitere Eingabe haben.
  3. Sie endete sicher bereits am Dateiende.
  4. Ihr Zeilenumbruch wurde von fgets gelesen und entfernt.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 6 von 10

Warum ist counts[c - 'a']++ bei ungeprüfter Zeicheneingabe unsicher?
  1. Der abgeleitete Index kann außerhalb des Arrays liegen.
  2. Das Inkrement kann Ganzzahlzähler nicht aktualisieren.
  3. Die Subtraktion ergibt immer einen negativen Wert.
  4. Der Ausdruck vergleicht zwei ganze Zeichenketten.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 7 von 10

Ein char-Array mit sechs Bytes wird an den Indizes 0 bis 5 mit Buchstaben gefüllt und dann an strlen übergeben. Welche Eigenschaft fehlt?
  1. Ein ungenutztes Byte außerhalb des Arrays für null.
  2. Ein ausdrücklicher Zeilenumbruch nach dem letzten Buchstaben.
  3. Ein abschließendes Nullzeichen innerhalb der sechs zulässigen Positionen.
  4. Eine separate Ganzzahl, die seine Bytekapazität speichert.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 8 von 10

Ein Programm speichert einen vollständigen Code in char out[6]. Auf den Aufruf snprintf(out, sizeof out, "%s%d", "R-", 1234) folgt if (r > sizeof out) reject();, wobei r der nicht negative Rückgabewert ist. Was wird gespeichert, weist der Test es zurück und was muss geändert werden?
  1. „R-1234“; akzeptiert; den vorhandenen Test beibehalten.
  2. „R-123“; zurückgewiesen; den vorhandenen Test beibehalten.
  3. „R-12“; akzeptiert; bei r >= Kapazität zurückweisen.
  4. „R-123“; akzeptiert; bei r >= Kapazität zurückweisen.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 9 von 10

Ein Dezimalparser weist ERANGE zurück und verlangt *end == '\0', prüft aber nicht end == start. Welche unzulässige Eingabe kann dennoch als null akzeptiert werden?
  1. Eine leere Zeichenkette
  2. Ein Wort aus Buchstaben
  3. Eine Ziffer und ein Buchstabe
  4. Eine Zahl außerhalb von long
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 10 von 10

Eine gültige UTF-8-Zeichenkette hat die Bytes 41 C3 A9 E2 82 AC 00. Ein Programm kopiert sie in char b[5] und reserviert die letzte Position für null. Wie lauten das resultierende strlen, die Anzahl der Anfangsbytes und die Gültigkeit der Kodierung?
  1. Länge 4; 3 Anfänge; gültiges UTF-8.
  2. Länge 4; 3 Anfänge; ungültiges UTF-8.
  3. Länge 3; 3 Anfänge; gültiges UTF-8.
  4. Länge 4; 2 Anfänge; ungültiges UTF-8.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Set 6: Kombinierte C-Operationen nachvollziehen und korrigieren10 Übungsfragen

Speichere diese Einheit, um jede Antwort zu sehen und jedes Set zu üben.

Frage 1 von 10

Welches Array wird durch int a[4] = {[2] = 9}; erstellt?
  1. {0, 9, 0, 0}
  2. {0, 0, 9, 0}
  3. {9, 0, 0, 0}
  4. {0, 0, 0, 9}
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 2 von 10

Ein Array mit acht Plätzen hat die Anzahl belegter Elemente 5. Ein Wert wird bei a[count] angehängt und count wird erhöht. Welcher Index wurde beschrieben und wie lautet die neue Anzahl?
  1. Index 5; Anzahl 6
  2. Index 4; Anzahl 6
  3. Index 6; Anzahl 6
  4. Index 5; Anzahl 5
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 3 von 10

Welche bei null beginnende Elementposition hat g[2][1] bei int g[3][4] in zeilenweiser Speicherung?
  1. 7
  2. 8
  3. 10
  4. 9
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 4 von 10

Eine Shell führt ./pick "" blue aus. Wie unterscheiden sich argv[1] und argv[argc]?
  1. argv[1] ist ein Nullzeiger; argv[argc] ist eine leere Zeichenkette.
  2. Beide Einträge sind leere Zeichenketten mit eigenem Endzeichen.
  3. argv[1] ist eine leere Zeichenkette; argv[argc] ist ein Nullzeiger.
  4. Beide Einträge sind Nullzeiger, die fehlenden Text markieren.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 5 von 10

Eine Funktion erhält int a[], size_t n und führt a[0] = 0; n = 0; aus. Der Aufrufer hat {5, 8, 2} und die Anzahl 3 übergeben und berechnet anschließend die ganzzahlige Summe seiner Elemente geteilt durch seine Anzahl. Welcher Mittelwert und welche Anzahl beim Aufrufer ergeben sich?
  1. Mittelwert 0; Anzahl beim Aufrufer 3
  2. Mittelwert 3; Anzahl beim Aufrufer 3
  3. Mittelwert 5; Anzahl beim Aufrufer 3
  4. Undefinierte Division; Anzahl beim Aufrufer 0
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 6 von 10

Ein Parser zerlegt den beschreibbaren Text „run,runner“ mit strtok an Kommas. Er erhöht einen Zähler, wenn strncmp(token, "run", 3) == 0 gilt. Welche Anzahl ergibt sich, welche Anzahl sollte der Vergleich auf exakte Befehle ergeben und welcher Vergleich korrigiert dies?
  1. Aktuell 2; exakt 1; strcmp(token, "run") == 0.
  2. Aktuell 1; exakt 1; strcmp(token, "run") == 0.
  3. Aktuell 2; exakt 2; strncmp(token, "run", 2) == 0.
  4. Aktuell 1; exakt 2; strstr(token, "run") != NULL.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 7 von 10

Ein Array b mit fünf Bytes wird durch strncpy(b, "planet", sizeof b); gefüllt. Das Programm verlangt das vollständige ursprüngliche Wort. Welche Korrektur erfüllt die Anforderung?
  1. Nach dem Kopieren null in b[4] schreiben.
  2. Nach dem Kopieren null bei b[5] anhängen.
  3. sizeof b - 1 übergeben und b unverändert verwenden.
  4. Das Wort vor dem Kopieren als zu lang zurückweisen.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 8 von 10

Ein Programm verwendet int values[4] = {14, 28, 35, 49};. Es konvertiert „04“ mit dezimalem strtol vollständig, akzeptiert die Auswahlwerte 1–4 und liest dann values[selected]. Es muss alle vier Auswahlwerte erlauben und Auswahl 1 dem ersten Wert zuordnen. Was geschieht und welche Indexregel korrigiert es?
  1. Es gibt 49 aus; Index selected beibehalten.
  2. Es gibt 49 aus; Index selected - 1 verwenden.
  3. Es zeigt undefiniertes Verhalten; Index selected - 1 verwenden.
  4. Es zeigt undefiniertes Verhalten; Auswahl < 4 verlangen.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 9 von 10

Eine Funktion read_text(char out[], size_t cap) ruft fgets(out, sizeof out, stdin) auf. Der Aufrufer übergibt ein Array mit 30 Bytes; die Zeigergröße ist 8. Wenn „sunflower\n“ wartet, welcher Abschnitt wird gespeichert, was bleibt übrig und welche Schranke sollte sizeof out ersetzen?
  1. „sunflower\n“; nichts bleibt; cap verwenden.
  2. „sunflowe“; „r\n“ bleibt; cap verwenden.
  3. „sunflow“; „er\n“ bleibt; strlen(out) verwenden.
  4. „sunflow“; „er\n“ bleibt; cap verwenden.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 10 von 10

Ein Zähler für ASCII-Buchstabenfolgen beginnt außerhalb eines Wortes und erhöht sich immer, wenn ein Buchstabe auf ein anderes Zeichen folgt. Wie viele Wörter zählt er in „go2fast now!“?
  1. 3
  2. 2
  3. 4
  4. 1
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Hängst du bei deiner eigenen Aufgabe fest? Hausaufgabenhilfe

Schlüsselbegriffe

85 Schlüsselbegriffe, jeweils mit Definition

A

abschließendes Nullzeichen
Ein abschließendes Nullzeichen ist das Zeichen mit dem Wert null, das das Ende einer C-Zeichenkette markiert. Es belegt Speicher, zählt aber nicht zu ihrer Textlänge.
Durch text[2] = '\0' stoppen Zeichenkettenfunktionen nach den ersten zwei Zeichen in diesem Array.
Abschneiden
Abschneiden ist der Verlust von Text über eine verfügbare Längengrenze hinaus. Es kann einen Überlauf vermeiden, aber dennoch die Bedeutung der Daten ändern.
Eine gekürzte Dateibezeichnung kann sich auf anderen Text beziehen; eine Schnittstelle für vollständige Namen sollte daher ein abgeschnittenes Ergebnis zurückweisen.
Akkumulator
Ein Akkumulator ist eine Variable, die ein laufendes Ergebnis wie eine Summe oder Anzahl sammelt, während eine Schleife aufeinanderfolgende Werte verarbeitet.
Der Start von sum bei null ermöglicht es jedem sum += a[i], das nächste Arrayelement zur aktuellen Summe zu addieren.
anwendungsspezifischer Wertebereich
Ein anwendungsspezifischer Wertebereich ist das Intervall der Werte, die ein Programm für einen Zweck erlaubt; es kann enger als der volle Bereich des numerischen Typs sein.
Eine konvertierte Anzahl von 120 passt in eine Ganzzahl, verletzt aber eine Programmregel, die eine Anzahl von 1 bis 100 verlangt.
Anzahl belegter Elemente
Die Anzahl belegter Elemente hält fest, wie viele Arrayelemente derzeit zu den aktiven Daten gehören, und bestimmt die Schranke der Verarbeitungsschleife.
Sind in einem Array mit 50 Elementen nur 12 Einträge gefüllt, stoppt eine Datenverarbeitungsschleife bei der Anzahl 12.
argc
Der Parameter argc gibt die Anzahl der an main übergebenen Argumentzeichenketten an, normalerweise einschließlich der Zeichenkette zum Starten des Programms.
Der Aufruf ./label red blue in einer normalen Shell ergibt für argc den Wert 3: den Startnamen und zwei Farben.
argv
Der Parameter argv bietet Zugriff auf Argumentzeichenketten über Indizes; an der durch argc angegebenen Position folgt ein Nullzeiger.
Nach der Prüfung von argc kann ein Programm mit strcmp(argv[1], "-v") seine erste Option erkennen.
Array
Ein Array ist eine Folge fester Größe aus benachbarten Elementen eines Typs, auf die über einen gemeinsamen Namen und einen ganzzahligen Index zugegriffen wird.
int levels[6] reserviert sechs Ganzzahlen; eine Aktualisierung von levels[2] ändert den dritten gespeicherten Wert.
Array variabler Länge
Ein Array variabler Länge erhält seine Größe bei der Ausführung seiner Deklaration und behält sie bis zum Ende seiner Lebensdauer.
In C17 erzeugt ein lokales const int n = 6; int a[n]; ein Array variabler Länge, weil n kein konstanter Ausdruck ist.
Array von Zeichenkettenzeigern
Ein Array von Zeichenkettenzeigern speichert einen Verweis pro Zeichenkette, sodass anderswo gespeicherter Text unterschiedliche Längen haben kann.
const char *names[] = {"Al", "Mina"} speichert zwei Verweise und erlaubt es, einen Verweis zu ersetzen.
Arrayparameter
Ein Arrayparameter wird zu einem Zeigerparameter angepasst und gibt einer Funktion Zugriff auf Elemente des Aufrufers, ohne deren Anzahl zu übergeben.
Eine mit int a[] deklarierte Funktion benötigt weiterhin eine separate Länge, bevor sie ihre Eingabe mit einer Schleife durchlaufen kann.
ASCII
ASCII ist eine Zeichenkodierung, die grundlegenden Buchstaben, Ziffern, Satz- und Steuerzeichen die Werte 0 bis 127 zuweist.
Die Rechnung '8' - '0' ergibt 8, weil die Codes der Ziffernzeichen aufeinanderfolgen.
atoi
Die Funktion atoi konvertiert einen anfänglichen numerischen Teil in int, ohne Fehler zuverlässig zu melden, und zeigt bei Überlauf undefiniertes Verhalten.
Die Eingabe „18x“ ergibt mit atoi den Wert 18; eine zurückgegebene Zahl belegt daher nicht, dass die gesamte Eingabe numerisch war.
Ausgabearray
Ein Ausgabearray ist Speicher, den ein Aufrufer mit einer vereinbarten Kapazität oder Anzahl bereitstellt, damit eine Funktion ihn mit mehreren Ergebnissen füllt.
Eine Funktion für Quadrate kann jedes Ergebnis nach out[i] schreiben, während der Aufrufer den Besitz des Arrays behält.

B

Bitmaske
Eine Bitmaske wählt bestimmte Bitpositionen für eine Prüfung aus; eine UND-Operation behält nur die Eingabebits, die in der Maske durch Einsen markiert sind.
Die Maske 0xC0 behält die beiden höchstwertigen Bits, sodass ein Programm das UTF-8-Fortsetzungsmuster erkennen kann.
byteweiser Vergleich
Ein byteweiser Vergleich ordnet Zeichenketten nach vorzeichenlosen gespeicherten Bytewerten; diese Reihenfolge kann sich von den alphabetischen Konventionen einer Sprache unterscheiden.
In UTF-8 ordnet strcmp einen lateinischen Buchstaben mit Akzent nach dem ASCII-Zeichen z ein, weil sein führendes Byte einen höheren Wert hat.

C

C-Zeichenkette
Eine C-Zeichenkette ist eine Folge von Zeichen, die an einem Nullzeichen endet. Der Text wird in einem Array statt in einem eigenen eingebauten Zeichenkettentyp gespeichert.
Das durch char pet[] = "dog" initialisierte Array enthält drei Buchstaben, gefolgt von einem Zeichen mit dem Wert null.
Caesar-Verschiebung
Eine Caesar-Verschiebung ersetzt jeden Buchstaben durch einen anderen, der eine feste Anzahl von Alphabetpositionen entfernt liegt, und beginnt innerhalb des jeweiligen Buchstabenbereichs wieder am Anfang.
Bei ASCII-Text führt die Verschiebung des Großbuchstabens Y um zwei über das Ende hinaus zu A, während eine Ziffer oder ein Komma unverändert bleiben kann.
const-qualifizierter Parameter
Ein const-qualifizierter Parameter verhindert, dass die referenzierten Elemente über diesen Parameter verändert werden, erlaubt aber weiterhin, sie zu lesen.
Eine Summenfunktion verwendet const int a[], weil das Addieren der Eingabewerte keine Änderung dieser Werte erfordert.

D

Dateiende
Das Dateiende markiert, dass keine weitere Eingabe verfügbar ist; fgets kann dennoch erfolgreich sein, wenn die Funktion vor Erreichen dieses Zustands Zeichen gelesen hat.
Eine Datei, die mit „done“ und ohne Zeilenumbruch endet, lässt einen ausreichend großen fgets-Puffer diesen letzten Text dennoch aufnehmen.

E

Element
Ein Element ist ein in einem Array gespeicherter Wert mit demselben deklarierten Typ wie die anderen Werte und einer eigenen indizierten Position.
In int a[3] = {8, 1, 6} enthält das Element a[1] den Wert 1 und kann eine andere Ganzzahl zugewiesen bekommen.
Endzeiger
Ein Endzeiger hält fest, wo eine Zahlenkonvertierung endete, sodass ein Programm eine ausgebliebene Konvertierung oder unerwünschte verbleibende Zeichen erkennen kann.
Für die Eingabe „9z“ gibt strtol den Wert 9 zurück, aber der Endzeiger verweist auf das nicht verarbeitete z.
ERANGE
Das Makro ERANGE kennzeichnet einen über errno gemeldeten Wertebereichsfehler, etwa ein für long zu großes oder zu kleines strtol-Ergebnis.
Setze errno vor der Konvertierung auf null und prüfe es dann auf ERANGE, bevor du eine lange Ziffernfolge akzeptierst.
Escape-Sequenz
Eine Escape-Sequenz ist eine Schreibweise im Quellcode mit umgekehrtem Schrägstrich für ein Zeichen, das eine besondere Notation benötigt, etwa einen Zeilenumbruch oder ein Anführungszeichen.
Das Literal "A\nB" enthält einen Zeilenumbruch zwischen seinen Buchstaben; der umgekehrte Schrägstrich und n kodieren ein Zeichen.

F

Feldbreite
Eine Feldbreite in einer %s-Konvertierung begrenzt die Anzahl der gespeicherten Eingabezeichen vor dem zusätzlichen abschließenden Nullbyte.
Für char word[10] reserviert die Konvertierung %9s das zehnte verfügbare Byte für das abschließende Nullzeichen.
fgets
Die Funktion fgets liest Zeileneingaben mit Größenbegrenzung, behält einen erreichten Zeilenumbruch und fügt ein abschließendes Nullzeichen hinzu, wenn sie erfolgreich Text liest.
Ein Puffer mit acht Bytes kann höchstens sieben Eingabezeichen aufnehmen und lässt weiteren Zeilentext für spätere Lesezugriffe übrig.
Filterung
Die Filterung erstellt ein Ergebnis aus ausgewählten Eingabezeichen und verwendet eine separate Schreibposition, weil verworfene Zeichen keinen Ausgabeplatz belegen.
Werden nur die Buchstaben aus „q2r7“ behalten, wird „qr“ in die Ausgabe geschrieben und anschließend das abschließende Nullbyte hinzugefügt.
Fortsetzungsbyte
Ein UTF-8-Fortsetzungsbyte hat das führende Bitmuster 10 und folgt innerhalb eines mehrbyteig kodierten Codepunkts auf ein führendes Byte.
Das Byte 0xA9 setzt die Folge für „é“ fort; es darf nicht allein als vollständiges UTF-8-Zeichen beibehalten werden.

H

Häufigkeitsarray
Ein Häufigkeitsarray speichert einen Zähler für jede Kategorie und verwendet einen auf Zulässigkeit geprüften Kategoriewert, um den zu erhöhenden Zähler auszuwählen.
Nach der Umwandlung einer Ziffer in den Wert 4 fügt counts[4]++ der Zählung dieser Ziffer ein Vorkommen hinzu.

I

In-place-Transformation
Eine In-place-Transformation verändert Werte in ihrem vorhandenen Speicher, statt ein separates Ausgabearray zu erstellen.
Die Umwandlung einer beschreibbaren ASCII-Zeichenkette in Großbuchstaben kann jedes konvertierte Zeichen wieder derselben indizierten Position zuweisen.
Index
Ein Index ist eine ganzzahlige Position zur Auswahl eines Arrayelements. Er beginnt bei null und endet eins unter der Elementanzahl.
Bei einem Array mit sieben Elementen erreicht Index 6 das letzte Element; Index 7 liegt bereits außerhalb.
Initialisierer
Ein Initialisierer liefert Anfangswerte bei der Deklaration eines Objekts; fehlende Werte in einem Arrayinitialisierer werden null.
int hits[4] = {3} initialisiert den ersten Zähler mit 3 und die anderen drei Zähler mit null.

K

Kapazität
Die Kapazität eines Arrays ist die Anzahl der Elemente, die sein zugewiesener Speicher aufnehmen kann, unabhängig davon, wie viele derzeit nutzbare Werte enthalten.
Ein Array mit Kapazität 80 und einer Anzahl belegter Elemente von 29 hat Platz für 51 zusätzliche Werte, wenn die Anzahl korrekt verwaltet wird.
Kommandozeilenargument
Ein Kommandozeilenargument ist eine beim Programmstart übergebene Zeichenkette, die vor dem Empfang durch das Programm häufig von einer Shell gruppiert oder erweitert wird.
Anführungszeichen um „North Hall“ in der Kommandozeile können ein Argument mit einem Leerzeichen statt zweier Wörter liefern.

L

leere Zeichenkette
Eine leere Zeichenkette enthält keine Textzeichen und wird durch ein abschließendes Nullzeichen an ihrer ersten Position dargestellt, sodass sie die Länge null hat.
char message[20] = "" stellt ein gültiges leeres Ziel für späteres Anhängen bereit, das in seine Kapazität passt.
lineare Suche
Eine lineare Suche prüft Elemente der Reihe nach, bis sie einen gesuchten Wert findet oder jeden Kandidaten ohne Treffer untersucht hat.
Die Suche nach 2 in {3, 8, 2} ist bei Index 2 erfolgreich, nachdem die früheren abweichenden Elemente geprüft wurden.
Locale
Eine Locale stellt Konventionen für Bibliotheksfunktionen bereit; die Standard-Locale „C“ liefert die grundlegenden Zeichenregeln für die Klassifikation einzelner Bytes.
Das byteweise arbeitende toupper wandelt in der Locale „C“ keinen vollständigen UTF-8-Buchstaben mit Akzent in seine Großbuchstabenform um.

M

memcmp
Die Funktion memcmp vergleicht eine feste Anzahl von Bytes und meldet ihre Reihenfolge. Sie prüft Speicherdarstellungen statt numerischer Werte.
Positive und negative Gleitkommanull können als Werte gleich sein, während memcmp unterschiedliche gespeicherte Bytes findet.
memcpy
Die Funktion memcpy kopiert genau eine angeforderte Anzahl von Bytes zwischen nicht überlappenden Bereichen, ohne Zeichenkettenenden zu interpretieren.
memcpy(b, a, sizeof a) kopiert den gesamten Speicherinhalt von Array a, wenn b genug Platz hat und ein separater Bereich ist.
memmove
Die Funktion memmove kopiert eine angeforderte Byteanzahl mit definiertem Verhalten, selbst wenn sich Quell- und Zielbereich überlappen.
Das Verschieben von drei Bytes innerhalb desselben Zeichenarrays benötigt eine überlappungssichere Operation wie memmove.
memset
Die Funktion memset füllt einen Bereich mit einem wiederholten Bytewert, ohne diesen numerischen Wert jedem Arrayelement einzeln zuzuweisen.
memset(counts, 0, sizeof counts) setzt ein Ganzzahlarray auf null; der Bytewert 1 würde nicht die Ganzzahl 1 zuweisen.

N

Nullzeiger
Ein Nullzeiger ist ein Zeigerwert, der auf kein Objekt verweist; Suchfunktionen signalisieren damit, dass keine passende Stelle existiert.
Prüfe strchr(name, '.') != NULL, bevor du das Ergebnis als Anfang einer Dateinamenerweiterung verwendest.

O

Off-by-one-Fehler
Ein Off-by-one-Fehler verwendet eine Grenze, die um eine Position zu weit oder zu kurz ist, oft durch die Verwechslung einer Elementanzahl mit ihrem letzten zulässigen Index.
Die Bedingung i <= n lässt einen bei null beginnenden Durchlauf durch n Elemente einen zusätzlichen Zugriff auf Index n versuchen.

P

Parameter mit variabel modifiziertem Typ
Ein Parameter mit variabel modifiziertem Typ verwendet frühere Größenparameter in seinem Arraytyp, sodass eine Funktion Tabellen mit unterschiedlichen Zeilenbreiten entgegennehmen kann.
GCC und Clang akzeptieren int g[rows][cols] nach Parametern, die positive, passende Zeilen- und Spaltenanzahlen definieren.
Puffer
Ein Puffer ist ein für zu lesende, zu schreibende oder umzuwandelnde Daten reservierter Speicherbereich mit einer Kapazität, die zulässige Schreibzugriffe begrenzt.
Ein Namenspuffer mit 32 Bytes kann dreißig ASCII-Textzeichen, einen beibehaltenen Zeilenumbruch und ein abschließendes Nullbyte aufnehmen.
Pufferüberlauf
Ein Pufferüberlauf ist ein Schreibzugriff über den verfügbaren Speicher eines Puffers hinaus, der benachbarte Daten oder Steuerinformationen des Programms beschädigen kann.
Das Kopieren von sechs Textbytes und einem abschließenden Nullzeichen in ein Array mit sechs Bytes überschreitet das Array um ein Byte.

R

Rückgabestatus
Ein Rückgabestatus meldet der aufrufenden Umgebung den Programmabschluss; die Rückgabe von null aus main signalisiert einen erfolgreichen Abschluss.
Ein fehlendes benötigtes Argument kann dazu führen, dass ein Programm Verwendungshinweise ausgibt und einen Fehlerstatus ungleich null zurückgibt.

S

size_t
Der Typ size_t ist ein vorzeichenloser Ganzzahltyp für Objektgrößen, der von sizeof zurückgegeben wird; er wird häufig für Arraylängen verwendet.
printf("%zu", sizeof data) verwendet das Größenformat, um die Anzahl der von data belegten Bytes auszugeben.
snprintf
Die Funktion snprintf formatiert Text innerhalb einer angegebenen Kapazität und gibt die benötigte Länge des vollständigen Textes oder bei einem Fehler einen negativen Wert zurück.
Ein Rückgabewert von 12 bei einer Kapazität von 10 bedeutet, dass das gespeicherte Ergebnis abgeschnitten wurde, obwohl es abgeschlossen bleibt.
Spalte
Eine Spalte gruppiert dieselbe Position innerhalb der Zeilen einer Tabelle; ihr Index ist der zweite Index in g[r][c].
Um Spalte 2 zu summieren, halte c gleich 2 und variiere r über jede Zeile, während du g[r][c] liest.
strcat
Die Funktion strcat hängt eine Quellzeichenkette am vorhandenen abschließenden Nullzeichen eines Ziels an und schreibt ein neues abschließendes Nullzeichen hinter den verbundenen Text.
Das Anhängen von „box“ an „red “ benötigt acht Bytes Zielspeicher einschließlich des abschließenden Nullbytes.
strchr
Die Funktion strchr findet das erste übereinstimmende Zeichen in einer Zeichenkette und gibt seine Position oder einen Nullzeiger zurück, wenn kein Treffer existiert.
Die Suche nach einem Punkt in „name.part.ext“ mit strchr findet den Rest „.part.ext“ einschließlich des ersten Punkts.
strcmp
Die Funktion strcmp vergleicht abgeschlossene Zeichenketten als vorzeichenlose Bytes und gibt entsprechend ihrer Reihenfolge ein negatives Ergebnis, null oder ein positives Ergebnis zurück.
strcmp("oak", "oak") == 0 prüft gleichen Inhalt, selbst wenn die beiden Zeichenketten getrennten Speicher belegen.
strcpy
Die Funktion strcpy kopiert eine vollständige Quellzeichenkette und ihr abschließendes Nullzeichen in ein nicht überlappendes Ziel mit ausreichend verfügbarem Speicher.
Das Kopieren von „pine“ schreibt einschließlich null fünf Bytes und passt daher in ein als char out[5] deklariertes Ziel.
strcspn
Die Funktion strcspn misst den Anfangsteil einer Zeichenkette, der keines der in ihrem zweiten Argument angegebenen Zeichen enthält.
strcspn("fir:oak", ":,") gibt 3 zurück, weil das erste aufgeführte Trennzeichen nach den drei Buchstaben von fir auftritt.
strlen
Die Funktion strlen durchsucht eine gültige Zeichenkette bis zu ihrem abschließenden Nullzeichen und gibt die Anzahl der vorangehenden Bytes als size_t-Wert zurück.
Für char tag[30] = "oak" ist strlen(tag) gleich 3, während sizeof tag die volle Kapazität von 30 Bytes meldet.
strncat
Die Funktion strncat hängt höchstens eine angegebene Anzahl von Quelltextbytes an und schreibt danach ein abschließendes Nullzeichen.
Für eine gültige Zielzeichenkette ist der verbleibende Platz für Text die Kapazität minus ihre aktuelle Länge minus eins.
strncmp
Die Funktion strncmp vergleicht höchstens eine angegebene Anzahl von Zeichenkettenbytes und stoppt bei einem Ende oder Unterschied früher.
Eine Übereinstimmung von drei Bytes zwischen „run-fast“ und „run-slow“ belegt ein gemeinsames Präfix, keine gleichen vollständigen Zeichenketten.
strncpy
Die Funktion strncpy kopiert bis zu einer Schranke und füllt kürzeren Text mit Nullen auf, schreibt aber kein abschließendes Nullzeichen, wenn die Quelle diese Schranke ausschöpft.
Das Kopieren von „abcd“ mit der Schranke 3 speichert a, b und c ohne Nullbyte; ein weiterer Schritt muss das Ergebnis daher abschließen.
strrchr
Die Funktion strrchr findet das letzte übereinstimmende Zeichen in einer Zeichenkette und gibt seine Position oder einen Nullzeiger zurück, wenn kein Treffer existiert.
Der letzte Punkt in „name.part.ext“ beginnt „.ext“, sodass strrchr das Trennzeichen der Erweiterung finden kann.
strtol
Die Funktion strtol konvertiert Text mit einer gewählten Basis in long und meldet eine Endposition sowie Informationen zu Wertebereichsfehlern.
Der Aufruf strtol(text, &end, 10) ermöglicht dem Programm, einen verbleibenden Buchstaben nach ansonsten gültigen Dezimalziffern zurückzuweisen.

T

Teilzeichenkette
Eine Teilzeichenkette ist eine aufeinanderfolgende Zeichenfolge innerhalb einer anderen Zeichenkette; strstr findet ihre erste übereinstimmende Anfangsstelle.
Die Suche nach „green“ in „blue-green“ gibt eine Stelle zurück, ab der die verbleibende Zeichenkette „green“ lautet.
Tokenisierung
Die Tokenisierung zerlegt Text anhand von Trennzeichen in Teile; strtok verändert dazu die Eingabe und merkt sich eine Position.
Ein zweiter Aufruf von strtok(NULL, ",") setzt die Zerlegung derselben beschreibbaren Eingabe fort, statt sie neu zu beginnen.
Trennzeichen
Ein Trennzeichen trennt Textteile, etwa das Komma oder Leerzeichen, das einer Funktion zur Tokenisierung übergeben wird.
Mit den Trennzeichen „, “ kann der Text „one, two“ in die Token „one“ und „two“ zerlegt werden.

U

undefiniertes Verhalten
Undefiniertes Verhalten bedeutet, dass der C-Standard keine Anforderungen an das Ergebnis einer Operation stellt und somit kein bestimmter Ausgang garantiert ist.
Ein unzulässiger Array-Schreibzugriff kann in einer erstellten Programmversion harmlos erscheinen und in einer anderen das Programm zum Absturz bringen oder Daten beschädigen.
Unicode-Codepunkt
Ein Unicode-Codepunkt ist eine nummerierte Position im Coderaum von Unicode, mit der Zeichen und andere zugewiesene Textwerte bezeichnet werden.
Der vorkombinierte Buchstabe „é“ ist U+00E9, obwohl seine UTF-8-Speicherung aus zwei getrennten Bytes besteht.
unvollständige Zeile
Eine unvollständige Zeile ist der gelesene Teil, bevor eine Puffergrößenbegrenzung die Eingabe stoppt, wobei möglicherweise mehr von dieser zusammengehörigen Zeile ungelesen bleibt.
Eine lange Antwort kann in zwei fgets-Abschnitten ankommen, sodass der zweite Abschnitt nicht zu einer neuen Antwort werden sollte.
UTF-8
UTF-8 ist eine Unicode-Kodierung, die jeden Skalarwert in einem bis vier Bytes speichert und ein Byte lange ASCII-Kodierungen beibehält.
Die Zeichenkette „café“ belegt fünf UTF-8-Textbytes plus ein Nullbyte bei der Speicherung als C-Zeichenkette.

V

Verkettung
Die Verkettung verbindet Textfolgen der Reihe nach und benötigt im Ziel genügend Platz für den verbundenen Text und sein abschließendes Zeichen.
Die Verbindung von „north“ und „east“ ergibt „northeast“, was als abgeschlossene ASCII-Zeichenkette zehn Bytes benötigt.
verschachtelte Schleifen
Verschachtelte Schleifen platzieren eine Schleife innerhalb einer anderen, sodass sich die innere Schleife für jeden Wert der äußeren wiederholt, etwa für Spalten innerhalb jeder Zeile.
Ein Durchlauf mit zwei Zeilen und fünf Spalten führt den inneren Schleifenrumpf zehnmal aus, wenn beide Indizes innerhalb ihrer Grenzen bleiben.

W

Wortgrenze
Eine Wortgrenze ist der Übergang, anhand dessen ein Programm gemäß seiner gewählten Wortdefinition entscheidet, wo ein Wort beginnt oder endet.
Ein Zähler für Buchstabenfolgen beginnt ein neues Wort, wenn er nach einem Leerzeichen oder Satzzeichen oder am Anfang der Zeichenkette einen Buchstaben sieht.
Wraparound bei vorzeichenlosen Ganzzahlen
Beim Wraparound bei vorzeichenlosen Ganzzahlen wird ein vorzeichenloses Rechenergebnis modulo des Wertebereichs seines Typs reduziert, sodass das Abziehen von eins von null einen großen Wert ergibt.
Ein size_t-Rückwärtszähler kann nicht durch einen negativen Wert stoppen; verwende i > 0 und greife auf Position i - 1 zu.

Z

Zahlenkonvertierung
Eine Zahlenkonvertierung deutet Text als Zahl und erfordert separate Prüfungen der akzeptierten Schreibweise, des verarbeiteten Textes und des zulässigen Wertebereichs.
Die Bytes „27“ müssen konvertiert werden, bevor ein Programm den numerischen Wert mit % 26 reduzieren kann.
Zeichenkettenlänge
Die Zeichenkettenlänge ist die Anzahl der Textbytes vor dem ersten Nullzeichen, ohne dieses Zeichen und ungenutzte Kapazität.
Ein Array mit 40 Bytes, das „June“ enthält, hat die Länge 4, obwohl sein verfügbarer Speicher weiterhin 40 Bytes beträgt.
Zeichenkettenliste mit fester Zeilenlänge
Eine Zeichenkettenliste mit fester Zeilenlänge speichert Text in den Zeilen eines 2D-char-Arrays, wobei jede Zeile ihre eigene feste Kapazität und Platz für ein abschließendes Nullzeichen reserviert.
In char labels[5][12] hat jede Bezeichnung zwölf Bytes Speicher, auch wenn ihr aktueller Text kürzer ist.
Zeichenkettenliteral
Ein Zeichenkettenliteral ist Text in doppelten Anführungszeichen, der ein abgeschlossenes Zeichenarray bereitstellt, dessen Inhalt das laufende Programm nicht verändern darf.
const char *label = "ready" verweist auf ein Literal; eine beschreibbare Kopie kann char label[] = "ready" verwenden.
Zeichenklassifikation
Die Zeichenklassifikation prüft mit Funktionen aus <ctype.h>, ob ein Zeichencode zu einer Kategorie wie Ziffer, Buchstabe oder Leerraumzeichen gehört.
isdigit((unsigned char) c) ergibt einen Wert ungleich null, wenn das gespeicherte Byte eine Dezimalziffer darstellt.
Zeichenkodierung
Eine Zeichenkodierung definiert eine Zuordnung zwischen Text und seiner gespeicherten numerischen Darstellung, einschließlich der für Übertragung oder Speicherung verwendeten Bytes.
Eine C-Zeichenkette mit demselben sichtbaren Wort kann je nach Kodierung unterschiedlich viele Bytes benötigen.
Zeichenkonstante
Eine Zeichenkonstante ist ein C-Ausdruck in einfachen Anführungszeichen mit dem Typ int, der einen Zeichencode darstellt, etwa den Code eines Buchstabens.
Mit ASCII-Codes zeigt printf("%c %d", 'B', 'B') den Buchstaben B und den numerischen Wert 66 an.
Zeile
Eine Zeile ist ein inneres Array innerhalb eines zweidimensionalen Arrays und enthält eine feste Anzahl aufeinanderfolgender Elemente desselben Typs.
In int table[2][5] bezeichnet table[1] die zweite Zeile, die fünf Ganzzahlpositionen enthält.
Zeilenbreite
Die Zeilenbreite ist die Anzahl der Elemente in jedem inneren Array und wird in einem Tabellenparameter benötigt, um indizierte Einträge zu finden.
Der Parameter int g[][6] nimmt Zeilen mit sechs Ganzzahlen auf, während der Aufrufer die Zeilenanzahl separat angibt.
zeilenweise Speicherung
Die zeilenweise Speicherung speichert alle Elemente einer Zeile aufeinanderfolgend vor der nächsten Zeile, wie es für mehrdimensionale Arrays in C vorgeschrieben ist.
Das Element nach grid[0][3] im Speicher einer Tabelle mit vier Spalten ist grid[1][0].
Zugriff außerhalb der Speichergrenzen
Ein Zugriff außerhalb der Speichergrenzen liest oder beschreibt eine indizierte Position außerhalb des tatsächlich angesprochenen Arrays oder der Zeile und hat undefiniertes Verhalten.
Der Zugriff auf a[8] in einem Array mit acht Elementen ist unzulässig, selbst wenn ein Testlauf scheinbar einen brauchbaren Wert zurückgibt.
Zuweisungsanzahl
Eine Zuweisungsanzahl ist die Anzahl der Eingabezuweisungen, die scanf erfolgreich abgeschlossen hat. Sie dient zur Prüfung, ob angeforderte Daten tatsächlich gelesen wurden.
Das Lesen einer Zahl erwartet den Rückgabewert 1; ein Übereinstimmungsfehler kann dieselben unzulässigen Bytes wartend in der Eingabe zurücklassen.
zweidimensionales Array
Ein zweidimensionales Array ist ein Array gleich breiter Zeilenarrays, bei dem zwei Indizes ein Element nach Zeile und Spalte auswählen.
int board[4][6] stellt 24 Ganzzahlen bereit, die über vier zulässige Zeilenindizes und sechs zulässige Spaltenindizes erreicht werden.

Über diese Einheit

Von StudeliFehler melden

Erstelle deine eigenen Lernmaterialien

Mach aus deinen Notizen, Folien und Dokumenten Lernleitfäden, Übungsfragen, Karteikarten und 6 weitere Tools.

Jetzt starten