ΠΕΡΙΕΧΟΜΕΝΑ

 

            1.  Ο ΠΡΟΕΠΕΞΕΡΓΑΣΤΗΣ ΤΗΣ TURBO-C

            2.  #define

            3.  #error

            4.  #include

5.  #if, #else, #elif ΚΑΙ #endif

            6.  #ifdef, #ifndef

            7.  #undef

            8.  #line

            9.  #pragma

            10. ΠΡΟΚΑΘΟΡΙΣΜΕΝΑ MACRO-NAMES  

 

 

ΚΕΦΑΛΑΙΟ 10ο

 

 

Ο ΠΡΟΕΠΕΞΕΡΓΑΣΤΗΣ ΤΗΣ TURBO C  &

ΟΙ ΕΠΙΛΟΓΕΣ  ΤΟΥ COMPILER

 

 

i)                    Ο προεπεξεργαστής της Turbo C

 

Ο προεπεξεργαστής της C ο οποίος ορίστηκε από την επιτροπή ANSI περιλαμβάνει τις παρακάτω ντιρεκτίβες:

 

                              #define

                              #error

                              #include

                              #if

                              #else

                              #elif

                              #endif

                              #ifdef

                              #ifundef

                              #undef

                              #line

                              #pragma

 

Όλες οι ντιρεκτίβες του προεπεξεργαστή ξεκινούν με το σύμβολο #. Η Turbo C τις υποστηρίζει όλες και θα τις εξετάσουμε παρακάτω μία – μία.

 

 

i)                    Η ντιρεκτίβα #define

 

Η ντιρεκτίβα #define  χρησιμοποιείται για να ορίσει μία ταυτότητα και μία τιμή η οποία αποδίδεται σ’ αυτή κάθε φορά που αυτή συναντάται στο πρόγραμμα. Η ταυτότητα ονομάζεται macro_name ενώ η διαδικασία αντικατάστασης καλείται macro substitution. Η σύνταξη της ντιρεκτίβας είναι:

 

#define macro_name string

 

Σημειώνεται ότι δεν υπάρχει “;” στη δήλωση. Μπορούν να υπάρξουν όσα κενά θέλουμε μεταξύ της ταυτότητας και του string, αλλά από τη στιγμή που αυτό θα ξεκινήσει, τελειώνει μόνο με new-line.

 

Π.χ. αν θέλουμε να αποδώσουμε την τιμή 1 στο TRUE και την τιμή 0 στο FALSE και τα χρησιμοποιούμε στο πρόγραμμά μας, θα πρέπει να χρησιμοποιήσουμε δύο #define.

 

         #define TRUE 1

         #define FALSE 0

 

Αυτό αναγκάζει τον compiler να εξισώνει το 1 με το TRUE και την τιμή 0 με το FALSE κάθε φορά που αυτά συναντώνται μέσα στο πρόγραμμά μας. Π.χ.:

 

         printf(“%d %d %d”, FALSE, TRUE, TRUE+1);

 

Αυτό θα εμφανίσει 0 1 2 στην οθόνη του υπολογιστή μας. Από τη στιγμή που ένα macro έχει οριστεί μπορεί να χρησιμοποιηθεί σαν τμήμα για τη δήλωση άλλων macros. Για παράδειγμα, ο παρακάτω κώδικας ορίζει τα ονόματα ΟΝΕ, ΤWO, THREE στις αντίστοιχες τιμές τους:

 

                     #define ONE 1

                     #define TWO ONE+ONE

                     #define THREE ONE+TWO

 

Πρέπει να γίνει κατανοητό ότι μία πράξη macro substitution απλώς αντικαθιστά μία ταυτότητα με ένα string με το οποίο σχετίζεται. Έτσι, αν θέλουμε να δημιουργήσουμε ένα στάνταρ μήνυμα λάθους θα πρέπει να γράψουμε :

 

                     #define ERR_MSG “Standard Error On Input \n”

 

Το macro αυτό θα μπορούσε να χρησιμοποιηθεί από την έκφραση:

 

                     printf (ERR_MSG);

 

