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

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

Δημοσ.

Εστω οτι εχουμε ενα 3 αρχεια f1.c ,f2.c, f.h και κανουμε σε καθε ενα απο τα .c την δηλωση int x;

Tοτε θα εχουμε προβλημα απο τον compiler...

 

Ποια θα ηταν η σωστη χρηση της μεταβλητης x με τo keyword extern;

 

Eγω θα εκανα μια δηλωση int x; στο ενα αρχειο και μια δηλωση extern int x; στο αλλο .c

Σωστα;

 

 

EDIT: Tο καθε αρχειο .c κανει include "f.h"

  • Απαντ. 1,6k
  • Δημ.
  • Τελ. απάντηση

Συχνή συμμετοχή στο θέμα

Δημοσ.

Η μεταβλητή πρέπει να γίνει define μόνο μία φορά οπότε μια τακτική που μπορείς να κάνεις είναι να την ορίσεις σε ένα αρχείο .c και μετά να την έχεις ως extern στα υπόλοιπα οπότε την extern δήλωση μπορείς να την βάλεις στο .h. Ένα χαζό παράδειγμα είναι το παρακάτω.

f1.c

#include <stdio.h>
#include "f.h"

int x;

void printx(void)
{
printf("x = %d\n", x);
}

f2.c
#include "f.h"

void incx(void)
{
x++;
}

f.h
#ifndef F_H
#define F_H

extern int x;

void printx(void);
void incx(void);

#endif /* F_H */


main.c
#include "f.h"

/* Το main.c λόγω του include θα πάρει την δήλωση extern
αλλά δεν την χρειάζεται μια και δεν χρησιμοποιεί την x */
int main(void)
{
printx();
incx();
printx();

return 0;
}
Έξοδος:
x = 0
x = 1
Δημοσ.

Σωστά!

 

Αρκεί να μην το ορίσεις στο .h που θα κάνεις include στα .c, διότι σε αυτή την περίπτωση θα σε σταματήσει ο compiler παραπονούμενος για διπλό σύμβολο (και θα έχει και δίκιο ;) )

 

Αν δεν μιλάμε για βιβλιοθήκη, το σύνηθες είναι να ορίζεις όλες τις καθολικές μεταβλητές σου στο κεντρικό .c (π.χ. main.c) και κατόπιν να τις δηλώνεις με extern στα υπόλοιπα .c πριν τις χρησιμοποιήσεις (βέβαια καλό είναι να τις αποφεύγεις τις καθολικές, αλλά υπάρχουν όντως περιπτώσεις που χρειάζονται ή/και εξυπηρετούν καλύτερα).

 

Μπορείς να την δηλώσεις και ως extern σε ένα .h αρχείο που θα κάνεις include στα .c αρχεία (πλην εκείνου στην οποία την έχεις ορίσει), κάτι που συνηθίζεται περισσότερο σε βιβλιοθήκες, όπου η extern  δήλωση γίνεται στο public .h της βιβλιοθήκης (ή σε μη βιβλιοθήκες όταν το .h δεν χρειάζεται να γίνει include και στο .c που ορίζει την καθολική μεταβλητή).

 

Ένα τέτοιο συνηθισμένο παράδειγμα της στάνταρ βιβλιοθήκης στην C είναι η errno, που δηλώνεται ως extern στο errno.h και συνήθως σε Unix περιβάλλοντα  ορίζεται στο lib/other/errno.c

Δημοσ.

Και κατι άλλο

 

Αν θελω να περασω ενα 2d array σε συναρτηση αλλα οτι αλλαγες κανω στον πινακα να ισχυσουν στην μαιν...

int g(int (*array)[]){

}

int main(){

int array [10][20];
 
 g(array);
}

Σωστα?

Δημοσ.

Αν θελω να περασω ενα 2d array σε συναρτηση αλλα οτι αλλαγες κανω στον πινακα να ισχυσουν στην μαιν...

int g(int (*array)[]){}

int main(){
int array [10][20];
 g(array);
}
Σωστα?

 

 

Όχι :P. Έκανες compile να δεις τι μήνυμα λάθους παίρνεις ?

 

Όταν περνάς ένα πίνακα ως όρισμα σε μια συνάρτηση (και σε άλλες περιπτώσεις αλλά δεν μας νοιάζει στην παρούσα περίπτωση), τότε αυτός μετατρέπεται σε "δείκτη στο τύπο των στοιχείων".

 

