ΠΕΡΙΕΧΟΜΕΝΑ
1.
Ο ΠΡΟΕΠΕΞΕΡΓΑΣΤΗΣ ΤΗΣ
TURBO-C
2. #define
3. #error
4. #include
5. #if, #else, #elif ΚΑΙ #endif
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 τις υποστηρίζει όλες και θα τις εξετάσουμε παρακάτω μία –
μία.
Η
ντιρεκτίβα #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 αντί για συναρτήσεις αυξάνει την ταχύτητα του
κώδικα, επειδή η κλήση συναρτήσεων είναι γενικά αργή. Πάντως η αύξηση της
ταχύτητας προκαλεί και αύξηση του κώδικα.
Η
ντιρεκτίβα #error αναγκάζει
τον compiler της Turbo C να σταματήσει όταν συμβεί ένα σφάλμα. Βασικά
χρησιμοποιείται για εκσφαλμάτωση. Η σύνταξή της είναι:
#error error_message
Το μήνυμα error_message δεν βρίσκεται
ανάμεσα σε εισαγωγικά. Όταν ο compiler συναντήσει τη ντιρεκτίβα
εμφανίζει το ακόλουθο μήνυμα και τερματίζει τη διαδικασία:
Fatal : filename lineum Error directive
: Error_Message
Η
ντιρεκτίβα #include αναγκάζει
τον compiler να ενσωματώσει
ακόμη ένα αρχείο πηγαίου κώδικα με αυτό που έχει την οδηγία #include. Το αρχείο πηγαίου κώδικα που θα
διαβαστεί πρέπει να βρίσκεται ανάμεσα σε < > ή “ “. Π.χ.:
#include “stdio.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”
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.
Μία ακόμη
μέθοδος μετάφρασης υπό συνθήκη χρησιμοποιεί τις ντιρεκτίβες #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.
Η
ντιρεκτίβα #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 να τοπικοποιούνται μόνο στα τμήματα που χρειάζονται.
Η
ντιρεκτίβα #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.
Η
ντιρεκτίβα #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
χρησιμοποιούνται σε πολύπλοκα προγραμματιστικά περιβάλλοντα όπου πολλές
εκδόσεις του ίδιου προγράμματος δουλεύουν σε διαφορετικά μηχανήματα ή
λειτουργικά.