C-Grundlagen: Datentypen, Operatoren und Programmablauf

Schreibe deine ersten C-Programme und verstehe genau, was sie tun. Kompiliere Code mit gcc oder clang, führe ihn aus und behebe die vom Compiler gemeldeten Probleme. Speichere Daten in Ganzzahl-, Zeichen- und Gleitkommavariablen und gib Werte mit printf aus und lies sie mit scanf ein. Bilde Ausdrücke mit den arithmetischen, Vergleichs-, logischen und bitweisen Operatoren von C und sage Typumwandlungen und Operatorpriorität voraus. Steuere dann ein Programm mit if, switch, while, do-while und for. Lerne abschließend, warum Ganzzahlen überlaufen, was dabei geschieht und wie du Code schreibst und testest, der Überläufe verhindert.

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

Was du lernst

  1. C-Quellcode in ein lauffähiges Programm umwandeln, die Aufgaben des Compilers in jeder Phase verstehen und seine Meldungen nutzen, um Fehler zu finden und zu beheben.
    • Ein erstes C-Programm schreiben, kompilieren und ausführen
      Die Teile eines minimalen C-Programms lesen, es mit gcc oder clang kompilieren und die ausführbare Datei von einem Terminal aus starten.
    • Compilermeldungen lesen und Fehler beheben
      Übersetzungsfehler, Warnungen, Linkerfehler, Laufzeitfehler und logische Fehler unterscheiden und sie mithilfe von Compilermeldungen und Testläufen beheben.
  2. Variablen deklarieren und initialisieren, zwischen den Ganzzahl- und Gleitkommatypen von C wählen und den Wertebereich und die Genauigkeit jedes Typs vorhersagen.
    • Variablen deklarieren, initialisieren und benennen
      Variablen mit einem Typ und einem aussagekräftigen Namen anlegen, ihnen Anfangswerte geben und Konstanten für feste Werte verwenden.
    • Einen Ganzzahltyp auswählen und seinen Wertebereich kennen
      Unter den vorzeichenbehafteten und vorzeichenlosen Ganzzahltypen, Zeichen- und booleschen Werten von C anhand ihrer Größen, Wertebereiche und Literalformen auswählen.
    • Mit Gleitkommazahlen arbeiten und ihre Grenzen kennen
      Reelle Zahlen in float und double speichern, ihre Genauigkeit und Rundungsfehler vorhersagen und sicher zwischen Gleitkommawerten und Ganzzahlen umwandeln.
  3. Zahlen und Text mit printf präzise formatieren und von Benutzern eingegebene Werte mit scanf lesen und prüfen.
    • Ausgaben mit printf formatieren
      Konvertierungsspezifikationen, Feldbreiten, Genauigkeit, Flags und Escape-Sequenzen verwenden, um genau die beabsichtigte Ausgabe zu erzeugen.
    • Eingaben mit scanf lesen und prüfen
      Von Benutzern eingegebene Zahlen und Zeichen lesen, den Leseerfolg prüfen und ungültige Eingaben behandeln.
  4. Die arithmetischen, Zuweisungs-, Vergleichs-, logischen und bitweisen Operatoren von C verwenden und Wert und Typ eines beliebigen Ausdrucks anhand von Typumwandlungen, Operatorpriorität und Assoziativität vorhersagen.
    • Mit arithmetischen Operatoren und Zuweisungsoperatoren rechnen
      +, -, *, / und %, Ganzzahldivision, zusammengesetzte Zuweisung sowie Inkrement und Dekrement anwenden und ihre typischen Fallstricke vermeiden.
    • Typumwandlungen in Ausdrücken mit unterschiedlichen Datentypen vorhersagen
      Erklären, wie C Operanden unterschiedlicher Typen umwandelt, explizite Typumwandlungen gezielt einsetzen und die Falle bei vorzeichenbehafteten und vorzeichenlosen Werten vermeiden.
    • Bedingungen mit Vergleichsoperatoren, logischen und bitweisen Operatoren verknüpfen
      Korrekte Vergleiche und zusammengesetzte Bedingungen schreiben, Kurzschlussauswertung nutzen und einzelne Bits bearbeiten.
    • Operatorpriorität, Assoziativität und Auswertungsreihenfolge berücksichtigen
      Vorhersagen, wie C die Operatoren in einem Ausdruck gruppiert, Ausdrücke schrittweise auswerten und bei unklarer Gruppierung Klammern verwenden.
  5. Mit if, else und switch zwischen Alternativen wählen und Aufgaben mit while-, do-while- und for-Schleifen wiederholen, die genau wie beabsichtigt starten, laufen und enden.
    • Entscheidungen mit if und else treffen
      if-, else if- und else-Anweisungen mit korrekten Bedingungen und geschweiften Klammern schreiben und häufige Fallstricke der C-Syntax vermeiden.
    • Mit switch zwischen Fällen auswählen
      Mit switch, case-Marken, break und default zwischen festen ganzzahligen Werten wählen und Fall-through nur gezielt einsetzen.
    • Mit while und do-while wiederholen, solange eine Bedingung erfüllt ist
      Bedingungsgesteuerte Schleifen schreiben, die korrekt starten, Fortschritt machen und enden, einschließlich Eingabeschleifen und Schleifen, die mindestens einmal ausgeführt werden müssen.
    • Mit for-Schleifen zählen, Schleifen verschachteln und vorzeitig abbrechen
      for-Schleifen schreiben, die genau so oft wie beabsichtigt ausgeführt werden, Schleifen ineinander verschachteln und break und continue gezielt einsetzen.
  6. Erklären, was geschieht, wenn ein Ergebnis im Ganzzahltyp nicht darstellbar ist, und Code schreiben und testen, der Überläufe vermeidet oder erkennt.
    • Erklären, was geschieht, wenn ein Wert nicht im Datentyp darstellbar ist
      Wraparound bei vorzeichenlosen Ganzzahlen, undefinierten Überlauf bei vorzeichenbehafteten Ganzzahlen und Konvertierungen außerhalb des Wertebereichs beschreiben und mit realen Fehlfunktionen verbinden.
    • Code schreiben, der Überläufe vermeidet oder erkennt
      Typen mit ausreichendem Wertebereich wählen, Operationen vor einem möglichen Überlauf prüfen und Compilerwerkzeuge sowie Grenzwerttests einsetzen, um Überläufe früh zu erkennen.

Datentypen und Operatoren in C einfach erklärt

6 Kapitel

1

C-Programme erstellen und ausführen

Durch die Programmerstellung wird C-Code zu einem ausführbaren Programm. Lerne die Teile eines vollständigen ersten Programms kennen, kompiliere es mit einem ausgewählten Sprachstandard und führe die entstandene ausführbare Datei aus. Wenn eine Erstellung oder ein Test fehlschlägt, nutze die Phase des Fehlers und die erste Compilermeldung, um die Ursache zu finden. Wenn du den Zyklus aus Bearbeiten, Erstellen und Testen sorgfältig durchläufst, kann eine alte ausführbare Datei eine fehlgeschlagene Änderung nicht verbergen, und jede Korrektur lässt sich leichter prüfen.

1.1 Vom Quellcode zum laufenden Programm

Ein C-Programm beginnt als reiner Text und läuft als Maschinencode. Der Compiler verbindet diese beiden Formen. Wenn du eine Änderung speicherst, änderst du den Quellcode; die zuvor erstellte ausführbare Datei ändert sich dadurch nicht.

Ein vollständiges Programm lesen

Speichere dies als hello.c:

#include <stdio.h>

int main(void)
{
    printf("Hello, world!\n");
    return 0;
}

#include <stdio.h> stellt Deklarationen für die Standardein- und -ausgabe bereit, darunter printf. Die Ausführung beginnt in main. In int main(void) bezeichnet int den Rückgabetyp und void bedeutet, dass es keine Parameter gibt. Geschweifte Klammern umschließen den Rumpf. Der Aufruf von printf gibt den Text in Anführungszeichen aus; \n wechselt in eine neue Zeile. return 0; beendet das Programm und meldet dem Betriebssystem Erfolg. Ein Status ungleich null kann einen Fehler melden. Seit C99 wird auch beim Erreichen des Endes von main null zurückgegeben.

Einfache Anweisungen enden mit einem Semikolon. Die include-Zeile nicht. C unterscheidet Groß- und Kleinschreibung: Printf und printf sind verschiedene Namen. Leerzeichen zwischen Tokens beeinflussen das Programm gewöhnlich nicht, aber einheitliche Einrückung macht die Blockstruktur sichtbar.

Kompilieren und ausführen

Öffne im Terminal den Ordner mit dem Quellcode und gib ein:

gcc -std=c17 -Wall -Wextra hello.c -o hello
./hello

Der erste Befehl erstellt eine ausführbare Datei namens hello; der zweite führt sie aus dem aktuellen Ordner aus. Ihre Ausgabe ist Hello, world! gefolgt von einem Zeilenumbruch. Du kannst clang anstelle von gcc verwenden. Ohne -o hello lautet der übliche Ausgabename unter Linux oder macOS a.out. Ausführbare Dateien unter Windows verwenden .exe. make hello von CS50 ist eine Abkürzung, die das Erstellungswerkzeug auffordert, den Compiler aufzurufen.

Speichere nach einer Änderung der Begrüßung, kompiliere erneut und führe das Programm wieder aus. Schlägt die Kompilierung fehl, kann noch eine ältere ausführbare Datei vorhanden sein. Deren Ausführung zeigt das alte Verhalten. Prüfe daher, ob die Erstellung erfolgreich war, bevor du deine Änderung beurteilst.

Was bei der Erstellung geschieht

Der Präprozessor verarbeitet Direktiven wie #include, indem er Headertext einfügt. Der eigentliche Compiler übersetzt C in Assemblersprache. Der Assembler erzeugt Objektcode, der noch nicht aufgelöste Namen enthalten kann. Der Linker verbindet Objektcode und löst Verweise wie printf auf. Bibliothekscode kann durch statisches Linken in eine ausführbare Datei kopiert oder über eine gemeinsam genutzte Bibliothek bereitgestellt werden, die bei der Programmausführung geladen wird.

Du kannst die Erstellung bei Zwischenstufen anhalten:

OptionErgebnis
-EVorverarbeiteter Text
-SAssemblersprache
-cEine Objektdatei, etwa hello.o

Eine Objektdatei ist noch nicht die fertige ausführbare Datei. Eine ausführbare Datei kann ohne Compiler laufen, wird aber für ein bestimmtes Betriebssystem und einen bestimmten Prozessor erstellt. Um das Programm auf eine andere Plattform zu übertragen, kompiliere dort seinen Quellcode.

Kommentare und Sprachversionen

// beginnt einen Kommentar, der am Zeilenumbruch endet. /* ... */ kann mehrere Zeilen umfassen, aber solche Kommentare können nicht verschachtelt werden: Das erste */ beendet den Kommentar. Nutze Kommentare, um eine Entscheidung zu erklären, die der Code allein offenlässt.

Wähle die Sprachversion ausdrücklich aus. C17 unterstützt die obigen Beispiele, einschließlich //-Kommentaren, long long und Deklarationen in for-Köpfen. C23 ergänzt Funktionen wie bool als Schlüsselwort; es wurde als ISO/IEC 9899:2024 veröffentlicht. -Wall -Wextra aktiviert nützliche Warnungen. Die Voreinstellungen der Compiler unterscheiden sich, sodass ein ausdrückliches -std=c17 die beabsichtigten Sprachregeln klarstellt.

Wichtige Erkenntnisse

  • C-Quellcode muss nach jeder Änderung erneut kompiliert werden.
  • Die Ausführung beginnt in main; die Rückgabe von null meldet Erfolg.
  • Die Phasen der Programmerstellung sind Vorverarbeitung, Kompilierung, Assemblierung und Linken.
  • Verwende -std=c17 -Wall -Wextra, um den Standard zu wählen und Warnungen zu aktivieren.

1.2 Die erste Ursache eines Fehlers finden

Der Zeitpunkt, zu dem ein Problem auftritt, hilft dir, seine Ursache zu finden. Ein Programm kann sich nicht erstellen lassen, während der Ausführung stoppen oder mit einem falschen Ergebnis enden. Diese Situationen verlangen unterschiedliche Prüfungen.

Problemarten unterscheiden
ArtWas du siehstWo du suchen solltest
ÜbersetzungsfehlerKeine neue ausführbare DateiSyntax, Deklarationen und Typen
WarnungEin wahrscheinliches Problem; die Erstellung läuft gewöhnlich weiterDer in der Meldung genannte Ausdruck
LinkerfehlerObjektcode ist vorhanden, aber die ausführbare Datei kann nicht fertiggestellt werdenNicht aufgelöste Funktionsnamen oder benötigte Bibliotheken
LaufzeitfehlerEin Problem tritt während der Ausführung aufOperationen und Werte, die bei dieser Eingabe erreicht werden
Logischer FehlerDas Programm läuft, liefert aber ein unbeabsichtigtes ErgebnisBedingungen, Formeln und Aktualisierungen

Eine Warnung kann neben einem gewöhnlichen Fehler auch unzulässigen Code oder undefiniertes Verhalten anzeigen. Eine erfolgreiche Kompilierung garantiert kein korrektes Programm.

Eine Compilermeldung lesen

Eine Compilermeldung nennt normalerweise eine Stelle im Quellcode, einen Schweregrad und eine Beschreibung. Eine Meldung wie hello.c:5:16: error: expected ';' after expression weist auf ein fehlendes Semikolon nahe Zeile 5 hin. Manchmal bemerkt der Compiler das Problem erst in der nächsten Zeile. Untersuche die gemeldete Zeile und die unmittelbar davor.

Behebe zuerst den ersten Fehler und kompiliere dann erneut. Ein nicht geschlossenes Anführungszeichen kann spätere Zeilen als Teil einer Zeichenkette erscheinen lassen; eine nicht geschlossene geschweifte Klammer kann spätere Deklarationen im falschen Block erscheinen lassen. Mehrere Meldungen können nach einer einzigen Korrektur verschwinden. Beginne nicht damit, jede in der Ausgabe genannte Zeile zu ändern.

Für diesen Ausschnitt:

int total = 6
printf("%d\n", total);

Die Deklaration benötigt ein Semikolon nach 6. In der printf-Zeile kann der Compiler erstmals feststellen, dass die vorangehende Deklaration nicht beendet wurde. Prüfe auch die genaue Schreibweise, zusammengehörige runde und geschweifte Klammern sowie die Headerdatei, die eine Bibliotheksfunktion deklariert. Ein Aufruf von printf benötigt <stdio.h>; Aufrufe nicht deklarierter Funktionen sind ab C99 nicht erlaubt.

Warnungen ernst nehmen

Erstelle das Programm mit -Wall -Wextra. Diese Optionen helfen, eine Zuweisung in einer Bedingung, ungenutzte Variablen, Vergleiche zwischen vorzeichenbehafteten und vorzeichenlosen Werten und unpassende Ausgabeformate zu erkennen. Zum Beispiel verlangt printf("%d\n", 3.5); eine Ganzzahl, übergibt aber einen double; das Format wandelt das Argument nicht für dich um. Korrigiere das Format zu %f oder wandle den Wert bewusst um. -Werror sorgt dafür, dass Warnungen die Erstellung stoppen.

Undefiniertes Verhalten verstehen

Undefiniertes Verhalten bedeutet, dass C keine Anforderungen an das Verhalten des Programms stellt. Ein Überlauf bei vorzeichenbehafteten Ganzzahlen, Ganzzahldivision durch null und manche Lesezugriffe auf nicht initialisierte Variablen können dazu führen. Ein Programm kann stoppen, einen unerwarteten Wert ausgeben oder scheinbar funktionieren. Compilereinstellungen können beeinflussen, was geschieht.

Eine Meldung wie „Floating point exception“ kann auf manchen Systemen sogar durch Ganzzahldivision durch null entstehen. Untersuche die Operation und die Eingabe, nicht nur den Wortlaut einer Betriebssystemmeldung. Prüfe einen Divisor, bevor du die Division ausführst.

Ein falsches Ergebnis nachvollziehen

Sage bei einem logischen Fehler einen kleinen Fall von Hand voraus und vergleiche ihn mit der tatsächlichen Ausgabe. Füge vorübergehend printf-Aufrufe hinzu, die eine laufende Summe oder den von einer Bedingung geprüften Wert anzeigen. Ändere jeweils nur eine Ursache und erstelle das Programm nach jeder Änderung erneut. Teste gewöhnliche Werte und Grenzfälle wie null, eins, eine negative Eingabe und die größte erwartete Eingabe. Ein Ablaufprotokoll zeigt, wo das Programm erstmals von deiner Vorhersage abweicht.

Wichtige Erkenntnisse

  • Behebe die erste Meldung und erstelle das Programm erneut, bevor du späteren Meldungen nachgehst.
  • Eine Warnung kann auf undefiniertes Verhalten hinweisen; die Kompilierung allein belegt keine Korrektheit.
  • Stimme Formatkonvertierungen auf die Argumenttypen ab.
  • Verfolge bei einem falschen Ergebnis eine kleine Eingabe und vergleiche die Zwischenwerte.
  • Teste neben gewöhnlichen Eingaben auch Grenzfälle.
2

Daten in Variablen der grundlegenden Datentypen speichern

Ein Typ bestimmt, wie ein gespeicherter Wert interpretiert wird und welche Werte er aufnehmen kann. Beginne mit Deklarationen, Anfangswerten und den Namen, die innerhalb eines Blocks verfügbar sind. Wähle dann Ganzzahltypen anhand ihres Wertebereichs und Gleitkommatypen anhand ihrer Genauigkeit und ihres Wertebereichs. Zeichencodes und boolesche Werte gehören ebenfalls dazu. Wenn du Literale lesen und Grenzen prüfen kannst, hilft dir das vorherzusagen, was sich bei einer Berechnung darstellen lässt, bevor du sie schreibst.

2.1 Variablen, Namen und feste Werte

Eine Variable gibt einem gespeicherten Wert einen Namen. Ihr Typ legt fest, wie C diesen Speicher interpretiert. Du deklarierst den Typ, bevor du die Variable verwendest; während ihrer gesamten Lebensdauer behält sie diesen Typ.

Gib jeder Variablen einen Anfangswert
int count = 0;
double length_m = 1.8;
count = count + 1;

Die ersten beiden Zeilen deklarieren und initialisieren Variablen. Die dritte weist einer vorhandenen Variablen einen neuen Wert zu: Sie liest den aktuellen Wert von count, addiert eins und speichert das Ergebnis zurück. Eine Zuweisung ist eine Aktion, daher ist count = count + 1 sinnvoll, auch wenn es keine mathematische Gleichung wäre.

Die Initialisierung erfolgt in einer Deklaration; eine Zuweisung kann später erfolgen. In int a, b = 5; wird nur b initialisiert. Ein lokales a hat einen unbestimmten Wert, bis du einen Wert darin speicherst. Das Lesen kann unvorhersehbares Verhalten einschließlich undefinierten Verhaltens hervorrufen. Initialisiere Zähler und Summen bei ihrer Deklaration, etwa mit int total = 0;.

Eine Zuweisung kopiert den Wert zum jeweiligen Zeitpunkt:

int width = 4;
int area = width * 3;
width = 7;
printf("%d %d\n", width, area);

Das gibt 7 12 aus. Die Zuweisung an area hat einmalig 12 berechnet. Sie hat keine Formel erzeugt, die bei jeder Änderung von width neu rechnet. Um area zu aktualisieren, weise der Variablen eine neue Berechnung zu.

Wähle lesbare Namen

Ein Bezeichner kann Buchstaben, Ziffern und Unterstriche enthalten, darf aber nicht mit einer Ziffer beginnen oder ein Schlüsselwort sein. height_cm und second_place sind zulässig; 2nd_place nicht, und my-score wird als Subtraktion gelesen. Bei Namen wird Groß- und Kleinschreibung unterschieden, sodass total und Total verschieden sind. Namen, die eine Bedeutung oder Einheit enthalten, erleichtern die Prüfung von Formeln.