Στον κώδικά σου έχεις ορίσει ένα δισδιάστατο πίνακα δηλαδή έχεις ορίσει ένα πίνακα 10 στοιχείων τα οποία είναι πίνακες 20 ακεραίων. Έτσι σύμφωνα με αυτό που είπαμε πριν, η συνάρτηση θα λάβει ένα δείκτη σε πίνακα 20 ακεραίων. Αυτό είναι το όρισμα που πρέπει να δώσεις στη συνάρτηση.

int g(int (*array)[20]){}
Άλλες δηλώσεις θα μπορούσαν να είναι
int g(int array[][20]){}
int g(int array[1321][20]){}
κτλ
Η μετατροπή αυτή σε δείκτη είναι ο λόγος που πολλοί οδηγοί αναφέρουν ότι μπορείς να παραλείψεις την τελευταία διάσταση (ή να γράψεις 1321 ή όποια άλλη μπούρδα θέλεις) όπως κάναμε παραπάνω.

 

ΥΓ: Μην το εκλάβεις ότι θέλω να σου την πω αλλά, αν δεν το έχεις κάνει ήδη, ενεργοποίησε τα warnings του compiler σου και θα στα λέει όλα αυτά. Πλέον οι compilers είναι σούπερ έξυπνοι και είναι κρίμα να μην το εκμεταλλευόμαστε (μη πω ότι πρέπει να βγάζει ένα χέρι ο compiler και να χαστουκίζει όποιον έχει κλειστά τα warnings :P)

Δημοσ.

 

 

int g(int array[][20]){}
int g(int array[1321][20]){}
κτλ
Η μετατροπή αυτή σε δείκτη είναι ο λόγος που πολλοί οδηγοί αναφέρουν ότι μπορείς να παραλείψεις την τελευταία διάσταση (ή να γράψεις 1321 ή όποια άλλη μπούρδα θέλεις) όπως κάναμε παραπάνω.

 

 

OK ευχαριστω πολυ

Επειδή μερικοί compilers (ειδικά σε windows) ακόμη και σήμερα δεν υλοποιούν την έκδοση C99 του προτύπου και επειδή ήταν και άσχετο με την ερώτησή σου, πριν που είπα πως ό,τι γράψεις αγνοείται από τον compiler, παρέλειψα να πω πως υπάρχουν και εκφράσεις που έχουν νόημα.

 

Για παράδειγμα μπορείς να χρησιμοποιήσεις τις εκφράσεις const, static (ειδικά η static δεν είχε αρκετά νοήματα οπότε προστέθηκε άλλο ένα :P) για να δώσεις hints στον compiler και να παράγει καλύτερο κώδικα.

 

Αν έγραφες "int g(int array[const 10][20]) {}" θα έλεγες στον compiler ότι οι δείκτες είναι const οπότε αν μέσα στη συνάρτηση πας να τους αλλάξεις να σου βαρέσει error.

Αν έγραφες "int g(int array[static 8][20]) {}" σημαίνει για τον compiler ότι το όρισμα που πετάς στην συνάρτηση δείχνει σε τουλάχιστον 8 πίνακες 20 ακεραίων (κατά συνέπεια δεν μπορεί να είναι και NULL) έτσι μπορεί να το χρησιμοποιήσει για κάποιο branch prediction ή για να αφαιρέσει κάποιο έλεγχο για NULL ή γενικά για όποιο optimization μπορεί να γίνει.

Δημοσ.

Οταν λέει

 

Functions that are intented for use only within foo.c shouldn't be declared in a header file , however to do so would be misleading.

 

Ποιο θεωρει παραπλανητικο τωρα? Το να βάλουμε τις δηλωσεις των συναρτήσεων που χρησιμοποιούνται μονο μεσα στο foo.c και στο foo.h?

 

We will assume that no word is longer than 20 characters. (A punctuation mark is considered part of the word to which it is adjacent.) That is a bit restrictive , of course , but once the program is written and debugged we can easily increase this limit to the point that it would virtually never be exceeded.

 

απο το that it would μέχρι το exceeded δεν καταλαβαινω τι εννοει το χανω λιγο με τα αγγλικα....

Δημοσ.

Οταν λέει

 

Functions that are intented for use only within foo.c shouldn't be declared in a header file , however to do so would be misleading.

 

Ποιο θεωρει παραπλανητικο τωρα? Το να βάλουμε τις δηλωσεις των συναρτήσεων που χρησιμοποιούνται μονο μεσα στο foo.c και στο foo.h?

 

Ναι, νομίζω αυτό εννοεί.

 

... we can easily increase this limit to the point that it would virtually never be exceeded.

 

απο το that it would μέχρι το exceeded δεν καταλαβαινω τι εννοει το χανω λιγο με τα αγγλικα....

 

