Ο Προεπεξεργαστής της C


Θυμηθείτε οτι η προεπεξεργασία είναι το πρώτο στάδιο στη μεταγλώττιση ενός προγράμματος C -- ένα ιδιαίτερο χαρακτηριστικό της C. Στη GNU gcc ο προεπεξεργαστής cpp καλείται αυτόματα ως πρώτο στάδιο της μεταγλώττισης. Όμως μπορούμε να τον χρησιμοοποιήσουμε και ανεξάρτητα, ώστε να δούμε τα αποτελέσματα της χρήσης του ή για να επεξεργαστούμε ένα αρχείο. Για παράδειγμα η γραμμή εντολών

cpp input.c > output.c

παράγει ένα αρχείο output.c που είναι το αρχείο input.c που έχει υποστεί προεπεξεργασία.

Ο προεπεξεργαστής σχεδόν παρέχει μια μικρή γλώσσα που μπορεί να αποτελέσει ιδιαίτερα ισχυρό εργαλείο για το προγραμματιστή, αλλά και γενικότερα για την επεξεργασία αρχείων. Θυμηθείτε οτι οι οδηγίες προεπεξεργαστή ξεκινούν με #.

Η χρήση προεπεξεργαστή είναι επωφελής γιατί καθιστά τα προγράμματα:

Ο προεπεξεργαστής επίσης επιτρέπει την προσαρμογή της γλώσσας, Για παράδειγμα, αν θέλουμε να αντικαταστήσουμε τις αγκύλες { ... } ως οριοθέτες block με τα PASCAL-like begin ... end μπορούμε να γράψουμε:


#define begin {
#define end }

Κατά τη μεταγλώττιση όλες οι εμφανίσεις των begin και end θα αντικατασταθούν αντίστοιχα με { και } ώστε τα επόμενα βήματα της μεταγλώττισης δεν αντιλαμβάνονται καμμία διαφορά.

Ας δουμε το #define πιο προσεκτικά

#define

Το χρησιμοποιούμε στη δήλωση σταθερών και στην αντικατάσταση μακροεντολών. Μερικά παραδείγματα:

 #define <macro> <replacement name>
 
#define FALSE 0
#define TRUE !FALSE
Μπορούμε να ορίσουμε μικρές "συναρτήσεις". Για παράδειγμα το μέγιστο δύο μεταβλητών:

 #define max(A, B) ((A) > (B) ? (A) : (B))

Σημείωση: αυτή δεν είναι κανονική δήλωση συνάρτησης max.  Το μόνο που κάνει ο προεπεξεργαστής είναι όπου συναντά το κείμενο max(Α,Β) να το αντικαθιστά με το κείμενο ((A) > (B) ? (A):(B))Η ευελιξία του προεπεξεργαστή βρίσκεται στο γεγονός οτι αντί για A, B μπορεί να αναγνωρίζει οποιαδήποτε ονόματα μεταβλητών, να αφαιρεί πιθανά κενά κ.λ.π.

Έτσι αν ο κώδικας C που έχετε είναι κάτι σαν:

 x = max(q+r,s+t);

μετά τη προεπεξεργασία, αν θα μπορούσαμε να δούμε τον κώδικα θα ήταν έτσι:

 x = ((q+r) > (r+s) ? (q+r) : (s+t));

Άλλα παραδείγματα του #define :

 #define Deg_to_Rad(X) (X*M_PI/180.0) 
/* converts degrees to radians, M_PI is the value
of pi and is defined in math.h library */
 
#define LEFT_SHIFT_8 <<8

Σημείωση
: η μακροεντολή LEFT_SHIFT_8 ισχύει φυσικά μόνο αν τα συμφραζόμενα είναι σωστά σωστά, π.χ.

 x = y LEFT_SHIFT_8.

#undef

Αυτή η οδηγία αναιρεί τον ορισμό μιας σταθεράς ή μακροεντολής. Προφανώς, για να υπάρχει αναίρεση πρέπει να έχει προηγηθεί ορισμός. Στη συνέχεια μπορεί να ακολουθήσει νέος ορισμός σε διαφορετική τιμή.

#include

Αυτή η οδηγία ενσωματώνει το κείμενο ενός αρχείου στο κείμενο του προγράμματός μας.

Έχει δύο πιθανές μορφές:

   #include <file>
#include ''file''  

Το <file> αναζητά το αρχείο στο κατάλογο που χρησιμοποιεί το σύστημα. Συνήθως σε συστήματα UNIX/Linux  πρόκειται για τον κατάλογο /usr/include.

Το "file" αναζητά το αχείο στον τρέχοντα κατάλογο (εκεί όπου ξεκίνησε η μεταγλώττιση).

Τα ενσωματωμένα (included) αρχεία συνήθως είναι αρχεία κεφαλής που περιέχουν πρότυπα συναρτήσεων και δηλώσεις, όχι (αλγοριθμικό) κώδικα C.

#if -- Ενσωμάτωση υπό συνθήκη

Το #if εκτιμά μια ακέραια έκφραση. Πάντα η οδηγία  #if πρέπει να τερματίζεται με ένα #endif. Μπορεί να έχουμε και else με τη χρήση του #else και του #elif -- δηλαδή else if.

Μια συνηθισμένη χρήση του #if είναι η:

#ifdef
-- if defined, αν έχει οριστεί
#ifndef
-- if not defined, αν δεν έχει οριστεί

Αυτή η οδηγία είναι χρήσιμη για να ελέγχουμε αν κάποια σταθερά ή κάποια μακροεντολή έχει ήδη λάβει τιμή από κάποιο άλλο αρχείο που έχει εσνωματωθεί.

