Προς το περιεχόμενο

Προτεινόμενες αναρτήσεις

Δημοσ.

Ένα macro αποτελεί ένα τρόπο για να αυτοματοποιήσουμε μία επαναλαμβανόμενη διαδικασία(απ'ότι ξέρω).Μια συνάρτηση βιβλιοθήκης-έτσι όπως το σκέφτομαι-κάνει κάτι παρόμοιο.Ποιες οι διαφορές τους;

Δημοσ.

>
#define MUL(x,y) x * y   (Χάριν ευκολίας ας μην βάλουμε παρενθέσεις)

int main(void)
{
int a = 3, b = 5;
int k = MUL(a,;
}

>
int mul(int x, int y)
{
return x * y;
}

int main(void)
{
int a = 3, b = 5;
int k = mul(a, ;
}

 

Στην περίπτωση του πρώτου κώδικα έχουμε το macro. Ό,τι βλέπεις να ξεκινάς με # δεν είναι δουλειά του compiler αλλά του preprocessor. Ο preprocessor θα αναλάβει να διαβάσει όλο τον κώδικα και όπου βρει την λέξη MUL να την αντικαταστήσει με αυτό που δηλώσαμε. Ο compiler δηλαδή θα δει "int k = a * b;" χωρίς να μπορεί να ξέρει ότι εσύ είχες δηλώσει το macro.

 

Στην δεύτερη περίπτωση, θα πρέπει να κληθεί η συνάρτηση να δημιουργηθεί νέο frame στη στοίβα, να εκχωρηθεί κάποια μνήμη, να αντιγραφεί η τιμή των μεταβληων a, b στις τοπικές μεταβλητές x, y και γενικά να γίνουν ένα σωρό διαδικασίες μέχρι να επιστραφεί η τιμή του γινομένου πίσω στην μεταβλητή k.

 

Οπότε θεωρητικά το macro έχει το καλό ότι είναι πιο γρήγορο αλλά το κακό ότι δεν δηλώνεις τύπο, δεν φαίνεται στον debugger και γενικά έχεις μικρότερο έλεγχο και μπορούν να γίνουν πιο εύκολα λάθη. Πρακτικά, μπορεί ο compiler να αντικαταστήσει και την συνάρτηση αν θέλει οπότε να έχεις την ίδια ταχύτητα.

 

Φυσικά το παράδειγμα μου δείχνει την πιο απλή χρήση macros-συναρτήσεων.

Δημοσ.

Δεν υπάρχει ορολογία "library functions" που να εννοεί πως οι συναρτήσεις μια βιβλιοθήκης διαφέρουν σε κάτι από τις συναρτήσεις γενικότερα.

 

Οπότε, η βασική διαφορά μεταξύ macros και συναρτήσεων είναι πως τα πρώτα είναι type-agnostic (και στα ορίσματά τους και στην τιμή επιστροφής τους) και πως ουσιαστικά πρόκειται για απλή search & replace διαδικασία, την οποία την διεκπεραιώνει ο προ-επεξεργαστής στα πηγαία σου αρχεία, πριν καν γίνουν compile.

 

@imitheos:

 

Πως έβαλες διαχωριστική γραμμή στα code contents?

 

EDIT:

 

Ρίξε επίσης μια ματιά εδώ: http://linux.web.cern.ch/linux/scientific4/docs/rhel-cpp-en-4/macro-pitfalls.html ( ή/και βάλε στο google "c macros pitfalls" )

Δημοσ.

Την είχα και εγώ αυτή την απορία και ύστερα από πολύ ψάξιμο κατέληξα περίπου σ αυτά που σου μεν ο mig και ο ημίθεος. Επίσης μπορείς να βάλεις τον modifier "inline" αμέσως πριν το return type μιας συνάρτησης ώστε να δώσεις hint προς τον compiler να προσπαθήσει να την κάνει macro. Δηλαδή:

>inline int max(int a, int a){
   return a >= b ? a : b;
}

Πέρα απ τα κακά του ότι με τα macros δεν έχεις έλεγχο στο debugging ή ότι είναι implict οι τύποι των παραμέτρων υπάρχουν κι άλλα. Όπως μεγαλύτερο μέγεθος του εκτελέσιμου ή να χειροτέρευση το instruction caching κατά το runtime. (αυτά τα δύο μπορεί να τα προκαλέσει και το inline). Γενικά όμως, άμα ψάξεις σε performance optimized κώδικες (πω κώδικας του linux) θα δεις μπόλικα inline.

Δημοσ. (επεξεργασμένο)

Την είχα και εγώ αυτή την απορία και ύστερα από πολύ ψάξιμο κατέληξα περίπου σ αυτά που σου μεν ο mig και ο ημίθεος. Επίσης μπορείς να βάλεις τον modifier "inline" αμέσως πριν το return type μιας συνάρτησης ώστε να δώσεις hint προς τον compiler να προσπαθήσει να την κάνει macro. Δηλαδή:

>inline int max(int a, int a){
   return a >= b ? a : b;
}

Πέρα απ τα κακά του ότι με τα macros δεν έχεις έλεγχο στο debugging ή ότι είναι implict οι τύποι των παραμέτρων υπάρχουν κι άλλα. Όπως μεγαλύτερο μέγεθος του εκτελέσιμου ή να χειροτέρευση το instruction caching κατά το runtime. (αυτά τα δύο μπορεί να τα προκαλέσει και το inline). Γενικά όμως, άμα ψάξεις σε performance optimized κώδικες (πω κώδικας του linux) θα δεις μπόλικα inline.

 

Το κακό με το inline είναι ότι πολλοί compilers θα το αγνοήσουν. Δεν το θυμάμαι στα σίγουρα αλλά έχω την εντύπωση πως ο gcc δεν κάνει inline καμμία συνάρτηση στο κανονικό μη-optimized σενάριο και κάνει inline όσες συναρτήσεις θέλει με -O2 optimization. Γενικά δηλαδή το αγνοεί πάντα. Είναι πολύ πιθανό όμως να θυμάμαι λάθος.

 

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

 

Edit: Χτες νύσταζα και βαριόμουν οπότε πέταξα ένα "static inline" χωρίς να το εξηγήσω.

 

>
#include <stdio.h>

inline int mul(int x, int y)
{
   return x * y;
}

extern inline int mul2(int x, int y)
{
   return x * y;
}

int main(void)
{
   int a = 3, b = 5;
   int k = mul(a, ;
   int l = mul2(a, ;

   printf("%d - %d\n", k, l);

   return 0;
}

 

Ας δούμε πάλι το χαζό πρόγραμμα που έγραψα πριν αλλά λίγο αλλαγμένο ώστε να χρησιμοποιούμε το inline.

 

>
% gcc -std=gnu89 -O2 inline_mul.c
% gcc -std=c99 -O2 inline_mul.c
% gcc -std=gnu89 -O0 inline_mul.c  
/tmp/cceLIxvz.o: In function `main':
inline_mul.c:(.text+0x46): undefined reference to `mul2'
collect2: σφάλμα: η ld επέστρεψε κατάσταση εξόδου 1
% gcc -std=c99 -O0 inline_mul.c 
/tmp/ccNItyf4.o: In function `main':
inline_mul.c:(.text+0x34): undefined reference to `mul'
collect2: σφάλμα: η ld επέστρεψε κατάσταση εξόδου 1

 

Όπως ξέρουμε, ο gcc από τη μάνα του χρησιμοποιεί (ακόμη) την κατάσταση gnu89 δηλαδή ansi c (c89) μαζί με κάποιες δικές του επεκτάσεις. Μία από αυτές τις επεκτάσεις είναι η υλοποίηση του inline μια και δεν υπήρχε στο πρότυπο c89.

 

Εδώ διαβάζουμε το παρακάτω κείμενο:

When an inline function is not static, then the compiler must assume that there may be calls from other source files; since a global symbol can be defined only once in any program, the function must not be defined in the other source files, so the calls therein cannot be integrated. Therefore, a non-static inline function is always compiled on its own in the usual fashion.

 

If you specify both inline and extern in the function definition, then the definition is used only for inlining. In no case is the function compiled on its own, not even if you refer to its address explicitly. Such an address becomes an external reference, as if you had only declared the function, and had not defined it.

 

Το παραπάνω κείμενο εξηγεί πώς πρώτο-υλοποίησε ο gcc το inline για την κατάσταση gnu89. Όπως λοιπόν διαβάζουμε στην 1η παράγραφο, όταν έχουμε σκέτο inline τότε ο compiler παράγει και την κανονική assembly της συνάρτησης σαν να μην υπήρχε το inline γιατί δεν ξέρει αν την καλούμε σε άλλο αρχείο. Αν όμως επιλέξουμε "extern inline", τότε δεν παράγεται assembly για τη συνάρτηση όπως εξηγεί η 2η παράγραφος. Σου λέει αφού έβαλες το extern σημαίνει ότι την δηλώνεις αλλού.

 

Σε αυτό το σημείο να πω πως όταν επιλέγουμε -O0 δηλαδή καθόλου optimizations δεν γίνεται inline καμμία συνάρτηση.

 

Έτσι λοιπόν, όταν τρέχουμε τον gcc σε κατάσταση gnu89, αυτός παράγει κανονική assembly για την mul αλλά όχι για την mul2 επειδή την έχουμε δηλώσει extern. Επειδή όμως έχουμε επιλέξει -O0 και δεν γίνεται inlining, η mul2 είναι σαν να μην υπήρξε ποτέ και παίρνουμε λάθος από τον linker ότι δεν υπάρχει τέτοια συνάρτηση.

 

Ερχόμαστε τώρα στο πρότυπο C99:

6

For a function with external linkage, the following restrictions apply: If a function is declared with an inline

function specifier, then it shall also be defined in the same translation unit. If all of the

file scope declarations for a function in a translation unit include the inline function

specifier without extern, then the definition in that translation unit is an inline

definition. An inline definition does not provide an external definition for the function,

and does not forbid an external definition in another translation unit. An inline definition

provides an alternative to an external definition, which a translator may use to implement

any call to the function in the same translation unit. It is unspecified whether a call to the

function uses the inline definition or the external definition.

 

Έτσι σε c99 κατάσταση το σκέτο inline δεν παράγει κανονική assembly ενώ το extern inline παράγει. Για αυτό το λόγο ο linker σε κατάσταση c99 δεν βρίσκει την mul και βρίσκει την mul2.

 

Δηλαδή η επιτροπή αντέστρεψε για κάποιο λόγο το νόημα των specifiers και έχουμε:

>
gnu89/default inline      == c99 extern inline
gnu89/default extern inline == c99 inline
gnu89/default static inline == c99 static inline

 

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

 

Βέβαια ένας έμπειρος προγραμματιστής που δουλεύει σε κάποιο project θα έχει φροντίσει να δηλώσει σωστά την συνάρτηση αν είναι σε header και επίσης θα έχει επιλέξει στο build system (πχ στο Makefile) ποιο πρότυπο θέλει να χρησιμοποιούν οι compilers αλλά ένας αρχάριος που έβαλε το "inline" (ή το "extern inline") επειδή έτσι το είδε σε κάποιο οδηγό μπορεί να φάει ώρα να ψάχνει τι πρόβλημα υπάρχει για αυτό είπα να χρησιμοποιούμε μαζί με το inline και το static όποτε αυτό είναι δυνατό.

Επεξ/σία από imitheos
  • Like 1

Δημιουργήστε ένα λογαριασμό ή συνδεθείτε για να σχολιάσετε

Πρέπει να είστε μέλος για να αφήσετε σχόλιο

Δημιουργία λογαριασμού

Εγγραφείτε με νέο λογαριασμό στην κοινότητα μας. Είναι πανεύκολο!

Δημιουργία νέου λογαριασμού

Σύνδεση

Έχετε ήδη λογαριασμό; Συνδεθείτε εδώ.

Συνδεθείτε τώρα
  • Δημιουργία νέου...