Εάν το string είναι μεγαλύτερο από μία γραμμή, θα μπορούσε να συνεχιστεί στην επόμενη γραμμή κάνοντας χρήση του χαρακτήρα  \  στο τέλος της γραμμής. Πχ.:

                        #define LONG_STRING  “This Is A Very Long   \

String That Is Used As An Examble”

 

Είναι κοινή πρακτική μεταξύ των προγραμματιστών να χρησιμοποιούν κεφαλαία γράμματα σε ταυτότητες ορισμένες με την #define. Μ’ αυτόν τον τρόπο όποιος διαβάζει το πρόγραμμα γνωρίζει ότι μία πράξη macro_substitution λαμβάνει χώρα. Είναι καλό όλα τα #define να τοποθετούνται στην αρχή του προγράμματος ή σε ένα ξεχωριστό include file.

 

Η ντιρεκτίβα #define έχει ακόμα μία ισχυρή χρήση. Το macro name μπορεί να λάβει παραμέτρους. Κάθε φορά που το macro συναντάται στο πρόγραμμα, οι παράμετροι που σχετίζονται με αυτό αντικαθιστώνται από τα ορίσματα στο πρόγραμμα. Π.χ.:

 

#define MIN(a, b) (a<b)  ? a:b

main ( )

{

         int  x, y;

         x = 10 ;

         y = 20 ;

         printf (“The minimum is : %d”, MIN (x, y) ) ;

}

 

θα πρέπει να είμαστε πολύ προσεκτικοί όταν ορίζουμε macros τα οποία παίρνουν ορίσματα, γιατί ενδέχεται να έχουμε αναπάντεχα αποτελέσματα. Π.χ. :

 

                        /  * Το πρόγραμμα αυτό δίνει ΛΑΘΟΣ ΑΠΟΤΕΛΕΣΜΑ   * /

                        #define EVEN (a) a%2==0  ? 1 : 0

                        main( )

                        {

                                    if (EVEN (9+1) ) printf (“Is Even”);

                                    else printf (“Is Odd”);

                        }

Το παραπάνω πρόγραμμα δίνει λάθος αποτέλεσμα εξαιτίας της εσφαλμένης χρήσης του macro-substitution. Όταν η C μεταφράζει το πρόγραμμα το EVEN(9+1) αναλύεται σε:

                                 9 + 1%2 ==0  ?  1 : 0

 

Επειδή ο τελεστής % θεωρείται ιεραρχικά ανώτερος από τον + η maodulo division πράξη εκτελείται πρώτη και το αποτέλεσμα προστίθεται στο 9. Φυσικά αυτό το αποτέλεσμα δεν είναι ίσο με το μηδέν. Για να λυθεί αυτό το πρόβλημα ο κώδικας θα πρέπει να γραφτεί ως εξής:

 

         / * ΤΟ ΠΡΟΓΡΑΜΜΑ ΑΥΤΟ ΔΙΝΕΙ ΤΟ ΣΩΣΤΟ ΑΠΟΤΕΛΕΣΜΑ  * /

         #define EVEN (a)  (a)%2==0  ?  1:0

         main( )

         {

                     if (EVEN(9+1) ) printf(“Is Even”) ;

                     else printf ( “Is Odd”);

         }

 

Η χρήση macros αντί για συναρτήσεις αυξάνει την ταχύτητα του κώδικα, επειδή η κλήση συναρτήσεων είναι γενικά αργή. Πάντως η αύξηση της ταχύτητας προκαλεί και αύξηση του κώδικα.

 

 

ii)                  #error

 

Η ντιρεκτίβα #error αναγκάζει τον compiler της Turbo C να σταματήσει όταν συμβεί ένα σφάλμα. Βασικά χρησιμοποιείται για εκσφαλμάτωση. Η σύνταξή της είναι:

 

                     #error   error_message

 