... μπορούμε εύκολα να αυξήσουμε αυτό το όριο έτσι ώστε να μην μπορεί ποτέ να ξεπεραστεί.

Δημοσ.

 

Οταν λέει

 

Functions that are intented for use only within foo.c shouldn't be declared in a header file , however to do so would be misleading.

 

Ποιο θεωρει παραπλανητικο τωρα? Το να βάλουμε τις δηλωσεις των συναρτήσεων που χρησιμοποιούνται μονο μεσα στο foo.c και στο foo.h?

 

...

 

Τώρα που το ξανά βλέπω νομίζω πως καταλαβαίνω καλύτερα τι εννοεί.

 

Όταν έχουμε ένα αρχείο foo.c στο οποίο ορίζονται συναρτήσεις που δεν πρόκειται να χρησιμοποιηθούν σε άλλα αρχεία, τότε δεν έχει νόημα τα πρότυπα αυτών των συναρτήσεων να τα δηλώσουμε σε κάποιο header file.

 

Κάτι τέτοιο θα έδινε την εντύπωση πως θέλουμε τις συναρτήσεις διαθέσιμες και σε άλλα .c αρχεία, και άρα δηλώνουμε τα πρότυπά τους στο header file με σκοπό να κάνουμε #include αυτό το header file στα (άλλα) .c αρχεία που θέλουμε να είναι διαθέσιμες οι συναρτήσεις.

 

Όταν θέλουμε να περιορίσουμε το scope συναρτήσεων μονάχα μέσα στο .c αρχείο που τις ορίζουμε, καλό και σωστό είναι να τις ορίζουμε ως static.

  • Like 2
Δημοσ.


Επίσης star_light  αν θες να κρύψεις τα πεδία ενός struct και να αναγκάσεις κάποιον να χρησιμοποιεί δικές σου μεθόδους ανάγνωσης και εγγραφής για το συγκεκριμένο τύπο struct κάνεις το εξής:

foo.h
struct blah_t;

foo.c
struct blah_t{ ..... ; .... ; .... ; } ;

Με αυτό τον τρόπο αν θέλει κάποιος να χρησιμοποιήσει το blah_t  που έφτιαξες, πρέπει να ορίσει εναν pointer σε blah_t και να χρησιμοποιεί τις συναρτήσεις του foo.c για να το διαχειρίζεται.

Βέβαια κάτι τέτοιο μπορεί να μη σου φαίνεται και καλή ιδέα,αλλά το έγραψα έτσι για να υπάρχει....

  • Like 2
Δημοσ.

Επίσης star_light  αν θες να κρύψεις τα πεδία ενός struct και να αναγκάσεις κάποιον να χρησιμοποιεί δικές σου μεθόδους ανάγνωσης και εγγραφής για το συγκεκριμένο τύπο struct κάνεις το εξής:

 

foo.h

struct blah_t;

 

foo.c

struct blah_t{ ..... ; .... ; .... ; } ;

 

Με αυτό τον τρόπο αν θέλει κάποιος να χρησιμοποιήσει το blah_t  που έφτιαξες, πρέπει να ορίσει εναν pointer σε blah_t και να χρησιμοποιεί τις συναρτήσεις του foo.c για να το διαχειρίζεται.

 

Βέβαια κάτι τέτοιο μπορεί να μη σου φαίνεται και καλή ιδέα,αλλά το έγραψα έτσι για να υπάρχει....

+1

 

Πολύ χρήσιμο και από τις λίγες περιπτώσεις που είναι δόκιμη η χρήση του typedef. Με το typedef οι συναρτήσεις θα έχουν σκέτο "blah_t" στο prototype οπότε δεν φαίνεται καν ότι δέχονται struct. Κλασικό παράδειγμα το γνωστό FILE που χρησιμοποιούμε στα αρχεία.

Δημοσ.

Ευχαριστω ολους για τις απαντησεις σας παιδια. Ρε συ migf1 οκ με το πρωτο ηταν αυτο που καταλαβα και εγω αρχικα

αλλα εκει με το

 

we can easily increase this limit to the point that it would virtually never be exceeded.
 

Ξεχασες το point νομιζω να μεταφρασεις... μηπως ειναι η αυξηση του οριου μεχρι το σημειο που πρακτικα δεν θα μπορουσε ποτε να ξεπεραστει ? Εχεις κανα παραδειγμα να δωσεις τι εννοει?

 

Μπορει να ρωταω και κατι ανουσιο.....

 