Beachte den Blockgültigkeitsbereich

Ein lokaler Name ist von seiner Deklaration bis zum Ende seines Blocks verfügbar. Seit C99 können Deklarationen zwischen anderen Anweisungen stehen.

int count = 2;
{
    int count = 9;
    printf("%d\n", count);
}
printf("%d\n", count);

Die innere Deklaration verdeckt den äußeren Namen, während dieser Block ausgeführt wird. Die Ausgabe ist 9 und danach 2, jeweils in einer eigenen Zeile. Es handelt sich um zwei Variablen, nicht um zwei Zuweisungen an eine Variable. Denselben Namen zweimal in einem Block zu deklarieren, ist ein Fehler. Namen in verschachtelten Blöcken erneut zu verwenden, ist erlaubt, macht den Code aber schwerer nachvollziehbar. Wähle daher Namen, die jede Rolle deutlich machen.

Benenne feste Werte
const int SECONDS_PER_MINUTE = 60;
#define MAX_HEIGHT 8

const erzeugt eine typisierte Variable, der über diesen Namen kein neuer Wert zugewiesen werden kann. Eine spätere Anweisung SECONDS_PER_MINUTE = 90; muss eine Compilermeldung auslösen. Der Initialisierer gibt bei der Deklaration den festen Wert an.

#define erzeugt eine Ersetzung durch den Präprozessor: Spätere Vorkommen von MAX_HEIGHT werden vor der Kompilierung durch das Token 8 ersetzt. In dieser Direktive stehen weder = noch ein Semikolon. Ein Semikolon würde Teil der Ersetzung und könnte einen Ausdruck wie height > MAX_HEIGHT fehlerhaft machen.

Ein Name wie SECONDS_PER_MINUTE erklärt Lesenden, warum 60 in einer Berechnung erscheint, und gibt dir eine zentrale Stelle für Änderungen. Großbuchstaben sind eine Namenskonvention, keine Regel, die einen Wert konstant macht. Dieses Verhalten wird durch die Deklaration oder Direktive festgelegt.

Wichtige Erkenntnisse

  • Initialisiere lokale Variablen, bevor du sie liest.
  • Eine Zuweisung kopiert einen Wert einmalig; abhängige Werte aktualisieren sich nicht selbst.
  • Ein Block begrenzt, wo ein lokaler Name verwendet werden kann.
  • Bei Namen wird Groß- und Kleinschreibung unterschieden; sie dürfen nicht mit einer Ziffer beginnen.
  • const stellt eine typisierte schreibgeschützte Variable bereit; #define stellt eine Token-Ersetzung bereit.

2.2 Ganzzahl-, Zeichen- und boolesche Typen auswählen

Ganzzahltypen speichern ganze Zahlen innerhalb eines festen Wertebereichs exakt. Wähle einen Typ anhand der Werte, die das Programm verarbeiten muss, einschließlich der Zwischenergebnisse, und danach, ob negative Werte sinnvoll sind.

Größen und Wertebereiche

Der Standard garantiert Mindestbreiten: mindestens 8 Bit für char, 16 für short und int, 32 für long und 64 für long long. Jeder Typ in dieser Reihenfolge ist mindestens so breit wie der vorherige. Die tatsächlichen Größen hängen von der Plattform ab.

TypTypische Bytes unter 64-Bit-Linux/macOSUnterschied unter 64-Bit-Windows
char1Keiner
short2Keiner
int4Keiner
long84 Bytes
long long8Keiner

sizeof(int) liefert die Größe in Bytes als size_t; gib sie mit %zu aus. Die Headerdatei <limits.h> stellt Namen wie INT_MIN, INT_MAX, UINT_MAX, LLONG_MAX und CHAR_BIT, die Anzahl der Bits pro Byte, bereit.

Mit N Wertbits nimmt eine vorzeichenlose Ganzzahl Werte von 0 bis 2ᴺ − 1 an. Eine vorzeichenbehaftete Ganzzahl mit N Bits im Zweierkomplement reicht von −2ᴺ⁻¹ bis 2ᴺ⁻¹ − 1. Ein 32-Bit-int reicht somit von −2.147.483.648 bis 2.147.483.647, während ein 32-Bit-unsigned int bis 4.294.967.295 reicht. C23 schreibt das Zweierkomplement vor; C17 erlaubte auch andere Darstellungen.

In einem 8-Bit-Muster im Zweierkomplement hat das höchstwertige Bit den Stellenwert −128. 11111111 stellt −1 dar und 10000000 stellt −128 dar. Um −5 zu bilden, beginne mit 00000101, invertiere die Bits zu 11111010 und addiere eins, um 11111011 zu erhalten.

Auch Zeichen sind Ganzzahlen

Ein char speichert einen Zeichencode in einem Byte. Verwende einfache Anführungszeichen für eine Zeichenkonstante, wie in 'A'. In ASCII hat 'A' den Code 65 und '0' den Code 48. printf("%c %d\n", 'A', 'A'); gibt in ASCII A 65 aus. Eine Zeichenkonstante hat in C den Typ int, sodass sizeof('A') gleich sizeof(int) ist.

Ein einfaches char kann je nach Implementierung vorzeichenbehaftet oder vorzeichenlos sein. Verwende signed char oder unsigned char, wenn der numerische Wertebereich wichtig ist. <stdint.h> bietet Ganzzahltypen mit exakter Bitbreite wie int32_t und uint8_t auf Plattformen an, die diese exakten Breiten unterstützen; gängige Plattformen tun das.

Ganzzahlliterale lesen

Eine Dezimalkonstante wie 42 hat den Typ int, wenn sie darin darstellbar ist. Suffixe fordern andere Typen an: 42u ist unsigned, 42L ist long, 42LL ist long long und 42ULL ist unsigned long long. Die hexadezimale Schreibweise verwendet 0x, wie in 0x2A. Eine führende null bedeutet oktale Schreibweise: 052 bedeutet ebenfalls 42 und 010 bedeutet 8. 09 ist keine gültige Oktalzahl. C23 ergänzt die binäre Schreibweise wie 0b101010.

Wahrheitswerte und Typwahl

In C17 bindest du für bool, true und false die Headerdatei <stdbool.h> ein; C23 macht sie zu Schlüsselwörtern. Eine Umwandlung in bool ergibt für null falsch und für jeden Wert ungleich null wahr, einschließlich 0.5. Gib einen booleschen Wert mit %d aus, um 0 oder 1 zu sehen.

Verwende int für gewöhnliche Anzahlen und Schleifenzähler. Verwende long long oder einen geeigneten Typ mit exakter Bitbreite, wenn der Wertebereich etwa 2,1 Milliarden überschreiten kann. Vorzeichenlose Typen eignen sich für Bitmuster, aber eine Subtraktion unter null erzeugt einen Wraparound zu einem großen Wert, sodass weiterhin Wertebereichsprüfungen nötig sind.

Wichtige Erkenntnisse

  • Nutze die Typgrenzen, statt die Größe von long zu schätzen.
  • Ein vorzeichenbehafteter 32-Bit-int reicht bis 2.147.483.647.
  • Vorzeichenlose Werte haben keinen negativen Wertebereich und erzeugen bei Unterschreitung von null einen Wraparound.
  • Eine führende null kennzeichnet ein oktales Ganzzahlliteral.
  • Ob ein einfaches char vorzeichenbehaftet ist, hängt von der Implementierung ab.
  • In C17 stammen die booleschen Namen aus <stdbool.h>.

2.3 Dezimalwerte und begrenzte Genauigkeit

Ein Gleitkommatyp speichert eine Näherung einer reellen Zahl. Er hat einen großen Wertebereich, aber nur eine feste Anzahl signifikanter Stellen. Wertebereich und Genauigkeit beantworten unterschiedliche Fragen: wie groß ein Wert sein kann und wie fein sich benachbarte Werte unterscheiden lassen.

Wähle einen Gleitkommatyp

Auf gängigen IEEE-754-Systemen belegt float 4 Bytes und bietet etwa 6–7 signifikante Dezimalstellen. double belegt 8 Bytes und bietet etwa 15–16. Ihre größten endlichen Werte betragen etwa 3,4 × 10³⁸ und 1,8 × 10³⁰⁸. long double ist mindestens so genau wie double und hat ein plattformabhängiges Format. double ist eine nützliche Standardwahl für Messgrößen.

Konstanten wie 2.0, 3.14 und 1e-3 sind double; 1e-3 bedeutet 0,001. Ergänze f für float, wie in 2.5f, oder L für long double. Die Konstante 2 ist eine Ganzzahl und kann daher zu einer Ganzzahldivision führen, wo 2.0 eine Gleitkommadivision ergeben würde.

Die gespeicherte Näherung ansehen

Viele Dezimalbrüche haben keine exakte endliche Binärdarstellung. Auf einem gängigen System mit doppelter Genauigkeit gilt:

printf("%.20f\n", 0.1);
printf("%.17f\n", 0.1 + 0.2);

Die Ausgaben sind 0.10000000000000000555 und 0.30000000000000004. Die zusätzlichen Stellen zeigen die gespeicherte Näherung. Sie erklären, warum 0.1 + 0.2 == 0.3 falsch sein kann.

Verwende beim Vergleich berechneter Messwerte eine Toleranz:

#include <math.h>
/* inside main */
if (fabs(a - b) < 1e-9) {
    printf("Close enough\n");
}

fabs liefert eine absolute Differenz. Wähle die Toleranz passend zur Größenordnung und Genauigkeit der Werte; 1e-9 ist ein Beispiel, keine allgemeingültige Einstellung. Manche Linux-Erstellungen benötigen -lm am Ende des Compilerbefehls, um mathematische Funktionen zu linken.

Die Genauigkeit wirkt sich auf große ganze Zahlen aus

Ein binärer double stellt jede ganze Zahl bis 2⁵³ exakt dar. Ein binärer float stellt oberhalb von 2²⁴ = 16.777.216 nicht mehr jede ganze Zahl dar: Wird einem solchen float der Wert 16.777.217 zugewiesen, ergibt sich 16.777.216. Ein größerer Wertebereich bedeutet nicht, dass jeder ganzzahlige Schritt verfügbar bleibt.

Die Ausgabe rundet den gespeicherten Binärwert. printf("%.2f\n", 2.675); gibt häufig 2.67 aus, weil der gespeicherte Wert etwas kleiner als 2,675 ist. Verwende für exakte Beträge in ganzen Cent eine Ganzzahl wie long long cents = 1999; und formatiere die Anzeige erst am Ende.

Bewusst umwandeln und runden

Die Umwandlung in eine Ganzzahl verwirft den Nachkommateil in Richtung null: (int)3.99 ist 3 und (int)-3.99 ist −3. round aus <math.h> gibt einen auf die nächste ganze Zahl gerundeten Gleitkommawert zurück; halbe Werte werden von null weg gerundet: round(-2.5) ergibt −3,0. Wandle das Ergebnis nur dann in eine Ganzzahl um, wenn es darstellbar ist. Eine Umwandlung von Gleitkommawerten in Ganzzahlen, deren Ganzzahlanteil außerhalb des Wertebereichs liegt, führt zu undefiniertem Verhalten.

In der IEEE-Arithmetik können Ergebnisse außerhalb des endlichen Wertebereichs unendlich werden. Die Gleitkommadivision eines Werts ungleich null durch null kann Unendlich ergeben; null durch null ergibt NaN, was „keine Zahl“ bedeutet. Ein NaN ist selbst bei einem Vergleich mit sich selbst ungleich. Dies ist das Verhalten von IEEE-Gleitkommawerten; die Ganzzahldivision durch null führt zu undefiniertem Verhalten.

Wichtige Erkenntnisse

  • Gleitkommatypen haben sowohl eine begrenzte Genauigkeit als auch einen begrenzten Wertebereich.
  • Vergleiche berechnete Messwerte mit einer geeigneten Toleranz.
  • Die Ausgabe rundet die gespeicherte Näherung.
  • Die Umwandlung von Gleitkommawerten in Ganzzahlen schneidet in Richtung null ab und setzt ein darstellbares Ergebnis voraus.
3

Mit printf Ausgaben anzeigen und mit scanf Eingaben lesen

Ein Programm braucht einen klaren Austausch von Text und Werten. Formatiere die Ausgabe so, dass Lesende Beschriftungen, Felder und gerundete Messwerte unterscheiden können, und stimme dann jede Eingabekonvertierung auf die Variable ab, die den Wert aufnimmt. Beim Lesen von Text sind mehrere Ergebnisse möglich: alle angeforderten Werte, nur einige Werte oder gar keine Konvertierung. Wenn du diese Ergebnisse und den ungelesenen Text prüfst, beruhen spätere Berechnungen auf Werten, die das Programm tatsächlich erhalten hat.

3.1 Werte im gewünschten Format ausgeben

printf wandelt Werte mithilfe eines Formatstrings in Text um. Gewöhnliche Zeichen werden wie geschrieben ausgegeben. Eine mit % beginnende Konvertierung gibt an, wie das nächste Argument angezeigt werden soll.

Stimme jede Konvertierung auf ihr Argument ab
printf("%d samples, %.2f kg\n", 4, 1.5);

Das gibt 4 samples, 1.50 kg und einen Zeilenumbruch aus. Die Konvertierungen entsprechen der Reihenfolge nach den Argumenten. Ein Format wandelt den Typ des übergebenen Arguments nicht in den von dir beabsichtigten Typ um.

Werttyp oder AnzeigeKonvertierung
int%d oder %i
unsigned int%u
long, long long%ld, %lld
size_t%zu
double oder ein zu double erweiterter float%f, %e, %g
long double%Lf
Zeichen, Zeichenkettenliteral%c, %s
Vorzeichenlos hexadezimal, oktal%x oder %X, %o

Ein float-Argument wird in printf zu double erweitert, sodass beide %f verwenden; %lf ist hier gleichbedeutend. Die Übergabe von 3 an %f ist eine Typabweichung und keine Aufforderung, 3.0 auszugeben. Verwende 3.0 oder (double)3. Ein unpassender Typ oder ein fehlendes Argument kann undefiniertes Verhalten verursachen. Erstelle das Programm mit aktivierten Warnungen, damit Formatstrings als Literale geprüft werden.

Breite und Genauigkeit festlegen

Eine Breite ist eine Mindestanzahl von Zeichen einschließlich Vorzeichen und Dezimalpunkt. Eine Gleitkomma-Genauigkeit nach dem Punkt steuert die Anzahl der Nachkommastellen für %f. In %8.2f ist acht die Mindestbreite und zwei die Anzahl der Stellen nach dem Dezimalpunkt. Eine breitere Zahl wird vollständig ausgegeben; die Breite schneidet sie nicht ab.

Für die Ganzzahl 42 ergibt %5d den Wert 42, %-5d ergibt 42 , %05d ergibt 00042 und %+d ergibt +42. Ohne Genauigkeitsangabe gibt %f sechs Nachkommastellen aus.

printf("%-6s%8s\n", "Item", "Price");
printf("%-6s%8.2f\n", "Tea", 2.5);
printf("%-6s%8.2f\n", "Cake", 12.75);

Jede Beschriftung belegt ein linksbündiges Feld der Breite sechs, gefolgt von einem rechtsbündigen Preisfeld der Breite acht. Beide Preise haben zwei Nachkommastellen, sodass ihre Dezimalpunkte untereinander stehen. Die Leerzeichen entstehen durch die Feldbreiten, nicht durch eine automatische Trennung zwischen Argumenten.

Wissenschaftliche und allgemeine Darstellung

%e verwendet wissenschaftliche Schreibweise: 12.345,678 wird bei der Standardgenauigkeit als 1.234568e+04 ausgegeben. Für %g zählt die Genauigkeit signifikante Stellen, standardmäßig sechs. Es verwendet wissenschaftliche Schreibweise, wenn der Dezimalexponent kleiner als −4 oder mindestens so groß wie die Genauigkeit ist, und entfernt nachgestellte Nullen. So gibt %g 0,0001 als 0.0001, 0,00001 als 1e-05 und 1.000.000,0 als 1e+06 aus. Es wählt nicht einfach die kürzestmögliche Schreibweise.

Sonderzeichen ausgeben

In einer C-Zeichenkette bedeutet \n einen Zeilenumbruch, \t einen Tabulator, \\ einen umgekehrten Schrägstrich und \" ein Anführungszeichen. Ein Prozentzeichen in einem Format lautet %%: printf("100%%\n"); gibt 100% und einen Zeilenumbruch aus.

printf fügt selbst keine Leerzeichen oder Zeilenumbrüche hinzu. printf("Count:"); printf("%d", 4); gibt Count:4 aus. Ein Terminal kann Text ohne Zeilenumbruch verzögert anzeigen; fflush(stdout); sendet eine noch ausstehende Eingabeaufforderung, bevor die Eingabe angefordert wird.

Wichtige Erkenntnisse

  • Stimme Konvertierungen auf die tatsächlichen Argumenttypen ab.
  • Die Feldbreite ist ein Minimum; die Genauigkeit von %f zählt Nachkommastellen.
  • Die Genauigkeit von %g zählt signifikante Stellen.
  • Schreibe %% für ein wörtliches Prozentzeichen in einem Format.
  • printf fügt keine Leerzeichen oder Zeilenumbrüche hinzu, sofern das Format sie nicht vorgibt.

3.2 Eingaben lesen und das Gelesene prüfen

scanf liest Text, konvertiert ihn und speichert Werte in Variablen. Prüfe das Ergebnis, bevor du diese Variablen verwendest. Text, der mit Ziffern beginnt, kann nur teilweise akzeptiert werden. Eine erfolgreiche Konvertierung und eine vollständig gültige Eingabezeile sind daher verschiedene Dinge.

Gib ein Ziel des richtigen Typs an
int count;
double mass_kg;
if (scanf("%d %lf", &count, &mass_kg) != 2) {
    return 1;
}

Das & übergibt die Adresse jeder Variablen, damit scanf darin speichern kann. %d benötigt einen int, %u eine vorzeichenlose Ganzzahl, %ld einen long und %lld einen long long. Für Gleitkommaeingaben benötigt %f einen float und %lf einen double. Anders als bei printf sind diese beiden Konvertierungen nicht austauschbar. Ein fehlendes & oder ein falscher Zieltyp kann undefiniertes Verhalten verursachen.

%d liest dezimal. %i erkennt auch hexadezimale und oktale Eingaben mit führender null; bei %i bedeutet der Text „010“ den Wert 8. Verwende %d für gewöhnliche dezimale Anzahlen.

Prüfe den Rückgabewert

scanf gibt die Anzahl der abgeschlossenen Zuweisungen zurück. Bei zwei angeforderten Werten bedeutet 2 Erfolg. Eine fehlgeschlagene Konvertierung lässt ihr Ziel unverändert. Es kann noch nicht initialisiert sein; verwende es daher nur, wenn die zurückgegebene Anzahl den Erfolg bestätigt. EOF bedeutet, dass die Eingabe endete oder ein Eingabefehler auftrat, bevor die erste Konvertierung abgeschlossen wurde.

Bei scanf("%d:%d", &h, &m) muss der Doppelpunkt wörtlich erscheinen. Die Eingabe „14:30“ füllt beide Variablen. Die Eingabe „14 30“ füllt h, findet dann keinen passenden Doppelpunkt und lässt m unverändert. Der Rückgabewert ist 1.

Leerraumzeichen und teilweise Eingaben verstehen

Numerische Konvertierungen überspringen führende Leerzeichen, Tabulatoren und Zeilenumbrüche. %c liest das unmittelbar nächste Zeichen, einschließlich eines verbliebenen Zeilenumbruchs. Verwende scanf(" %c", &choice), um vor dem Lesen eines sichtbaren Zeichens Leerraumzeichen zu überspringen. Vermeide einen abschließenden Zeilenumbruch im Format: Er kann dazu führen, dass der Aufruf auf ein späteres Zeichen wartet, das kein Leerraumzeichen ist.