Το μήνυμα error_message δεν βρίσκεται ανάμεσα σε εισαγωγικά.  Όταν ο compiler συναντήσει τη ντιρεκτίβα εμφανίζει το ακόλουθο μήνυμα και τερματίζει τη διαδικασία:

         Fatal : filename lineum Error directive  : Error_Message

 

iii)                #include

 

Η ντιρεκτίβα #include αναγκάζει τον compiler να ενσωματώσει ακόμη ένα αρχείο πηγαίου κώδικα με αυτό που έχει την οδηγία #include. Το αρχείο πηγαίου κώδικα που θα διαβαστεί πρέπει να βρίσκεται ανάμεσα σε < > ή “ “. Π.χ.:

 

                     #includestdio.h

                     #include <stio.h>

 

Και οι δύο οδηγίες αναγκάζουν τον compiler να μεταφράσει και να συμπεριλάβει το αρχείο stdio.h. Είναι αποδεκτό για τα ενσωματωμένα αρχεία να περιλαμβάνουν τη ντιρεκτίβα include μέσα τους. Αυτή η κατάσταση καλείται nested include. Π.χ.:

 

main( )

{

         include “one”

}

Include_File_One:

         printf(“This Is From First Include File”);

         #include “Two”

 

Include_File_Two

         printf (“This Is From Second Include File”);

 

Αν τα ονόματα των αρχείων αναφέρονται ρητά με το path στο οποίο βρίσκονται, θα γίνει ανίχνευση μόνο σε αυτά τα directory. Εάν το όνομα του αρχείου βρίσκεται μέσα σε εισαγωγικά “ “, τότε πρώτα ανιχνεύεται το τρέχον directory. Εάν το αρχείο δε βρεθεί τότε ανιχνεύονται οι κατάλογοι που έχουν οριστεί στο command line. Εάν πάλι δε βρεθεί ανιχνεύονται οι standard κατάλογοι.

 

Εάν δεν έχουν αναφερθεί ρητά κατάλογοι και το όνομα του αρχείου βρίσκεται σε < > τότε πρώτα ανιχνεύονται οι κατάλογοι που έχουν οριστεί στο command line μέσω της επιλογής –Ι, ή αυτοί που έχουν οριστεί στο IDE από την επιλογή options. Εάν το αρχείο δε βρεθεί ανιχνεύονται οι standard κατάλογοι. Σε καμία περίπτωση δεν ανιχνεύεται ο τρέχων κατάλογος, εκτός αν έχει οριστεί στο menu options ή στην επιλογή –I.

 

 

iv)               #if, #else, #elif και #endif

 

Οι οδηγίες αυτές μας επιτρέπουν να μεταφράσουμε συγκεκριμένα τμήματα του κώδικά μας. Η διαδικασία αυτή καλείται conditional compilation και χρησιμοποιείται ευρέως από διάφορα software houses τα οποία αναπτύσσουν και συντηρούν customized εκδόσεις ενός προγράμματος.

 

  Η γενική ιδέα πίσω από την #if είναι ότι αν η σταθερή έκφραση που ακολουθεί την #if είναι αληθής, τότε μεταφράζεται ο κώδικας μεταξύ #if και #endif, αλλιώς υπερπηδάται. Η ντιρεκτίβα #endif χρησιμοποιείται για να δηλώσει το τέλος ενός #if block. Η σύνταξή της είναι:

 

                     #if constant_expression

                                 statement sequence

                     #endif

 

Αν η constant_expression είναι αληθής τότε ο κώδικας μεταφράζεται, αλλιώς υπερπηδάται. Π.χ.:

 

 

                     /  * Παράδειγμα της #if  * /

                     #define MAX 100

                     main( )

                     {

                              #if MAIN>99

                                   printf(“Compiled For Array Greater Than 99 \n”);

                              #endif

                     }

 

