C-Programmierung: Funktionsaufrufe und Parameterübergabe

Choose a study mode

Play Quiz
Study Flashcards
Spaced Repetition
Chat to Lesson

Podcast

Play an AI-generated podcast conversation about this lesson

Questions and Answers

Welche Aussage beschreibt am besten die Parameterübergabe in C?

  • Die Parameterübergabe in C verwendet standardmäßig 'call-by-reference', wobei die Originalwerte der Argumente verändert werden können.
  • Die Parameterübergabe in C ist immer 'call-by-value', wobei eine Kopie des Arguments an die Funktion übergeben wird. (correct)
  • Die Parameterübergabe in C ist 'call-by-name', wobei der Name des Arguments an die Funktion übergeben wird.
  • In C kann sowohl 'call-by-value' als auch 'call-by-reference' explizit durch den Programmierer bestimmt werden.

Wie kann 'call-by-reference' in C simuliert werden?

  • Durch die Verwendung von statischen Variablen innerhalb der Funktion.
  • Durch das Nutzen von Makros, die den Code der aufrufenden Funktion direkt manipulieren.
  • Durch die Übergabe von Zeigern auf Variablen, wodurch die Funktion die Speicheradresse der Variable erhält. (correct)
  • Durch die Verwendung globaler Variablen, die in der Funktion verändert werden.

Was ist die Hauptfunktion eines Funktionsprototyps in C?

  • Den Speicher für die Parameter der Funktion reservieren.
  • Die Definition der Funktion zu verstecken, um die Implementierung zu schützen.
  • Dem Compiler die Signatur einer Funktion bekannt zu machen, die später definiert wird. (correct)
  • Einen alternativen Einstiegspunkt für das Programm definieren.

Wann ist die Deklaration eines Funktionsprototyps besonders wichtig?

<p>Wenn zwei Funktionen sich gegenseitig aufrufen (rekursive Funktionen). (B)</p> Signup and view all the answers

Was ist der Unterschied zwischen Funktionskommentaren und Implementierungskommentaren?

<p>Funktionskommentare beschreiben, <em>was</em> eine Funktion macht, und befinden sich beim Funktionsprototypen, während Implementierungskommentare beschreiben, <em>wie</em> etwas gemacht wird, und sich innerhalb der Funktionsdefinition befinden. (A)</p> Signup and view all the answers

Wo werden Funktionen typischerweise im Bezug zum Hauptprogramm definiert?

<p>Im Hauptprogramm außerhalb aller Blöcke. (C)</p> Signup and view all the answers

Was passiert, wenn eine lokale Variable denselben Namen wie eine globale Variable hat?

<p>Die lokale Variable überschreibt die globale Variable innerhalb ihres Gültigkeitsbereichs. (C)</p> Signup and view all the answers

Was kennzeichnet die Lebensdauer einer lokalen Variable in C?

<p>Sie existiert ab ihrer Definition bis zum Ende des Blocks, in dem sie deklariert wurde. (D)</p> Signup and view all the answers

Welche Aussage bezüglich der Initialisierung von Auto-Variablen ist korrekt?

<p>Auto-Variablen werden nicht automatisch initialisiert, ihr Wert ist undefiniert bis zur expliziten Initialisierung. (A)</p> Signup and view all the answers

Was passiert mit dem Speicherplatz einer lokalen Variable, wenn der Block, in dem sie definiert ist, verlassen wird?

<p>Der Speicherplatz wird automatisch freigegeben. (C)</p> Signup and view all the answers

Wie unterscheiden sich formale Parameter von lokalen Variablen in Bezug auf ihre Lebensdauer?

<p>Formale Parameter leben ab dem Aufruf einer Funktion bis zu deren Ende, während lokale Variablen ab ihrer Definition bis zum Ende ihres Blocks leben. (D)</p> Signup and view all the answers

Was ist der Mechanismus, durch den formale Parameter ihren Wert erhalten?

<p>Call-by-value, wobei der Wert des Arguments in den Speicherplatz des formalen Parameters kopiert wird. (D)</p> Signup and view all the answers

Welche Speicherklasse wird verwendet, um Variablen zu definieren, die im gesamten Programm ansprechbar sind, aber mit lokalen Variablen gleichen Namens verdeckt werden können?

<p><code>extern</code> (B)</p> Signup and view all the answers

Welche Aussage trifft auf die Speicherklasse static bei globalen Variablen zu?

<p>Sie sind nur innerhalb der Quelldatei sichtbar, in der sie definiert sind. (C)</p> Signup and view all the answers

Was bewirkt die Deklaration einer lokalen Variable als static?

<p>Die Variable behält ihren Wert zwischen den Aufrufen der Funktion. (C)</p> Signup and view all the answers

Welchen Zweck hat die Speicherklasse register?

<p>Sie gibt dem Compiler einen Hinweis, die Variable in einem Prozessorregister zu speichern. (C)</p> Signup and view all the answers

Was ist der Vorteil der Verwendung des const-Attributs bei Variablen?

<p>Der Wert der Variable kann nach der Initialisierung nicht mehr verändert werden. (C)</p> Signup and view all the answers

Warum sollte man const-Deklarationen #define-Direktiven vorziehen?

<p><code>const</code>-Deklarationen berücksichtigen Gültigkeitsbereiche und Typisierung. (C)</p> Signup and view all the answers

Welchen Zweck hat das volatile-Attribut bei Variablen?

<p>Es stellt sicher, dass der Wert der Variablen bei jedem Zugriff neu aus dem Hauptspeicher gelesen wird. (C)</p> Signup and view all the answers

In welchem Szenario wäre die Verwendung des volatile-Attributs besonders sinnvoll?

<p>Bei der Abfrage von Werten in Geräteregistern in der Treiberprogrammierung. (C)</p> Signup and view all the answers

Welche der folgenden Aussagen beschreibt am besten die Ziele des C-Präprozessors?

<p>Erhöhung der Lesbarkeit und Wartbarkeit von C-Programmen. (A)</p> Signup and view all the answers

Welche der folgenden Aufgaben wird typischerweise vom C-Präprozessor NICHT ausgeführt?

<p>Speicherverwaltung zur Laufzeit. (C)</p> Signup and view all the answers

Wie fügt man den Inhalt einer Header-Datei in eine C-Quelldatei ein?

<p>Mit der <code>#include</code>-Direktive. (A)</p> Signup and view all the answers

Was passiert, wenn eine #include-Direktive in C-Code auf eine Datei mit spitzen Klammern (z.B. #include <stdio.h>) verweist?

<p>Die Suche nach der Datei beginnt im Verzeichnis für Standard-Bibliotheken. (B)</p> Signup and view all the answers

Wozu dienen #include-Anweisungen hauptsächlich?

<p>Um Bibliotheksfunktionen bekannt zu machen. (D)</p> Signup and view all the answers

Was ist der Zweck von Makros, die mit der #define-Direktive definiert werden?

