Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

Luca

Treue Seele

  • »Luca« ist der Autor dieses Themas

Beiträge: 188

Wohnort: Braunschweig

  • Private Nachricht senden

1

20.03.2012, 16:40

Präprozessor - Wozu?

Hallo,

Ich habe mich heute gefragt, wozu man den Präprozessor eigentlich verwendet. Ich Programmiere gerade nach einem Buch-Beispiel ein Quizspiel. In der Header der Quiz.h steht beispielsweise ganz oben:

#ifndef QUIZ_HEADER
#define QUIZ_HEADER

Wieso benutzt man das eigentlich? Und warum wird der Präprozessor immer groß geschrieben? Dient das nur zur Übersichtlichkeit oder hat das noch eine andere Funktion?

Gruß,
Luca

BlueCobold

Community-Fossil

Beiträge: 10 738

Beruf: Teamleiter Mobile Applikationen & Senior Software Engineer

  • Private Nachricht senden

2

20.03.2012, 17:01

Haua, haua, haua. Also. Der Präprozessor ist ein Programm, keine eigens definierte Konstante. Groß oder klein schreiben kann man "ihn" also nicht (außer das Wort "Präprozessor" natürlich).
Das da oben sind so genannte Include-Guards. Google wird Dir da gern weiterhelfen.

Der Präprozessor oder nennen wir ihn lieber Precompiler verarbeitet die Dateien bevor sie an den eigentlichen Compiler übergeben werden. Damit sind Statements, Ausdrücke oder Verhalten möglich, die mit einem feststehenden Quellcode nicht möglich wären.
Teamleiter von Rickety Racquet (ehemals das "Foren-Projekt") und von Marble Theory

Willkommen auf SPPRO, auch dir wird man zu Unity oder zur Unreal-Engine raten, ganz bestimmt.[/Sarkasmus]

Beiträge: 1 223

Wohnort: Deutschland Bayern

Beruf: Schüler

  • Private Nachricht senden

3

20.03.2012, 18:11

C-/C++-Quelltext

1
2
#ifndef QUIZ_HEADER
#define QUIZ_HEADER

Das wird benutzt, damit ein Code Abschnitt der mehrmals inkludiert wurde, nicht mehrmals definiert\deklariert wird.
Im MSVC-Complier ist auch ein einfaches "#pragma once" möglich.

Generell dient der Präprozessor dazu, bestimmte Codeabschnitte vor der Compilezeit automatisch anzupassen.
Zb. um mehrere Compiler\Platformen ohne Codeänderungen zu unterstützen.

Ob du GROß oder klein oder AbWeChSeLnD spielt eigentlich keine Rolle.
Der Übersichtlichkeit halber aber, wird normalerweiße zweiteres gewählt.



Ansonsten schau mal bei Google.

Und ja, der Präprozessor ist etwas anderes als ein Präprozessorbefehl... ^^

4

20.03.2012, 18:31

Ob du GROß oder klein oder AbWeChSeLnD spielt eigentlich keine Rolle.
Der Übersichtlichkeit halber aber, wird normalerweiße zweiteres gewählt.
Ich denke, du meinst ersteres. ;)

5

20.03.2012, 18:39

In C hat man den für viele Dinge benutzt, für die man in C++ Tempaltes hat. Zum Beispiel Makros wie min() oder max(). Templates sind da wegen einer ganzen Reihe von Gründen aber die bessere Wahl. Aber man kann mit dem Präprozessor auch die aktuelle Zeit, Ziele, Funktion oder Datei ausgeben, was beispielsweise für Debugausgaben ganz cool ist.
Lieber dumm fragen, als dumm bleiben!

CodingCat

1x Contest-Sieger

Beiträge: 420

Beruf: Student (KIT)

  • Private Nachricht senden

6

20.03.2012, 18:41