Το πρόγραμμα αυτό εμφανίζει το μήνυμα γιατί η MAX είναι μεγαλύτερη από 99. Εδώ βλέπουμε το εξής παράξενο. Η έκφραση που ακολουθεί τη #if εκτιμάται τη στιγμή της μετάφρασης. Έτσι μπορεί να περιλαμβάνει ταυτότητες που προηγουμένως έχουν οριστεί ή σταθερές. Δε μπορεί να περιλαμβάνει μεταβλητές.

 

 

  Το #else εργάζεται με τον ίδιο τρόπο που εργάζεται το else. Μας δίνει μία εναλλακτική λύση όταν το #if αποτύχει. Το προηγούμενο παράδειγμα θα μπορούσε να διαμορφωθεί ως εξής:

 

#define MAX 10

main( )

{

         #if MAX > 99

                     printf (“Compiled For Array Greater Than 99  \n”);

         #else

                     printf(“Compiled For Small Array  \n”);

         #endif

}

 

Στην περίπτωση αυτή το ΜΑΧ έχει οριστεί μικρότερο του 99 οπότε το τμήμα κώδικα που βρίσκεται στο #if δεν μεταφράζεται. Αντίθετα μεταφράζεται το τμήμα του #else.

 

 

  Η ντιρεκτίβα #elif (σημαίνει else if) χρησιμοποιείται για να φτιάξουμε μία if-else-if σκάλα, για πολλαπλές επιλογές μετάφρασης. Το #elif ακολουθείται από μία σταθερή έκφραση. Εάν η έκφραση είναι αληθής μεταφράζεται αυτό το τμήμα κώδικα και όλες οι υπόλοιπες #elif παρακάμπτονται. Σε αντίθετη περίπτωση ελέγχεται το αμέσως επόμενο #elif. Η γενική του μορφή είναι:

 

#if        expression

         statement sequence

#elif      expression1

         statement sequence

#elif      expression2

         statement sequence

                     .

                     .

                     .

#elif      expressionN

         statement sequence

#endif

 

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

 

#define US 0

#define ENGLAND 1

#define FRANCE 2

#define ACTIVE_COUNTRY  US

#if ACTIVE_COUNTRY == US

         char currency [ ] == ”dollar”;

#elif ACTIVE_COUNTRY == ENGLAND

         char currency [ ] = “pound”;

#else

         char currency [ ] = “franc”;

#endif

 

Οι ντιρεκτίβες #if και #elif μπορούν να φωλιαστούν με τις #endif, #else ή #elif. Π.χ.:

#if MAX > 100

         #if SERIAL_VERSION

                     int port = 198;

         #elif

                     int port = 200;

                        #endif

#else

                        char out buffer [100];

#endif

 

Στην Turbo C (και όχι στην ANSI C) μπορούμε να χρησιμοποιήσουμε τον τελεστή sizeof σε μία δήλωση #if. Για παράδειγμα, το παρακάτω τμήμα κώδικα εμφανίζει στην οθόνη αν χρησιμοποιείται το small ή το large μοντέλο μνήμης.

 

#if (sizeof (char  *) ==2)

         printf (“Program Compiled For Small Model. “);

#else

         printf(“Program Compiled For Large Model.”);

#endif

 

Υπενθυμίζεται ότι ένας pointer σε χαρακτήρα είναι στην ουσία ένας δεκαεξαδικός αριθμός μεγέθους 2 bytes π.χ. 0FA2. Σε περίπτωση small μοντέλου οι pointer είναι near με αποτέλεσμα να καταλαμβάνουν 2 bytes αφού δείχνουν μόνο το offset. Σε περίπτωση large μοντέλου, οι pointers είναι far με αποτέλεσμα να δείχνουν segment και offset άρα καταλαμβάνουν 4 bytes. Π.χ.:   00F2 : 0001.

 

 

 

v)                 #ifdef και #ifndef

 

Μία ακόμη μέθοδος μετάφρασης υπό συνθήκη χρησιμοποιεί τις ντιρεκτίβες #ifdef και #ifndef, που σημαίνουν if defined και if not defined, αντίστοιχα. Η γενική τους σύνταξη είναι:

 

#ifdef  macro_name

                     statement sequence

#endif

ή

         #ifndef  macro_name

                     statement sequence

         #endif

 