Mit %d speichert „12abc“ den Wert 12 und lässt „abc“ ungelesen. „3.7“ speichert 3 und lässt „.7“ zurück. „abc“ speichert nichts und gibt 0 zurück. Wird dieselbe Konvertierung erneut aufgerufen, ohne die fehlerhafte Eingabe zu verbrauchen, kann sie endlos am selben Zeichen scheitern.

Eine fehlgeschlagene Konvertierung behandeln
int n, result;
while ((result = scanf("%d", &n)) != 1) {
    if (result == EOF) {
        return 1;
    }
    int ch;
    while ((ch = getchar()) != '\n' && ch != EOF) {
    }
    printf("Try again: ");
    fflush(stdout);
}

getchar gibt einen int zurück und kann so sowohl ein Zeichen als auch EOF darstellen. Die innere Schleife verwirft die fehlerhafte Zeile. Die äußere Schleife versucht es erneut, akzeptiert aber weiterhin einen numerischen Anfang wie 12 in „12abc“. Wenn du verlangst, dass das nächste Zeichen '\n' ist, prüfst du die strengere Regel, dass auf eine Ganzzahl unmittelbar ein Zeilenumbruch folgt; diese Regel lehnt auch nachfolgende Leerzeichen ab. Eine Regelung, die Leerzeichen oder eine letzte Zeile mit EOF als Ende zulässt, muss diese Fälle ausdrücklich behandeln.

Prüfe nach einer erfolgreichen Konvertierung den zulässigen Bereich, etwa 1 bis 8 für eine Höheneinstellung. scanf weist einen numerischen Wert außerhalb des Wertebereichs nicht sicher zurück: Ein für seinen Zieltyp zu großer Wert führt zu undefiniertem Verhalten. Verwende Testeingaben, die im gewählten Typ darstellbar sind. Um ein Ergebnis wie Masse dividiert durch Größe zum Quadrat zu berechnen, prüfe, ob die Messwerte positiv sind, und halte das Quadrat als height_m * height_m zusammen.

Wichtige Erkenntnisse

  • Übergib Variablenadressen und verwende %lf, um einen double zu lesen.
  • Prüfe, ob die zurückgegebene Anzahl der benötigten Anzahl von Werten entspricht.
  • Eine erfolgreiche Konvertierung kann zusätzliche Eingaben ungelesen lassen.
  • Verbrauche fehlerhafte Eingaben, bevor du eine fehlgeschlagene numerische Konvertierung erneut versuchst.
  • Prüfe nach dem Lesen die zulässigen Werte; die Eingabekonvertierung hat eigene Wertebereichsgrenzen des Typs.
  • Verwende " %c", wenn beim Lesen eines Zeichens Leerraumzeichen übersprungen werden sollen.
4

Mit Operatoren und Ausdrücken rechnen

Ein Ausdruck verbindet Werte, Typen und Operatoren. Verfolge zunächst Ganzzahldivisionen und Aktualisierungen und danach die Typumwandlungen, die bei jeder Operation erfolgen. Vergleiche und logische Schutzbedingungen steuern, ob eine Berechnung erreicht wird, während bitweise Operationen auf einzelne Bits wirken. Unterscheide schließlich die durch die Operatorpriorität festgelegte Gruppierung von der nötigen Sequenzierung, wenn ein Ausdruck eine Variable ändert. Sowohl der Wert als auch der Typ eines Zwischenergebnisses sind wichtig.

4.1 Arithmetik, Zuweisung und Aktualisierung um eins

Jeder C-Ausdruck hat einen Wert und einen Typ. Der Typ bestimmt, wie eine Operation funktioniert. Eine Ganzzahldivision kann zum Beispiel einen gebrochenen Teil des Ergebnisses nicht erhalten, selbst wenn du das Ergebnis später in einer Gleitkommavariablen speicherst.

Quotienten und Divisionsreste

+, - und * addieren, subtrahieren und multiplizieren. / dividiert und % liefert einen ganzzahligen Rest. Multiplikation, Division und Restbildung binden stärker als Addition und Subtraktion.

Sind beide Divisionsoperanden Ganzzahlen, schneidet C den Quotienten in Richtung null ab. 17 / 5 ist 3, -7 / 2 ist −3 und 7 / -3 ist −2. Der Rest erfüllt die Divisionsgleichung: Multiplizierst du den Quotienten mit dem Divisor und addierst den Rest, erhältst du den Dividenden. Daher ist -7 % 2 gleich −1 und 7 % -3 gleich 1. Ein Rest ungleich null hat das Vorzeichen des Dividenden.

Um auf eine gerade Zahl zu prüfen, verwende n % 2 == 0. Um bei positiven oder negativen Ganzzahlen auf eine ungerade Zahl zu prüfen, verwende n % 2 != 0; eine Prüfung auf genau 1 erfasst negative ungerade Werte nicht. % benötigt ganzzahlige Operanden; <math.h> stellt fmod für Gleitkommareste bereit. Ein ganzzahliger Divisor von null macht sowohl / als auch % undefiniert. Prüfe ihn daher vor der Operation.

Für 135 Minuten ergibt 135 / 60 zwei volle Stunden und 135 % 60 ergibt 15 verbleibende Minuten. Bei einer positiven dezimalen Ganzzahl ermittelt % 10 ihre letzte Ziffer und / 10 entfernt diese Ziffer: 4096 ergibt 6 und 409.

Erhalte Nachkommateile, wenn du sie brauchst
int correct = 45, total = 60;
double percent = 100.0 * correct / total;

Die Gleitkommakonstante macht die Multiplikation zu einer Gleitkommaoperation, und die Division ergibt 75,0. In correct / total * 100 ist die erste Operation eine Ganzzahldivision und ergibt null. Durch Multiplikation dieser null lässt sich der verlorene Nachkommateil nicht zurückgewinnen. Die vorherige ganzzahlige Multiplikation correct * 100 / total ergibt hier 75, sofern das Produkt darstellbar ist.

Einen neuen Wert speichern

Eine Zuweisung wertet ihre rechte Seite aus und speichert den umgewandelten Wert links. Der Zuweisungsausdruck hat diesen gespeicherten Wert, sodass a = b = 0; beide Variablen auf null setzt, wobei von rechts gruppiert wird.

Zusammengesetzte Zuweisungen verbinden eine Berechnung und das Speichern: x += 5 addiert fünf zu x, und x /= 2 dividiert x unter Verwendung seines aktuellen Typs durch zwei. Der gesamte rechte Operand gehört zur Berechnung. Ist x gleich 4, macht x *= 2 + 1 daraus 12, weil mit drei multipliziert wird.

Präfix- und Postfixaktualisierungen

++ addiert eins und -- subtrahiert eins. Als eigenständige Anweisungen haben i++; und ++i; dieselbe abschließende Wirkung. Innerhalb eines größeren Ausdrucks liefert die Postfixform den alten und die Präfixform den neuen Wert.

AnfangAnweisungEndwerte
a = 5y = a++;y = 5, a = 6
a = 5y = ++a;y = 6, a = 6

Halte Änderungen so, dass ihre Reihenfolge leicht erkennbar ist. i = i++ + 1; und printf("%d %d\n", n, n++); enthalten nicht sequenzierte, widersprüchliche Verwendungen derselben Variablen und führen zu undefiniertem Verhalten. Trenne Lesen, Aktualisieren und Ausgeben in einzelne Anweisungen, wenn ihre Reihenfolge wichtig ist.

Wichtige Erkenntnisse

  • Die Ganzzahldivision schneidet in Richtung null ab.
  • Ein ganzzahliger Divisionsrest ungleich null hat das Vorzeichen des Dividenden.
  • Verwende vor der Division einen Gleitkommaoperanden, wenn du einen Nachkommateil benötigst.
  • Die Postfixform liefert den alten Wert; die Präfixform liefert den aktualisierten Wert.
  • Setze widersprüchliche Lesezugriffe und Aktualisierungen in getrennte Anweisungen.

4.2 Typen werden vor dem Speichern eines Ergebnisses bestimmt

C wandelt Werte bei der Anwendung jedes Operators um. Die Zielvariable erhält das fertige Ergebnis; sie greift nicht nachträglich in den Ausdruck ein und ändert frühere Operationen.

Vor der Division umwandeln
int sum = 7, count = 2;
double a = sum / count;
double b = (double)sum / count;
double c = (double)(sum / count);

a wird zu 3,0: Die beiden ganzzahligen Operanden ergeben die Ganzzahl 3, die anschließend umgewandelt wird. b wird zu 3,5: Die explizite Typumwandlung wandelt sum vor der Division um, sodass auch count in double umgewandelt wird. c wird zu 3,0: Seine explizite Typumwandlung steht außerhalb der Division und erfolgt, nachdem der Nachkommateil verworfen wurde.

Dieselbe Regel gilt innerhalb eines längeren Ausdrucks. 7 / 2 * 2.0 ergibt 6,0, weil das erste Ergebnis die Ganzzahl 3 ist. 7 / 2.0 * 2 ergibt 7,0. Gehe die Operatoren in ihrer Gruppierungsreihenfolge durch und notiere den Typ jedes Zwischenwerts.

Einen gemeinsamen arithmetischen Typ finden

Ist einer der Operanden ein Gleitkommawert, wandelt die Arithmetik im Allgemeinen in den breitesten beteiligten Gleitkommatyp um: long double, dann double, dann float. Bei Ganzzahlen durchlaufen kleine Typen zuerst die Ganzzahlerweiterung. Auf Plattformen mit 8-Bit-char, 16-Bit-short und 32-Bit-int sind alle Werte der kleineren Typen in int darstellbar, sodass diese Typen zu int erweitert werden.

Bei unsigned char a = 200, b = 100; ist a + b auf einem System mit 8-Bit-Bytes und 32-Bit-int die Ganzzahl 300. Während der Addition erfolgt bei 255 kein Wraparound. Das Speichern von 300 in einem vorzeichenlosen 8-Bit-Zeichentyp reduziert den Wert auf 44. Die Erweiterung kann auch einen Überlauf bei vorzeichenbehafteten Ganzzahlen erzeugen: Zwei unsigned-short-Werte von 50.000 werden als int multipliziert, aber 2.500.000.000 überschreitet einen 32-Bit-INT_MAX.

Bei vorzeichenbehafteten und vorzeichenlosen Typen bestimmen Rang und darstellbarer Wertebereich den gemeinsamen Typ. int mit unsigned int wird in einen vorzeichenlosen Typ umgewandelt. Ein vorzeichenbehafteter Typ höheren Rangs kann dafür sorgen, dass die Operation vorzeichenbehaftet bleibt, wenn er jeden Wert des vorzeichenlosen Typs darstellen kann; andernfalls kann dennoch vorzeichenlose Arithmetik entstehen.

Die Falle bei vorzeichenbehafteten und vorzeichenlosen Werten

Bei einer vorzeichenlosen 32-Bit-Ganzzahl ist -1 < 0u falsch. Die vorzeichenbehaftete −1 wird vor dem Vergleich in den vorzeichenlosen Wert 4.294.967.295 umgewandelt. Ebenso ergibt 2u - 10 den Wert 4.294.967.288. Auch ein sizeof-Ergebnis ist vorzeichenlos, was beim Vergleich mit einer möglicherweise negativen Anzahl wichtig ist. Aktiviere Warnungen und verwende für zusammengehörige Größen kompatible Typen.

Umwandlungen beim Speichern können Informationen verlieren

Eine Umwandlung von Gleitkommawerten in Ganzzahlen schneidet in Richtung null ab. Der Ganzzahlanteil muss darstellbar sein, sonst ist das Verhalten undefiniert. Eine in einen vorzeichenlosen N-Bit-Typ umgewandelte Ganzzahl wird modulo 2ᴺ reduziert; −1 wird zum größten vorzeichenlosen Wert. Unter C17 liefert eine Ganzzahlkonvertierung außerhalb des Wertebereichs in einen vorzeichenbehafteten Typ ein implementierungsdefiniertes Ergebnis oder löst ein implementierungsdefiniertes Signal aus. Diese Umwandlung unterscheidet sich vom undefinierten arithmetischen Überlauf bei vorzeichenbehafteten Ganzzahlen.

Auch Zeichenkonstanten nehmen an arithmetischen Operationen teil. '7' - '0' ergibt 7, weil C aufeinanderfolgende Codes für Dezimalziffern garantiert. In ASCII folgen auch die Buchstaben aufeinander, aber gehe nicht für jeden Zeichensatz davon aus. Bei Ganzzahlformeln kann eine Multiplikation vor der Division nur dann nützliche Informationen erhalten, wenn das Produkt in seinem Berechnungstyp darstellbar ist.

Wichtige Erkenntnisse

  • Der Zieltyp ändert frühere arithmetische Operationen nicht.
  • Wandle einen Operanden vor der Operation explizit um, die einen breiteren Typ oder einen Gleitkommatyp benötigt.
  • Kleine Ganzzahltypen werden vor arithmetischen Operationen erweitert.
  • Gemischte vorzeichenbehaftete und vorzeichenlose Operanden können Vergleiche und Subtraktionen verändern.
  • Für Konvertierungen außerhalb des Wertebereichs und arithmetische Überläufe bei vorzeichenbehafteten Ganzzahlen gelten unterschiedliche Regeln.
  • Achte auf Zwischentypen ebenso wie auf Endwerte.

4.3 Bedingungen und Bitmuster

Eine Bedingung prüft, ob etwas wahr ist. Eine bitweise Operation arbeitet auf den einzelnen Bits einer Ganzzahl. C stellt für diese Aufgaben unterschiedliche Operatoren bereit, auch wenn ihre Zeichen ähnlich aussehen.

Werte vergleichen und Prüfungen verknüpfen

Die Vergleiche <, >, <=, >=, == und != liefern einen int: 1 für wahr und 0 für falsch. In einer Bedingung ist null falsch und jeder Wert ungleich null wahr. Somit prüft if (count), ob count ungleich null ist. == vergleicht zwei Werte; = weist einen neuen zu.

&& bedeutet, dass beide Bedingungen wahr sein müssen, || bedeutet, dass mindestens eine wahr sein muss, und ! kehrt den Wahrheitswert um. Sie geben 0 oder 1 zurück. Ein Bereich von 1 bis einschließlich 8 benötigt zwei Vergleiche:

height >= 1 && height <= 8

1 <= height <= 8 drückt diesen Bereich in C nicht aus. Der Ausdruck wird als (1 <= height) <= 8 gruppiert, sodass der zweite Vergleich 0 oder 1 erhält und immer wahr ist. Um auf Werte außerhalb des Bereichs zu prüfen, schreibe height < 1 || height > 8.

Die Negation einer zusammengesetzten Bedingung verändert beide Teile: !(a && b) entspricht !a || !b und !(a || b) entspricht !a && !b. Das sind die de Morganschen Regeln. Sie erklären, warum für Werte außerhalb eines Intervalls ein „oder“ nötig ist, während für Werte innerhalb ein „und“ nötig ist.

Eine Schutzbedingung vor die riskante Operation setzen
if (d != 0 && n / d > 2) {
    printf("Above two\n");
}

&& wertet zuerst seine linke Seite aus. Ist sie falsch, wird die rechte Seite übersprungen. || überspringt seine rechte Seite, wenn die linke Seite wahr ist. Diese Kurzschlussauswertung ermöglicht es einer Schutzbedingung, eine spätere Operation abzusichern. Würdest du die beiden obigen Teile umkehren, erfolgte die Division vor der Prüfung des Divisors. Bei vorzeichenbehafteten Ganzzahlen gibt es ein separates Problem mit dem Wertebereich, wenn INT_MIN auf einem System mit Zweierkomplement durch −1 dividiert wird; prüfe auch dieses Wertepaar, wenn beliebige Werte möglich sind.

Ein Schaltjahrtest verbindet Teilbarkeit und Ausnahmen: (y % 4 == 0 && y % 100 != 0) || y % 400 == 0. Das Jahr 2000 besteht den Test auf 400; 1900 besteht ihn nicht, weil die Ausnahme für Jahrhundertjahre gilt.

Bit für Bit arbeiten

Das bitweise & behält Bits, die in beiden Operanden gesetzt sind; | behält Bits, die in mindestens einem gesetzt sind; ^ behält Bits, die sich unterscheiden; ~ invertiert Bits. Mit 12 (1100) und 10 (1010) ergeben sich für &, | und ^ die Werte 8, 14 und 6.

Bitverschiebungen verschieben Bits. Für nicht negative Werte, bei denen das Ergebnis darstellbar ist, multipliziert x << k mit 2ᵏ, und x >> k verwirft k niederwertige Bits, dividiert also durch 2ᵏ und verwirft den Rest. Verwende für Bitoperationen vorzeichenlose Typen. Unter C17 ist eine Verschiebung um eine negative Anzahl oder um mindestens die Breite des erweiterten Typs undefiniert; die Linksverschiebung eines negativen vorzeichenbehafteten Werts ist undefiniert, und seine Rechtsverschiebung ist implementierungsdefiniert.

Bitpositionen beginnen bei null. flags | (1u << 3) setzt Bit 3; flags & ~(1u << 3) löscht es; (flags >> 3) & 1u liest es aus. 1 & 2 ist 0, weil kein Bit in beiden Werten gesetzt ist, während 1 && 2 gleich 1 ist, weil beide Werte ungleich null sind. Bitweise Operatoren werten beide Operanden aus und können logische Schutzbedingungen nicht ersetzen.

Wichtige Erkenntnisse

  • Vergleiche und logische Operatoren liefern 0 oder 1.
  • Eine Bereichsprüfung verbindet zwei Vergleiche mit &&.
  • Schutzbedingungen mit Kurzschlussauswertung müssen vor der geschützten Operation stehen.
  • Verwende logische Operatoren für Wahrheitswerte und bitweise Operatoren mit vorzeichenlosen Werten für Masken.
  • Bitpositionen beginnen bei null; die Anzahl der Verschiebungen muss innerhalb der Breite des erweiterten Typs liegen.

4.4 Ausdrücke gruppieren und Seiteneffekte ordnen

Zwei Fragen helfen dir beim Lesen eines Ausdrucks: Welche Operanden gehören zu welchem Operator, und wann erfolgen Operationen mit Seiteneffekten? Operatorpriorität und Assoziativität beantworten die Frage nach der Gruppierung. Sie legen im Allgemeinen nicht die Reihenfolge der Operandenauswertung fest.

Beachte die Operatorpriorität

Operatoren, die in dieser Tabelle höher stehen, binden stärker. Jede Zeile steht unter der vorherigen; die durch „dann“ getrennten Operatoren haben eigene Stufen.

Von hoch nach niedrigOperatoren
Postfixx++, x--, Aufrufe
Unär++x, --x, +, -, !, ~, explizite Typumwandlungen, sizeof, Adressoperator &
Multiplikativ*, /, %
Additiv+, -
Bitverschiebung<<, >>
Vergleich<, <=, >, >=
Gleichheit==, !=
Bitweise&, dann ^, dann `
Logisch&&, dann `
Bedingt?:
Zuweisung=, += und andere zusammengesetzte Zuweisungen
Komma,

Klammern setzen die Gruppierung außer Kraft. 2 + 3 * 4 ist 14; (2 + 3) * 4 ist 20.

Assoziativität bei gleichen Stufen nutzen

Die meisten binären arithmetischen Operatoren gruppieren von links nach rechts. 10 - 4 - 3 bedeutet (10 - 4) - 3, also 3. 100 / 10 / 2 ergibt 5. Zuweisungen gruppieren von rechts nach links, sodass a = b = 0 in beiden Variablen null speichert. Die Gruppierung beschreibt die Struktur des Ausdrucks; sie legt keine allgemeine Ausführungsregel von links nach rechts fest.