Der Präprozessor ist Teil eines jeden C oder C++ Compilers und bezeichnet in etwa die erste Stufe, die im Rahmen der Übersetzung eines Quelltextes durchlaufen wird. Tatsächlich ist der Präprozessor im C++ Standard nicht klar vom Rest des Compilers abgegrenzt, im alltäglichen Sprachgebrauch bezeichnet er in etwa all die Schritte der Übersetzung, die einer einfachen Textersetzung gleichkommen. Präprozessordirektiven lassen sich deshalb auch stets an dem vorangestellten '#'-Zeichen erkennen, das es dem Übersetzer sehr leicht macht, die Direktiven von anderen Symbolen im Quelltext zu unterscheiden.

Eine Direktive, die du mit Sicherheit kennst, ist #include "SomeHeader.h". Diese Direktive weist den Präprozessor einfach nur an, den gesamten Inhalt der angegebenen Datei an Stelle des #includes in den Quelltext zu kopieren. Deshalb sind Fehlermeldungen, die durch fehlerhafte Includes hervorgerufen werden, mitunter auch so schwer zu lokalisieren. Moderne Compiler geben zwar ihr bestes, den Ursprung von Fehlern auch nach dieser simplen Textersetzung so genau wie möglich zu lokalisieren, manche Fehler (z.B. fehlende Semikola am Ende) lassen sich jedoch nur dann wirklich verstehen, wenn man sich dieser einfachen Funktionsweise von #include bewusst ist.

Eine weitere gebräuchliche Direktive ist #define. So definiert #define MY_PI 3.14 zum Beispiel eine Konstante MY_PI, mit der Folge, dass der Präprozessor bei der Übersetzung in deinem Quelltext alle nachfolgenden Vorkommen von MY_PI durch 3.14 ersetzen wird. Aber Vorsicht, auch hier handelt es sich um eine einfache Textersetzung. Diese Art von Konstanten stammen noch aus Zeiten, in denen es keine andere Möglichkeit der Definition von Konstanten gab. Genau wie du bei der manuellen Bearbeitung von Quelltext mit Textersetzungstools sehr leicht Fehler machen kannst, so kann dir das auch mit solchen Konstanten passieren. Hier liegt auch der Grund, warum solche Konstanten in der Regel GROSSGESCHRIEBEN werden: Würde sich der Namen irgendeiner Konstante mit dem Namen irgendeines anderen Symbols deines Quelltexts, zum Beispiel einer Funktion oder Variablen, überschneiden, so würde jedes Vorkommen dieser Funktion oder Variable ebenfalls ersetzt. Hierin liegt auch der Grund, warum man derartige Definitionen von Konstanten mit Hilfe des Präprozessors heute nach Möglichkeit immer vermeidet. Ein großes Ärgernis bis heute sind zum Beispiel die Windows API Headers, die zur automatischen Umbenennung von Funktionen solche Dinge wie #define CreateFont CreateFontA enthalten, was dazu führt, dass auch deine eigene Funktion auf einmal CreateFontA heißen, wenn du sie eigentlich CreateFont nennen willst. Zur Definition von Konstanten deshalb immer const verwenden, in obigem Fall float const MyPI = 3.14f.

In manchen Fällen ist der Präprozessor leider bis heute unvermeidbar, so zum Beispiel in deinem Include-Guard-Beispiel. #ifndef QUIZ_HEADER ist eine weitere Präprozessordirektive, die prüft, ob eine Präprozessorkonstante mit dem Namen QUIZ_HEADER existiert. Existiert eine solche Konstante noch nicht, dann wird sie im Anschluss mit #define QUIZ_HEADER definiert. Existiert sie hingegen schon, so wird aller Quelltext bis zur nächsten schließenden #endif-Direktive übersprungen. Dein Beispiel ist hier etwas unvollständig, in der Regel sieht das vollständige Szenario wie folgt aus:

C-/C++-Quelltext

1
2
3
4
5
6
// Quiz.h
#ifndef QUIZ_HEADER
#define QUIZ_HEADER
// Quelltext des Headers, z.B.:
float const MyPI = 3.14f;
#endif