Εάν το macro_name έχει προηγουμένως οριστεί σε μία δήλωση #define, τότε το τμήμα κώδικα μεταξύ του #ifdef και του #endif μεταφράζεται. Οι ντιρεκτίβες #ifdef και #ifndef μπορούν να χρησιμοποιήσουν τη ντιρεκτίβα #else αλλά όχι την #elif. Π.χ.:

 

#define TED 10

main( )

{

         #ifdef TED

                     printf( “Hi, Ted  \n”);

         #else

                     printf(“Hi Anyone  \n”);

         #endif

         #ifndef   RALPH

                     printf (“Ralph Not Defined   \n”);

         #endif

}

 

Το παραπάνω πρόγραμμα εμφανίζει Hi Ted και Ralph Not Defined. Σε περίπτωση που το TED δεν είχε οριστεί θα εμφανιζόταν Hi Anyone και Ralph Not Defined.

Μπορούμε να φωλίασουμε #ifdef και #ifndef σε όποιο επίπεδο θέλουμε όπως ακριβώς και με τα #if.

 

 

 

vi)               #undef

 

Η ντιρεκτίβα #undef χρησιμοποιείται για να διαγράψει μία προηγουμένως ορισμένη δήλωση ενός macro_name που την ακολουθεί. Η σύνταξή της είναι:

 

                                 #undef  macro_name

 

Π.χ.:

 

                     #define  LEN 100

                     #define WIDTH  100

                     char array [LEN] [WIDTH];

                     #undef LEN

                     #undef WIDTH

 

Και το LEN και το WIDTH υπάρχουν ορισμένα έως ότου εκτελεστεί η #undef. Η βασική χρήση της #undef είναι να επιτρέπει στα macro_names να τοπικοποιούνται μόνο στα τμήματα που χρειάζονται.

 

 

 

vii)             #line

 

Η ντιρεκτίβα #line αλλάζει τα περιεχόμενα των προκαθορισμένων από την Turbo C macros __LINE__ και __FILE__. Το macro __LINE__ περιέχει τον τρέχοντα αριθμό γραμμής που μεταφράζεται, ενώ το macro __FILE__ το όνομα του αρχείου που μεταφράζεται. Η γενική σύνταξη της #line είναι:

 

                                 #line number [“filename”]

 

όπου number, οποιοσδήποτε θετικός ακέραιος και το προαιρετικό filename οποιοδήποτε αποδεκτό όνομα αρχείου. Η #line βασικά χρησιμοποιείται για λόγους εκσφαλμάτωσης (debuging). Π.χ.:

 

                        #line 100  / * Αρχικοποίηση του μετρητή  * /

                        main( )                                                 /  * 100 */

                        {                                                           /  * 101  */

                                    printf(“%d”, __LINE__);           /  * 102  */

                        }

 

            Το ανωτέρω πρόγραμμα θα εμφανίσει τον αριθμό 102.

 

viii)           #pragma

 

Η ντιρεκτίβα #pragma ορίστηκε από το ANSI Standard να είναι μία συμπληρωματική ντιρεκτίβα η οποία επιτρέπει στον προγραμματιστή να δώσει διάφορες εντολές στον compiler. Η γενική της μορφή είναι:

 

                                 #pragma name

 

όπου name, το όνομα του #pragma που θέλουμε. Η Turbo C ορίζει δύο (2) #pragma δηλώσεις: warn και inline. Η ντιρεκτίβα warn προκαλεί την Turbo C να προσπεράσει τις επιλογές των warning μηνυμάτων. Έχει μορφή:

                                   

                                                #pragma warn setting

 

όπου setting είναι ένα από τα διάφορα warning errors. Στις περισσότερες εφαρμογές δε χρειάζεται αυτή η μορφή της #pragma. Η ντιρεκτίβα inline έχει τη μορφή:

 

                                                #pragma inline

 