Verfolge für int a = 3, b = 4, c = 5; den Ausdruck a + b * c > 20 && c % 2 == 1. Die Multiplikation ergibt 20, die Addition 23 und 23 > 20 ergibt 1. Die Restbildung ergibt 1 und ihre Gleichheitsprüfung ergibt 1. Das abschließende 1 && 1 ist 1.

Bitprüfungen und Zuweisungen mit Klammern absichern

x & 1 == 0 bedeutet x & (1 == 0) und ergibt daher immer null. Schreibe (x & 1) == 0, um das niederwertigste Bit zu prüfen. Auch die Addition bindet stärker als die Bitverschiebung: 1 << 2 + 1 ist 1 << 3, also 8.

Die Zuweisung bindet schwächer als der Vergleich. a = b == c speichert das Vergleichsergebnis 0 oder 1 in a. In while ((ch = getchar()) != '\n') bewirken die inneren Klammern, dass die Zuweisung zuerst das Zeichen erhält. Ohne sie würde ch ein Vergleichsergebnis erhalten.

Einen Wert auswählen oder zwei Aktionen sequenzieren

condition ? first : second wertet die Bedingung und dann nur den ausgewählten Wertausdruck aus. max = (a > b) ? a : b; wählt einen Wert; ein if kann einen Block von Aktionen auswählen. Der Kommaoperator wertet zuerst seinen linken Ausdruck aus und liefert den Wert seines rechten Ausdrucks. Er kann zwei Aktualisierungen in einem for-Kopf koordinieren. Kommas zwischen Funktionsargumenten sind Trennzeichen und legen diese Reihenfolge nicht fest.

Seiteneffekte trennen

C lässt viele Reihenfolgen von Operanden und Argumenten offen. n++ * n++ enthält nicht sequenzierte Änderungen an derselben Variablen und führt daher zu undefiniertem Verhalten. Zusätzliche Klammern beheben die fehlende Sequenzierung nicht. Verwende getrennte Anweisungen, wenn eine Aktion vor einer anderen erfolgen muss. &&, ||, ?: und der Kommaoperator haben bestimmte Sequenzierungsregeln; die gewöhnliche arithmetische Operatorpriorität bietet keine solche Garantie.

Wichtige Erkenntnisse

  • Die Operatorpriorität bestimmt die Gruppierung; sie bestimmt im Allgemeinen nicht die Auswertungsreihenfolge.
  • Auf einer arithmetischen Stufe wird gewöhnlich von links nach rechts gruppiert.
  • Setze ein bitweises Ergebnis in Klammern, bevor du es vergleichst.
  • Der bedingte Operator wertet nur den ausgewählten Wertausdruck aus.
  • Der Kommaoperator sequenziert Operanden; Kommas zwischen Argumenten tun das nicht.
  • Trenne widersprüchliche Aktualisierungen, um undefiniertes Verhalten zu vermeiden.
5

Den Programmablauf mit Bedingungen und Schleifen steuern

Der Programmablauf bestimmt, welche Anweisungen wie oft ausgeführt werden. Nutze Bedingungen, um einen Zweig auszuwählen, exakte ganzzahlige Fälle für den Einstieg in ein switch und Schleifen, um eine Aufgabe zu wiederholen, bis ihre Abbruchregel erfüllt ist. Ablaufprotokolle machen die Reihenfolge sichtbar: Anfangswerte, Prüfungen, Anweisungen im Rumpf und Aktualisierungen. Achte besonders auf Eingaben an den Grenzen und darauf, wohin break oder continue innerhalb verschachtelter Konstrukte führen, wo eine kleine Syntaxänderung den gesamten Ablauf verändern kann.

5.1 Den richtigen Zweig auswählen

Eine if-Anweisung entscheidet, ob eine Aktion ausgeführt wird. Ihre Bedingung muss in runden Klammern stehen. Null bedeutet falsch; jeder Wert ungleich null bedeutet wahr.

Zeige den Zweig mit geschweiften Klammern
if (temperature < 0) {
    printf("Freezing\n");
} else {
    printf("At or above freezing\n");
}

Der erste Block wird bei einer negativen Temperatur ausgeführt. Bei genau null ist der Vergleich falsch und der else-Block wird ausgeführt. Lies den Grenzwert direkt aus <, <=, > oder >= ab; die Meldung sollte zu dieser Grenze passen.

Ohne geschweifte Klammern wird nur die nächste Anweisung gesteuert:

if (temperature > 30)
    printf("Hot\n");
printf("Drink water\n");

Die zweite Meldung wird bei jeder Temperatur ausgegeben. Einrückung hilft Menschen, ändert aber nicht die Grammatik. Mit geschweiften Klammern kannst du einem Zweig eine zweite Anweisung hinzufügen, ohne sie versehentlich außerhalb zu lassen.

Eine else-if-Kette ordnen

Eine if–else if–else-Kette führt den ersten Zweig aus, dessen Bedingung wahr ist, und überspringt dann den Rest. Beginne bei Punktebereichen mit der höchsten unteren Schranke:

if (score >= 90) {
    grade = 'A';
} else if (score >= 80) {
    grade = 'B';
} else if (score >= 70) {
    grade = 'C';
} else {
    grade = 'F';
}

Eine Punktzahl von 85 besteht die erste Prüfung nicht und besteht die zweite. Eine Punktzahl von 90 besteht die erste. Würdest du score >= 70 zuerst prüfen, gelangte 85 in diesen Zweig und erreichte nie den vorgesehenen B-Zweig. Getrennte if-Anweisungen verhalten sich anders: Jede Bedingung wird unabhängig geprüft, sodass mehrere Blöcke ausgeführt werden können.

Achte auf zwei kleine Syntaxfehler

if (x = 5) weist 5 zu und behandelt diesen Wert ungleich null dann als wahr. Verwende if (x == 5) zum Vergleichen. Bei if (x = 0) erfolgt ebenfalls eine Zuweisung, aber die Bedingung ist falsch. Warnungen helfen, versehentliche Zuweisungen in Bedingungen zu erkennen.

In if (x > 0); ist das Semikolon eine leere gesteuerte Anweisung. Ein danach gesetzter Block wird unabhängig ausgeführt, selbst wenn x negativ ist. Setze kein Semikolon zwischen eine if-Bedingung und den dazugehörigen Block.

Ordne jedes else ausdrücklich zu

Ein else gehört zum nächstgelegenen früheren if, das noch kein else hat. Verwende bei verschachtelten Entscheidungen geschweifte Klammern, um die beabsichtigte Zuordnung klarzustellen. Eine Verschachtelung ist nützlich, wenn fehlgeschlagene Prüfungen unterschiedliche Meldungen benötigen: Prüfe zuerst, ob ein Messwert gültig ist, und dann, ob sein Wert im gewünschten Bereich liegt. Die Verknüpfung beider Prüfungen mit && funktioniert, wenn nur eine gemeinsame Aktion nötig ist.

Ausnahmen vor allgemeinen Regeln prüfen

Schaltjahre bieten ein nützliches Beispiel für eine geordnete Entscheidung. Prüfe zuerst die Teilbarkeit durch 400, die ein Schaltjahr bedeutet. Prüfe als Nächstes die Teilbarkeit durch 100, die bedeutet, dass es kein Schaltjahr ist. Prüfe dann die Teilbarkeit durch 4; andernfalls ist das Jahr kein Schaltjahr. Für 2000 gilt der erste Zweig. Für 1900 gilt der zweite. Eine Prüfung nur auf Teilbarkeit durch vier würde die Ausnahme für Jahrhundertjahre übersehen. Die Reihenfolge ist wichtig, weil allgemeine Regeln Fälle mit einer spezielleren Ausnahme einschließen können.

Wichtige Erkenntnisse

  • Eine else-if-Kette führt nur ihren ersten wahren Zweig aus.
  • Geschweifte Klammern steuern, welche Anweisungen zu einem Zweig gehören.
  • == vergleicht; = weist zu.
  • Ein Semikolon nach einer if-Bedingung erzeugt einen leeren Zweig.

5.2 Feste Fälle mit switch auswählen

Ein switch wählt anhand eines ganzzahligen Werts einen Einstiegspunkt. Das ist nützlich für ein Befehlszeichen oder eine Menünummer mit mehreren festen Alternativen. Die Ausführung kann anschließend in spätere Fälle übergehen, wenn du sie nicht stoppst.

Einen Fall auswählen und verlassen
switch (choice) {
    case 1:
        printf("Start\n");
        break;
    case 2:
        printf("Pause\n");
        break;
    default:
        printf("Unknown choice\n");
        break;
}

Ist choice gleich 2, beginnt das Programm bei case 2, gibt Pause aus und verlässt das switch bei break. Passt kein Fall, beginnt die Ausführung bei default. Ohne default überspringt ein nicht passender Wert das switch.

Der steuernde Ausdruck muss einen Ganzzahltyp haben, dazu gehören auch char und Aufzählungstypen. Jeder Fall muss ein eindeutiger ganzzahliger konstanter Ausdruck sein, etwa 3, 'q' oder ein geeigneter #define-Name. In C17 ist eine const int-Variable kein ganzzahliger konstanter Ausdruck für eine case-Marke. Gleitkommawerte und Zeichenketten können keine steuernden Werte eines switch sein.

Fall-through verstehen

case-Marken kennzeichnen Einstiegspunkte, keine getrennten Blöcke, die automatisch enden. Entfernst du oben das erste break, gibt die Auswahl 1 sowohl Start als auch Pause aus. Das Programm prüft nicht, ob choice gleich 2 ist, bevor es diesen Fall weiter ausführt. Die Ausführung stoppt erst bei einer Anweisung, die den Programmablauf an eine andere Stelle überträgt, oder am Ende des switch.

Mehrere Marken können eine Aktion gemeinsam nutzen:

case 'y':
case 'Y':
    printf("Yes\n");
    break;

Beide Zeichen führen zu derselben Aktion. Wenn ein Fall absichtlich eigene Anweisungen ausführt und in einen anderen übergeht, erkläre die Absicht mit einem Kommentar. C23 bietet außerdem [[fallthrough]];. Warnungen können helfen, ein fehlendes break zu finden, aber bei absichtlich gruppierten Marken muss die Aktion nicht wiederholt werden.

Gib falllokalen Variablen einen Block

Unter C17 kann eine Deklaration nicht direkt auf eine case-Marke folgen. Setze die Anweisungen des Falls in geschweifte Klammern:

case 3: {
    int delay = 5;
    printf("%d\n", delay);
    break;
}

Der Block hält delay außerdem lokal für diesen Fall. C23 lockert die Einschränkung für Marken vor Deklarationen, aber geschweifte Klammern machen den Gültigkeitsbereich weiterhin deutlich.

Zwischen switch und if wählen

Verwende switch für exakte ganzzahlige Alternativen. Verwende if–else if für Ungleichungen, Bedingungen mit mehreren Variablen oder Gleitkommamesswerte. Gleich breite Punktebereiche lassen sich manchmal durch Ganzzahldivision in exakte Fälle umwandeln. Bei Punktzahlen von 0 bis 100 ergibt score / 10 für 85 den Wert 8; die Fälle 9 und 10 können den höchsten Bereich gemeinsam nutzen. Prüfe diesen Wertebereich, damit negative oder zu hohe Punktzahlen nicht ohne Meldung einem Bereich zugeordnet werden.

Ein switch innerhalb einer Schleife

Ein break verlässt das nächstgelegene umschließende switch oder die nächstgelegene umschließende Schleife. Innerhalb eines switch, das selbst in einer Schleife liegt, beendet das break nur das switch. Um die Menüschleife zu beenden, setze eine Variable, die von ihrer Bedingung geprüft wird, oder kehre aus main zurück, wenn das Programm enden soll. Ein continue in diesem switch bezieht sich auf die umschließende Schleife und führt zu deren nächster Iteration. Verfolge, auf welches Konstrukt sich jede Ablaufübertragung richtet, bevor du entscheidest, ob das Menü stoppt.

Wichtige Erkenntnisse

  • Ein switch wählt anhand eines ganzzahligen Werts eine Einstiegsmarke aus.
  • Die Ausführung läuft bis zu break, einer anderen Ablaufübertragung oder dem Ende weiter.
  • case-Werte müssen unterschiedliche ganzzahlige konstante Ausdrücke sein.
  • Verwende in C17 einen Block für eine falllokale Deklaration.
  • Das break eines switch beendet nicht zugleich eine umschließende Schleife.

5.3 Wiederholen, bis die Daten das Ende bestimmen

Eine while-Schleife prüft vor jedem Durchlauf eine Bedingung. Eine do–while-Schleife prüft nach dem Rumpf und läuft daher mindestens einmal. Wähle die Form, bei der die Prüfung dort steht, wo das Programm sie benötigt.

Eine bedingungsgesteuerte Berechnung nachvollziehen
int n = 4096, sum = 0;
while (n > 0) {
    sum += n % 10;
    n /= 10;
}

n % 10 ermittelt eine Ziffer und n /= 10 entfernt sie. Die Werte nach den Durchläufen sind:

Durchlaufsumn
16409
21540
3154
4190

Die nächste Bedingung ist falsch, sodass die Schleife mit einer Ziffernsumme von 19 endet. Beginnt n bei null, wird der Rumpf kein einziges Mal ausgeführt und die Summe bleibt null. Auch negative Eingaben überspringen diese Schleife, sodass ein Programm, das sie akzeptiert, eine festgelegte Behandlung benötigt.

Eine Zählschleife setzt einen Zähler, prüft und aktualisiert ihn. Andere Schleifen machen Fortschritt, indem sie Daten lesen oder break erreichen. Beim wiederholten ganzzahligen Halbieren von 100 ergeben sich die Werte 50, 25, 12, 6, 3 und 1: Es sind sechs Durchläufe nötig, um eins zu erreichen.

Die Eingabe steuert die Wiederholung
int x, sum = 0, count = 0;
while (scanf("%d", &x) == 1 && x != 0) {
    sum += x;
    count++;
}
if (count != 0) {
    printf("%.2f\n", (double)sum / count);
}

Bei der Eingabe „5 8 3 0“ beendet der Wächterwert null die Schleife und wird nicht addiert. Die Summe ist 16, die Anzahl ist 3 und das angezeigte arithmetische Mittel ist 5.33. Die Prüfung der zurückgegebenen Anzahl sichert die Verwendung von x ab; die Prüfung der Anzahl schützt die Division, falls der erste Wert null ist. Diese Berechnung setzt außerdem voraus, dass Eingaben und Summen in int darstellbar sind.

Ohne die Prüfung auf den Wächterwert stoppt while (scanf("%d", &x) == 1) bei einer fehlgeschlagenen numerischen Konvertierung oder am Eingabeende. In einem Terminal signalisiert unter Linux und macOS gewöhnlich Strg+D das Eingabeende; unter Windows verwende Strg+Z und anschließend Enter.

Vor der Prüfung einmal ausführen
int height;
do {
    printf("Height (1-8): ");
    fflush(stdout);
    if (scanf("%d", &height) != 1) {
        return 1;
    }
} while (height < 1 || height > 8);

Die Aufforderung und das Lesen erfolgen vor der Bereichsprüfung. Bei den Werten null und neun wird wiederholt; die Werte eins und acht werden akzeptiert. Das Semikolon nach while (...) ist in dieser Form erforderlich. Die Konvertierungsprüfung beendet dieses Beispiel bei nicht numerischer Eingabe; die Bereichsprüfung allein kann eine fehlgeschlagene Konvertierung nicht behandeln.

Auf jedem fortgesetzten Pfad Fortschritt machen

break verlässt sofort die innerste Schleife. continue überspringt den Rest des Rumpfs und kehrt in einer while-Schleife zur Bedingung zurück. Steht eine Aktualisierung unter einem continue, wird auch sie übersprungen. Eine Schleife kann dann endlos denselben Wert prüfen.

Stelle vor der Ausführung einer Schleife fest, was sich ändert und warum die Abbruchbedingung schließlich erreicht wird. Ein bewusst gesetztes while (1) kann durch break enden; eine fehlende Zähleraktualisierung ist eine andere Situation. Strg+C stoppt ein Programm, das in einem Terminal festhängt. Teste die kleinste Eingabe und den Wert an der Abbruchgrenze, um Fehler durch null oder einen zusätzlichen Durchlauf zu erkennen.

Wichtige Erkenntnisse

  • while prüft zuerst; do–while prüft nach mindestens einem Durchlauf.
  • Ein Wächterwert beendet die Eingabe, ohne Teil der Daten zu werden.
  • Initialisiere Akkumulatoren außerhalb der Schleife.
  • Auf jedem fortgesetzten Pfad muss die Schleife der Abbruchbedingung näherkommen.
  • Ein continue in while kann eine spätere Aktualisierung überspringen.
  • Prüfe vor der Berechnung eines arithmetischen Mittels, ob keine Datenwerte vorliegen.

5.4 Durchläufe zählen und Schleifen kombinieren

Eine for-Schleife hält ein Zählmuster in einem Kopf zusammen. Verschachtelte Schleifen wiederholen für jeden äußeren Durchlauf eine innere Aufgabe. Beides lässt sich leichter prüfen, wenn du die tatsächlichen Zählerwerte auflistest und jeden Ausstieg bestimmst.

Den Kopf der Reihe nach verfolgen
for (int i = 0; i < 3; i++) {
    printf("%d ", i);
}

Die Initialisierung läuft einmal. Dann wird die Bedingung geprüft, der Rumpf bei wahrer Bedingung ausgeführt und vor der nächsten Prüfung die Aktualisierung ausgeführt. Die Ausgabe ist 0 1 2 . Die im Kopf deklarierte Variable ist lokal für die Schleife; deklariere sie vor der Schleife, wenn du ihren Endwert danach benötigst.

Für ein nicht negatives n läuft i = 0; i < n; i++ n-mal und durchläuft null bis n − 1. Mit i <= n läuft die Schleife einmal zusätzlich. Für eine positive Schrittweite s von a bis einschließlich b beträgt die Anzahl bei Ganzzahldivision (b - a) / s + 1, sofern die arithmetischen Ergebnisse und Zähleraktualisierungen darstellbar sind. Von 2 bis 20 in Schritten von 3 lauten die Werte 2, 5, 8, 11, 14, 17, 20: sieben Durchläufe.

Den Akkumulator außerhalb halten
int total = 0;
for (int i = 1; i <= 100; i++) {
    total += i;
}

Die endgültige Summe ist 5050. Die Initialisierung muss einmal vor der Schleife erfolgen. Verschiebst du int total = 0; in den Rumpf, entsteht bei jedem Durchlauf eine neue Summe und die bisherige Arbeit geht verloren.

Zähler wählen, die das Ende erreichen können

Ein Semikolon direkt nach dem for-Kopf macht den Rumpf leer. Ein folgender Block wird dann nach der Schleife einmal ausgeführt. Verwende direkt an den Kopf anschließende geschweifte Klammern, um den Rumpf sichtbar zu machen.

Ein vorzeichenloser rückwärts zählender Zähler mit i >= 0 kann so nicht stoppen: Vorzeichenlose Werte sind immer nicht negativ, und das Dekrementieren von null erzeugt einen Wraparound zum Maximum. Ein Gleitkommazähler, der wiederholt 0.1 addiert, kann den exakten Wert 1.0 überspringen, sodass x != 1.0 als Abbruchregel unzuverlässig ist. Zähle mit einer Ganzzahl und berechne im Rumpf einen Messwert wie x = i / 10.0.

Mit verschachtelten Schleifen Zeilen bilden

Für jeden äußeren Durchlauf startet die innere Schleife und durchläuft ihre eigene Folge. Drei äußere und vier feste innere Durchläufe führen den inneren Rumpf zwölfmal aus. Hängt die innere Schranke von der Zeile ab, zähle jede Zeile gesondert.