ps Στην Σελ.360 εκεινο που δεν εχω καταλαβει ακομα ειναι πως θα αποφασιζει το justify απο ποια λεξη θα κοβει κενα ετσι ωστε να εχουμε ακριβως 60 χαρακτηρες μηκος καθε γραμμη... ισως παρακατω το εξηγει καλυτερα με τον κωδικα....

Δημοσ.

Επίσης star_light  αν θες να κρύψεις τα πεδία ενός struct και να αναγκάσεις κάποιον να χρησιμοποιεί δικές σου μεθόδους ανάγνωσης και εγγραφής για το συγκεκριμένο τύπο struct κάνεις το εξής:

 

foo.h

struct blah_t;

 

foo.c

struct blah_t{ ..... ; .... ; .... ; } ;

 

Με αυτό τον τρόπο αν θέλει κάποιος να χρησιμοποιήσει το blah_t  που έφτιαξες, πρέπει να ορίσει εναν pointer σε blah_t και να χρησιμοποιεί τις συναρτήσεις του foo.c για να το διαχειρίζεται.

 

Βέβαια κάτι τέτοιο μπορεί να μη σου φαίνεται και καλή ιδέα,αλλά το έγραψα έτσι για να υπάρχει....

 

+1

 

Αξίζει ιδιαίτερη έμφαση σε αυτό που γράφει ο Χρήστος, ότι δηλαδή για να μπορέσει να δουλευτεί "εξωτερικά" το κρυμμένο struct (η επίσημη ορολογία είναι incomplete type) πρέπει η μεταβλητή που θα οριστεί από τον end-programmer να είναι δείκτης στο κρυμμένο struct, αλλιώς ο compiler θα δώσει σφάλμα "unknown storage size".

 

Αυτό που περιγράφει ο Χρήστος είναι ο κλασικός τρόπος για information hiding στη C, κάτι πολύ συνηθισμένο στις βιβλιοθήκες. Θα δώσω ένα παράδειγμα, όσο μπορώ πιο απλά, γιατί είναι πολύ χρήσιμο concept στην C και πάρα πολύ συνηθισμένο.

 

Ας πούμε λοιπόν πως θέλουμε να φτιάξουμε μια βιβλιοθήκη (ας την πούμε xlib) η οποία παρέχει ρουτίνες διαχείρισης σημείων στην οθόνη (points). Ας πούμε επίσης πως όλες οι ρουτίνες της βιβλιοθήκες ορίζονται στο αρχείο xlib.c...

 

 

#include <stdio.h>
#include <stdlib.h>

/* here we complete struct Point (we define it) */
struct Point { int x, y; };

/* ----------------------------------------- */
struct Point *new_point( void )
{
	return calloc( 1, sizeof(struct Point) );
}
/* ----------------------------------------- */
int point_set_xy( struct Point *point, int x, int y )
{
	/* sanity check */
	if ( NULL == point || x < 0 || y < 0 )
		return 0;

	point->x = x;
	point->y = y;

	return 1;
}
/* ----------------------------------------- */
int point_print( const struct Point *point )
{
	/* sanity check */
	if ( NULL == point )
		return 0;

	printf( "(x,y) = (%d,%d)\n", point->x, point->y );

	return 1;
}
/* ----------------------------------------- */
int point_free( struct Point **point )
{
	/* sanity check */
	if ( NULL == point )
		return 0;

	if ( NULL != *point ) {
		free( *point );
		*point = NULL;
	}

	return 1;
}
/* ----------------------------------------- */

 

Η βασική δομή που διαχειρίζεται η βιβλιοθήκη μας είναι η struct Point, την οποία και την ορίζουμε κανονικά μέσα στο αρχείο xlib.c.

 

Όταν όμως διαθέσουμε την βιβλιοθήκη στον end-programmer και δεν θέλουμε να του δώσουμε τον πηγαίο κώδικα της βιβλιοθήκης μας, του δίνουμε μονάχα το εκτελέσιμο της βιβλιοθήκης (δηλαδή το object file xlib.o που προκύπτει όταν κάνουμε compile το xlib.c με command-line -c στον gcc, δλδ: gcc -c xlib.c ) καθώς και το public header της βιβλιοθήκης μας, ας το πούμε xlib_public.h ...

 

 

typedef struct Point Point;	/* incomplete type (it gets completed privately by the library) */

extern struct Point *new_point( void );
extern int point_set_xy( Point *point, int x, int y );
extern int point_print( const Point *point );
extern int point_free( Point **point );

 