Benötigt werden solche Include Guards genau weil der Präprozessor so ein primitives Programm ist, das Includes wie eingangs beschrieben nur per Textersetzung durchführt. Schreibst du nun zweimal hintereinander #include "Quiz.h", dann ersetzt der Präprozessor einfach zweimal das jeweilige #include mit dem Inhalt deines Quiz-Headers. Das wiederum würde ohne Include-Guard zu einem Fehler führen, weil nun MyPI in deiner Übersetzungseinheit (= Cpp-Datei) zweimal hintereinander definiert würde. In diesem Fall entsteht jedoch kein solcher Fehler, weil der Präprozessor nach der Ersetzung der ersten #include-Direktive, bei der QUIZ_HEADER noch nicht definiert ist, QUIZ_HEADER mit dem nachfolgenden #define in die Liste der definierten Konstanten aufnimmt. Bei Ersetzung der zweiten #include-Direktive ist deshalb QUIZ_HEADER nun schon definiert worden, was den Präprozessor bei der zweiten Kopie des #ifdef QUIZ_HEADER dazu veranlasst, diesmal den Quelltext bis zum #endif zu überspringen, eine Doppeldefinition von MyPI findet somit nicht mehr statt.

In diesem Rahmen mag ein solcher Include Guard nicht allzu sinnvoll erscheinen, wenn du viele Headers hast, die sich gegenseitig einbinden, passiert es jedoch sehr schnell, dass einige Headers auf Umwegen mehrfach in deine Übersetzungseinheiten eingefügt werden. Deshalb ist es gängige Praxis, alle Headers mit derartigen Include Guards zu versehen.

Es sei noch angemerkt, dass fast alle modernen Compiler eine weitere Direktive #pragma once anbieten, die nur einmal an den Anfang eines jeden Headers geschrieben werden muss, um dasselbe Verhalten ohne umständliches #ifdef ... #define ... #endif zu erhalten. Im Standard ist diese Direktive leider immer noch nicht enthalten, wodurch du streng genommen mit #pragma once keinen portablen C++-Code mehr schreibst. Dafür umgehst du jedoch Fehler, die entstehen, wenn versehentlich mehrere Headers denselben Namen für ihre Include Guards nutzen, mit #pragma once automatisch, was die Nutzung von #pragma once durchaus empfehlenswert macht. In diesem Fall sähe obiges Beispiel so aus:

C-/C++-Quelltext

1
2
3
4
// Quiz.h
#pragma once
// Quelltext des Headers, z.B.:
float const MyPI = 3.14f;
alphanew.net (last updated 2011-06-26) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite

Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von »CodingCat« (20.03.2012, 18:58)


Beiträge: 1 223

Wohnort: Deutschland Bayern

Beruf: Schüler

  • Private Nachricht senden

7

20.03.2012, 18:50

Zitat

Ich denke, du meinst ersteres.

Ups... :dead:
Ja natürlich.

Ansonsten sollte mit dem Roman von CodingCat eigentlich alles erklärt sein. ^^

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »Spiele Programmierer« (20.03.2012, 18:55)


xardias

Community-Fossil

Beiträge: 2 731

Wohnort: Santa Clara, CA

Beruf: Software Engineer

  • Private Nachricht senden

8

20.03.2012, 18:55

Cat: Ich bin dafuer, dass aus deiner Antwort ein Wiki Artikel gemacht wird!

9

20.03.2012, 19:28

Cat: Ich bin dafuer, dass aus deiner Antwort ein Wiki Artikel gemacht wird!
/sign

NachoMan

Community-Fossil

Beiträge: 3 885

Wohnort: Berlin

Beruf: (Nachhilfe)Lehrer (Mathematik, C++, Java, C#)

  • Private Nachricht senden

10

20.03.2012, 19:50

Leider gibts ja die Regel, dass man keine allgemeinen Programmierfragen beantworten soll oder wurde das mittlerweile geändert?
90% der Fragen von Anfängern, die mit C++ für Spieleprogrammierer arbeiten, wurden irgendwann schonmal beantwortet.
"Der erste Trunk aus dem Becher der Erkenntnis macht einem zum Atheist, doch auf dem Grund des Bechers wartet Gott." - Werner Heisenberg
Biete Privatunterricht in Berlin und Online.
Kommt jemand mit Nach oMan?

Werbeanzeige