int height = 3;
for (int row = 1; row <= height; row++) {
    for (int s = 0; s < height - row; s++) printf(" ");
    for (int h = 0; h < row; h++) printf("#");
    printf("\n");
}

Die Zeilen enthalten zwei Leerzeichen und ein Rautenzeichen, ein Leerzeichen und zwei Rautenzeichen, dann drei Rautenzeichen. Der Zeilenumbruch gehört hinter die inneren Schleifen, sodass jeder äußere Durchlauf eine Zeile erzeugt.

Arbeit überspringen oder vorzeitig abbrechen

continue in einer for-Schleife führt zur Aktualisierung und dann zur nächsten Prüfung. Eine Schleife, die nur ungerade Werte von 1 bis 9 addiert, kann gerade Werte überspringen und mit 25 enden. break verlässt sofort die innerste Schleife. Bei verschachtelten Schleifen stoppt ein inneres break die äußere Schleife nicht.

Eine for-Schleife ist bei einem bekannten Zählerbereich oft die klarste Wahl; while eignet sich für datengesteuerte Wiederholungen und do–while für Aufgaben, die vor der Prüfung erfolgen müssen. Beim Umschreiben einer for-Schleife in eine while-Schleife ist bei continue besondere Sorgfalt nötig, weil die Aktualisierung auch auf diesem Pfad erfolgen muss.

Wichtige Erkenntnisse

  • Eine for-Schleife führt die Initialisierung einmal und die Aktualisierung nach jedem fortgesetzten Durchlauf aus.
  • Prüfe die durchlaufenen Werte, um Off-by-one-Fehler zu erkennen.
  • Verwende Ganzzahlzähler mit ausreichendem Wertebereich.
  • Verschachtelte Schleifen starten die innere Folge für jeden äußeren Durchlauf erneut.
  • Ein inneres break verlässt nur die innere Schleife.
  • Ein continue in for führt die Aktualisierung im Kopf weiterhin aus.
  • Initialisiere eine laufende Summe außerhalb des wiederholten Rumpfs.
6

Ganzzahlüberläufe erkennen und verhindern

Endliche Ganzzahlbereiche beeinflussen Berechnungen auch dann, wenn das Endergebnis klein genug erscheint. Unterscheide Wraparound bei vorzeichenlosen Ganzzahlen, Überlauf bei vorzeichenbehafteten Ganzzahlen und bei der Konvertierung verlorene Werte. Untersuche dann die Typen der Zwischenoperationen. Vorbeugung beginnt vor einer riskanten Berechnung: Erweitere einen Operanden, weise Schranken nach oder verwende eine überlaufgeprüfte Operation. Wertebereiche von Zählern und Grenzwerttests helfen dir, Fehler zu finden, die bei gewöhnlichen kleinen Eingaben und kurzen Testläufen unentdeckt bleiben können.

6.1 Was geschieht, wenn eine Ganzzahl nicht darstellbar ist

Der Wertebereich einer Ganzzahl ist endlich. Ein mathematisches Ergebnis kann korrekt und dennoch für den zur Berechnung verwendeten C-Typ zu groß sein. Die Folgen hängen davon ab, ob die Operation vorzeichenlos, vorzeichenbehaftet oder eine Konvertierung ist.

Vorzeichenlose Arithmetik erzeugt einen Wraparound

Ein vorzeichenloses N-Bit-Ergebnis wird modulo 2ᴺ reduziert. Bei einem 32-Bit-unsigned int ergibt UINT_MAX + 1u null, 0u - 1u ergibt 4.294.967.295 und 3u - 5u ergibt 4.294.967.294. Das Ergebnis ist definiert, kann aber für die Aufgabe falsch sein: Fünf Artikel von einem Bestand von drei abzuziehen, sollte nicht Milliarden Artikel ergeben.

Für uint8_t b = 255; speichert b = b + 1; den Wert null. Auf einem typischen System mit 32-Bit-int erfolgt die Addition zunächst als int und ergibt 256; die Rückkonvertierung dieses Werts in einen vorzeichenlosen 8-Bit-Typ ergibt null. Eine kleine vorzeichenlose Speichervariable bedeutet nicht, dass jede Zwischenoperation diesen kleinen Typ hat.

Arithmetischer Überlauf bei vorzeichenbehafteten Ganzzahlen ist undefiniert

Bei einem 32-Bit-int benötigt die Addition von eins zu INT_MAX den Wert 2.147.483.648. C definiert das vorzeichenbehaftete arithmetische Ergebnis nicht. Die Hardware scheint möglicherweise einen Wraparound zu INT_MIN durchzuführen, aber der Compiler kann unter der Annahme optimieren, dass ein Überlauf bei vorzeichenbehafteten Ganzzahlen nie auftritt. Eine Prüfung wie if (x + 1 < x) kann bei vorzeichenbehafteten Ganzzahlen entfernt werden, weil jede definierte vorzeichenbehaftete Addition von eins einen größeren Wert ergibt.

Berechne nicht zuerst und untersuche dann den Schaden. Eine undefinierte Operation ist bereits geschehen, bevor eine spätere Prüfung ausgeführt wird.

Ein breiterer Zieltyp kann zu spät kommen
int a = 50000, b = 50000;
long long product = a * b;

Die Multiplikation wird als int ausgeführt und läuft daher auf einem System mit 32-Bit-int über. Die Breite des Zieltyps kann das nicht beheben. (long long)a * b ändert den Typ der Operation vor der Multiplikation und ergibt 2.500.000.000.

Fakultäten wachsen schnell: 12! ist 479.001.600 und in einer vorzeichenbehafteten 32-Bit-Ganzzahl darstellbar; 13! ist 6.227.020.800 und nicht darstellbar. Mit vorzeichenloser 32-Bit-Arithmetik ergibt 13! durch Wraparound 1.932.053.504. Ein vorzeichenbehafteter 64-Bit-long long fasst 20! = 2.432.902.008.176.640.000, aber nicht 21!.

Für Konvertierungen gelten eigene Regeln

Die Umwandlung einer Ganzzahl in einen vorzeichenlosen Typ verwendet eine Reduktion modulo. Unter C17 ergibt die Umwandlung einer Ganzzahl außerhalb des Wertebereichs in einen vorzeichenbehafteten Typ ein implementierungsdefiniertes Ergebnis oder Signal. Die Umwandlung eines Gleitkommawerts, dessen abgeschnittener Ganzzahlanteil im ganzzahligen Zieltyp nicht darstellbar ist, führt zu undefiniertem Verhalten. Auch das Einlesen einer Zahl außerhalb des Wertebereichs durch eine numerische scanf-Konvertierung ist undefiniert.

Fehler beim Wertebereich können große Folgen haben

Der erste Flug der Ariane 5 am 4. Juni 1996 ging verloren, nachdem ein Gleitkommawert in der Trägheitsreferenzsoftware nicht in einer vorzeichenbehafteten 16-Bit-Ganzzahl darstellbar war. Das Programm war in Ada geschrieben; die Lehre, Wertebereiche bei Konvertierungen zu prüfen, gilt auch für C.

Ein vorzeichenbehafteter 32-Bit-Sekundenzähler, der zur Unix-Epoche beginnt, erreicht am 19. Januar 2038 um 03:14:07 Uhr UTC den Wert 2.147.483.647. Systeme, die diese Zeitdarstellung verwenden, benötigen eine breitere. 2015 befasste sich die FAA außerdem mit der Generatorsteuerungssoftware der Boeing 787, die nach 248 Tagen ununterbrochener Stromversorgung überlaufen konnte. Diese Fehlfunktionen zeigen, warum sowohl lange Laufzeiten als auch Zwischenwerte in eine Wertebereichsschätzung gehören.

Für Gleitkommawerte gelten andere Grenzen: Gängige IEEE-Systeme können Unendlich erzeugen, und ein float kann die Genauigkeit für ganze Zahlen verlieren, bevor er sein Maximum erreicht. Ein Wraparound bei vorzeichenlosen Ganzzahlen erzeugt einen gewöhnlichen Zahlenwert; ein Überlauf bei vorzeichenbehafteten Ganzzahlen bietet überhaupt kein verlässliches Ergebnis.

Wichtige Erkenntnisse

  • Vorzeichenlose Arithmetik verwendet eine Reduktion modulo.
  • Ein arithmetischer Überlauf bei vorzeichenbehafteten Ganzzahlen ist undefiniertes Verhalten.
  • Ein breiterer Zieltyp kann eine Zwischenoperation in einem schmaleren Typ nicht korrigieren.
  • Für Ganzzahlkonvertierung, Gleitkommakonvertierung und arithmetischen Überlauf gelten unterschiedliche Regeln.
  • Schätze Werte über die gesamte Betriebsdauer ab.

6.2 Überläufe verhindern, bevor sie auftreten

Beginne mit dem größten Wert, den jede Berechnung erzeugen kann. Berücksichtige Zwischenprodukte und Zähleraktualisierungen, nicht nur das Endergebnis. Wähle dann einen Typ oder eine Vorabprüfung, die jede Operation im Wertebereich hält.

Den Typ für die Berechnung früh erweitern

Eine vorzeichenbehaftete 32-Bit-Ganzzahl fasst etwa 2,1 Milliarden, während ein vorzeichenbehafteter 64-Bit-long long bis zu 9.223.372.036.854.775.807 fasst. Ein Millisekundenzähler erreicht die vorzeichenbehaftete 32-Bit-Grenze nach etwa 24,9 Tagen. Verwende einen 64-Bit-Typ, wenn eine Zeitdauer länger sein kann.

Schreibe (long long)a * b, um den Typ eines Ganzzahlprodukts vor dessen Auswertung zu erweitern. Auch Konstanten sind wichtig: 86400LL * days beginnt die Multiplikation in long long. (long long)(a * b) wandelt erst nach dem Produkt im schmaleren Typ um und kann dessen Überlauf daher nicht verhindern. Auch breitere Typen haben Grenzen; prüfe die größtmöglichen Werte dagegen.

Addition durch Subtraktion prüfen

Binde für int a und int b die Headerdatei <limits.h> ein und prüfe:

if ((b > 0 && a > INT_MAX - b) ||
    (b < 0 && a < INT_MIN - b)) {
    printf("Sum out of range\n");
} else {
    int sum = a + b;
    printf("%d\n", sum);
}

Jede Subtraktion wird durch das Vorzeichen von b abgesichert, sodass die Prüfung selbst darstellbar bleibt. Für 2.000.000.000 plus 200.000.000 ist INT_MAX - b gleich 1.947.483.647 und damit kleiner als a, sodass die Addition abgelehnt wird. Der Fall der Gleichheit ist darstellbar: Wenn a gleich INT_MAX - b ist, ist die Summe genau INT_MAX.

Positive Multiplikation durch Division prüfen

Für positive int-Operanden bedeutet a > INT_MAX / b, dass das Produkt das Maximum überschreiten würde. Die Bedingung vermeidet die Berechnung des Produkts und einen Divisor von null, weil b positiv ist. Bei einer Fakultätsberechnung erkennt die Prüfung vor der Multiplikation von 12! mit 13, dass 479.001.600 größer als 165.191.049 ist. Stoppe vor dieser Multiplikation.

Prüfe bei einem vorzeichenlosen Bestand vor der Subtraktion stock >= requested. Der alleinige Wechsel zu einem vorzeichenlosen Typ verhindert kein unsinniges Ergebnis durch Wraparound.

Riskante Zwischenausdrücke umschreiben

Der Mittelpunkt (low + high) / 2 kann überlaufen, selbst wenn sein Ergebnis darstellbar ist. Wenn 0 <= low <= high gilt und beide Werte int sind, verwende low + (high - low) / 2. Die Differenz ist nicht negativ und liegt im Wertebereich, und das Ergebnis überschreitet high nie. Diese Bedingungen sind wichtig; die Formel ist kein allgemeiner Nachweis für beliebige vorzeichenbehaftete Endpunkte.

Auch Schleifenzähler benötigen einen ausreichenden Wertebereich. Ein 16-Bit-short kann 40000 nicht erreichen, sodass eine Schleife, die damit so weit zählt, nicht wie beabsichtigt enden kann. Verwende einen Zählertyp, der die Schranke und jede abschließende Aktualisierung darstellen kann.

Überlaufgeprüfte Arithmetik und Tests verwenden

C23 stellt <stdckdint.h> mit ckd_add, ckd_sub und ckd_mul bereit. Zum Beispiel speichert ckd_add(&r, a, b) ein Ergebnis und gibt wahr zurück, wenn das mathematische Ergebnis nicht in r darstellbar ist. GCC und clang stellen außerdem __builtin_add_overflow, __builtin_sub_overflow und __builtin_mul_overflow als Compilererweiterungen bereit.

Warnungen erkennen manche konstanten Überläufe und Fehler mit vorzeichenbehafteten und vorzeichenlosen Werten. In GCC oder clang kann -fsanitize=undefined -g einen bei einem Test erreichten Überlauf bei vorzeichenbehafteten Ganzzahlen melden; -ftrapv fordert das Anhalten bei Überläufen vorzeichenbehafteter Addition, Subtraktion und Multiplikation an. Teste null, eins, minus eins, die größte erwartete Eingabe und Werte an und knapp unter den Grenzen. Ein Testwerkzeug prüft die Pfade, die du ausführst. Halte daher die Überlegungen zum Wertebereich im Code fest.

Wichtige Erkenntnisse

  • Erweitere einen Operanden vor einer riskanten Operation.
  • Verwende bei vorzeichenbehafteter Addition Vorabprüfungen, die das Vorzeichen berücksichtigen.
  • Die Divisionsprüfung vor einer Multiplikation setzt positive Operanden voraus.
  • Auch die vorzeichenlose Subtraktion benötigt eine Wertebereichsprüfung.
  • Die sicherere Mittelpunktformel setzt geordnete nicht negative Endpunkte voraus.
  • Überlaufgeprüfte Arithmetik meldet, ob das Ergebnis im Zieltyp darstellbar ist.
  • Grenzwerttests und Sanitizer ergänzen die Überlegungen zum Wertebereich.

Datentypen und Operatoren in C: Beispiele

6 Interaktive Lektionen

1

Eine Begrüßung erstellen und eine fehlgeschlagene Änderung untersuchen

9 Schritte
Ein erstes Programm vom Quellcode bis zur Ausführung verfolgen und dann mithilfe von Compilermeldungen und einem Ablaufprotokoll Probleme beheben.
  1. 1
    EinführungDie Programmerstellung verfolgen

    Ein gespeichertes C-Programm ist Text. Für seine Ausführung wird eine aus diesem Text erzeugte ausführbare Datei benötigt. Halte die Rollen der beiden Dateien auseinander: Eine Bearbeitung ändert den Quellcode, eine erfolgreiche Erstellung ersetzt die ausführbare Datei.

    Du wirst eine kurze Begrüßung erstellen, untersuchen, was in jeder Phase geschieht, und eine fehlgeschlagene Änderung analysieren. Entscheidend ist, das Ergebnis der Erstellung zu prüfen, bevor du einen Programmlauf als Beleg für den neuen Quellcode heranziehst.

  2. 2
    ErklärungDas Grundgerüst des Programms lesen

    Das Programm beginnt bei main:

    #include <stdio.h>
    int main(void)
    {
        printf("Ready\n");
        return 0;
    }
    

    Die Headerdatei stellt die Deklaration von printf bereit. Der Rumpf gibt eine Zeile aus und meldet dann Erfolg zurück. void bedeutet hier, dass main keine Parameter hat; int bezeichnet den Typ seines Rückgabewerts. Geschweifte Klammern umschließen den Rumpf, und einfache Anweisungen enden mit Semikolons.

  3. 3
    BeispielErstellen und ausführen

    Speichere den Quellcode als ready.c. Gib in seinem Ordner ein:

    gcc -std=c17 -Wall -Wextra ready.c -o ready
    ./ready
    

    Die Erstellung wählt C17 und nützliche Warnungen aus und gibt der ausführbaren Datei den Namen ready. Der Programmlauf gibt Ready gefolgt von einem Zeilenumbruch aus. Unter Linux oder macOS wählt ./ den aktuellen Ordner aus. Du kannst an derselben Stelle im Befehl clang verwenden; ausführbare Dateien unter Windows erhalten die Endung .exe.

  4. 4
    VerbindungDie Rolle des Objektcodes erkennen

    Die Vorverarbeitung behandelt die include-Direktive. Die Kompilierung erzeugt anschließend Assemblersprache, und die Assemblierung erzeugt Objektcode. Das Linken löst Verweise auf und stellt die ausführbare Datei fertig; Bibliothekscode kann zur Laufzeit von einer gemeinsam genutzten Bibliothek bereitgestellt werden.

    Die Optionen -E, -S und -c stoppen in jeweils späteren Phasen. Insbesondere stoppt -c bei einer Objektdatei: Der für die ausführbare Datei nötige Linkschritt ist noch nicht abgeschlossen.

  5. 5
    HerausforderungDen nächsten Programmlauf vorhersagen

    Du erstellst das Begrüßungsprogramm und speicherst dann eine Änderung, die „Ready“ in „Begin“ ändert. Du führst die vorhandene ausführbare Datei aus, ohne zu kompilieren. Welcher Text erscheint und wie änderst du ihn?

    ---reveal---

    Sie gibt weiterhin Ready aus. Die ausführbare Datei entspricht der früheren erfolgreichen Erstellung. Kompiliere die gespeicherte Änderung und führe die neue ausführbare Datei aus, um Begin auszugeben. Das bloße Speichern des Quellcodes aktualisiert den Maschinencode nicht.

  6. 6
    BeispielDen ersten Fehler beheben

    Nach einer Änderung fehlt dieser Zeile ein Semikolon:

    printf("Begin\n")
    return 0;
    

    Der Compiler kann auf die erste Zeile oder auf return zeigen, wo er das fehlende Ende der Ausdrucksanweisung bemerkt. Ergänze das Semikolon nach dem Aufruf und erstelle das Programm erneut. Beginne mit dieser ersten Ursache, auch wenn spätere Meldungen andere Zeilen nennen; ein einzelner Syntaxfehler kann das Lesen des folgenden Codes durch den Compiler beeinträchtigen.

  7. 7
    TippWarnungen als nützliche Hinweise behandeln

    Eine Warnung lässt die Erstellung gewöhnlich weiterlaufen, kann aber auf einen schwerwiegenden Fehler hinweisen. printf("%d\n", 2.5); übergibt einen double, wo das Format einen int verlangt, was zu undefiniertem Verhalten führt.

    Behebe die Typabweichung, statt eine beliebige Ausgabe eines Testlaufs zu akzeptieren. Warnungen von -Wall -Wextra helfen, solche Fehler aufzudecken; -Werror lässt sie die Erstellung stoppen. Gib bei einer falschen Berechnung Zwischenwerte aus und vergleiche sie mit einem kleinen Ablaufprotokoll von Hand.

  8. 8
    HerausforderungEine fehlgeschlagene Erstellung von einem falschen Programmlauf unterscheiden

    Nach einer erfolgreichen Erstellung des Begrüßungsprogramms lässt sich eine neue Änderung nicht kompilieren. Die Ausführung von ./ready gibt weiterhin eine Begrüßung aus. Zeigt das, dass die Änderung zulässig ist, und was solltest du als Nächstes untersuchen?

    ---reveal---

    Nein. Die alte ausführbare Datei kann nach einer fehlgeschlagenen Erstellung erhalten bleiben. Untersuche den ersten Compilerfehler einschließlich der vorherigen Quellcodezeile, behebe ihn und erstelle das Programm erfolgreich neu. Führe dann diese neue ausführbare Datei aus und vergleiche ihre Ausgabe mit deiner Vorhersage. Die vorhandene Begrüßung sagt nichts über die fehlgeschlagene Änderung aus.

  9. 9
    ZusammenfassungEinen klaren Korrekturzyklus verwenden

    Beziehe jeden Test auf den Quellcode, aus dem tatsächlich eine ausführbare Datei erstellt wurde. Speichere die Änderung, kompiliere, lies die erste Meldung, behebe ihre Ursache und erstelle das Programm erneut, bevor du es ausführst.

    Wenn das Programm erstellt wird, aber den falschen Wert berechnet, verwende ein kleines Ablaufprotokoll und Eingaben an den Grenzen. Führt eine Operation zu undefiniertem Verhalten, ändere den Code so, dass es nicht auftreten kann; eine normal aussehende Ausgabe ist für diese Operation kein verlässliches Ergebnis.