<p>Sie definieren symbolische Namen, die durch Ersatztext ersetzt werden. (A)</p> Signup and view all the answers

Wie werden symbolische Namen in der Regel in C-Programmen geschrieben?

<p>In Großbuchstaben. (C)</p> Signup and view all the answers

Was bewirkt die #undef-Direktive?

<p>Sie löscht eine existierende Makro-Definition. (D)</p> Signup and view all the answers

Welche Aufgabe hat der Präprozessor in Bezug auf bedingte Kompilierung?

<p>Er erlaubt, Codeabschnitte basierend auf bestimmten Bedingungen ein- oder auszuschließen. (D)</p> Signup and view all the answers

Was ist Modularisierung in der Programmierung?

<p>Die Aufteilung eines Programms in kleinere, unabhängige Module. (A)</p> Signup and view all the answers

Welche Dateien werden typischerweise verwendet, um Schnittstellen in C zu definieren?

<p>Headerdateien (.h). (C)</p> Signup and view all the answers

Was ist der Hauptunterschied zwischen einer Deklaration und einer Definition in C?

<p>Eine Definition reserviert Speicherplatz, während eine Deklaration nur den Namen und Typ bekanntmacht. (B)</p> Signup and view all the answers

Welche Art von Informationen sollte ein Projektkommentar am Beginn einer Datei enthalten?

<p>Eine allgemeine Beschreibung des Projekts und seines Zwecks. (A)</p> Signup and view all the answers

Wo sollte die Beschreibung externer Funktionen in Bezug auf Kommentare platziert werden?

<p>In der zugehörigen Header-Datei beim Funktionsprototyp. (B)</p> Signup and view all the answers

Was ist ein Zeiger in C?

<p>Eine Variable, die die Adresse einer Speicherstelle enthält. (B)</p> Signup and view all the answers

Was ist der Unterschied zwischen dem Inhaltsoperator * und dem Adressoperator & bezogen auf Zeiger?

<p><code>*</code> liefert den Wert, auf den ein Zeiger zeigt, während <code>&amp;</code> die Adresse einer Variablen liefert. (A)</p> Signup and view all the answers

Wie definierst du einen Zeiger auf eine Variable vom Typ int?

<p><code>int *pointer;</code> (C)</p> Signup and view all the answers

Was bedeutet es, wenn ein Zeiger 'nicht initialisiert' ist?

<p>Der Zeiger zeigt auf eine zufällige, möglicherweise ungültige Speicheradresse. (D)</p> Signup and view all the answers

Welche der folgenden Benennungen deutet üblicherweise auf einen Zeiger hin?

<p><code>p_variable</code> oder <code>ptr_variable</code> (D)</p> Signup and view all the answers

Wie kann ein Zeiger initialisiert werden, wenn er noch nicht auf eine bestimmte Variable zeigen soll?

<p>Mit dem Wert <code>0</code> oder <code>NULL</code>. (A)</p> Signup and view all the answers

Was ist ein 'wilder Zeiger'?

<p>Ein Zeiger, der nicht initialisiert wurde und auf eine unbekannte Speicherstelle zeigt. (D)</p> Signup and view all the answers

Was versteht man unter einem 'hängenden Zeiger' (dangling pointer)?

<p>Ein Zeiger, der auf eine Speicherstelle zeigt, die bereits freigegeben wurde. (A)</p> Signup and view all the answers

Was bedeutet 'Zeigerarithmetik'?

<p>Das Addieren und Subtrahieren von ganzen Zahlen zu Zeigern, um Speicheradressen zu verschieben. (A)</p> Signup and view all the answers

Welche Operation ist nicht möglich?

<p>Multiplikation zweier Speicheradresse. (A)</p> Signup and view all the answers

Flashcards

Funktionsaufruf

Ein Aufruf einer Funktion von einer anderen oder von sich selbst.

Call-by-Value

Die Übergabe von Argumentwerten wird kopiert und an die Funktion übergeben; Änderungen der Kopie beeinflussen die Umgebung nicht.

Call-by-Reference (Simulation)

Simulation von Call-by-Reference durch die Übergabe von Zeigern.

Funktionsprototyp

Eine Deklaration, die die Signatur einer Funktion angibt und dem Compiler mitteilt, dass die Funktion existiert.

Signup and view all the flashcards

Funktionskommentare

Beschreiben, was eine Funktion macht, nicht wie.

Signup and view all the flashcards

Implementierungskommentare

Beschreiben auf abstrakter Ebene, wie etwas gemacht wird.

Signup and view all the flashcards

Funktionen

Werden im Hautprogramm außerhalb aller Blöcke definiert und sind im gesamten Programm ansprechbar.

Signup and view all the flashcards

Globale Variablen

Werden im Hauptprogramm außerhalb aller Blöcke definiert und sind im gesamten Programm ansprechbar, können jedoch mit lokalen Variablen desselben Namens verdeckt werden.

Signup and view all the flashcards

Lokale Variablen

Werden in einem Block definiert und sind nur innerhalb der Funktion / des Blocks ansprechbar und können durch Variablen in inneren Blöcken überdeckt werden.

Signup and view all the flashcards

Formale Parameter

Werden in einem Funktionskopf definiert. Sie leben ab ihrer Definition bis zum Ende der Funktion und sind nur innerhalb der Funktion ansprechbar.

Signup and view all the flashcards

Speicherklassen

extern: ...externe Variablen und ...externe Funktionen, static: ...statische Funktionen, ...statische globale Variablen, ...statische lokale Variablen

Signup and view all the flashcards

Globale Variable

...darf es insgesamt nur eine Definition geben.

Signup and view all the flashcards

Funktion

...darf es insgesamt nur eine Definition geben.

Signup and view all the flashcards

Statische Funktion

Die Sichtbarkeit wird auf den Rest der Quelldatei begrenzt, in der sich die Definition befindet.

Signup and view all the flashcards

Statische globale Variable

Die Sichtbarkeit wird auf die Quelldatei begrenzt, in der sich die Definition befindet.

Signup and view all the flashcards

Statische lokale Variable

Ihr Speicherplatz und ihr Wert bleiben über den Funktionsaufruf erhalten.

Signup and view all the flashcards

Register-Variable

Man kann dem Compiler mitteilen, dass eine Variable in einem Register gehalten werden soll

Signup and view all the flashcards

const

Variablen können nur einmal initialisiert und anschließend nicht mehr geändert werden können.

Signup and view all the flashcards

volatile

Der Wert einer Variablen wird bei jedem Zugriff neu aus dem Hauptspeicher gelesen.

Signup and view all the flashcards

Ziele des Präprozessors

Erhöhung der Lesbarkeit und Wartbarkeit von C-Programmen. Modularisierung von C-Programmen. Parallele Entwicklung von Software-Systemen im Team.

Signup and view all the flashcards

#include-Direktive

Der gesamte Inhalt einer Datei wird in den Quellcode eingefügt.