Αυτό το public header δίνει στον end-programmer τα πρότυπα των συναρτήσεων που μπορεί να χρησιμοποιήσει χωρίς να αποκαλύπτει την εσωτερική τους υλοποίηση, καθώς επίσης και τις δομές που μπορεί να χρησιμοποιήσει και πάλι χωρίς να αποκαλύπτει την εσωτερική τους υλοποίηση.

 

Στην περίπτωσή μας, η δομή αυτή είναι η struct Point, την οποία στο public header του την παρέχουμε και ως custom τύπο Point μέσω της typedef (για να μην χρειάζεται να γράφει και το struct κάθε φορά... θα μπορούσαμε να την παρέχουμε και χωρίς το typedef, γράφοντας στη θέση του ένα σκέτο: struct Point; και προφανώς να αλλάζαμε αντίστοιχα και τα function signatures).

 

Άρα λοιπόν, δίνοντας στον end-programmer το xlib.o και το xlib_public.h μπορεί να γράψει ένα δικό του πρόγραμμα, ας πούμε user.c το οποίο θα χρησιμοποιεί την βιβλιοθήκη μας, χωρίς να χρειάζεται να γνωρίζει την εσωτερική της υλοποίηση (και προφανώς χωρίς να έχει τον πηγαίο της κώδικα).

 

Με μόνη λεπτομέρεια πως κάθε φορά που θα θέλει να φτιάξει μια μεταβλητή τύπου Point θα πρέπει να την ορίζει ως δείκτη...

 

 

#include <stdio.h>
#include <stdlib.h>

#include "xlib_public.h"	/* contains declaration of incomplete type Point */

/* ----------------------------------------- */
int main( void )
{
	/* Point pt; <---- ΔΕΝ ΠΕΡΝΑΕΙ ΑΠΟ ΤΟΝ COMPILER, ΓΙΑΤΙ ΔΕΝ ΕΙΝΑΙ ΔΕΙΚΤΗΣ */
	Point *point = new_point();

	if ( NULL == point ) {
		puts( "*** fatal error: possible memory shortage (aborting program)..." );
		return 1;
	}

	point_set_xy( point, 10, 20 );
	point_print( point );

	point_free( &point );

	system( "pause" );	/* Windows only */
	return 0;
}

 

 

Compile:

gcc user.c xlib.o -o user.exe
Έξοδος:
(x,y) = (10,20)
Press any key to continue . . .
  • 3 εβδομάδες αργότερα...
Δημοσ.

Σωστά!

 

Αρκεί να μην το ορίσεις στο .h που θα κάνεις include στα .c, διότι σε αυτή την περίπτωση θα σε σταματήσει ο compiler παραπονούμενος για διπλό σύμβολο (και θα έχει και δίκιο ;) )

 

Αν δεν μιλάμε για βιβλιοθήκη, το σύνηθες είναι να ορίζεις όλες τις καθολικές μεταβλητές σου στο κεντρικό .c (π.χ. main.c) και κατόπιν να τις δηλώνεις με extern στα υπόλοιπα .c πριν τις χρησιμοποιήσεις (βέβαια καλό είναι να τις αποφεύγεις τις καθολικές, αλλά υπάρχουν όντως περιπτώσεις που χρειάζονται ή/και εξυπηρετούν καλύτερα).

 

Μπορείς να την δηλώσεις και ως extern σε ένα .h αρχείο που θα κάνεις include στα .c αρχεία (πλην εκείνου στην οποία την έχεις ορίσει), κάτι που συνηθίζεται περισσότερο σε βιβλιοθήκες, όπου η extern δήλωση γίνεται στο public .h της βιβλιοθήκης (ή σε μη βιβλιοθήκες όταν το .h δεν χρειάζεται να γίνει include και στο .c που ορίζει την καθολική μεταβλητή).

 

Ένα τέτοιο συνηθισμένο παράδειγμα της στάνταρ βιβλιοθήκης στην C είναι η errno, που δηλώνεται ως extern στο errno.h και συνήθως σε Unix περιβάλλοντα ορίζεται στο lib/other/errno.c

 

Δεν πρεπει να συμπεριλάβεις το αρχειο επικεφαλιδας(που ειναι δηλωμενη με το extern) στο .c οπου οριζεις την global για να μπορει να τσεκαρει ετσι ο μεταγλωτιστης αν υπάρχει συνεπεια μεταξυ ορισμου και δηλωσης ? Οπως το εκανε και πιο πανω απο το δικο σου ποστ ο ημιθεος με τα αρχεια που εδωσε

Επισκέπτης
Αυτό το θέμα είναι πλέον κλειστό για περαιτέρω απαντήσεις.

  • Δημιουργία νέου...