2

Speicher für Anzahlen und Messwerte auswählen

13 Schritte
Zuweisung und Gültigkeitsbereich nachvollziehen und dann Ganzzahlbereiche und Gleitkommagenauigkeit mit praktischen Typentscheidungen verbinden.

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

  1. 1
    EinführungFragen, was ein Wert benötigt
  2. 2
    BeispielEin kopierter Wert bleibt eine Kopie
  3. 3
    ErklärungNamen innerhalb ihrer Blöcke halten
  4. 4
    HerausforderungEinen verdeckten Namen verfolgen
  5. 5
    ErklärungVor der Auswahl Wertebereiche vergleichen
  6. 6
    BeispielEin Bitmuster und ein Literal lesen
  7. 7
    HerausforderungEinen portablen Wertebereich wählen
  8. 8
    ErklärungErkennen, warum Dezimalbrüche abweichen
  9. 9
    BeispielMit einem erlaubten Fehler vergleichen
  10. 10
    HerausforderungKonvertierung und Rundung unterscheiden
  11. 11
    KernaussageDie Genauigkeit kann vor dem Wertebereich an ihre Grenze stoßen
  12. 12
    HerausforderungDen Typ auf die Aufgabe abstimmen
  13. 13
    ZusammenfassungDie Speicherung auf zwei Arten prüfen
3

Messwerte einlesen und Ergebnisse klar ausgeben

11 Schritte
Formate auf Typen abstimmen, teilweise Eingaben untersuchen und eine geprüfte Messwertberechnung verfolgen.

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

  1. 1
    EinführungTypisierte Werte mit Text verbinden
  2. 2
    ErklärungDas Ausgabeformat lesen
  3. 3
    BeispielEine ausgerichtete Anzeige erstellen
  4. 4
    HerausforderungEine aufgefüllte Ganzzahl vorhersagen
  5. 5
    ErklärungIn Ziele des richtigen Typs einlesen
  6. 6
    BeispielEine teilweise Konvertierung erkennen
  7. 7
    TippEinen verbliebenen Zeilenumbruch nicht versehentlich lesen
  8. 8
    HerausforderungVerfolgen, was bei einem fehlgeschlagenen Lesen erhalten bleibt
  9. 9
    BeispielDen zulässigen Bereich der Berechnung prüfen
  10. 10
    HerausforderungEinlesen und Anzeige verbinden
  11. 11
    ZusammenfassungZwei Prüfungen bei der Eingabe verwenden
4

Einen Ausdruck von den Typen bis zu den Auswirkungen lesen

17 Schritte
Division, Typumwandlungen, logische Schutzbedingungen, Masken, Gruppierung und Aktualisierungen in einer festgelegten Reihenfolge durcharbeiten.

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

  1. 1
    EinführungWerte und Typen notieren
  2. 2
    BeispielEine Größe in ganze Teile zerlegen
  3. 3
    ErklärungDie beiden Inkremente unterscheiden
  4. 4
    HerausforderungEine zusammengesetzte Aktualisierung verfolgen
  5. 5
    BeispielDen Gleitkommaoperanden früh genug einsetzen
  6. 6
    KernaussageKleine vorzeichenlose Werte können als vorzeichenbehafteter int berechnet werden
  7. 7
    HerausforderungEinen vorzeichenbehafteten Wert in vorzeichenloser Arithmetik verfolgen
  8. 8
    ErklärungVollständige Vergleiche verknüpfen
  9. 9
    BeispielEine Schutzbedingung zuerst ausführen
  10. 10
    BeispielEin Flag ändern, ohne andere zu beeinflussen
  11. 11
    HerausforderungWahrheitswerte mit gemeinsam gesetzten Bits vergleichen
  12. 12
    ErklärungVor dem Nachvollziehen gruppieren
  13. 13
    TippDie Maske innerhalb des Vergleichs setzen
  14. 14
    VerbindungEinen Wert wählen, ohne beide auszuwerten
  15. 15
    HerausforderungKönnen Klammern zwei Aktualisierungen korrigieren?
  16. 16
    HerausforderungGruppierung mit einer Bereichsprüfung verbinden
  17. 17
    ZusammenfassungEinen Ausdruck in drei Durchgängen untersuchen
5

Entscheidungen verfolgen und eine laufende Summe bilden

18 Schritte
Zweige, ein switch-Menü, Eingaben mit Wächterwert und verschachtelte Zählschleifen mit gezielten Abbrüchen durcharbeiten.

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

  1. 1
    EinführungDen tatsächlich genommenen Pfad verfolgen
  2. 2
    ErklärungDen ersten wahren Zweig verwenden
  3. 3
    TippGeschweifte Klammern und Gleichheit sind beide wichtig
  4. 4
    HerausforderungDen ersten passenden Bereich finden
  5. 5
    ErklärungEinen Befehlseinstieg auswählen
  6. 6
    BeispielEine Aktion bewusst gemeinsam nutzen
  7. 7
    HerausforderungDas Ziel von break bestimmen
  8. 8
    ErklärungEingaben vor der Verwendung lesen
  9. 9
    BeispielDie Berechnung mit Wächterwert verfolgen
  10. 10
    HerausforderungEinen sofortigen Abbruch behandeln
  11. 11
    VerbindungMit do-while mindestens einmal fragen
  12. 12
    ErklärungGenau die beabsichtigten Durchläufe zählen
  13. 13
    BeispielEine Summe zwischen Durchläufen erhalten
  14. 14
    TippZähler vermeiden, die den Ausstieg verfehlen
  15. 15
    BeispielZeilen gesondert zählen
  16. 16
    HerausforderungEinen vorzeitigen Abbruch in einer Verschachtelung verfolgen
  17. 17
    HerausforderungEin Flag mit einer Menüschleife verbinden
  18. 18
    ZusammenfassungJeden Weg zum nächsten Durchlauf prüfen
6

Ein Produkt vor seiner Berechnung absichern

12 Schritte
Überlaufregeln unterscheiden, an der richtigen Stelle erweitern und Grenzfälle prüfen, ohne den zu prüfenden Fehler auszulösen.

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

  1. 1
    EinführungDie Berechnung vor dem Speichern untersuchen
  2. 2
    BeispielDie Operation im schmalen Typ finden
  3. 3
    ErklärungVorzeichenlose Arithmetik liefert ein definiertes, aber möglicherweise falsches Ergebnis
  4. 4
    HerausforderungKorrigiert eine späte Typumwandlung das Produkt?
  5. 5
    BeispielDen Typ der Operation selbst erweitern
  6. 6
    ErklärungEine Addition prüfen, ohne zu addieren
  7. 7
    BeispielEin positives Produkt durch Division prüfen
  8. 8
    HerausforderungDie Grenze korrekt prüfen
  9. 9
    KernaussageEin darstellbares Ergebnis kann eine unsichere Summe verbergen
  10. 10
    TippWerkzeuge verwenden und Grenzen testen
  11. 11
    HerausforderungEinen vollständigen Entwurf des Wertebereichs prüfen
  12. 12
    ZusammenfassungDen Nachweis vor der Operation führen

Datentypen und Operatoren in C: Übungsaufgaben

50 Übungsfragen in 5 Sets

Set 1: Programme erstellen und gespeicherte Werte verfolgen10 Übungsfragen

Frage 1 von 10

Ein Programm wurde erfolgreich erstellt, dann wurde seine Begrüßung bearbeitet und gespeichert. Die vorhandene ausführbare Datei gibt weiterhin die alte Begrüßung aus. Welche Aktion lässt die Änderung wirksam werden?
  1. Dieselbe ausführbare Datei erneut ausführen.
  2. Den Ausgabenamen der ausführbaren Datei ändern.
  3. Den gespeicherten Quellcode erneut kompilieren.
  4. Der Begrüßung einen weiteren Zeilenumbruch hinzufügen.
Antwort zeigenAntwort ausblenden
Antwort: C
Die ausführbare Datei enthält das Ergebnis der früheren Erstellung. Das Speichern des Quelltexts erstellt diese Datei nicht neu. Kompiliere daher den bearbeiteten Quellcode und führe anschließend die neue ausführbare Datei aus. Eine erneute Ausführung der alten Datei verwendet denselben früheren Maschinencode und kann die geänderte Begrüßung nicht übernehmen.

Frage 2 von 10

Ein Compiler meldet ein fehlendes Semikolon und anschließend mehrere Fehler in späteren Zeilen. Was solltest du zuerst beheben?
  1. Das zuerst gemeldete Syntaxproblem.
  2. Das zuletzt gemeldete Syntaxproblem.
  3. Jede spätere Zeile vor der Neuerstellung.
  4. Die Laufzeiteingabe der ausführbaren Datei.
Antwort zeigenAntwort ausblenden
Antwort: A
Beginne mit dem ersten Fehler und untersuche die genannte Zeile und die vorherige. Ein fehlendes Semikolon kann die Interpretation späterer Anweisungen durch den Compiler durcheinanderbringen. Wenn du zuerst alle späteren Zeilen änderst, kannst du korrekten Code verändern, der nur wegen des früheren Fehlers gemeldet wurde.

Frage 3 von 10

Welche Werte gibt dieser Code aus?

int units = 6;
int total = units * 4;
units = 9;
printf("%d %d\n", units, total);
  1. 9 36
  2. 6 24
  3. 6 36
  4. 9 24
Antwort zeigenAntwort ausblenden
Antwort: D
Die Deklaration von total berechnet einmal sechs mal vier und speichert 24. Die spätere Zuweisung ändert units auf neun, berechnet total aber nicht erneut, sodass die Ausgabe 9 und 24 ist. Wer 36 erwartet, behandelt die frühere Zuweisung als fortlaufend aktualisierte Formel, die beide Variablen verbindet.

Frage 4 von 10

Ein Wert muss auf typischen 64-Bit-Systemen mit Linux und Windows negative Ergebnisse und positive Anzahlen bis zu 6.000.000.000 aufnehmen können. Welcher Standard-Ganzzahltyp bietet den benötigten Wertebereich?
  1. int
  2. long long
  3. unsigned int
  4. long
Antwort zeigenAntwort ausblenden
Antwort: B
Ein standardkonformer long long hat mindestens 64 Bit und kann diese positiven und negativen Werte darstellen. Ein typischer long unter Windows hat nur 32 Bit, selbst wenn Windows selbst ein 64-Bit-System ist. Auch unsigned int hat auf diesen Systemen einen unzureichenden Wertebereich und kann keine negativen Werte darstellen.

Frage 5 von 10

Welchen Dezimalwert stellt das C17-Ganzzahlliteral 014 dar?
  1. 14
  2. 10
  3. 12
  4. 16
Antwort zeigenAntwort ausblenden
Antwort: C
Die führende null macht 014 zu einem Oktalliteral. Seine Ziffern stehen für eine Gruppe zu acht plus vier und ergeben zwölf. Wer es als dezimal vierzehn liest, ignoriert das Präfix für die Zahlenbasis; ein Dezimalliteral für vierzehn wird ohne führende null als 14 geschrieben.

Frage 6 von 10

Ein berechneter double unterscheidet sich nach mehreren Dezimaloperationen geringfügig von einem double-Zielwert. Welcher Vergleich passt zu einer Messung, die einen kleinen absoluten Fehler erlaubt?
  1. Die absolute Differenz mit dem erlaubten Fehler vergleichen.
  2. Die ausgegebenen Werte bei höchster Genauigkeit vergleichen.
  3. Beide Werte vor der Gleichheitsprüfung in int umwandeln.
  4. Nach Zuweisung an double exakte Gleichheit verlangen.
Antwort zeigenAntwort ausblenden
Antwort: A
Eine Prüfung der absoluten Differenz wie fabs(actual - target) < tolerance prüft den für die Messung erlaubten Fehler. Exakte Gleichheit kann scheitern, weil Dezimalberechnungen binäre Rundungsunterschiede erzeugen. Die Umwandlung in int verwirft Nachkommateile und kann einen Unterschied verbergen, der viel größer als der erlaubte Fehler ist.

Frage 7 von 10

Was gibt printf("|%04d|", 23); zwischen den senkrechten Strichen aus?
  1. | 23|
  2. |2300|
  3. |23 |
  4. |0023|
Antwort zeigenAntwort ausblenden
Antwort: D
Die Breite verlangt mindestens vier Zeichen und das Null-Flag liefert führende Nullen, sodass 23 zu 0023 wird. Eine gewöhnliche Rechtsausrichtung würde zwei führende Leerzeichen verwenden, aber das ist das Verhalten von %4d ohne Null-Flag. Die Breite hängt keine Nullen an den Wert an.

Frage 8 von 10

Gegeben ist int a = 2, b = 9;. Ein Aufruf von scanf("%d:%d", &a, &b) erhält „17 42“. Welcher Rückgabewert und welche gespeicherten Werte ergeben sich?
  1. Rückgabe 2; a = 17, b = 42.
  2. Rückgabe 1; a = 17, b = 9.
  3. Rückgabe 0; a = 2, b = 9.
  4. Rückgabe 1; a = 17, b = 0.
Antwort zeigenAntwort ausblenden
Antwort: B
Das erste %d speichert 17. Danach steht in der Eingabe ein Leerzeichen, wo das Format einen wörtlichen Doppelpunkt verlangt. Deshalb stoppt der Abgleich und b behält seinen alten Wert neun. Die zurückgegebene Anzahl ist eins; Leerraumzeichen zwischen Zahlen können die vom Format verlangten Satzzeichen nicht ersetzen.

Frage 9 von 10

Was gibt dies auf einem System mit 8-Bit-unsigned-char und 32-Bit-int aus?

unsigned char a = 230, b = 40;
int sum = a + b;
unsigned char small = sum;
printf("%d %u\n", sum, (unsigned)small);
  1. 14 14
  2. 270 270
  3. 14 270
  4. 270 14
Antwort zeigenAntwort ausblenden
Antwort: D
Beide Zeichenwerte werden vor der Addition zu int erweitert, sodass sum den Wert 270 ohne Wraparound bei 255 erhält. Die Konvertierung in small reduziert 270 dann modulo 256 und ergibt 14. Wer für beide Werte 14 vorhersagt, überträgt den kleinen Speicherbereich auf die Addition nach Ganzzahlerweiterung, wo er nicht gilt.

Frage 10 von 10

Der Befehl gcc -std=c17 -c sensor.c ist erfolgreich. Was hat er erzeugt und welche Phase der Programmerstellung ist vor einer gewöhnlichen Ausführung noch nötig?
  1. Eine ausführbare Datei; Vorverarbeitung ist noch nötig.
  2. Assemblertext; Assemblierung ist noch nötig.
  3. Eine Objektdatei; Linken ist noch nötig.
  4. Vorverarbeitetes C; Kompilierung ist noch nötig.
Antwort zeigenAntwort ausblenden
Antwort: C
Die Option -c kompiliert und assembliert den Quellcode zu Objektcode, gewöhnlich sensor.o, und stoppt dann vor dem Linken. Das Linken ist noch nötig, um die ausführbare Datei zu erstellen und externe Verweise aufzulösen. Assemblertext ist die frühere Ausgabe, die mit -S und nicht mit -c ausgewählt wird.
Set 2: Eingaben lesen und mit C-Typen rechnen10 Übungsfragen

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

Frage 1 von 10

Eine Variable wird als double distance_m; deklariert. Welche scanf-Konvertierung und welches Argument passen zu ihrem Typ?
  1. scanf("%f", &distance_m)
  2. scanf("%lf", &distance_m)
  3. scanf("%lf", distance_m)
  4. scanf("%d", &distance_m)
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 2 von 10

Was ergeben -23 / 6 und -23 % 6 in C17?
  1. −4 und 1
  2. −3 und 5
  3. −4 und −5
  4. −3 und −5
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 3 von 10

Welcher Ausdruck berechnet für int distance = 19, time = 4; eine Geschwindigkeit als double und erhält den Nachkommateil?
  1. (double)distance / time
  2. (double)(distance / time)
  3. distance / time
  4. (int)(distance / (double)time)
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 4 von 10

Ein mit %d gelesener int akzeptiert die Eingabe „46kg“. Welcher Wert wird gespeichert und welche Eingabe bleibt unmittelbar nach der Konvertierung ungelesen?
  1. 46; nichts bleibt übrig.
  2. 0; „46kg“ bleibt übrig.
  3. 46; „kg“ bleibt übrig.
  4. Kein Wert; „kg“ bleibt übrig.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 5 von 10

Welche Bedingung prüft für nicht negative int-Werte total und count, ob das ganzzahlige arithmetische Mittel über 5 liegt, ohne durch null zu dividieren?
  1. total / count > 5 && count != 0
  2. count != 0 && total / count > 5
  3. count != 0 & total / count > 5
  4. count == 0 || total / count > 5
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 6 von 10

Welchen Wert ergibt 2 + 5 * 3 % 7?
  1. 1
  2. 0
  3. 7
  4. 3
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 7 von 10

Welchen Text gibt printf("|%6.1f|", 8.375); bei gewöhnlicher Rundung auf das nächste Zehntel aus?
  1. | 8.375|
  2. |8.4 |
  3. | 8.4|
  4. |0008.4|
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 8 von 10

Welche Werte werden ausgegeben?

int n = 8;
int a = n++;
int b = ++n;
a *= b % 4;
printf("%d %d %d\n", a, b, n);
  1. 16 10 10
  2. 18 10 10
  3. 16 9 10
  4. 24 11 11
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 9 von 10

Was gibt dies bei 32-Bit-int und unsigned int aus?

int delta = -3;
unsigned int limit = 2;
printf("%d %u\n", delta < limit, limit + delta);
  1. 1 4294967295
  2. 1 -1
  3. 0 1
  4. 0 4294967295
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 10 von 10

Ein vorzeichenloser Wert ist flags = 10u. Welcher Wert entsteht durch Setzen von Bit 2, wenn das niederwertigste Bit als Bit 0 gezählt wird?
  1. 12
  2. 14
  3. 8
  4. 2
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Set 3: Zweige, switch-Anweisungen und Schleifen verfolgen10 Übungsfragen

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

Frage 1 von 10

Eine Punktzahl beträgt 76. Welches Zeichen wird zugewiesen?

if (score >= 70) grade = 'C';
else if (score >= 85) grade = 'B';
else grade = 'F';
  1. C
  2. B
  3. F
  4. Es wird kein Zeichen zugewiesen.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 2 von 10

Was wird ausgegeben, wenn mode gleich 2 ist?

switch (mode) {
case 1: printf("A"); break;
case 2: printf("B");
case 3: printf("C"); break;
default: printf("D");
}
  1. B
  2. BCD
  3. BC
  4. ABC
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 3 von 10

Welche Endwerte haben n und passes?

int n = 45, passes = 0;
while (n > 2) {
    n /= 2;
    passes++;
}
  1. n = 1, passes = 5
  2. n = 2, passes = 5
  3. n = 3, passes = 4
  4. n = 2, passes = 4
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 4 von 10

Wie viele Durchläufe erfolgen bei for (int i = 4; i < 19; i += 4)?
  1. 3
  2. 4
  3. 5
  4. 15
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 5 von 10

Was wird ausgegeben?

int level = 2;
if (level = 0) {
    printf("Low");
} else {
    printf("Other");
}
  1. Low
  2. LowOther
  3. Es wird nichts ausgegeben.
  4. Other
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 6 von 10

Welche Summe wird ausgegeben?