Signup and view all the flashcards

#define-Direktive

Es werden symbolische Namen definiert, die ab der Definition bis zum Ende der Datei durch den Ersatztext ersetzt werden.

Signup and view all the flashcards

Zusammenfassendes über den Präprozessor

Der Präprozessor überführt eine Quelldatei in ein Modul mit reinem C-Code.

Signup and view all the flashcards

Modularisierung von C-Programmen

Aufteilung eines Programms in mehrere Dateien und Auslagerung von Schnittstellen in Definitionsdateien.

Signup and view all the flashcards

Externe Variable

Soll eine globale Variable verwendet werden, bevor sie definiert wird, muss sie als extern deklariert werden.

Signup and view all the flashcards

Externe Funktionen

Soll eine extern deklarierte Funktion verwendet werden, die in einer anderen Quelldatei definiert ist, muss sie als extern deklariert werden.

Signup and view all the flashcards

Deklarationen

Deklarationen machen den Namen und Typ bekannt.

Signup and view all the flashcards

Definitionen

Definitionen definieren und reservieren den entsprechenden Speicherplatz.

Signup and view all the flashcards

Zeiger

Variablen, deren Wert eine Speicheradresse ist.

Signup and view all the flashcards

Normale Variablen

Bei ihrer Definition ein Speicherbereich reserviert, in dem der Wert der Variablen abgelegt wird.

Signup and view all the flashcards

Zeigervariablen

Bei ihrer Definition ein Speicherbereich reserviert, in dem die Adresse auf eine andere Speicherstelle abgelegt wird, an der der Wert abgelegt wird.

Signup and view all the flashcards

Zugriff auf die Variable

Liefert bei Zugriff auf die Variable den in diesem Speicherbereich abgelegte Wert.

Signup and view all the flashcards

Inhalts- oder Verweisoperator

Signup and view all the flashcards

&

Adressoperator

Signup and view all the flashcards

Zeigervariable

Zeigervariable muss vor ihrer ersten Verwendung definiert werden, ein definierter Zeiger, der noch nicht initialisiert wurde, zeigt auf eine zufällige Adresse.

Signup and view all the flashcards

Konstanten

Mit der const-Deklaration kann ein Zeiger oder der über ihn referenzierte Wert als konstant deklariert werden.

Signup and view all the flashcards

Hängende Zeiger

Zeiger, die auf alten, aber freigegebenen Speicherplatz verweisen, werden als „hängende Zeiger“ bezeichnet.

Signup and view all the flashcards

Wilde Zeiger

Zeigervariablen, die nicht initialisiert sind, nennt man „wilde Zeiger“, da sie auf einen beliebigen Speicherplatz verweisen.

Signup and view all the flashcards

Zeiger in Funktionen

Die Übergabe von Adressen an Funktionen. Die Idee ist, der Funktion die Möglichkeit zu geben, Variablen außerhalb ihres eigenen Gültigkeitsbereichs zu manipulieren.

Signup and view all the flashcards

Zeigerarithmetik

Rechnen mit Zeigern der gleichen Datentyp.

Signup and view all the flashcards

Zeiger und Vektoren

Jeder Ausdruck mit Vektorindizes kann auch mit Zeigern formuliert werden. p_a + 1 zeigt auf das nachfolgende Vektorelement / p_a - 1 zeigt auf das vorausgehende Vektorelement.

Signup and view all the flashcards

Zeiger auf Zeiger

Da Zeiger selbst Variablen sind, kann man – genau wie bei anderen Variablen – Zeiger auf sie definieren.

Signup and view all the flashcards

Void-Zeiger

Ein Zeiger auf den Datentyp void. Einem void-Zeiger kann eine beliebige Adresse zugewiesen werden. Erst bei Zugriff auf den Wert muss der Datentyp bekannt sein.

Signup and view all the flashcards

Funktionszeiger

Zeiger können auch auf Funktionen zeigen. Auch Funktionen belegen Speicherplatz ab einer bestimmten Adresse. Funktionen können über Zeiger aufgerufen werden.

Signup and view all the flashcards

Problemstellung Garbage Collection

Das Laufzeitsystem sucht bei einer Speicheranforderung nach der besten passenden Lücke, um die Anzahl der Lücken, d.h. die Fragmentierung des Speichers, einzuschränken.

Signup and view all the flashcards

Warum hat C keine automatische Garbage Collection?

Da es es nicht möglich, zu erkennen, welche Speicherbereiche vom Programm nicht mehr referenziert werden können. …und demzufolge freigegeben werden können.

Signup and view all the flashcards

Dynamische Vektoren

Zeiger und dynamische Speicherverwaltung zusammen ermöglichen die Verwaltung dynamischer Vektoren.

Signup and view all the flashcards

Study Notes

Funktionsaufrufe

  • Funktionsaufrufe haben die allgemeine Form <Funktionsname>(<Argumentliste>).
  • Beispiele für Funktionsaufrufe sind exponent = power(2, n);, power(2, n); und printf("%d %d\n", ++n, power(2, n));.
  • Funktionen können innerhalb anderer Funktionen aufgerufen werden.
  • Funktionen können sich selbst (Rekursion) oder gegenseitig aufrufen.
  • Die Funktion main wird automatisch von der Laufzeitumgebung aufgerufen.
  • Das Ergebnis eines Funktionsaufrufs kann ignoriert werden.

Parameterübergabe

  • In C erfolgt die Parameterübergabe immer als call-by-value.
  • Die Argumentwerte werden kopiert, und diese Kopie wird an die aufgerufene Funktion übergeben.
  • Änderungen an der Kopie innerhalb der Funktion haben keine Auswirkungen auf die Umgebung außerhalb der Funktion.
  • call-by-reference kann in C durch Übergabe von Speicheradressen (Zeigern) simuliert werden.
  • Zum Beispiel durch die Übergabe der Startadresse eines Vektors.
  • Die Adresse selbst wird jedoch wieder als call-by-value übergeben.
  • Beim Funktionsaufruf werden die Argumente, falls nötig, implizit in den Typ der Parameter umgewandelt.
  • Beim Verlassen der Funktion wird das Ergebnis im return-Statement implizit in den Ergebnistyp der Funktion umgewandelt, falls nötig.

Funktionsprototypen

  • In C muss jeder Bezeichner vor seiner ersten Verwendung deklariert sein; dies gilt auch für Funktionen.
  • Es ist nicht immer möglich, eine Funktion vor ihrer ersten Verwendung zu vereinbaren, zum Beispiel bei gegenseitiger Rekursion oder wenn eine Funktion in einer anderen Datei definiert ist.
  • Funktionen können durch Funktionsprototypen deklariert werden.
  • Funktionsprototypen geben die Signatur der Funktion an und werden mit einem Semikolon (;) abgeschlossen.
  • Ein Funktionsprototyp teilt dem Compiler mit, dass es die Funktion geben wird.