Για παράδειγμα, το μέγεθος του ακεραίου σε ένα σύστημα μπορεί να είναι 16 bits (π.χ. παλιές εφραμογές γραμμένες σε TurboC για MSDOS) ενώ σε πιο σύγχρονα συστήματα UNIX/Linux και MS-Windows ο ακέραιος καταλαμβάνει 32 bits. Έστω οτι o μεταγλωττιστης αναγνωρίζεται και δηλώνεται σε μια σταθερά. Τότε μπορεί να υπάρχει ένας έλεγχος όπως:

  #ifdef TURBOC
#define INT_SIZE 16
#else
#define INT_SIZE 32
#endif

Σε ένα άλλο παράδειγμα, αν το πρόγραμμα εκτελείται σε σύστημα MSDOS πρέπει να ενσωματώσουμε ένα αρχείο κεφαλής αλλιώς χρησιμοποιούμε ένα προεπιλεγμένο αρχείο.

  #if SYSTEM == MSDOS
  #include <msdos.h>
#else
#include "default.h"
#endif

Έλεγχος Μεταγλωττιστή μέσω του Προεπεξεργαστή

Μέσω του προεπεξεργαστή μπορείτε επίσης να ελέγξετε τον μεταγλωττιστή gcc. Έτσι μπορούμε να επιτύχουμε αποτελέσματα παρόμοια με τη προσθήκη ορισμάτων στη γραμμή εντολών. Για παράδειγμα, έστω οτι χρησιμοποιούμε  την επιλογή -D του μεταγλωττιστή:

   gcc -DLINELENGTH=80 prog.c -o prog

Θα είχαμε το ίδιο αποτέλεσμα αν γράφαμε:

   #define LINELENGTH 80

Σημειώστε οτι αν υπάρχει κάποιο #define ή #undef σε ένα πρόγαμμα (εδώ στο prog.c ) αυτό υπερισχύει έναντι αντίστοιχης επιλογής στη γραμμή εντολών.

Μπορείτε επίσης να ελέγχετε τις επιλογές της γραμμής εντολών. Για παράδειγμα, έστω οτι θέτουμε:

   gcc -DDEBUG prog.c -o prog

Εδώ η τιμή της μεταβλητής DEBUG τίθεται στο 1. Μέσα στο πρόγραμμα μπορούμε να έχουμε κάτι σαν :

#ifdef DEBUG
print("Debugging: Program Version 1\");
#else
print("Program Version 1 (Production)\");
#endif

Ή θα μπορούσαμε να τροποποιούσαμε τη τιμή της DEBUG. Σημειώστε οτι οι οδηγίες προεπεξεργασίας μπορεί να βρίσκονται οπουδήποτε μέσα στο πρόγραμμα, οπότε με το παραπάνω #ifdef ίμπορούμε να ελέγχουμε την εμφάνιση μηνυμάτων εκσφαλμάτωσης ή οχι κατά την εκτέλεση, ανάλογα με την επιλογή της γραμμής εντολών:

x = y *3;

#ifdef DEBUG
print("Debugging: Variables (x,y) = \",x,y);
#endif

Η επιλογή -E στη γραμμή εντολών, χωρίς να είναι ιδιαίτερα πρακτική είναι χρήσιμη κυρίως για εκπαιδευτικούς σκοπούς. Επιβάλλει τη διακοπή της μεταγλώττισης αμέσως μετά τη προεπεξεργασία και εμφανίζει τη τρέχουσα κατάσταση του προγράμματος. Εκτός από την εκσφαλμάτωση των οδηγιών προεπεξεργαστή η επιλογή είναι χρήσιμη ως εκπαιδευτικό εργαλείο, για να δούμε το πρόγραμμα μετά την ενσωμάτωση αρχείων κεφαλής κλπ. 

 

Άλλες Οδηγίες Προεπεξεργαστή

Υπάρχουν μερικές άλλες οδηγίες προεπεξεργαστή:

#error
text of error message -- δημιουργεί κατάλληλο μήνυμα λάθους, π.χ.
#ifdef OS_MSDOS
#include <msdos.h>
#elifdef OS_UNIX
#include "default.h"
#else
#error Wrong OS!!
#endif
# line
number "string" -- πληροφορεί τον προεπεξεργαστή για τον αριθμό της επόμενης γραμμής.
Το "string" είναι προαιρετικό και δίνει ένα όνομα στην επόμενη γραμμή εισόδου. Αυτή η οδηγία χρησιμοποιείται συχνά κατά τη μετάφραση προγραμμάτων από άλλη γλώσσα στη C. Για παράδειγμα μηνύματα λάθους που παράγονται από το μεταγλωττιστή C μπορούν να αναφερθούν σε όνομα αρχείου και αριθμό γραμμής του ενδιάμεσου (μεταφρασμένου) αρχείου πηγαίου κώδικα.
Το πλήρες εγχειρίδιο του προεπεξεργαστή μπορεί να κληθεί ως man cpp. Eπίσης βρίσκεται σε μορφή HTML στη διεύθυνση

http://gnu.org/

Ασκήσεις

Άσκηση 1

Ορίστε μια μακροεντολή swap(t, x, y) που αλλάζει τις τιμές δύο ορισμάτων x και y του τύπου t.

Άσκηση 2

Ορίστε μια μακροεντολή που να επιλέγει:

Άσκηση 3

Δοκιμάστε την κλήση cpp με ένα απλό πρόγραμμα C με χρήση μερικών #define, #line και ένα #include <stdio.h> και ελέξτε το παραγόμενο αποτέλεσμα. Μελετήστε το εγχειρίδιο του cpp για να αντιληφθείτε τη λειτουργία του.
Dave Marshall
1/5/1999
μετάφραση και προσαρμογή στα Ελληνικά Κ.Γ. Μαργαρίτης
29/2/2008