Πριν ένα πρόγραμμα σε γλώσσα C μεταγλωττιστεί, οδηγείται πρώτα στον προμεταγλωττιστή, ο οποίος εκτελεί τις λεγόμενες «οδηγίες» που δεν είναι τίποτε άλλο παρά εντολές ειδικά γραμμένες για τον προμεταγλωττιστή. Οι οδηγίες αυτές δεν είναι μέρος της γλώσσας αλλά τμήμα του περιβάλλοντος μεταγλώττισης.
Οι οδηγίες προς τον προμεταγλωττιστή, μπορούν να εκτελέσουν την συμπερίληψη άλλων αρχείων προς στο αρχείο που πρόκειται να μεταγλωττιστεί, μπορούμε να ορίσουμε συμβολικές σταθερές και μακροεντολές, καθώς και να έχουμε την μεταγλώττιση υπό συνθήκη του πηγαίου κώδικα. Όλες οι οδηγίες προμεταγλωττιστή αρχίζουν με το σύμβολο # και μόνο χαρακτήρες κενού διαστήματος μπορούν να εμφανίζονται πριν από το σύμβολο # που ορίζει την αρχή της οδηγίας που γράφεται μέσα σε μια γραμμή.
Οι οδηγίες προμεταγλωττιστή δεν αποτελούν μέρος της γλώσσας C αλλά αποτελούν τμήμα του περιβάλλοντος μεταγλώττισης. Οι οδηγίες εκτελούνται από τον προμεταγλωττιστή, ένα στάδιο πριν ξεκινήσει η διαδικασία μεταγλώττισης.
Η οδηγία #include
Η οδηγία προμεταγλωττιστή #include προκαλεί την συμπερίληψη ενός αντιγράφου ενός καθορισμένου αρχείου στη θέση της οδηγίας. Συνήθως με την οδηγία #include ζητούμε την συμπερίληψη αρχείων κεφαλίδας. Ένα αρχείο κεφαλίδας περιέχει διάφορες δηλώσεις που είναι κοινές στα ξεχωριστά αρχεία προγράμματος.
Η οδηγία #include χρησιμοποιείται επίσης σε προγράμματα αποτελούμενα από διάφορα αρχεία πηγαίου κώδικα που πρόκειται να μεταγλωττιστούν μαζί. Οι δύο μορφές της οδηγίας #include είναι:
#include <όνομα_αρχείου>
#include “όνομα_αρχείου”
Η πρώτη μορφή χρησιμοποιείται για την συμπερίληψη αρχείων κεφαλίδας της καθιερωμένης βιβλιοθήκης, ενώ η δεύτερη μορφή αναγκάζει τον μεταγλωττιστή να συμπεριλάβει το αρχείο από τον ίδιο φάκελο στον οποίο βρίσκεται και το πηγαίο αρχείο του προγράμματος μας. Αυτός ο τρόπος μας δίνει την δυνατότητα να καθορίσουμε και τη διαδρομή στην οποία βρίσκεται το αρχείο. Για παράδειγμα:
#include “c:/myfolder/mylib.h”
#include “../mylib.h”
Η οδηγία #define
Η οδηγία #define δημιουργεί συμβολικές σταθερές και μακροεντολές. Αυτή η οδηγία ορίζει ένα αναγνωριστικό και ένα σύνολο χαρακτήρων που θα αντικαταστήσει αυτό το αναγνωριστικό κατά τη διαδικασίας μεταγλώττισης του πηγαίου κώδικα. Η σύνταξη της είναι:
#define αναγνωριστικό χαρακτήρες
Για παράδειγμα η οδηγία:
#define PI 3.14159
Αντικαθιστά όλες τις επόμενες εμφανίσεις της συμβολικής σταθεράς PI με την αριθμητική σταθερά 3.14159 Οι συμβολικές σταθερές δίνουν τη δυνατότητα στον προγραμματιστή να δημιουργεί ένα όνομα για μια σταθερά και να χρησιμοποιεί το όνομα αυτό στο πρόγραμμα. Εάν η σταθερά χρειάζεται να τροποποιηθεί στο πρόγραμμα, μπορεί να τροποποιηθεί μια φορά στην οδηγία #define και μετά την εκ νέου μεταγλώττιση όλες οι εμφανίσεις της σταθεράς στο πρόγραμμα θα τροποποιηθούν αυτόματα.
Μακροεντολές
Η οδηγία #define μπορεί να χρησιμοποιηθεί για τον ορισμό μιας μακροεντολής. Οι μακροεντολές μοιάζουν με τις συναρτήσεις και μπορούν να έχουν παραμέτρους. Ο ορισμός μιας μακροεντολής είναι πανομοιότυπος με τον ορισμό μιας συμβολικής σταθεράς. Ακολουθεί ένα παράδειγμα ορισμού μακροεντολής με μια παράμετρο για την επιφάνεια ενός κύκλου.
#define CIRCLE_AREA(X) ((PI)*(X)*(X))
Μια μακροεντολή αποτελείται από το όνομα της και την παράμετρο ή τις παραμέτρους της, μέσα σε παρενθέσεις που την ακολουθούν. Όταν ο προμεταγλωττιστής συναντήσει την μακροεντολή μέσα στον κώδικα, όπως για παράδειγμα:
area = CIRCLE_AREA(5);
αντικαθιστά το όνομα της μακροεντολής με το σύνολο χαρακτήρων του ορισμού της και αντικαθιστά επίσης τις παραμέτρους με τα ορίσματα που καλείται η μακροεντολή. Έτσι η μακροεντολή του παραδείγματος μας επεκτείνεται σε:
area = ((3.14159)*(5)*(5));
και η τιμή της έκφρασης υπολογίζεται και εκχωρείται στη μεταβλητή area. Οι παρενθέσεις γύρω από όλα τα Χ εξαναγκάζουν την εφαρμογή της κατάλληλης σειράς υπολογισμού όταν το όρισμα της μακροεντολής είναι μια έκφραση. Για παράδειγμα η πρόταση:
area=CIRCLE_AREA(c+2); επεκτείνεται σε area = ((3.14159)*(c+2)*(c+2));
που υπολογίζεται σωστά επειδή οι παρενθέσεις αναγκάζουν να εφαρμοστεί η σωστή σειρά του υπολογισμού. Εάν οι παρενθέσεις παραληφθούν, η επέκταση της μακροεντολής είναι:
area = 3.14159*c+2*c+2; που βάσει της προτεραιότητας των τελεστών υπολογίζεται ως area=(3.14159*c)+(2*c)+2;
Μια μακροεντολή μπορεί να ορίζεται με περισσότερες από μια παραμέτρους, όπως στο επόμενο παράδειγμα για την επιφάνεια ενός ορθογωνίου:
#define RECTANGLE_AREA(X, Y) ((X)*(Y))
Αν το μήκος του ορθογωνίου είναι α+5 και το πλάτος του είναι b+10, η επιφάνεια του είναι:
rectArea = RECTANGLE_AREA(α+5, b+10); που επεκτείνεται σε rectArea = ((α+5)*(b+10));
όπου η τιμή της έκφρασης υπολογίζεται και εκχωρείται στη μεταβλητή rectArea
Ο ορισμός μιας συμβολικής σταθεράς ή μιας μακροεντολής γίνεται μέσα σε μια γραμμή. Εάν το κείμενο αντικατάστασης για μια μακροεντολή ή συμβολική σταθερά είναι μακροσκελέστερο από το υπόλοιπο της γραμμής, πρέπει να τοποθετηθεί μια ανάποδη κεκλιμένη \ στο τέλος της γραμμής, η οποία δηλώνει ότι το κείμενο αντικατάστασης συνεχίζεται στην επόμενη γραμμή.
Μια μακροεντολή μπορεί να μην επιστρέφει μια τιμή ή να μην έχει παραμέτρους και έτσι να μην δέχεται ορίσματα. Δείτε το ακόλουθο απόσπασμα κώδικα:
#define DDRC_OUT() (DDRC=0xFF)
#define PORT_OUT(X) (PORTC=X)
Οι οδηγίες #ifdef και #ifndef
Οι οδηγίες #ifdef και #ifndef χρησιμοποιούνται για να ελέγξουν αν έχει οριστεί με την οδηγία #define κάποιο αναγνωριστικό και μετά να μεταγλωττιστεί ο κατάλληλος κώδικας. Το συντακτικό της οδηγίας #ifdef είναι το ακόλουθο:
#ifdef αναγνωριστικό
κώδικας
#endif
Με αυτή την οδηγία θα μεταγλωττιστεί ο κώδικας, όταν το αναγνωριστικό έχει οριστεί με την οδηγία #define. To Το συντακτικό της οδηγίας #ifndef είναι το ακόλουθο:
#ifndef αναγνωριστικό
κώδικας
#endif
Με αυτή την οδηγία θα μεταγλωττιστεί ο κώδικας, όταν το αναγνωριστικό δεν έχει οριστεί με την οδηγία #define.
Σημείωση: κατά τον ορισμό ενός αναγνωριστικού με την οδηγία #define δεν είναι απαραίτητο να του ανατεθεί κάποια τιμή.
Η οδηγία #ifndef χρησιμοποιείται για την αποφυγή πολλαπλής συμπερίληψης αρχείου κεφαλίδας κάτι που θα προκαλούσε σφάλματα μεταγλώττισης. Σε αυτή την περίπτωση έχουμε την σύνταξη:
#ifndef MACRO_NAME
#define MACRO_NAME
//Κώδικας που περιλαμβάνεται μόνο αν το MACRO_NAME δεν έχει οριστεί
#endif // τέλος της συνθήκης
Στην προηγούμενη οδηγία, αν το MACRO_NAME δεν έχει οριστεί, ο προμεταγλωττιστής το ορίζει και περιλαμβάνει τον κώδικα. Αν το MACRO_NAME έχει ήδη οριστεί (δηλαδή το αρχείο έχει ήδη συμπεριληφθεί) τότε ο κώδικας αγνοείται αποτρέποντας την διπλή συμπερίληψη.
Παράδειγμα: (προστασία αρχείου κεφαλίδας)
Ας υποθέσουμε ότι έχουμε το αρχείο κεφαλίδας myheader.h το εξής
#ifndef MYHEADER_H
#define MYHEADER_H
#define N 64
void printNumber(uint8_t);
#endif
Έχουμε και ένα αρχείο main.c το οποίο περιλαμβάνει το παραπάνω αρχείο δυο φορές
#include “myheader.h”
#include “myheader.h” // Δεν θα προκαλέσει σφάλμα λόγω της #ifndef
#include <avr/io.h>
void printNumber(uint_t num)
{
DDRC=0xFF;
PORTC=num;
}
int main()
{
DDRD=0xFF;
PORTD=N;
printNumber(32);
while(1);
}
Στον παραπάνω κώδικα Α] την πρώτη φορά που το αρχείο myheader.h περιλαμβάνεται, τότε σε αυτή την περίπτωση το MYHEADER_H δεν είναι ορισμένο, οπότε ορίζεται και περιλαμβάνεται το περιεχόμενο του. Β] την δεύτερη φορά που περιλαμβάνεται το MYHEADER_H, είναι ήδη ορισμένο, άρα το περιεχόμενο του αγνοείται αποτρέποντας τον διπλό ορισμό.