int total = 0;
for (int r = 1; r <= 4; r++) {
    for (int c = 1; c <= r; c++) {
        if (c == 3) break;
        total += c;
    }
}
printf("%d\n", total);
  1. 10
  2. 7
  3. 13
  4. 20
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 7 von 10

Ein Schleifenrumpf muss einmal eine Eingabe anfordern, bevor er die erste Antwort prüfen kann. Welche Schleifenform drückt diese Reihenfolge direkt aus?
  1. while mit einer Prüfung vor dem Rumpf
  2. do–while
  3. for mit einer Prüfung vor dem Rumpf
  4. switch mit einer default-Marke
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 8 von 10

Welche Summe ergibt sich?

int sum = 0;
for (int i = 2; i <= 8; i++) {
    if (i % 3 == 0) continue;
    sum += i;
}
  1. 35
  2. 9
  3. 26
  4. 8
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 9 von 10

Wie oft wird „tick“ ausgegeben?

int running = 1, passes = 0;
while (running && passes < 3) {
    switch (passes) {
    case 1: break;
    default: printf("tick\n");
    }
    passes++;
}
  1. 1
  2. 3
  3. 0
  4. 2
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 10 von 10

Die Eingabe ist „9 4 0 7“. Welche Anzahl und welches arithmetische Mittel werden ausgegeben?

int x, sum = 0, count = 0;
while (scanf("%d", &x) == 1 && x != 0) {
    sum += x;
    count++;
}
if (count) printf("%d %.1f\n", count, (double)sum / count);
  1. 2 6.5
  2. 3 4.3
  3. 3 6.7
  4. 4 5.0
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Set 4: Konvertierungen und Wertebereichsfehler untersuchen10 Übungsfragen

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

Frage 1 von 10

Welchen Wert ergibt 0u - 1u, wenn unsigned int 32 Wertbits hat?
  1. −1
  2. 2.147.483.647
  3. 4.294.967.295
  4. 0
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 2 von 10

Welcher Ausdruck berechnet für die 32-Bit-int-Werte a = 60000 und b = 40000 ihr exaktes Produkt sicher in einem typischen 64-Bit-long long?
  1. (long long)(a * b)
  2. (long long)a * b
  3. a * b
  4. (double)(a * b)
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 3 von 10

Welches Ergebnis liefert flags & 2u == 0u für das vorzeichenlose flags = 6u?
  1. 1
  2. 2
  3. 6
  4. 0
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 4 von 10

Welcher Test erkennt für beliebige int-Werte a und b einen Überlauf bei der vorzeichenbehafteten Addition, bevor a + b ausgewertet wird?
  1. (b > 0 && a > INT_MAX - b) || (b < 0 && a < INT_MIN - b)
  2. (b > 0 && a > INT_MAX - b) || (b < 0 && a > INT_MIN - b)
  3. (b > 0 && a >= INT_MAX - b) || (b < 0 && a <= INT_MIN - b)
  4. (b > 0 && a < INT_MAX - b) || (b < 0 && a < INT_MIN - b)
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 5 von 10

Auf einer Plattform mit 16-Bit-unsigned-short und 32-Bit-int enthalten zwei unsigned-short-Variablen jeweils 48000. Was geschieht, wenn ihr Produkt ohne explizite Typumwandlungen ausgewertet wird?
  1. Sie multiplizieren vorzeichenlos mit Wraparound modulo 65536.
  2. Sie werden zu unsigned int erweitert und laufen bei 2³² um.
  3. Sie werden zu int erweitert, dessen Produkt überläuft.
  4. Sie werden zu long long erweitert und ergeben das exakte Produkt.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 6 von 10

Ein C17-Programm addiert eins zu einem int, der bereits gleich INT_MAX ist. Auf welches Ergebnis kann sich portabler C-Code verlassen?
  1. Das Ergebnis muss auf jedem System INT_MIN sein.
  2. Das Ergebnis muss gleich INT_MAX bleiben.
  3. Das Ergebnis muss in unsigned int umgewandelt werden.
  4. Die Sprache garantiert kein Ergebnis.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 7 von 10

Zwei nicht negative int-Endpunkte erfüllen low <= high. Auf einem System mit 32-Bit-int sind sie 1.800.000.000 und 2.000.000.000. Welcher Ausdruck berechnet ihren Mittelpunkt ohne Überlauf?
  1. (low + high) / 2
  2. low + (high - low) / 2
  3. low + high / 2
  4. low / 2 + high
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 8 von 10

Ein C17-Programm wandelt den darstellbaren double-Wert −6,875 in int um. Welcher Wert wird gespeichert?
  1. −6
  2. −7
  3. 6
  4. −6,875
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 9 von 10

Ein positiver int-Akkumulator für eine Fakultät ist f, und der nächste Multiplikator ist ein positiver int-Wert i. Welche Bedingung erkennt vor der Multiplikation, dass f * i den Wert INT_MAX überschreiten würde?
  1. f >= INT_MAX / i
  2. f / i > INT_MAX
  3. f > INT_MAX / i
  4. f * i > INT_MAX
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 10 von 10

Eine int-Berechnung liefert in einer Erstellung die erwartete Ausgabe, löst aber unter -fsanitize=undefined eine Meldung über einen Überlauf bei vorzeichenbehafteten Ganzzahlen aus. Welche Schlussfolgerung sollte die Korrektur leiten?
  1. Die erfolgreiche Erstellung belegt, dass das Ergebnis portabel ist.
  2. Die Meldung lässt sich durch eine breitere printf-Konvertierung beheben.
  3. Die Meldung garantiert einen Wraparound des int zu einem bestimmten Wert.
  4. Der erreichte Überlauf muss in der Berechnung verhindert werden.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Set 5: Eingabe, Programmablauf und sichere Arithmetik verbinden10 Übungsfragen

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

Frage 1 von 10

Ein Programm fordert in einem scanf-Aufruf zwei double-Messwerte an. Welcher Rückgabewert bestätigt, dass beide Zuweisungen abgeschlossen wurden?
  1. 0
  2. 2
  3. 1
  4. EOF
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 2 von 10

Welcher Wert wird ausgegeben?

int total = 0;
for (int r = 2; r <= 5; r++) {
    for (int c = 1; c < r; c++) {
        if (c % 2 == 0) continue;
        total += r;
    }
}
printf("%d\n", total);
  1. 14
  2. 18
  3. 40
  4. 23
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 3 von 10

Ein 32-Bit-int days ist 30000. Welcher Ausdruck berechnet seine Dauer in Millisekunden korrekt in einem 64-Bit-long long?
  1. 86400000LL * days
  2. (long long)(86400000 * days)
  3. 86400000 * days
  4. (long long)(86400 * days) * 1000
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 4 von 10

Was garantiert dieser C17-Ausschnitt über seine Ausgabe?

int n = 4;
printf("%d %d\n", n++, n++);
  1. Er gibt 4 und 5 aus, weil Argumente von links nach rechts laufen.
  2. Er gibt 5 und 4 aus, weil Argumente von rechts nach links laufen.
  3. Er zeigt undefiniertes Verhalten durch nicht sequenzierte Änderungen.
  4. Er gibt als definierte Alternativen 4 und 5 oder 5 und 4 aus.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 5 von 10

Was wird ausgegeben, wenn a = -2 und b = 7 gilt?

if (a > 0)
    if (b > 0) printf("Both");
    else printf("A only");
  1. A only
  2. Both
  3. BothA only
  4. Es wird nichts ausgegeben.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 6 von 10

Warum macht diese Schleife keinen Fortschritt mehr, nachdem sie einmal null ausgegeben hat?

int i = 0;
while (i < 4) {
    if (i == 1) continue;
    printf("%d\n", i);
    i++;
}
  1. Bei eins verlässt continue die Schleife vor der nächsten Prüfung.
  2. Bei eins überspringt continue die Aktualisierung, sodass i eins bleibt.
  3. Bei null verhindert die Bedingung die erste Aktualisierung.
  4. Bei vier setzt der Schleifenrumpf i auf eins zurück.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 7 von 10

Ein vorzeichenloser Bestandswert ist 7u und eine angeforderte Menge ist 11u. Welche Regel verhindert einen Bestand mit Wraparound und erhält zugleich eine gültige Subtraktion, wenn genügend Bestand verfügbar ist?
  1. Nur subtrahieren, wenn stock >= requested gilt.
  2. Subtrahieren und dann ablehnen, wenn der neue Bestand negativ ist.
  3. Nur subtrahieren, wenn stock <= requested gilt.
  4. Subtrahieren und dann einen Bestand ungleich null verlangen.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 8 von 10

Welchen Wert hinterlässt dieser Code bei einem 32-Bit-unsigned-int in flags?

unsigned int flags = 13u;
flags &= ~(1u << 2);
flags |= 1u << 1;
  1. 15
  2. 9
  3. 11
  4. 7
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 9 von 10

Ein Menü beginnt mit running = 1 und actions = 0. Die Eingabe ist „s q s“. Welchen Wert hat actions nach dieser Schleife?

char cmd;
while (running && scanf(" %c", &cmd) == 1) {
    switch (cmd) {
    case 'q': running = 0; break;
    case 's': actions++; break;
    }
}
  1. 0
  2. 1
  3. 2
  4. 3
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 10 von 10

Welcher Wert wird rate zugewiesen?

int completed = 14, planned = 20;
double rate = completed / planned * 100.0;
  1. 70,0
  2. 0,7
  3. 14,0
  4. 0,0
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Hängst du bei deiner eigenen Aufgabe fest? Hausaufgabenhilfe

Schlüsselbegriffe

91 Schlüsselbegriffe, jeweils mit Definition

A

Ablaufprotokoll
Ein Ablaufprotokoll hält die sich ändernden Werte und ausgeführten Schritte eines Programms fest, damit sein Verhalten der Reihe nach geprüft werden kann.
In einer Ziffernsummenschleife hilft ein Ablaufprotokoll der verbleibenden Ziffern und der laufenden Summe nach jedem Durchlauf, eine falsche Schleifenaktualisierung zu finden.
Abschneiden in Richtung null
Abschneiden in Richtung null entfernt den Nachkommateil einer Zahl; bei einer negativen Zahl liegt das Ergebnis dadurch näher an null.
Durch Abschneiden in Richtung null ergibt die Umwandlung des darstellbaren Werts −7,9 in int den Wert −7 statt −8.
Akkumulator
Ein Akkumulator hält ein laufendes Ergebnis über Schleifendurchläufe hinweg und beginnt mit einem geeigneten Anfangswert außerhalb der wiederholten Arbeit.
Der Akkumulator für die Ziffernsumme beginnt bei null und addiert bei jedem Schleifendurchlauf eine ermittelte Ziffer.
Anweisungsblock
Ein Anweisungsblock gruppiert Deklarationen und Anweisungen in geschweiften Klammern, sodass sie als ein gesteuerter Rumpf dienen können.
Ein Anweisungsblock hält eine Meldung und eine Zähleraktualisierung innerhalb desselben bedingten Zweigs zusammen.
Assoziativität
Die Assoziativität bestimmt, wie Operatoren derselben Prioritätsstufe gruppiert werden, ohne im Allgemeinen die Auswertungsreihenfolge festzulegen.
Die Assoziativität der Division von links nach rechts gruppiert 24 / 4 / 2 als (24 / 4) / 2 und ergibt drei.
ausführbare Datei
Eine ausführbare Datei ist ein erstelltes Programm in einer Form, die das vorgesehene Betriebssystem auf dem vorgesehenen Prozessor laden und ausführen kann.
Die Ausführung von ./hello startet die ausführbare Datei im aktuellen Ordner, ohne den C-Compiler erneut aufzurufen.
Ausgabepufferung
Die Ausgabepufferung hält erzeugten Text vorübergehend zurück, bevor er weitergesendet wird. Dadurch kann sich eine Aufforderung ohne Zeilenumbruch verzögern.
Eine Eingabeaufforderung kann im Ausgabepuffer warten, bis fflush(stdout) sie vor einem Eingabeaufruf sendet.
Auswertungsreihenfolge
Die Auswertungsreihenfolge beschreibt, wann Operanden berechnet und Seiteneffekte ausgeführt werden; sie ist eine von der Gruppierung eines Ausdrucks getrennte Frage.
Für Funktionsargumente in C17 ist trotz ihrer geschriebenen Position keine Auswertungsreihenfolge von links nach rechts vorgeschrieben.

B

bedingter Operator
Der bedingte Operator verwendet condition ? a : b, um nach der Prüfung der Bedingung einen Wertausdruck auszuwählen.
Bei einer Bedingung gleich null wertet der bedingte Operator in condition ? a : b den Ausdruck b aus und überspringt a.
Bedingung
Eine Bedingung ist ein Ausdruck, dessen Wert null oder ungleich null darüber entscheidet, welchem Pfad des Programmablaufs gefolgt wird.
Die Bedingung in if (items) ist für null falsch, während eine negative Anzahl ungleich null den Zweig ausführt.
Bezeichner
Ein Bezeichner ist ein Name im C-Code, etwa ein Variablenname, für den Schreibregeln gelten und bei dessen Abgleich Groß- und Kleinschreibung unterschieden werden.
Der Bezeichner distance_m ist gültig; 2distance kann kein Name sein, weil er mit einer Ziffer beginnt.
Bitmaske
Eine Bitmaske ist ein ganzzahliges Muster, mit dem bestimmte Bits eines anderen Werts ausgewählt, gesetzt, gelöscht oder geprüft werden.
Die Bitmaske 1u << 5 wählt Bit fünf aus, wobei das niederwertigste Bit als Position null gezählt wird.
Bitverschiebung
Eine Bitverschiebung verschiebt das Bitmuster einer Ganzzahl um eine Anzahl nach links oder rechts, wobei die Regeln für Breite und Vorzeichen des erweiterten Typs gelten.
Die Verschiebung eines vorzeichenlosen Werts um zwei Stellen nach rechts verwirft die zwei niederwertigsten Bits seiner Darstellung.
bitweiser Operator
Ein bitweiser Operator wirkt auf einzelne Bits ganzzahliger Operanden, um ihre Bitmuster zu verbinden, zu invertieren oder zu verschieben.
Der bitweise Operator & behält nur die in zwölf und zehn gemeinsam gesetzten Bits und ergibt acht.
Blockgültigkeitsbereich
Der Blockgültigkeitsbereich macht einen lokalen Namen von seiner Deklaration bis zum Ende des umschließenden Blocks verfügbar.
Der Blockgültigkeitsbereich verhindert, dass eine innerhalb eines if-Blocks deklarierte temporäre Variable nach dessen Ende verwendet wird.
boolescher Wert
Ein boolescher Wert steht für falsch oder wahr; die Umwandlung in bool bildet in C null auf falsch und jeden Wert ungleich null auf wahr ab.
Nach dem Einbinden von <stdbool.h> in C17 speichert bool ready = 2; den booleschen Wert wahr.
break-Anweisung
Eine break-Anweisung verlässt sofort das nächstgelegene umschließende switch oder die nächstgelegene umschließende Schleife und setzt nach diesem Konstrukt fort.
Eine break-Anweisung innerhalb eines Menü-switch verlässt dieses switch, während die umgebende while-Schleife weiterläuft.

C

case-Marke
Eine case-Marke benennt einen eindeutigen ganzzahligen konstanten Ausdruck, der als Einstiegspunkt in einem switch dienen kann.
Die case-Marke case 4: ist in C17 gültig; eine gewöhnliche const-int-Variable kann ihren Wert nicht liefern.
Compiler
Ein Compiler übersetzt ein Programm aus einer Quellsprache in eine andere Form, etwa Assembler- oder Maschinencode.
Der Compiler prüft eine C-Deklaration und meldet ein fehlendes Semikolon, bevor eine neue ausführbare Datei erstellt wird.
continue-Anweisung
Eine continue-Anweisung überspringt den restlichen Schleifenrumpf und führt zum nächsten Fortsetzungspunkt der Schleife.
Eine continue-Anweisung in einer for-Schleife erreicht die Aktualisierung im Kopf; in einer while-Schleife kehrt sie zur Prüfung zurück.

D

default-Marke
Eine default-Marke stellt einen switch-Einstiegspunkt für einen Steuerwert bereit, der zu keiner case-Marke passt.
Eine default-Marke kann eine unerwartete Menünummer zu einer Fehlermeldung führen, statt das switch zu überspringen.
Deklaration
Eine Deklaration führt einen Namen und seinen Typ ein, damit C prüfen kann, wie das benannte Objekt oder die benannte Funktion verwendet wird.
Die Deklaration int count = 0; gibt dem Namen einen Ganzzahltyp und liefert zugleich seinen Anfangswert.
Divisionsrest
Ein Divisionsrest ist der Wert, den die ganzzahlige %-Operation in C liefert; ein Ergebnis ungleich null hat das Vorzeichen des Dividenden.
Der Divisionsrest -22 % 5 ist −2, weil der abgeschnittene Quotient −4 ist und die Divisionsgleichung gelten muss.
do-while-Schleife
Eine do-while-Schleife führt ihren Rumpf vor der Prüfung ihrer Wiederholungsbedingung aus und garantiert damit mindestens eine Ausführung des Rumpfs.
Eine do-while-Schleife liest die erste Antwort für eine Bereichsprüfung in ihrem Rumpf, bevor sie entscheidet, ob erneut gefragt wird.

E

Eingabeende
Das Eingabeende bedeutet, dass der eingehende Datenstrom keine weiteren Zeichen zum Lesen bereithält; es unterscheidet sich von gewöhnlichem nicht passendem Text.
Eine numerische Eingabeschleife stoppt am Eingabeende, kann aber auch früher bei nicht numerischem Text stoppen.
Endlosschleife
Eine Endlosschleife wiederholt sich, ohne einen Ausstieg zu erreichen, oft weil sich ihr geprüfter Zustand nicht wie beabsichtigt ändert.
Ein continue vor der einzigen Aktualisierung kann eine Endlosschleife erzeugen, die immer wieder denselben unveränderten Wert prüft.
Escape-Sequenz
Eine Escape-Sequenz verwendet in einem C-Zeichen- oder Zeichenkettenliteral einen umgekehrten Schrägstrich, um ein Sonderzeichen darzustellen.
Die Escape-Sequenz \n fügt innerhalb einer C-Zeichenkette einen Zeilenumbruch ein; printf verwendet %% für ein Prozentzeichen.
explizite Typumwandlung
Eine explizite Typumwandlung fordert die Umwandlung in einen benannten Typ ausdrücklich an, indem der Typ in Klammern vor einen Ausdruck gesetzt wird.
Die explizite Typumwandlung in (double)sum / count wirkt vor der Division und erhält einen gebrochenen Quotienten.

F

Fall-through
Fall-through bezeichnet die Fortsetzung der Ausführung von einem switch-Fall in den folgenden Code, ohne dass dazwischen eine Ablaufübertragung stattfindet.
Fall-through nach einem fehlenden break kann die Ausgabe des folgenden Falls ausführen, ohne dessen Marke erneut zu prüfen.
Feldbreite
Eine Feldbreite ist die Mindestanzahl von Zeichen, die für einen formatierten Ausgabewert einschließlich Vorzeichen und Dezimalpunkt reserviert wird.
Eine Feldbreite von sechs in %6d fügt vor 42 vier Leerzeichen ein, während ein siebenstelliger Wert trotzdem vollständig ausgegeben wird.
for-Schleife
Eine for-Schleife setzt Initialisierung, Wiederholungsbedingung und Aktualisierung in einen gemeinsamen Kopf für einen wiederholten Rumpf.
Eine for-Schleife, die bei null beginnt und i < n sowie i++ verwendet, führt für nicht negatives n genau n Durchläufe aus, wenn die Aktualisierungen darstellbar sind.
Formatstring
Ein Formatstring verbindet wörtlichen Text und Konvertierungsspezifikationen, die steuern, wie ein Ein- oder Ausgabeaufruf Werte behandelt.
Der Formatstring in printf("Mass: %.1f kg\n", mass) liefert eine Beschriftung, die Anzahl der Nachkommastellen und eine Einheit.