Kommentare in Funktionen

  • Funktionskommentare beschreiben, was die Funktion macht, nicht wie es gemacht wird, und befinden sich beim Funktionsprototyp.
  • Implementierungskommentare beschreiben auf abstrakter Ebene, wie etwas gemacht wird, und befinden sich innerhalb der Funktionsdefinition.

Lebensdauer und Sichtbarkeit (1)

  • Funktionen werden im Hauptprogramm außerhalb aller Blöcke definiert.
  • Lebensdauer: Funktionen "leben" ab ihrer Definition bis zum Ende des Programms.
  • Sichtbarkeit: Funktionen sind im gesamten Programm ansprechbar.
  • Globale Variablen bieten eine Alternative zum Austausch von Daten über Funktionsargumente und Funktionsergebnisse.
  • Globale Variablen werden im Hauptprogramm außerhalb aller Blöcke definiert und bei Definition automatisch mit 0 initialisiert.
  • Lebensdauer: Globale Variablen "leben" ab ihrer Definition bis zum Ende des Programms.
  • Sichtbarkeit: Globale Variablen sind im gesamten Programm ansprechbar, können aber durch Variablen mit gleichem Namen in lokalen Blöcken verdeckt werden.

Lebensdauer und Sichtbarkeit (2)

  • Lokale Variablen werden in einem Block definiert und mit dem Schlüsselwort auto eingeleitet, das üblicherweise weggelassen werden kann.
  • Bei der Definition wird Speicherplatz zugewiesen, der beim Schließen des Blocks automatisch wieder freigegeben wird.
  • Daraus resultiert, dass lokale Variablen auch Auto-Variablen oder automatische Variablen genannt werden.
  • Auto-Variablen werden bei ihrer Definition nicht initialisiert, ihr Wert ist zufällig.
  • Lebensdauer: Sie "leben" ab ihrer Definition bis zum Ende des Blocks.
  • Sichtbarkeit: Sie sind nur innerhalb der Funktion / des Blocks ansprechbar und können durch Variablen in inneren Blöcken überdeckt werden.

Lebensdauer und Sichtbarkeit (3)

  • Formale Parameter werden im Funktionskopf definiert: int my_param
  • Bei Aufruf einer Funktion wird dem formalen Parameter Speicherplatz zugewiesen und dieser beim Verlassen der Funktion automatisch wieder freigegeben.
  • Formale Parameter erhalten ihren Wert, indem der Wert des aktuellen Parameters in ihren Speicherplatz kopiert wird, d.h., call-by-value.
  • Lebensdauer: Sie "leben" ab ihrer Definition bis zum Ende der Funktion.
  • Sichtbarkeit: Sie sind nur innerhalb der Funktion ansprechbar und können durch Variablen in inneren Blöcken überdeckt werden.

Speicherklassen

  • Variablen und Funktionen können verschiedenen Speicherklassen zugeordnet werden:
    • extern
      • Externe Variablen
      • Externe Funktionen
    • static
      • Statische Funktionen
      • Statische globale Variablen
      • Statische lokale Variablen
    • register
      • Register-Variablen
  • auto ist ebenfalls eine Speicherklasse.

Externe Variable

  • Globale Variablen, die verwendet werden, bevor sie definiert wurden, oder globale Variablen die in einer anderen Quelldatei definiert sind, müssen als extern deklariert werden
  • Eine extern -Deklaration reserviert keinen Speicherplatz und ermöglicht keine Initialisierung.
  • Vektorgrößen können weggelassen werden.
  • Eine extern-Deklaration stellt sicher, dass dem Compiler Name und Typ einer globalen Variablen bekannt sind.
  • Für eine globale Variable darf es insgesamt nur eine Definition, aber mehrere Deklarationen geben.

Externe Funktionen

  • Funktionen, die in einer anderen Quelldatei definiert sind, müssen als extern deklariert werden, wenn sie verwendet werden sollen.
  • Das Schlüsselwort extern wird bei Funktionsprototypen automatisch angenommen.
  • Für eine Funktion darf es insgesamt nur eine Definition, aber mehrere extern-Deklarationen geben.

Statische Funktionen

  • Funktionen, die als static vereinbart wurden, sind nur innerhalb der Quelldatei sichtbar, in der sie definiert sind. static int ulam(int n); static void get_sub_matrix(matrix[][MAX_SIZE], submatrix[][MAX_SIZE]);
  • Statische Funktionen können als „private“ Funktionen betrachtet werden.

Statische globale Variablen

  • Werden globale Variablen als static vereinbart, ist die Sichtbarkeit auf die Quelldatei, in der sie definiert wurden, beschränkt. static int position; static int buffer[BUFSIZE];
  • Variablen können gemeinsam von verschiedenen Funktionen innerhalb einer Datei verwendet werden aber sind nicht extern verfügbar.
  • Statische Variablen werden bei Definition automatisch mit 0 initialisiert.

Statische lokale Variablen

  • Wird eine lokale Variable als static vereinbart, behält sie ihren Speicherplatz und Wert über den Funktionsaufruf hinweg.
  • Die Variable wird nicht bei jedem Aufruf der Funktion neu erzeugt.
  • Statische lokale Funktionen sind nur in der Funktion sichtbar, in der sie definiert sind.
  • Die Lebensdauer beginnt mit der Definition bei dem ersten Aufruf der Funktion und endet erst mit der gesamten Programmausführung.

Register-Variablen

  • Mittels register kann man dem Compiler mitteilen, dass eine Variable in einem Register gehalten werden soll.
  • register kann bei lokalen Variablen und formalen Parametern einer Funktion angewendet werden.
void function(register unsigned int m)
{
register int i;
}

  • Registervariablen werden nicht automatisch initialisiert.
  • Deklaration nur bei häufig verwendeten Variablen sinnvoll.
  • Der Compiler darf den register Hinweis ignorieren.
  • Compiler ignorieren fehlerhafte Deklarationen.

Konstanten

  • Mittels const können Variablen nur einmal initialisiert und anschließend nicht mehr geändert werden können. const double PI = 3.141592654;
  • Eine const-Vereinbarung ist für jeden Datentyp möglich.
  • Bei einem Vektor bedeutet dies, dass die Elemente des Vektors nicht geändert werden können: const char PRIMES[] = {2, 3, 5, 7, 11, 13};
  • const-Deklarationen sind #define-Direktiven vorzuziehen, da sie Gültigkeitsbereiche und Typisierungen berücksichtigen.
  • Eine const-Vereinbarung ist zudem ein Optimierungshinweis für den Compiler.
  • Wird ein Parameter einer Funktion als const deklariert, kann innerhalb der Funktion keine Änderung der entsprechenden Variablen erfolgen.