Η μορφή αυτή πληροφορεί τον compiler ότι το πρόγραμμα περιλαμβάνει inline assembly κώδικα. Για μεγαλύτερη αποτελεσματικότητα η Turbo C πρέπει να το γνωρίζει αυτό.

 

 

 

ix)                Προκαθορισμένα Macro-Names

 

Το προτεινόμενο πρότυπο ANSI ορίζει πέντε ενσωματωμένα macro_names. Αυτά είναι:

 

                                             __LINE__

                                             __FILE__

                                             __DATE__

                                             __TIME__

                                             __STDC__

 

H Turbo C ορίζει επίσης τα εξής:

 

                                             __CDECL__

                                             __COMPACT__

                                             __HUGE__

                                             __LARGE__

                                             __MEDIUM__

                                             __MSDOS__

                                             __PASCAL__

                                             __SMALL__

                                             __TINY__

                                             __TURBOC__

 

¢            To macro __DATE__ περιέχει ένα string του τύπου:

                                                                                                         

Month / Day / Year

 

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

¢      To macro __ΤΙΜΕ__ περιέχει ένα string του τύπου:

                                                                                                         

Hour : Minute : Second

 

που μας δείχνει την ώρα που ξεκίνησε το compilation.

 

¢      To macro __STDC__  περιλαμβάνει μία δεκαδική σταθερά η οποία συνήθως είναι 1. Εάν περιλαμβάνει άλλη τιμή αυτό δείχνει ότι η εφαρμογή διαφέρει από τα συνηθισμένα.

 

¢            To macro __CDECL__ ορίζεται αν χρησιμοποιείται ο στάνταρ τρόπος της C για κλήση συναρτήσεων (και όχι της Pascal).

 

¢            Τα macros __TINY__, __SMALL__, __COMPACT__, __MEDIUM__, __LARGE__ και __HUGE__ ορίζονται ανάλογα με το μοντέλο μνήμης που χρησιμοποιείται.

 

¢            Το macro __MSDOS__ ορίζεται με την τιμή 1 σε όλες τις περιπτώσεις όταν χρησιμοποιούμε μία DOS έκδοση της Turbo C.

 

¢            To macro __PASCAL__ χρησιμοποιείται όταν κάνουμε χρήση του τρόπου κλήσεων της Pascal για συναρτήσεις (και όχι της C). Σε άλλη περίπτωση δεν είναι ορισμένο.

 

¢            Τέλος, το macro __TURBOC__ περιέχει την έκδοση της Turbo C. Αυτή αναπαρίσταται με ένα δεκαεξαδικό αριθμό. Τα δύο δεξιά ψηφία δείχνουν την υποέκδοση, ενώ το αριστερό την κύρια έκδοση. Π.χ.: ο αριθμός 202 δείχνει τη version 2.02.

 

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

 

main( )

{

         printf(“%s %d %s %s  \n, __FILE__, __LINE__, __DATE__, __TIME__);

         printf( “Program Being Compiled Using The “);

         #ifdef __TINY__

                     printf (“Tiny Model”);

         #endif

         #ifdef __SMALL__

                     printf (“Small Model”);

         #endif

         #ifdef __COMPACT__

                     printf( “Compact Model”);

         #endif

         #ifdef __MEDIUM__

                     printf( “Medium Model”);

         #endif

         #ifdef __LARGE__

                     printf( “Large Model”);

         #endif

         #ifdef __HUGE__

                     printf (“Huge Model”);

         #endif

         printf ( “\n”);

         printf (“Using Ver. %X of Turbo C.”, __TURBOC__);

}

 

Το αποτέλεσμα του παραπάνω προγράμματος θα είναι:

 

example.c 5 Nov 17 1992 15:43:36

Program Being Compiled Using The Small Model

Using Ver 201 of Turbo C.

 

Κυρίως αυτά τα ενσωματωμένα macros χρησιμοποιούνται σε πολύπλοκα προγραμματιστικά περιβάλλοντα όπου πολλές εκδόσεις του ίδιου προγράμματος δουλεύουν σε διαφορετικά μηχανήματα ή λειτουργικά.