G

Ganzzahldivision
Die Ganzzahldivision dividiert ganzzahlige Operanden und verwirft den Nachkommateil des Quotienten in Richtung null.
Die Ganzzahldivision in 22 / 5 ergibt vier, selbst wenn das fertige Ergebnis einem double zugewiesen wird.
Ganzzahlerweiterung
Eine Ganzzahlerweiterung wandelt einen kleinen Ganzzahltyp in int um oder in unsigned int, wenn int dessen gesamten Wertebereich nicht darstellen kann.
Auf einem System mit 16-Bit-short und 32-Bit-int ermöglicht die Ganzzahlerweiterung, unsigned-short-Operanden als int zu multiplizieren.
Ganzzahlliteral
Ein Ganzzahlliteral ist eine im Quellcode geschriebene Ganzzahlkonstante mit einer Schreibweise für die Zahlenbasis und einem optionalen Typsuffix.
Das Ganzzahlliteral 0x30 verwendet die hexadezimale Schreibweise, während 48u denselben Wert vorzeichenlos dezimal schreibt.
Ganzzahltyp mit exakter Bitbreite
Ein Ganzzahltyp mit exakter Bitbreite ist ein, sofern verfügbarer, mit typedef definierter Ganzzahltyp, dessen Name seine exakte Bitbreite und sein Vorzeichenverhalten festlegt.
Der Ganzzahltyp mit exakter Bitbreite uint8_t enthält, sofern er bereitgestellt wird, Werte von null bis 255.
Ganzzahlüberlauf
Ein Ganzzahlüberlauf tritt auf, wenn das mathematische Ergebnis einer ganzzahligen arithmetischen Operation außerhalb des Wertebereichs des Ergebnistyps liegt.
Ein int-Produkt kann einen Ganzzahlüberlauf verursachen, bevor sein Ergebnis ein long-long-Ziel erreicht.
Genauigkeit
Die Genauigkeit gibt an, wie viele signifikante Informationen ein numerisches Format bewahren kann; sie unterscheidet sich von seinem größten Betrag.
Die begrenzte float-Genauigkeit verliert einzelne Ganzzahlschritte bei Beträgen, die noch weit unter dem Maximum des Typs liegen.
Gleitkommawert
Ein Gleitkommawert stellt eine reelle Zahl mit einer festen Genauigkeit und einer Skalierung dar, häufig mit Rundung.
Eine als Gleitkommawert gespeicherte Länge kann die eingegebene Dezimalzahl annähern, ohne ihr exakt zu entsprechen.
Grenzwerttest
Ein Grenzwerttest prüft das Verhalten mit Werten an oder nahe einer relevanten Grenze, an der sich eine Regel oder ein Wertebereich ändert.
Ein Grenzwerttest für eine Addition umfasst eine Summe gleich INT_MAX und ein Wertepaar, das diesen Wert um eins überschreiten würde.

H

hängendes else
Ein hängendes else ist ein else in verschachtelten Bedingungen, dessen Zuordnung ohne geschweifte Klammern für Lesende unklar sein kann.
Die Regel zum hängenden else ordnet ein else unabhängig von der Einrückung dem nächstgelegenen if ohne zugeordnetes else zu.

I

implementierungsdefiniertes Verhalten
Implementierungsdefiniertes Verhalten ist eine Wahl, die eine C-Implementierung dokumentieren muss, wenn der Standard mehrere Verhaltensweisen erlaubt.
Eine Ganzzahlkonvertierung außerhalb des Wertebereichs in einen vorzeichenbehafteten Typ hat in C17 implementierungsdefiniertes Verhalten oder löst ein Signal aus.
implizite Typumwandlung
Eine implizite Typumwandlung ändert den Typ eines Werts automatisch, wenn eine Operation oder Zuweisung einen anderen Typ benötigt.
Die Addition eines int zu einem double wendet vor der Addition eine implizite Typumwandlung auf den ganzzahligen Operanden an.
Initialisierung
Die Initialisierung gibt einem Objekt als Teil seiner Deklaration seinen Anfangswert, bevor spätere Zuweisungen ihn ändern.
Die Initialisierung von total auf null vor einer Schleife gibt der ersten Addition einen definierten Anfangswert.
Iteration
Eine Iteration ist eine Ausführung des Rumpfs einer Schleife, nachdem ihre Steuerungsprüfung den Beginn dieses Durchlaufs erlaubt hat.
Eine for-Schleife, die 3, 6, 9 und 12 durchläuft, hat vier Iterationen, obwohl die abschließende Zähleraktualisierung 15 erreicht.

K

Kommaoperator
Der Kommaoperator wertet seinen linken Operanden vor seinem rechten aus und liefert den Wert des rechten Operanden.
Der Kommaoperator ordnet die Aktualisierungen in i++, j--; Kommas zwischen printf-Argumenten tun das nicht.
Konvertierung außerhalb des Wertebereichs
Eine Konvertierung außerhalb des Wertebereichs versucht, einen Quellwert darzustellen, der außerhalb der zulässigen Werte des Zieltyps liegt.
Eine Konvertierung eines sehr großen double in int außerhalb des Wertebereichs ist undefiniert, wenn der Ganzzahlanteil nicht darstellbar ist.
Konvertierungsspezifikation
Eine Konvertierungsspezifikation ist ein mit einem Prozentzeichen beginnender Teil eines Formats, der den benötigten Typ und die Darstellung eines Werts angibt.
Die Konvertierungsspezifikation %lld passt zu einem long-long-Argument; %d kann es nicht automatisch umwandeln.
Kurzschlussauswertung
Die Kurzschlussauswertung überspringt den rechten Operanden eines logischen Operators, wenn dessen linker Operand das Ergebnis bereits bestimmt.
Die Kurzschlussauswertung in count != 0 && sum / count > 4 überspringt die Division, wenn count null ist.

L

Leerraumzeichen
Leerraumzeichen sind Zeichen wie Leerzeichen, Tabulatoren und Zeilenumbrüche, die Formate je nach Konvertierung überspringen oder lesen können.
Numerische scanf-Konvertierungen überspringen führende Leerraumzeichen, aber %c liest einen verbliebenen Zeilenumbruch, sofern es nicht angewiesen wird, ihn zu überspringen.
Linker
Der Linker verbindet Objektcode und löst Verweise auf benannten Code und benannte Daten auf, um ein fertiges Programm zu erzeugen.
Der Linker löst den Verweis auf printf auf; der zugehörige Bibliothekscode kann von einer gemeinsam genutzten Bibliothek bereitgestellt werden.
logischer Fehler
Ein logischer Fehler führt dazu, dass ein Programm etwas anderes als das beabsichtigte Ergebnis berechnet oder auswählt, selbst wenn es läuft.
Eine Prüfung von Punktebereichen in der falschen Reihenfolge erzeugt einen logischen Fehler, der die falsche Note zuweist.
logischer Operator
Ein logischer Operator verbindet Wahrheitswerte oder kehrt sie um und erzeugt mit &&, || oder ! null oder eins.
Der logische Operator in ready && valid prüft, ob beide gespeicherten Werte ungleich null sind.

M

Makro
Ein Makro definiert eine Ersetzung durch den Präprozessor, die vor der eigentlichen Kompilierung in späteren Quellcode eingesetzt wird.
Bei #define LIMIT 8 wird das Token LIMIT überall dort durch 8 ersetzt, wo das Makro gilt.

N

NaN
Ein NaN ist ein IEEE-Gleitkommawert mit der Bedeutung „keine Zahl“, der für Ergebnisse wie null dividiert durch null verwendet wird.
Ein NaN ist bei einem Vergleich mit sich selbst ungleich, sodass gewöhnliche Gleichheit es nicht wie einen gewöhnlichen Messwert behandeln kann.

O

Off-by-one-Fehler
Ein Off-by-one-Fehler führt dazu, dass eine Schleife oder Grenzberechnung einen Wert zu viel oder zu wenig einbezieht.
Das Ersetzen von i < n durch i <= n in einer bei null beginnenden Zählschleife verursacht einen Off-by-one-Fehler.
Operand
Ein Operand ist ein Wert oder Ausdruck, auf den ein Operator angewendet wird; sein Typ bestimmt mit, wie sich die Operation verhält.
In sum / count bestimmen die Typen der Operanden, ob die Division einen Nachkommateil im Ergebnis erhält.
Operatorpriorität
Die Operatorpriorität bestimmt, welche Operatoren stärker an ihre Operanden binden, wenn keine Klammern die Gruppierung vorgeben.
Die höhere Operatorpriorität der Multiplikation gruppiert in 3 + 4 * 2 den Ausdruck 4 * 2 vor der Addition.

P

Postfix-Inkrement
Ein Postfix-Inkrement addiert eins zu einer Variablen und liefert zugleich ihren alten Wert zur Verwendung im umgebenden Ausdruck.
Bei n = 3 gibt das Postfix-Inkrement in old = n++; der Variablen old den Wert drei und lässt n bei vier.
Präprozessor
Der Präprozessor verarbeitet Direktiven wie #include und Makroersetzungen vor den eigentlichen Phasen der C-Übersetzung.
Der Präprozessor fügt Deklarationen aus einer Standardheaderdatei ein, bevor der Compiler ihre Verwendung prüft.

Q

Quellcode
Quellcode ist der für Menschen lesbare C-Text, den ein Compiler in Anweisungen übersetzt, aus denen ein Programm erstellt wird.
Nachdem du im Quellcode den Begrüßungstext geändert hast, den ein Programm ausgibt, erstelle das Programm erneut, bevor du die neue Version ausführst.

R

Rückgabestatus
Ein Rückgabestatus ist der Wert, den ein Programm bei seinem Ende an seine aufrufende Umgebung meldet, um Erfolg oder Misserfolg anzuzeigen.
Ein Rückgabestatus von null aus main lässt ein aufrufendes Skript den erfolgreichen Abschluss erkennen.
Rundungsfehler
Ein Rundungsfehler ist die Differenz, die entsteht, wenn ein Wert oder eine Berechnung durch einen nahegelegenen darstellbaren Wert ersetzt wird.
Mehrere Dezimaladditionen können einen kleinen Rundungsfehler erzeugen, durch den eine exakte Gleichheitsprüfung fehlschlägt.

S

Sanitizer
Ein Sanitizer ist ein bei der Kompilierung hinzugefügtes Prüfwerkzeug für die Laufzeit, das bestimmte ungültige Operationen auf ausgeführten Pfaden meldet.
Ein Sanitizer für undefiniertes Verhalten kann die Multiplikation bestimmen, bei der eine vorzeichenbehaftete Zählerberechnung überläuft.
Seiteneffekt
Ein Seiteneffekt ist eine Änderung wie das Verändern gespeicherter Daten oder das Ausführen einer Ein- oder Ausgabe während der Auswertung eines Ausdrucks.
Ein Inkrement ändert als Seiteneffekt eine Variable, sodass zwei widersprüchliche Inkremente eine definierte Reihenfolge benötigen.
Standardeingabe
Die Standardeingabe ist der übliche eingehende Textstrom eines Programms, der oft mit der Tastatur oder einer umgeleiteten Eingabe verbunden ist.
Ein scanf-Aufruf liest aus der Standardeingabe und wandelt die nächsten passenden Zeichen in gespeicherte Werte um.
switch-Anweisung
Eine switch-Anweisung wählt eine Einstiegsmarke, indem sie einen ganzzahligen Steuerwert mit festen ganzzahligen Fallwerten vergleicht.
Eine switch-Anweisung kann den Befehlszeichen kleines r und großes R dieselbe Aktion zuweisen.

T

Toleranz
Eine Toleranz ist eine erlaubte numerische Differenz, die passend zur Größenordnung und zur für einen Vergleich erforderlichen Genauigkeit gewählt wird.
Das Programm vergleicht fabs(measured - target) mit der Toleranz, statt exakte Gleichheit zu verlangen.
Typerweiterung
Eine Typerweiterung wandelt einen Wert in einen Typ mit ausreichend großem darstellbarem Wertebereich um, bevor er an einer riskanten Berechnung teilnimmt.
Die Typerweiterung von a zu long long vor a * b ändert den Typ des Produkts; eine explizite Typumwandlung nach dem Produkt kommt zu spät.

Ü

Übereinstimmungsfehler
Ein Übereinstimmungsfehler tritt auf, wenn die verfügbare Eingabe nicht zu der von scanf erwarteten Konvertierung oder dem erwarteten wörtlichen Zeichen passt.
Buchstaben am Anfang einer %d-Konvertierung verursachen einen Übereinstimmungsfehler und bleiben für einen erneuten Versuch ungelesen.
überlaufgeprüfte Arithmetik
Überlaufgeprüfte Arithmetik berechnet ein Ergebnis und meldet zugleich, ob das exakte mathematische Ergebnis im gewählten Zieltyp darstellbar ist.
Die überlaufgeprüfte arithmetische Operation ckd_mul aus C23 kann ein Produkt melden, das im Zieltyp nicht darstellbar ist.
Übersetzungsfehler
Ein Übersetzungsfehler ist ein beim Übersetzen von Quellcode erkanntes Problem, das eine erfolgreiche Neuerstellung verhindert.
Eine nicht zugeordnete schließende runde Klammer verursacht einen Übersetzungsfehler, bevor eine neue ausführbare Datei erstellt werden kann.
übliche arithmetische Umwandlungen
Die üblichen arithmetischen Umwandlungen wählen für viele arithmetische Operationen und Vergleiche gemeinsame Operandentypen aus.
Die üblichen arithmetischen Umwandlungen wandeln einen negativen int in einen vorzeichenlosen Wert um, wenn er mit unsigned int verglichen wird.

U

undefiniertes Verhalten
Undefiniertes Verhalten tritt auf, wenn ein C-Programm eine Operation ausführt, an die die Sprache keine Anforderungen stellt.
Ein überlaufendes vorzeichenbehaftetes Produkt führt zu undefiniertem Verhalten, selbst wenn ein Lauf ohne Optimierung scheinbar einen Wraparound zeigt.

V

Variable
Eine Variable ist ein benannter Speicherplatz mit einem deklarierten Typ, der bestimmt, wie ihr gespeicherter Wert interpretiert wird.
Die Variable mass_kg speichert einen Messwert, während count festhält, wie viele Proben eingegeben wurden.
Vergleichsoperator
Ein Vergleichsoperator vergleicht numerische Werte mit <, <=, > oder >= und liefert den int-Wert null oder eins.
Der Vergleichsoperator in height >= 1 prüft eine Schranke; ein weiterer Vergleich prüft die andere.
verschachtelte Schleife
Eine verschachtelte Schleife ist eine Schleife innerhalb einer anderen Schleife, deren innere Folge für jeden erreichten äußeren Durchlauf erneut ausgeführt wird.
Eine verschachtelte Schleife mit vier inneren Durchläufen für jede von drei Zeilen führt ihren inneren Rumpf zwölfmal aus.
vorzeichenbehaftete Ganzzahl
Eine vorzeichenbehaftete Ganzzahl gehört zu einem Ganzzahltyp, der innerhalb eines festen Wertebereichs sowohl negative als auch nicht negative Werte darstellt.
Eine vorzeichenbehaftete Ganzzahl kann einen negativen Temperaturwert speichern, aber spätere Additionen müssen weiterhin in ihrem Wertebereich liegen.
vorzeichenlose Ganzzahl
Eine vorzeichenlose Ganzzahl stellt nur nicht negative ganze Zahlen dar; arithmetische Ergebnisse werden durch Reduktion modulo in ihren Wertebereich zurückgeführt.
Eine vorzeichenlose Ganzzahl eignet sich gut als Bitmaske, aber das Abziehen einer zu großen Menge vom Bestand kann einen Wraparound zu einem sehr großen Wert erzeugen.

W

Wächterwert
Ein Wächterwert ist ein festgelegter Wert, der das Ende von Daten signalisiert und vor der gewöhnlichen Verarbeitung erkannt wird.
Ein Wächterwert null kann eine Berechnung des arithmetischen Mittels beenden, ohne deren Summe oder Datenanzahl zu erhöhen.
Warnung
Eine Warnung ist eine Compilermeldung, die auf ein wahrscheinliches Problem hinweist und die Erstellung gewöhnlich weiterlaufen lässt.
Eine Formatwarnung kann aufdecken, dass %d mit einem double-Argument kombiniert wurde, was eine ungültige Kombination ist.
Wertebereichsprüfung
Eine Wertebereichsprüfung testet vor einer unsicheren Operation, ob Eingaben oder ein vorgesehenes Ergebnis Schranken einhalten.
Eine Wertebereichsprüfung für den Bestand vergleicht verfügbare und angeforderte Mengen, bevor eine vorzeichenlose Subtraktion einen Wraparound erzeugen könnte.
while-Schleife
Eine while-Schleife wiederholt ihren Rumpf, solange eine Bedingung wahr ist, und prüft diese Bedingung vor jeder Ausführung des Rumpfs.
Eine while-Schleife zur Ziffernverarbeitung stoppt, sobald die Ganzzahldivision den verbleibenden Wert auf null reduziert hat.
Wraparound bei vorzeichenlosen Ganzzahlen
Wraparound bei vorzeichenlosen Ganzzahlen ist die Reduktion eines vorzeichenlosen arithmetischen Ergebnisses modulo, die es in den nicht negativen Wertebereich des Typs zurückführt.
Die Subtraktion von eins von einer vorzeichenlosen null zeigt den Wraparound bei vorzeichenlosen Ganzzahlen, indem sie den Maximalwert ergibt.

Z

Zeichenkonstante
Eine Zeichenkonstante ist eine Schreibweise mit einem Zeichen in einfachen Anführungszeichen, deren Wert ein ganzzahliger Code ist; gewöhnliche C-Zeichenkonstanten haben den Typ int.
Die ASCII-Zeichenkonstante 'A' kann mit %c als Buchstabe A oder mit %d als Zahl 65 ausgegeben werden.
zusammengesetzte Zuweisung
Eine zusammengesetzte Zuweisung verbindet einen Operator mit dem Zurückspeichern seines Ergebnisses im Ziel auf der linken Seite.
Die zusammengesetzte Zuweisung total += item; aktualisiert die laufende Summe, ohne item zu ändern.
Zuweisung
Eine Zuweisung berechnet einen Wert und speichert ihn in einem vorhandenen Ziel, wobei sie ihn in den Zieltyp umwandelt.
Die Zuweisung count = count + 1; liest den alten Wert und ersetzt ihn durch einen um eins größeren Wert.
Zuweisungsanzahl
Die von scanf zurückgegebene Zuweisungsanzahl ist die Anzahl der Ziele, die vor dem Ende des Aufrufs erfolgreich gefüllt wurden.
Eine Zuweisungsanzahl von zwei beim Lesen von drei Messwerten lässt das dritte Ziel unbestätigt.
Zweierkomplement
Das Zweierkomplement ist eine vorzeichenbehaftete Binärdarstellung, in der das höchstwertige Bit einen negativen Stellenwert hat.
Im Zweierkomplement mit acht Bit stellt das Muster 10000000 den Wert −128 dar.
Zweig
Ein Zweig ist ein durch eine Entscheidung ausgewählter Pfad von Anweisungen, etwa der if- oder der else-Teil einer Bedingung.
Ein Punkteprogramm mit einer else-if-Kette erreicht nur einen Zweig, beginnend mit der ersten wahren Bedingung.
Zwischenergebnis
Ein Zwischenergebnis ist ein Wert, der innerhalb eines größeren Ausdrucks vor einer späteren Operation oder Zuweisung berechnet wird.
Die Summe in (low + high) / 2 ist ein Zwischenergebnis, das überlaufen kann, selbst wenn der Mittelpunkt darstellbar ist.

Ü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