Volatile

  • Mittels volatile kann festgelegt werden, dass der Wert einer Variablen bei jedem Zugriff neu aus dem Hauptspeicher gelesen wird.
  • Sinnvoll eingesetzt werden kann dies in der Treiberprogrammierung, wenn Werte aus Geräteregistern gelesen werden oder wenn sich Werte außerhalb des Programmkontextes ändern.
  • Optimierende Compiler würden beispielsweise eine Schleife als überflüssig ansehen und wegoptimieren, obwohl das Ergebnis der Schleife durch Änderungen in Geräteregistern maßgeblich beeinflusst werden würde.

Ziele des Präprozessors

  • Erhöhung der Lesbarkeit und Wartbarkeit von C-Programmen
  • Durch die Definition symbolischer Konstanten oder Makros
  • Modularisierung von C-Programmen
  • Funktionen, die inhaltlich nicht zusammen gehören, können auf verschiedene Dateien (Module) verteilt werden
  • Parallele Entwicklung von Software-Systemen im Team
  • Schnittstellendefinitionen (Funktionsprototypen, Typdefinitionen und Konstanten) können in Definitionsdateien ausgelagert werden und dann vom gesamten Team während der Entwicklung genutzt werden.

Einfügen von Dateien (1)

  • Mit der #include-Direktive kann der gesamte Inhalt einer Datei in den Quellcode eingefügt werden.
  • Die Zeile mit der #include-Anweisung wird textuell durch den Inhalt der Datei ersetzt.
  • Der Dateiname wird (üblicherweise) ohne Pfad angegeben.
    • #include <Dateiname>: Die Suche beginnt im Verzeichnis für Standard-Bibliotheken. Im Allgemeinen ist dieses über eine Umgebungsvariable definiert.
    • #include "Dateiname": Die Suche nach der Datei beginnt in dem Verzeichnis, in dem sich auch die Quelldatei befindet. Wird sie dort nicht gefunden, geht die Suche wie bei <Dateiname> weiter.

Einfügen von Dateien (2)

  • Die eingefügte Datei darf weitere #include-Anweisungen enthalten, die auch ersetzt werden.
  • Es gibt keinen Abbruch bei rekursivem include.
  • #include-Anweisungen stehen häufig am Anfang von Dateien, um
    • Bibliotheksfunktionen bekannt zu machen (beispielsweise #include <stdio.h>).
    • eigene Definitionsdateien, das heißt, Deklarationen von gemeinsam verwendeten Variablen und Funktionen, einzufügen.
  • Dadurch werden Programme gut strukturiert und Code-Redundanz vermieden.

Makros: Symbolische Namen

  • Mit der #define-Direktive können symbolische Namen definiert werden: #define <Name> <Ersatztext>
  • Alle Vorkommen des symbolischen Namens werden ab der Definition bis zum Ende der Datei durch den Ersatztext ersetzt.
  • Konvention: Namen in Großbuchstaben #define YES ja
  • Es werden nur Token ersetzt.
  • Keine Ersetzung in printf("YES") oder in YESterday = today - 1;
  • Mit der #undef-Direktive können symbolische Namen wieder gelöscht werden.
  • Mit der #if-Direktive (und #elif, #else, #endif) kann die Ausführung des Präprozessors kontrolliert werden. Programmtexte können Quellcode und weitere Präprozessordirektiven enthalten.

Zusammenfassendes über den Präprozessor

  • Der Präprozessor überführt eine Quelldatei in ein Modul mit reinem C-Code.
  • Präprozessor-Anweisungen beginnen mit # und enden mit dem Zeilenumbruch.
  • Parametrisierte Makros können wie kleine Funktionen verwendet werden.
  • Sorgfältige Klammerung, mehrfache Bewertung von Parametern. Makros haben gegenüber Funktionen sowohl Vorteile als auch Nachteile
  • Bedingte Übersetzung
    • Ermöglicht die Aufteilung von C-Programmen in mehrere Dateien (Verhinderung mehrfacher Includes)
    • Ist sinnvoll, wenn ein Projekt für unterschiedliche Ziele übersetzt werden soll, beispielsweise für verschiedene Plattformen oder zur Unterscheidung von Debug- oder Release-Modus.
  • Header-Dateien definieren die Schnittstelle eines C-Programms.
  • #include-Direktiven fügen die gesamte Datei ein.
  • Ein Include der Standard Header-Dateien importiert nur die Namen. Die Bibliothek wird zu einem späteren Zeitpunkt vollständig zum Programm hinzugelinkt.

Modularisierung

  • Es gibt in C kein explizites Modulkonzept.
  • Eine Strukturierung von C-Programmen ist trotzdem möglich:
    • durch Aufteilung eines Programms in mehrere Dateien
    • Auslagerung von Schnittstellen in Definitionsdateien: Headerdateien (Suffix .h)
  • Schnittstelle meint: Deklarationen von extern verwendeten
    • symbolischen Konstanten
    • Variablen
    • Funktionen

Zur Erinnerung...

  • Externe Variablen:
    • Globale Variablen, die verwendet werden, bevor sie definiert wurden, oder globale Variablen, die in einer anderen Quelldatei definiert wurden, müssen als extern deklariert werden.
    • Es darf nur eine Definition geben, es kann aber mehrere Deklarationen geben.
  • Externe Funktionen:
    • Funktionen, die in einer anderen Quelldatei definiert sind, müssen als extern deklariert werden, wenn sie verwendet werden sollen.
    • Es darf nur eine Definition geben, es kann aber mehrere Deklarationen geben.
  • Deklarationen machen den Namen und Typ bekannt.
  • Definitionen definieren und reservieren den entsprechenden Speicherplatz.

Kommentare

  • Projektkommentar zu Beginn der Datei mainpage.h oder main.c
/**
 * @mainpage RSA-Verschlüsselung
 *
 *  Dieses Projekt realisiert die RSA-Verschlüsselung ...
 *
 * @author Elfie Fast
 * @date    2012-08-24
 */
  • Dateikommentare zu Beginn der Datei main.c und der Header-Dateien
/**
 * @file
 * In diesem Modul wird ...
 *
 * @author Dieter Hacker
 * @date    2012-08-24
 */

  • Funktionskommentare:
    • Bei externen Funktionen gehört die Beschreibung, (soweit es sich nicht um Interna der Implementierung handelt) zum Prototyp der Funktion in die Header-Datei.
  • Die Beschreibung externer Variablen gehört ebenfalls in die Header-Datei.
    • Externe Variablen sollten jedoch vermieden werden.

Prinzipien (1)

  • Zeiger sind Variablen, deren Wert eine Speicheradresse ist; sie sind Zeiger auf eine Speicherstelle.
  • Im Gegensatz dazu wird für normale Variablen bei ihrer Definition ein Speicherbereich reserviert, in dem der Wert abgelegt wird. Beim Zugriff auf die Variable wird der in diesem Speicherbereich abgelegte Wert geliefert.
  • Für Zeigervariablen wird bei ihrer Definition ein Speicherbereich reserviert, in dem die Adresse auf eine andere Speicherstelle abgelegt wird, an der der Wert abgelegt wird.
  • Beim Zugriff auf die Zeigervariable kann direkt auf die Adresse oder indirekt auf den Wert zugegriffen werden.

Prinzipien (2)

  • Der Speicher kann man sich als einen Vektor von Speicherzellen, die fortlaufend nummeriert sind, vorstellen.
  • Die Speicherzellen können einzeln oder in zusammenhängenden Gruppen bearbeitet werden, wobei beispielsweise der Datentyp char ein Byte, der Datentyp int eine Gruppe von vier hintereinander liegenden Bytes benötigt.
  • Ein Zeiger benötigt eine Bytegruppe, die so groß ist, dass jede gültige Speicheradresse dort abgelegt werden kann.
  • Üblich: vier Byte...
  • Zeiger zeigen immer auf den Anfang einer Bytegruppe.

Operatoren

  • Im Zusammenhang mit Zeigern gibt es in C zwei unäre Operatoren:
    • Inhalts- oder Verweisoperator *
      • Syntax: *Zeigervariable
      • Mit dem Inhaltsoperator kann auf den Wert zugegriffen werden, auf den der Zeiger verweist.
    • Adressoperator &
      • Syntax: &Variable
      • Mit dem Adressoperator kann auf die Adresse einer Variablen zugegriffen werden.

Zeigervariablen

  • Zeigervariablen müssen vor ihrer ersten Verwendung definiert werden. Typname *Bezeichner;
  • Durch die Definition wird Speicherplatz für einen Zeiger angelegt.
  • Ein definierter Zeiger, der noch nicht initialisiert wurde, zeigt auf eine zufällige Adresse.
  • Die Typangabe bezieht sich auf den referenzierten Wert, nicht die Variable selbst; die ist ein Zeiger.
  • Die Definition eines Zeigers ist als Muster zu verstehen.
  • Der Wert des Ausdrucks *Bezeichner ist vom Typ Typname.
  • Typname *Bezeichner und Typname* Bezeichner sind semantisch gleich.
  • Wichtig ist, dass man weiß, welcher Datentyp sich hinter einer Adresse verbirgt.
  • Der Zeiger zeigt nur auf die Startadresse der Bytegruppe.
  • Es gibt keine Möglichkeit herauszufinden, wie viel Speicher an der Stelle, auf die der Zeiger zeigt, zusammengehört.

Benennung

  • Typname *Bezeichner; und Typname* Bezeichner; sind semantisch gleich. In der Literatur zu C findet sich beides.
  • Die Namen von Zeigervariablen sollten darauf hinweisen, dass sie Zeiger sind:
    • p... oder
    • p_… oder
    • ptr_… oder
    • …_ptr
  • Ergibt dann z.B. *p_int oder *ptr_char oder *long_int_ptr ...

Initialisierung von Zeigervariablen

  • Zeiger desselben Typs können einander zugewiesen werden
  • beide Zeiger verweisen auf denselben Speicherbereich.
  • Eine Zeigervariable kann bei der Definition initialisiert werden...
    • entweder mit NULL (benötigt: #include <stdlib.h>). NULL ist nichts anderes als die Adresse 0
    • oder mit einem Ausdruck, der einen Zeiger zurückliefert.

Spezialfälle

  • Wilde Zeiger:
    • Zeigervariablen, die nicht initialisiert sind, zeigen auf keinen definierten Speicherbereich.
  • Hängende Zeiger
    • Zeiger, die auf einen alten, aber freigegebenen Speicherplatz verweisen, werden als "hängende Zeiger" (engl. dangling pointer) bezeichnet
  • Konstanten
    • ein Zeiger oder der über ihn referenzierte Wert als konstant deklariert
    • jeder Versuch, den Zeiger oder den Wert zu verändern zu einem Kompilierfehler führt

Zeiger als Funktionsargument

  • Die Übergabe von Adressen an Funktionen ermöglicht die Parameterübergabe call-by-reference.
  • Die Übergabe der Speicheradresse ist weiterhin call-by-value.
  • Die Adresse, die der Zeiger als Wert hat, wird in den Speicherbereich für die Parameter der Funktion kopiert.
  • Funktionen können Zeiger als Rückgabewerte liefern.

Zeigerarithmetik

  • Da Zeiger ganzzahlige Adressen enthalten, kann mit ihnen gerechnet werden.
  • Zu einem Zeiger kann eine ganze Zahl addiert oder subtrahiert werden. Damit sind beliebige Speicherbereiche adressierbar.
  • Bei der Addition und Subtraktion mit Zeigern spielt der Datentyp des Zeigers eine wesentliche Rolle.
  • Eine Addition (oder Subtraktion) mit 1 bedeutet, dass so viele Bytes addiert (oder subtrahiert) werden wie der referenzierte Datentyp groß ist.
  • Ist wirklich nur mit Vektoren sinnvoll.

Zeiger und Vektoren

  • Die Zeigerarithmetik wird am häufigsten im Zusammenhang mit Vektoren eingesetzt.
  • Jeder Ausdruck mit Vektorindizes kann auch mit Zeigern formuliert werden, z.B. array[i] kann immer als *(array + i) geschrieben werden.
  • Der C-Compiler wandelt alle Vektorzugriffe mittels [] intern in die Zeigerdarstellung um, also z.B. array[i] ⇒ *(array + i);
  • Unabhängig vom zugrunde liegenden Datentyp des Vektors gilt:
    • p_a + 1 zeigt auf das nachfolgende Vektorelement.
    • p_a - 1 zeigt auf das vorausgehende Vektorelement.
  • Die Subtraktion von zwei Zeigern liefert die Anzahl der Elemente zwischen den Zeigern.
  • Der Wert einer Variablen oder eines Ausdrucks vom Typ Vektor ist die Adresse des Anfangselements des Vektors, d.h. des Elements 0. Diese Adresse kann auch einem anderen Zeiger zugewiesen werden.

Zeiger auf Zeiger

  • Da Zeiger selbst Variablen sind, kann man – genau wie bei anderen Variablen – Zeiger auf sie definieren.
  • Zeiger können – genau wie andere Variablen – in Vektoren zusammengefasst werden.
  • Die Startadresse eines Zeigervektors kann als Parameter an eine Funktion übergeben werden.

Kommandozeilenargumente

  • Eine main Funktion hat Zugriff auf Parameter:
int main(int argc, char **argv)
{ ... }

  • argc enthält die Anzahl der übergebenen Parameter (mindestens 1, da der Programmname immer übergeben wird).
  • argv ein Vektor von char-Zeigern, also Zeichenketten.
    • Diese Zeichenketten enthalten die übergebenen Parameter als Strings
    • Das erste Element enthält den Programmnamen, alle weiteren Argumente in der Reihenfolge, wie sie übergeben worden sind.

void-Zeiger

  • Ein Zeiger auf den Datentyp void
    • ist ein typenloser Zeiger und wird wie folgt deklariert: void *p;
    • Einem void-Zeiger kann eine beliebige Adresse zugewiesen werden.
  • void-Zeiger werden verwendet, wenn der Datentyp des Wertes noch nicht feststeht oder es egal ist, um welchen Typ es sich handelt.
  • Erst bei Zugriff auf den Wert muss der Datentyp bekannt sein.
  • void-Zeiger können durch eine explizite Typumwandlung in jeden anderen beliebigen Zeigertyp umgewandelt werden.

Funktionszeiger

  • Zeiger können auch auf Funktionen zeigen.
  • Auch Funktionen belegen Speicherplatz ab einer bestimmten Adresse.
  • Funktionen können über Zeiger aufgerufen werden.
  • Beim Funktionsaufruf wird zur Anfangsadresse der Funktion im Speicher verzweigt.
  • Funktionen können über Zeiger als Argumente an andere Funktionen übergeben werden.
  • Auch für Funktionszeiger können eigene Typen definiert werden.
  • Die Übergabe von Funktionen als Parameter an andere Funktionen ist sinnvoll, wenn
    • man eine Funktion für verschiedene Typen (polymorphe Funktionen) realisieren möchte
    • man eine übergeordnete Datenstruktur realisieren will und dabei vom Typ der Elemente abstrahieren möchte

Dynamische Speicherverwaltung

  • Bezeichnet, dass Speicher erst zur Laufzeit angefordert wird, wenn bekannt ist, wie viel Speicher benötigt wird, wieder freigegeben und dessen Größe bei Bedarf angepasst wird.
  • Die Anforderung eines Speicherblocks liefert die Anfangsadresse des Blocks als Rückgabewert.
  • Funktionen zur dynamischen Speicherverwaltung sind in der Standard-Bibliothek stdlib.h vordefiniert:
    • Anfordern eines Speicherblocks (malloc, calloc)
    • Freigeben eines Speicherblocks (free)
    • Vergrößern und Verkleinern eines Speicherblocks (realloc)

Problemstellung Garbage Collection (1)

  • Bei jeder Speicheranforderung ist ein zusammenhängender Speicherbereich in der gewünschten Größe notwendig.
  • Nicht mehr genutzter Speicher muss wieder freigegeben werden.
  • Das Laufzeitsystem sucht nach einer passenden Speicherlücke, die Anzahl der Speicherlücken und die Fragmentierung des Speichers zu reduzieren.
  • Die benutzten Speicherlücken werden immer weiter aufgeteilt.
  • Wenn der Speicher nach vielen malloc- und free-Aufrufen so zerstückelt ist, dass kein zusammenhängender Speicherbereich in der benötigten Größe mehr existiert, obwohl insgesamt noch genügend Platz ist, tritt ein Problem auf.

Problemstellung Garbage Collection (2)

  • Wenn das Programm nicht mehr genutzten Speicher nicht freigibt, kommt es auf dem System zu Speicherlecks .
  • Auf C gibt es keine automatische Speicherbereinigung:
    • Der Garbage Collector entfernt alle Objekte, auf die vom Programm nicht mehr verwiesen wird.
    • Der Garbage Collector stellt den Speicherplatz der Speicherverwaltung zur Wiederverwendung zurück.
    • Der Garbage Collector hält die Speicherfragmentierung möglichst gering.

Problemstellung Garbage Collection (3)

  • C hat keine automatische Garbage Collection:
    • In C und C++ kann mit Speicheradressen gerechnet und jede beliebige Adresse zugewiesen werden.
    • Damit können Zeigerprinzipiell auf jede Stelle im Speicher zeigen.
  • Es ist nicht möglich, Speicherbereiche vom Programm zu erkennen, die nicht mehr referenziert werden und freigegeben werden können...

Dynamische Vektoren

  • Zeiger und dynamische Speicherverwaltung zusammen ermöglichen die Verwaltung dynamischer Vektoren:
    • Die dynamische Speicheranforderung liefert einen zusammenhängenden Speicherbereich.
    • Dieser kann als Vektor verwendet werden.
    • Funktionen können Zeiger als Rückgabewerte liefern, beispielsweise die Startadresse von dynamisch allokiertem Speicher.
  • Beispiel: „Dynamische Matrizen“

Strukturen

  • Syntax
struct <Strukturname>
{
    <Liste von Variablendeklarationen>
};

  • Die Definition einer Struktur beginnt mit dem Schlüsselwort struct.
  • Danach folgt der optionale Name der Struktur (das Etikett).
  • Die Variablen der Struktur heißen Komponenten der Struktur.
  • Die Definition wird mit einem Semikolon ; abgeschlossen.
struct PUNKT
{
    int x;    /* x-Koordinate des Punkts */
    int y;    /* y-Koordinate des Punkts */
};

Strukturvariablen

  • es können Variablen definiert werden, die Strukturen wie PUNKT als Typ haben.
  • Bei der Definition einer Auto-Variablen über einem Strukturtyp wird – wie bei anderen Variablen auch – Speicherplatz reserviert.
  • Die Komponenten der Struktur werden dabei direkt hintereinander im Speicher abgelegt.
  • Strukturtypen müssen nicht benannt werden
  • es ist möglich, direkt bei Definition einer Variablen einen anonymen Strukturtyp zu definieren
  • Anonyme Strukturen können nicht als Rückgabetyp von Funktionen definiert werden
  • in C sind zwei Strukturtypen nur dann gleich, wenn sie denselben Namen haben
  • Für lokale Variablen über Strukturtypen...
    • erfolgt – wie üblich – keine automatische Initialisierung.
  • Für globale und statische Variablen über Strukturtypen...
    • erfolgt – wie üblich – eine automatische Initialisierung mit 0.

Zugriff auf die Komponenten einer Struktur

  • Mit dem Auswahloperator . kann auf die Komponenten einer Struktur zugegriffen werden.
  • Der Punktoperator ist analog zu dem Auswahloperator in objektorientierten Programmiersprachen zu verstehen.

Kopiervorgang bei Strukturen

  • Da beliebige Variablen-Deklarationen in einer Struktur erlaubt sind, können hier auch Zeiger definiert sein
  • In diesem Fall wird nur die gespeicherte Adresse kopiert, nicht aber das referenzierte Objekt.
  • Man spricht daher von einer "flachen Kopie": shallow copy.
  • Das Pendant zur shallow copy ist die "tiefe Kopie": deep copy.

Vergleich von Strukturen

  • Strukturen lassen sich nicht ohne weiteres vergleichen!
  • Der Vergleich muss für alle zu vergleichenden Komponenten explizit programmiert werden.
  • Zum Vergleich von Strukturen müssen daher eigene Funktionen geschrieben werden, in denen festgelegt werden kann, ob flach oder deep verglichen werden soll.

Speicherorganisation

  • Strukturen nur indirekt vergleichen, weil:
    • Die Komponenten einer Struktur werden direkt hintereinander im Hauptspeicher abgelegt.
    • Es entstehen kleine Lücken, sogenannte Pad-Bytes, in denen zufällige Werte stehen.
    • Bei einem direkten Vergleich könnte die Strukturen deshalb einen falschen Wert annehmen.
  • Die Größe einer Struktur kann mit dem sizeof-Operator bestimmt werden.
  • Strukturelemente können sich durch das Alignment der Speicherorganisation unterscheiden.

Zeiger auf Strukturen

  • Zeiger auf Strukturen können definiert und verwendet werden.
  • Die Adresse von Strukturvariablen kann mit dem Adressoperator & bestimmt werden.
  • Der Operator . zur Auswahl von Strukturkomponenten bindet enger als der Inhaltsoperator *.
  • Zeiger auf Strukturen verwendet, um unnötiges Kopieren großer Strukturen zu vermeiden.
  • Zeiger auf Strukturen wird oft verwendet abkürzende Schreibweise für den Komponentenzugriff:
    • (*p_point).x kann mit p_point->x dargestellt werden
  • Vor der Anwendung des Auswahloperators -> muss sichergestellt sein, dass der Zeiger auch auf einen Speicherplatz verweist.

Parameter bei Prozeduren: Übergabe via Zeiger

  • Vorteile, einen Zeiger auf eine Struktur an eine Funktionen zu übergeben: Wenn die Struktur in der Funktion verändert und die Änderungen in der aufrufenden Umgebung sichtbar sein müssen
  • Übergabe mit call-by-value bei großen Strukturen aufwändig
  • Die Rückgabe von Zeigern auf Strukturen im Zusammenhang mit dynamischer Speicherallokation ist sinnvoll

Rekursive Strukturen

  • Strukturen können wechselseitig referenziert werden.
  • Die Referenzierung kann nur über Zeiger erfolgen
  • Strukturelemente dürfen sich selbst nicht enthalten, aber auf sich selbst verweisen.
  • Beispiel: Struktur für Elemente einer doppelt verketteten Liste 
  • Es ist sinnvoll, Strukturen über Zeiger zu referenzieren. Verwendung von Strukturvariablen in manchen Fällen angebracht.
  • Beispiele könnten bei der verketteten Liste sinnvoll sein
  • Die Liste über eine Strukturvariable...
  • Die Einträge über Zeiger zu referenzieren

Wissenswertes über Strukturen

  • Mit Strukturen können Daten, die inhaltlich zusammengehören, zusammengefasst werden. Sie können (fast) wie primitive Datentypen verwendet werden.
  • Bei der Definition von Strukturvariablen wird Speicherbereich für die ganze Struktur mit allen Komponenten bereitgestellt.
  • Bei Zuweisungen, der Übergabe an Funktionen als Argumente, oder der Rückgabe von Funktionen wird die ganze Struktur kopiert.
    • Daher sollten große Strukturen über Zeiger referenziert werden.
  • Strukturen sind als Elemente von Vektoren erlaubt.

Speicherorganisation

  • Die Komponenten einer Struktur liegen nicht immer lückenlos im Speicher hintereinander.
  • Strukturen können daher nicht mit == verglichen werden, und die Größe der Struktur kann nicht aus der Größe ihrer Komponenten berechnet werden.
  • Strukturen können sich nicht selbst enthalten. Bei rekursiven Strukturen muss auf die Komponenten desselben Strukturtyps über Zeiger verwiesen werden.
  • Der Zugriff auf die Komponenten erfolgt mit dem Operator ., bei Zugriffen über einen Zeiger steht der Pfeiloperator -> zur Verfügung.

Unionen

  • Eine Union ist eine Struktur, bei der nur eine der Komponenten belegt werden kann.
  • Die Komponenten einer Union werden Alternativen genannt.
  • Eine Union benötigt den Speicherplatz der grössten Alternative.
  • Unionen sparen gegenüber Strukturen Speicherplatz, wenn Komponenten einer Struktur nie zusammen auftreten.
  • Unionen sind nur bedingt über verschiedene Systeme/Plattformen austauschbar sind und vom Alignment abhängig.
  • Über Unionstypen können Variablen definiert werden:
    • Einer Unionsvariablen kann ein Wert zugewiesen werden - aus Datentypen der enthaltenen Alternativen.
    • Unionsvariablen können bei Definition initialisiert werden.
    • Wird auf eine Union zugegriffen, muss man wissen, welcher Datentyp zuletzt gespeichert wurde.
  • Unionen können in Strukturen und Vektoren vorkommen.

Bitfelder

  • Bitfelder sind Strukturkomponenten, die mit weniger als einem Byte in eine Struktur gepackt werden können.
  • Ein Bitfeld besteht aus einer bestimmten Anzahl an Bits.
  • Die Länge wird vom Bitfeld-Namen durch einen Doppelpunkt getrennt. unsigned int is_keyword : 1
  • Ein Bitfeld kann Komponente einer Struktur oder Union sein.
  • Da Bitfelder keine adressierbare Speicherstelle belegen, ist der Adressoperator & nicht anwendbar.
  • Bitfelder werden vor allem in der hardwarenahen Programmierung eingesetzt.

Variable Argumentlisten (Ellipsen)

  • C erlaubt die Definition von Funktionen mit einer variablen Anzahl an Argumenten.
  • Die Signatur solcher Funktionen enthält eine Ellipse in der formalen - Parameterliste:
    • Eine Ellipse besteht aus drei Punkten: ...
    • Nach dem letzten Parameter in der Parameterliste angegeben
    • Die Funktion muss explizit einen Parameter haben.
  • Variable Argumentlisten sind gefährlich:
    • Der Compiler prüft die Argumente nicht.
    • Es liegt an der Hand des Aufrufers, dass die Argumente korrekt sind.
  • Es gibt folgende vier Makros und einen Datentyp in der Standardheaderdatei stdarg.h der Programmiersprache C:
    • va_list - einen Datentyp zur Darstellung eines Zeigers auf ein Argument in einer Argumentliste
    • va_start - ein Makro, das diesen Zeiger auf das erste Argument in der Liste setzt
    • va_arg - ein Makro, das das aktuelle Argument liefert und dann den Zeiger auf das nächste Argument setzt
    • va_copy - ein Makro, das einen Zeiger in einen anderen kopiert
    • va_end - ein Makro, das Aufräumarbeiten vornimmt

Studying That Suits You

Use AI to generate personalized quizzes and flashcards to suit your learning preferences.

Quiz Team

Related Documents

More Like This

Programming Function Calls Quiz
16 questions
Estructura de Programación en C#
5 questions

Estructura de Programación en C#

CostEffectiveRationality3754 avatar
CostEffectiveRationality3754
ARMv7 Assembly: Nested Function Calls
9 questions

ARMv7 Assembly: Nested Function Calls

CharismaticSerpentine5245 avatar
CharismaticSerpentine5245
Use Quizgecko on...
Browser
Browser