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

pointers c++


ALLisCHAOS

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

Το οτι την πατησα με αυτη τη παπαρια ειναι αληθεια. Αυτο το φορματ( το double terminated ) μπαινει στο openfiledialog των win. Βαζω ο ανθρωπος "mpla mpla\03dx\0" και εχασα 5 ωρες απο την ζωη μου. Τοσο απλα.

Καταλαβαίνω :(

 

Την πάτησες, γιατί το συγκεκριμένο φεύγει από τα conventions της γλώσσας για τα c-strings, οπότε δεν θέλει και πολύ για να την πατήσει κανείς. Από περιέργεια, εφόσον κάνει που κάνει τα δικά της η openfiledialog, δεν σου δίνει το API έτοιμες συναρτήσεις για να τα διαχειρίζεσαι αυτά τα double-NUL strings?

 

ΥΓ. Άσχετο, τελικά που τον έχεις αυτόν τον ρημαδιασμένο τον τόνο, στο ί ή στο ά... είσαι δηλαδή ελληνικό παπί ή αμερικάνικο κουτάβι (puppy) :P

 

@geomagas: No worries! Στοιχηματίζω πως σε άλλη στιγμή θα έχεις εσύ πιο ξεκάθαρο μυαλό και θα αντιστρφούν οι ρόλοι ;)

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

  • Απαντ. 86
  • Δημ.
  • Τελ. απάντηση

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

Δημοφιλείς Ημέρες

@geomagas: No worries! Στοιχηματίζω πως σε άλλη στιγμή θα έχεις εσύ πιο ξεκάθαρο μυαλό και θα αντιστρφούν οι ρόλοι ;)

 

Θα το χάσεις το στοίχημα...

Ειδικά αν μιλάμε για C*

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

να ρωτήσω  κάτι άλλο ,αυτό το πράγμα τι ειναι ?
 

char **pp

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


Πρέπει να ξεκλειδωθεί το νήμα της C για να μπορούμε να αναλύουμε μ.κίες ελεύθερα :P

χαχα,feel free to edit what ever in my post :P

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

να ρωτήσω  κάτι άλλο ,αυτό το πράγμα τι ειναι ?

 

char **pp
δείκτης στο τετράγωνο?? :P είναι δηλαδή ένας δείκτης που δείχνει σε ένα δείκτη? και ποια η χρησιμότητα?

 

Ναι είναι διπλός δείκτης. Όπως ίσως έχεις διαβάσει, στην C τα πάντα μεταχειρίζονται ως "call by value" δηλαδή δημιουργείται ένα νέο αντικείμενο και αντιγράφεται σε αυτό η τιμή του αρχικού. Για αυτό το λόγο θα έχεις σίγουρα δει να χρησιμοποιούνται οι δείκτες σε διάφορα προγράμματα. Δες το παρακάτω χαζό κώδικα.

 

#include <stdio.h>

int test = 5;
void mitsos(int *k)
{

	printf("Stin mitsos to k exei address %p kai timi %p\n", (void *)&k, (void *)k);
	*k = 5;
	k = &test;
	printf("Stin mitsos to k exei address %p kai timi %p\n", (void *)&k, (void *)k);
}

int main(void)
{
	int ll = 3;
	int *p = &ll;
	printf("To p exei addr %p, deixnei sthn %p kai exei timi %d\n", (void *)&p, (void *)p, *p);
	mitsos(p);
	printf("To p exei addr %p, deixnei sthn %p kai exei timi %d\n", (void *)&p, (void *)p, *p);
	return 0;
}
Έξοδος:
To p exei addr 0x78, deixnei sthn 0x7c kai exei timi 3
Stin mitsos to k exei address 0x60 kai timi 0x7c
Stin mitsos to k exei address 0x60 kai timi 0x24
To p exei addr 0x78, deixnei sthn 0x7c kai exei timi 5
Όταν γράψαμε int ll = 3, δημιουργήθηκε ένα νέο αντικείμενο τύπου int και του εκχωρήθηκε η διεύθυνση 7c και γράφτηκε σε αυτήν η τιμή 3. Εκεί που γράφουμε int *p δημιουργείται και πάλι ένα νέο αντικείμενο τύπου δείκτης σε int και του εκχωρείται μια διεύθυνση μνήμης εν προκειμένω η 78. Επειδή όμως είναι δείκτης, παίρνει ως τιμή διευθύνσεις μνήμης για αυτό όταν του είπα p = &ll του δώθηκε η τιμή 7c ώστε να δείχνει στην μεταβλητή ll.

 

Μετά καλούμε την συνάρτηση mitsos με όρισμα τον δείκτη. Στην C το k με το p δεν είναι το ίδιο αντικείμενο όπως θα περιμέναμε. Όταν έτρεξε η συνάρτηση δημιουργήθηκε ένα νέο αντικείμενο με διεύθυνση 60 (σε σύγκριση με την 78 που έχει ο p) και απλά του αντιγράφηκε η τιμή που έχει η μεταβλητή p δηλαδή ο αριθμός 7c. Έτσι και το k δείχνει στην μεταβλητή ll για αυτό τρέχοντας *k = 5 αλλάζει την τιμή της μεταβλητής ll.

 

Ας υποθέσουμε όμως ότι εγώ θέλω να αλλάξω μέσα από την συνάρτηση mitsos την τιμή του δείκτη p. Εκεί που γράφω k = &test πάω και πειράζω την τιμή του νέου αυτού αντικειμένου k για αυτό βλέπεις ότι ενώ το k έχει πλέον τιμή 24, το p δεν έχει αλλάξει και εξακολουθεί να έχει τιμή 7c.

 

Επειδή λοιπόν γίνεται αυτή η αντιγραφή των περιεχομένων, αν θέλω μέσα από την mitsos να πειράξω τον p, τότε πρέπει να χρησιμοποιήσω διπλό δείκτη **pp.

  • Like 1
Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

να ρωτήσω  κάτι άλλο ,αυτό το πράγμα τι ειναι ?

 

char **pp
δείκτης στο τετράγωνο?? :P είναι δηλαδή ένας δείκτης που δείχνει σε ένα δείκτη? και ποια η χρησιμότητα?

 

χαχα,feel free to edit what ever in my post :P

 

Ναι, δείκτης σε δείκτη είναι. Στο είχα αναφέρει και στην 1η μου απάντηση αν θυμάσαι. Χρησιμότητες υπάρχουν πολλές, αλλά αρκετά συνηθισμένη είναι για να προσομοιώσεις by reference πέρασμα ενός δείκτη σε μια συνάρτηση. Πολύ χρήσιμο π.χ. στις δομές δεδομένων και ιδιαίτερα στο generic programming (π.χ. ADT).

 

Ένα αρκετά πιο απλό παράδειγμα που μου ήρθε τώρα (αλλά και scary για αρχάριο) είναι το παρακάτω:

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

void pswap( void **p1, void **p2 )
{
	void *temp = *p1;
	*p1 = *p2;
	*p2 = temp;
}

int main( void )
{
	char *s1 = "Hello cruel cold world";
	char *s2 = "To be or not to be";

	puts( "Before: " );
	puts( s1 );
	puts( s2 );

	pswap( (void **)&s1, (void **)&s2 );

	puts( "\nAfter: " );
	puts( s1 );
	puts( s2 );

	/* ----------------------------------- */

	int   n = 10,   *pn = &n;
	float f = 1.5f, *pf = &f;

	puts( "\nBefore: " );
	printf( "%d | %g\n", *pn, *pf );

	pswap( (void **)&pn, (void **)&pf );

	puts( "After: " );
	printf( "%g | %d\n", *(float *)pn, *(int *)pf );

	return 0;
}
/*
Έξοδος:

Before:
Hello cruel cold world
To be or not to be

After:
To be or not to be
Hello cruel cold world

Before:
10 | 1.5
After:
1.5 | 10
 */
Η συνάρτηση pswap() δέχεται ως ορίσματα τις διευθύνσεις 2 δεικτών αγνώστου τύπου ( δηλαδή void **) και τους αντιμεταθέτει (πώς λέγεται το swap στα ελληνικά; ).

 

Σου δείχνω 2 περιπτώσεις, μια που οι 2 δείκτες είναι ίδιου τύπου (c-strings) και μια που ο ένας δείχνει σε int και ο άλλος σε float. Η αντιμετάθεση και στις 2 περιπτώσεις γίνεται με την ίδια συνάρτηση (την pswap() ) αλλά στη 2η περίπτωση που είναι διαφορετικού τύπου οι δείκτες χρειάζονται cast-o-παπαδιές για να τα τυπώσεις. Επίσης, η 2η περίπτωση ΔΕΝ είναι καθόλου καλή ιδέα να την χρησιμοποιείς σε κανονικό κώδικα.

 

ΥΓ. Διάβασες καθόλου τις σημειώσεις μου που σου είπα στο προηγούμενο ποστ; Άμα τις διαβάσεις και φτάσεις στις linked-lists, έχω παραδείγματα με διπλούς δείκτες για by reference πέρασμα των λιστών σε συναρτήσεις... μετά το απλοποιώ όμως, βάζοντάς τες σε πιο υψηλή δομή οπότε ξανα-δουλεύω με μονούς δείκτες αντί για διπλούς.

 

Θα το χάσεις το στοίχημα...

Ειδικά αν μιλάμε για C*

Η πλάκα ξέρεις ποια είναι; Πως αρχικά δεν είχα καταλάβει πως η συνάρτηση του παπι είναι για c-strings που ΤΕΛΕΙΩΝΟΥΝ σε double-NUL. Νόμιζα πως ήθελε να προσπεράσει απλώς τα 2 πρώτα NUL που θα βρει μέσα στο c-string.

 

Βέβαια, το βασικό νόημα περί '\0' και '\000' δεν αλλάζει, αλλά θα είχαμε γλιτώσει 1-2 σελίδες ποσταρίσματος :P

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Βέβαια, το βασικό νόημα περί '\0' και '\000' δεν αλλάζει, αλλά θα είχαμε γλιτώσει 1-2 σελίδες ποσταρίσματος :P

 

Προφανώς και δεν αλλάζει... Γι αυτό έλεγα ότι όλοι το ίδιο λέμε.

Απλά ήταν μία περίπτωση (εύστοχη, πρέπει να παραδεχτείς) που κάποιος μπορεί άνετα να παρασυρθεί.

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Προφανώς και δεν αλλάζει... Γι αυτό έλεγα ότι όλοι το ίδιο λέμε.

Απλά ήταν μία περίπτωση (εύστοχη, πρέπει να παραδεχτείς) που κάποιος μπορεί άνετα να παρασυρθεί.

Ναι, εννοείται πως είναι εύστοχη. Και είναι και από τα γνωστά traps, απλά δεν πήγε καθόλου εκεί το μυαλό μου. Ήταν και άσχετη με το zero initialization για το οποίο μιλάγαμε, οπότε δεν το 'πιασα.

 

Η συνάρτηση του παπι με αποπροσανατόλισε ακόμα περισσότερο. Το ξεκαθάρισα μόνο όταν έκανε το ποστ για την openfiledialog, το οποίο αν το είχε κάνει από την αρχή αντί για εκείνη τη συνάρτηση θα είχαμε γλιτώσει αρκετά ποστς :)

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Λίγο έλειψα και έγινε hot το topic. Εγώ λέω τώρα να το πάμε αλλού το θέμα!

 

"Ναι ή Όχι στο typecast της επιστρεφόμενης τιμής της malloc στη C; Παίζει κάποιος ουσιαστικός σημασιολογικός λόγος, ή είναι μία δικλείδα ασφαλείας και ηρεμίας για τους προγραμματιστές C που πρέπει να έχουν τα μάτια τους 14; Τι γίνεται με τη C++;"

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Λίγο έλειψα και έγινε hot το topic. Εγώ λέω τώρα να το πάμε αλλού το θέμα!

 

"Ναι ή Όχι στο typecast της επιστρεφόμενης τιμής της malloc στη C; Παίζει κάποιος ουσιαστικός σημασιολογικός λόγος, ή είναι μία δικλείδα ασφαλείας και ηρεμίας για τους προγραμματιστές C που πρέπει να έχουν τα μάτια τους 14; Τι γίνεται με τη C++;"

int main(void)
{
	int k = 5;
	int *i;
	void *v;
	float *f;

	i = &k;
	v = i;
	i = v;
	f = i;

	return 0;
}
gcc -Wall -x c -
<stdin>: In function ‘main’:
<stdin>:11:4: προειδοποίηση: assignment from incompatible pointer type [enabled by default]

gcc -Wall -x c++ -
<stdin>: In function ‘int main()’:
<stdin>:10:4: σφάλμα: invalid conversion from ‘void*’ to ‘int*’ [-fpermissive]
<stdin>:11:4: σφάλμα: cannot convert ‘int*’ to ‘float*’ in assignment
Στο παραπάνω χαζό παράδειγμα έχουμε τρεις δείκτες σε int, void, float αντίστοιχα. Όταν θέτουμε ως τιμή του i την διεύθυνση του k όλα φυσικά λειτουργούν σωστά μια και μιλάμε για int. Όταν όμως πάμε να θέσουμε ως τιμή ενός δείκτη την τιμή ενός δείκτη άλλου τύπου (εννοείται χωρίς cast), τότε βλέπουμε μια διαφοροποίηση στην συμπεριφορά. Στην περίπτωση float<->int βαράνε και οι δύο γλώσσες με την C++ που είναι πιο αυστηρή να βαράει error ενώ η C πετάει απλά warning.

 

Ως εδώ καλά. Το float-int το έβαλα απλά για να φανεί η διαφορά σε σχέση με τον void. Στην γραμμή 9 που έχουμε v = i δηλαδή θέτουμε την τιμή ενός int-δείκτη σε ένα void δεν βαράει καμμία γλώσσα. Στην γραμμή 10 όμως που πάμε να κάνουμε το αντίθετο δηλαδή να θέσουμε την τιμή ενός void-δείκτη σε ένα int, ενώ η C το δέχεται κανονικά η C++ βαράει error.

 

Στην C ισχύει πως ένας απλός (και όχι διπλός, τριπλός, κτλ) δείκτης σε void μετατρέπεται αυτόματα από και προς οποιονδήποτε άλλο τύπο δείκτη ενώ στην C++ δεν ισχύει αυτό και προς τις δύο πλευρές.

 

Ας δούμε τώρα τον ορισμό της malloc:

 

#include <stdlib.h>

 

void *malloc(size_t size);

Αν εξαιρέσουμε πολύ παλιές pre-ansi υλοποιήσεις, η malloc ορίζεται ότι επιστρέφει ένα δείκτη σε void. Αν εμείς δηλαδή έχουμε "int *p = malloc(τάδε)" θα πρέπει να μετατραπεί ο δείκτης σε void που επιστρέφει η malloc σε δείκτη σε int. Στην C αυτό γίνεται αυτόματα οπότε δεν χρειάζεται να βάλουμε το cast σε int. Στην C++ όμως είπαμε ότι δεν γίνεται και θα μας βαρέσει error οπότε αν χρησιμοποιούμε C++ compiler για το C κώδικά μας (είτε γιατί θέλουμε τα πιο αυστηρά warnings του c++ compiler είτε γιατί χρησιμοποιούμε Visual Studio που υλοποιεί μόνο C++ compiler ή για τον Χ-Ψ λόγο), τότε πρέπει να εισάγουμε το cast και να γράψουμε την παραπάνω δήλωση ως "int *p = (int *)malloc(τάδε)".

 

Αφού λοιπόν είναι έτσι τα πράγματα, γιατί να μην βάζουμε πάντα το cast και έτσι ο κώδικάς μας να παίζει σε όλους τους compilers ?

 

Η έλλειψη του cast σου παρέχει προστασία (που ομολογουμένως θα χρειαστείς σπάνια).

 

#include <stdio.h>

int main(void)
{
	int *k = malloc(5);
	int *l = (int *)malloc(9);

	*k = 5;
	printf("k = %d\n", *k);
	*l = 3;
	printf("l = %d\n", *l);

	return 0;
}
Ας δούμε το παραπάνω χαζό πρόγραμμα. Κοιτάζοντας τον κώδικα και τον ορισμό της malloc που έδωσα παραπάνω, βλέπουμε ότι ξέχασα να εισάγω το αρχείο stdlib.h που περιέχει τον ορισμό. Έτσι ο compiler και ο linker δεν ξέρουν τι συνάρτηση είναι αυτή η malloc και (ανάλογα και με την έκδοση του προτύπου που επιλέγουμε) θα υποθέσουν ότι είναι μια συνάρτηση που δέχεται απροσδιόριστο αριθμό ορισμάτων και σημαντικότερα συνάρτηση που επιστρέφει int.

 

Ας τρέξουμε τώρα τον gcc με διάφορους τρόπους και ας δούμε τι μας λέει:

1)
gcc -x c -Wall -
<stdin>: In function ‘main’:
<stdin>:5:2: προειδοποίηση: implicit declaration of function ‘malloc’ [-Wimplicit-function-declaration]
<stdin>:5:11: προειδοποίηση: incompatible implicit declaration of built-in function ‘malloc’ [enabled by default]
2)
gcc -x c -Wall -fno-builtin-malloc -m32 -
<stdin>: In function ‘main’:
<stdin>:5:2: προειδοποίηση: implicit declaration of function ‘malloc’ [-Wimplicit-function-declaration]
<stdin>:5:11: προειδοποίηση: initialization makes pointer from integer without a cast [enabled by default]
3)
gcc -x c -Wall -fno-builtin-malloc -
<stdin>: In function ‘main’:
<stdin>:5:2: προειδοποίηση: implicit declaration of function ‘malloc’ [-Wimplicit-function-declaration]
<stdin>:5:11: προειδοποίηση: initialization makes pointer from integer without a cast [enabled by default]
<stdin>:6:11: προειδοποίηση: cast to pointer from integer of different size [-Wint-to-pointer-cast]
Με την πρώτη εκτέλεση ο gcc μας λέει αφενός ότι χρησιμοποιούμε την συνάρτηση malloc αυθαίρετα χωρίς να την έχουμε ορίσει και ότι ο τρόπος που την χρησιμοποιούμε δεν είναι συμβατός με την malloc που ξέρει εκείνος. Α οπότε ο gcc κλέβει εδώ και μας παρέχει μια (ίσως) πιο γρήγορη δική του malloc για την οποία φυσικά γνωρίζει πως ορίζεται και τι πρέπει να επιστρέψει.

 

Άρα ας του πούμε να μην χρησιμοποιήσει την δική του έκδοση αλλά αυτήν την βιβλιοθήκης libc (οπότε και δεν θα ξέρει τι είδους συνάρτηση είναι η malloc). Στην δεύτερη εκτέλεση παίρνουμε ξανά το μήνυμα ότι χρησιμοποιούμε αυθαίρετα την malloc και επίσης παίρνουμε μια προειδοποίηση ότι προσπαθούμε να θέσουμε ένα integer σε ένα δείκτη. Γιατί να το λέει άραγε αυτό ?

 

Βλέπει ότι το αποτέλεσμα της malloc το θέτουμε στο int *k δηλαδή σε ένα δείκτη αλλά είπαμε πως χωρίς ορισμό της malloc υποθέτει ότι επιστρέφει int (άσχετα αν επιστρέφει δείκτη) οπότε σου λέει "τι πας να κάνεις ?". Κάτι που ίσως δεν έχουμε προσέξει είναι ότι παίρνουμε μόνο μία ειδοποίηση άσχετα αν έχουμε δύο κλήσεις της malloc. Γιατί ?

 

Η δεύτερη κλήση περιέχει το cast σε δείκτη οπότε σου λέει δείκτη επιστρέφει η malloc (μέσω του cast), σε δείκτη θέτουμε την τιμή άρα όλα είναι οκ.

 

Στην 3η εκτέλεση που είναι ίδια αλλά για 64bit βλέπουμε για πρώτη φορά ένα warning για τη γραμμή που έχει το cast και που ίσως εξηγήσει καλύτερα ποιο είναι το πρόβλημα που κρύβεται με το cast. Μπορεί να είπαμε πριν στον compiler ότι ξέρεις μεταχειρίσου τον int (που υποθέτει ότι επιστρέφει η malloc) σαν να είναι δείκτης αλλά ο compiler δεν ξέχασε ότι είναι int. Δεν μας σφαλιάριζε πριν γιατί ο δείκτης με τον int είχαν ίδιο μέγεθος σε 32bit οπότε σου λέει "κάτι θα ξέρει για να το ζητάει έτσι". Εδώ όμως μας λέει "όπα μεγάλε που πας. ο integer και ο δείκτης έχουν διαφορετικό μέγεθος οπότε πώς μου ζητάς να τους μεταχειριστώ το ίδιο".

 

Με λίγα λόγια, η έλλειψη του cast στην malloc σε βοηθάει στην ελάχιστα πιθανή περίπτωση να ξεχάσεις να εισάγεις το stdlib.h. Γενικά όμως τα cast μπορούν να δημιουργήσουν τέτοια προβλήματα και είχαν δημιουργήσει πολλά προβλήματα στις αρχές του porting κώδικα σε 64bit. Μην ξεχνάμε ότι εκτός από την αρκετά ανεκτική x86 αρχιτεκτονική υπάρχουν και αρχιτεκτονικές που μπορεί δείκτες διαφόρων τύπων να έχουν διαφορετικό μέγεθος ή ποιος ξέρει τι.

  • Like 3
Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

int main(void)
{
	int k = 5;
	int *i;
	void *v;
	float *f;

	i = &k;
	v = i;
	i = v;
	f = i;

	return 0;
}
gcc -Wall -x c -
<stdin>: In function ‘main’:
<stdin>:11:4: προειδοποίηση: assignment from incompatible pointer type [enabled by default]

gcc -Wall -x c++ -
<stdin>: In function ‘int main()’:
<stdin>:10:4: σφάλμα: invalid conversion from ‘void*’ to ‘int*’ [-fpermissive]
<stdin>:11:4: σφάλμα: cannot convert ‘int*’ to ‘float*’ in assignment
Στο παραπάνω χαζό παράδειγμα έχουμε τρεις δείκτες σε int, void, float αντίστοιχα. Όταν θέτουμε ως τιμή του i την διεύθυνση του k όλα φυσικά λειτουργούν σωστά μια και μιλάμε για int. Όταν όμως πάμε να θέσουμε ως τιμή ενός δείκτη την τιμή ενός δείκτη άλλου τύπου (εννοείται χωρίς cast), τότε βλέπουμε μια διαφοροποίηση στην συμπεριφορά. Στην περίπτωση float<->int βαράνε και οι δύο γλώσσες με την C++ που είναι πιο αυστηρή να βαράει error ενώ η C πετάει απλά warning.

 

Ως εδώ καλά. Το float-int το έβαλα απλά για να φανεί η διαφορά σε σχέση με τον void. Στην γραμμή 9 που έχουμε v = i δηλαδή θέτουμε την τιμή ενός int-δείκτη σε ένα void δεν βαράει καμμία γλώσσα. Στην γραμμή 10 όμως που πάμε να κάνουμε το αντίθετο δηλαδή να θέσουμε την τιμή ενός void-δείκτη σε ένα int, ενώ η C το δέχεται κανονικά η C++ βαράει error.

 

Στην C ισχύει πως ένας απλός (και όχι διπλός, τριπλός, κτλ) δείκτης σε void μετατρέπεται αυτόματα από και προς οποιονδήποτε άλλο τύπο δείκτη ενώ στην C++ δεν ισχύει αυτό και προς τις δύο πλευρές.

 

Ας δούμε τώρα τον ορισμό της malloc:

 

 

Αν εξαιρέσουμε πολύ παλιές pre-ansi υλοποιήσεις, η malloc ορίζεται ότι επιστρέφει ένα δείκτη σε void. Αν εμείς δηλαδή έχουμε "int *p = malloc(τάδε)" θα πρέπει να μετατραπεί ο δείκτης σε void που επιστρέφει η malloc σε δείκτη σε int. Στην C αυτό γίνεται αυτόματα οπότε δεν χρειάζεται να βάλουμε το cast σε int. Στην C++ όμως είπαμε ότι δεν γίνεται και θα μας βαρέσει error οπότε αν χρησιμοποιούμε C++ compiler για το C κώδικά μας (είτε γιατί θέλουμε τα πιο αυστηρά warnings του c++ compiler είτε γιατί χρησιμοποιούμε Visual Studio που υλοποιεί μόνο C++ compiler ή για τον Χ-Ψ λόγο), τότε πρέπει να εισάγουμε το cast και να γράψουμε την παραπάνω δήλωση ως "int *p = (int *)malloc(τάδε)".

 

Αφού λοιπόν είναι έτσι τα πράγματα, γιατί να μην βάζουμε πάντα το cast και έτσι ο κώδικάς μας να παίζει σε όλους τους compilers ?

 

Η έλλειψη του cast σου παρέχει προστασία (που ομολογουμένως θα χρειαστείς σπάνια).

 

#include <stdio.h>

int main(void)
{
	int *k = malloc(5);
	int *l = (int *)malloc(9);

	*k = 5;
	printf("k = %d\n", *k);
	*l = 3;
	printf("l = %d\n", *l);

	return 0;
}
Ας δούμε το παραπάνω χαζό πρόγραμμα. Κοιτάζοντας τον κώδικα και τον ορισμό της malloc που έδωσα παραπάνω, βλέπουμε ότι ξέχασα να εισάγω το αρχείο stdlib.h που περιέχει τον ορισμό. Έτσι ο compiler και ο linker δεν ξέρουν τι συνάρτηση είναι αυτή η malloc και (ανάλογα και με την έκδοση του προτύπου που επιλέγουμε) θα υποθέσουν ότι είναι μια συνάρτηση που δέχεται απροσδιόριστο αριθμό ορισμάτων και σημαντικότερα συνάρτηση που επιστρέφει int.

 

Ας τρέξουμε τώρα τον gcc με διάφορους τρόπους και ας δούμε τι μας λέει:

1)
gcc -x c -Wall -
<stdin>: In function ‘main’:
<stdin>:5:2: προειδοποίηση: implicit declaration of function ‘malloc’ [-Wimplicit-function-declaration]
<stdin>:5:11: προειδοποίηση: incompatible implicit declaration of built-in function ‘malloc’ [enabled by default]
2)
gcc -x c -Wall -fno-builtin-malloc -m32 -
<stdin>: In function ‘main’:
<stdin>:5:2: προειδοποίηση: implicit declaration of function ‘malloc’ [-Wimplicit-function-declaration]
<stdin>:5:11: προειδοποίηση: initialization makes pointer from integer without a cast [enabled by default]
3)
gcc -x c -Wall -fno-builtin-malloc -
<stdin>: In function ‘main’:
<stdin>:5:2: προειδοποίηση: implicit declaration of function ‘malloc’ [-Wimplicit-function-declaration]
<stdin>:5:11: προειδοποίηση: initialization makes pointer from integer without a cast [enabled by default]
<stdin>:6:11: προειδοποίηση: cast to pointer from integer of different size [-Wint-to-pointer-cast]
Με την πρώτη εκτέλεση ο gcc μας λέει αφενός ότι χρησιμοποιούμε την συνάρτηση malloc αυθαίρετα χωρίς να την έχουμε ορίσει και ότι ο τρόπος που την χρησιμοποιούμε δεν είναι συμβατός με την malloc που ξέρει εκείνος. Α οπότε ο gcc κλέβει εδώ και μας παρέχει μια (ίσως) πιο γρήγορη δική του malloc για την οποία φυσικά γνωρίζει πως ορίζεται και τι πρέπει να επιστρέψει.

 

Άρα ας του πούμε να μην χρησιμοποιήσει την δική του έκδοση αλλά αυτήν την βιβλιοθήκης libc (οπότε και δεν θα ξέρει τι είδους συνάρτηση είναι η malloc). Στην δεύτερη εκτέλεση παίρνουμε ξανά το μήνυμα ότι χρησιμοποιούμε αυθαίρετα την malloc και επίσης παίρνουμε μια προειδοποίηση ότι προσπαθούμε να θέσουμε ένα integer σε ένα δείκτη. Γιατί να το λέει άραγε αυτό ?

 

Βλέπει ότι το αποτέλεσμα της malloc το θέτουμε στο int *k δηλαδή σε ένα δείκτη αλλά είπαμε πως χωρίς ορισμό της malloc υποθέτει ότι επιστρέφει int (άσχετα αν επιστρέφει δείκτη) οπότε σου λέει "τι πας να κάνεις ?". Κάτι που ίσως δεν έχουμε προσέξει είναι ότι παίρνουμε μόνο μία ειδοποίηση άσχετα αν έχουμε δύο κλήσεις της malloc. Γιατί ?

 

Η δεύτερη κλήση περιέχει το cast σε δείκτη οπότε σου λέει δείκτη επιστρέφει η malloc (μέσω του cast), σε δείκτη θέτουμε την τιμή άρα όλα είναι οκ.

 

Στην 3η εκτέλεση που είναι ίδια αλλά για 64bit βλέπουμε για πρώτη φορά ένα warning για τη γραμμή που έχει το cast και που ίσως εξηγήσει καλύτερα ποιο είναι το πρόβλημα που κρύβεται με το cast. Μπορεί να είπαμε πριν στον compiler ότι ξέρεις μεταχειρίσου τον int (που υποθέτει ότι επιστρέφει η malloc) σαν να είναι δείκτης αλλά ο compiler δεν ξέχασε ότι είναι int. Δεν μας σφαλιάριζε πριν γιατί ο δείκτης με τον int είχαν ίδιο μέγεθος σε 32bit οπότε σου λέει "κάτι θα ξέρει για να το ζητάει έτσι". Εδώ όμως μας λέει "όπα μεγάλε που πας. ο integer και ο δείκτης έχουν διαφορετικό μέγεθος οπότε πώς μου ζητάς να τους μεταχειριστώ το ίδιο".

 

Με λίγα λόγια, η έλλειψη του cast στην malloc σε βοηθάει στην ελάχιστα πιθανή περίπτωση να ξεχάσεις να εισάγεις το stdlib.h. Γενικά όμως τα cast μπορούν να δημιουργήσουν τέτοια προβλήματα και είχαν δημιουργήσει πολλά προβλήματα στις αρχές του porting κώδικα σε 64bit. Μην ξεχνάμε ότι εκτός από την αρκετά ανεκτική x86 αρχιτεκτονική υπάρχουν και αρχιτεκτονικές που μπορεί δείκτες διαφόρων τύπων να έχουν διαφορετικό μέγεθος ή ποιος ξέρει τι.

 

 

Πολύ ωραίος! Είχα διαβάσει πιο παλιά αυτή την απάντηση. Η δική σου μου άρεσε περισσότερο!

 

Και φαντάζομαι όλα αυτά κρύβονται πίσω απ'το γεγονός που αναφέρει ο Linden στο βιβλίο Expert C programming:

 

Early Experiences with C

The type system was added primarily to help the compiler-writer distinguish floats, doubles, and

characters from words on the new PDP-11 hardware. This contrasts with languages like Pascal, where
the purpose of the type system is to protect the programmer by restricting the valid operations on a
data item. With its different philosophy, C rejects strong typing and permits the programmer to make
assignments between objects of different types if desired. The type system was almost an afterthought,
never rigorously evaluated or extensively tested for usability. To this day, many C programmers
believe that "strong typing" just means pounding extra hard on the keyboard.
Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

...

Με λίγα λόγια, η έλλειψη του cast στην malloc σε βοηθάει στην ελάχιστα πιθανή περίπτωση να ξεχάσεις να εισάγεις το stdlib.h. Γενικά όμως τα cast μπορούν να δημιουργήσουν τέτοια προβλήματα και είχαν δημιουργήσει πολλά προβλήματα στις αρχές του porting κώδικα σε 64bit. Μην ξεχνάμε ότι εκτός από την αρκετά ανεκτική x86 αρχιτεκτονική υπάρχουν και αρχιτεκτονικές που μπορεί δείκτες διαφόρων τύπων να έχουν διαφορετικό μέγεθος ή ποιος ξέρει τι.

 

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

 

Επίσης σε αντίθεση με το '\0' που είναι πιο επεξηγηματικό από το 0 και το "" αλλά παραμένει μικρό οπτικά, το casting στα malloc()/calloc() βρίσκω ότι σε προσωπικό επίπεδο μου χαλάει πολύ και το readability. Για μένα εδώ κολλάει το "πονάνε τα μάτια μου"... ειδικά σε κώδικες που κάνουν απανωτά allocations διαφορετικών τύπων, πάει το littering σύννεφο.

 

Αν είσαι σε C++ δεν μπορείς να κάνεις και διαφορετικά όμως.

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Επίσης σε αντίθεση με το '\0' που είναι πιο επεξηγηματικό από το 0 και το "" αλλά παραμένει μικρό οπτικά, το casting στα malloc()/calloc() βρίσκω ότι σε προσωπικό επίπεδο μου χαλάει πολύ και το readability. Για μένα εδώ κολλάει το "πονάνε τα μάτια μου"... ειδικά σε κώδικες που κάνουν απανωτά allocations διαφορετικών τύπων, πάει το littering σύννεφο.

 

 

Έχεις δίκαιο. Οι malloc/calloc είναι οι μόνες συναρτήσεις στη C που μαθαίνουμε να τις "κακομαθαίνουμε". Είναι άδικο για τις άλλες συναρτήσεις. :P Παρεπιπτόντως, δε ξέρω τι γίνεται σε άλλες σχολές, αλλά στη σχολή που πήγαινα, στον Προγραμματισμό 1 μας έκαναν να συνηθίσουμε με typecasting της επιστρεφόμενης τιμής της malloc. Υπάρχει κάποιος διδακτικός στόχος πίσω από αυτό; Σίγουρα πρέπει να υπάρχει γιατί δεν πιστεύω να μην ήταν ενημερωμένοι. Φαντάζομαι ότι ήθελαν να καταλάβουμε πως περίπου δουλεύει το typecasting.

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Μπορεί να μην ήταν ενημερωμένοι! Υπάρχει μεγάλη διαφορά στο <<Ξέρω τέλεια την C>> και στο <<Έχω πραγματικές γνώσεις με εφαρμογές>>. Οι φοιτητές τις σχολής σου ως επί το πλείστον είναι χρήστες Windows σε I32/64...  Είναι σαν να απαιτούμε τέλεια ορθογραφεία από έναν ποιητή :-D . Πάντως αν ενδιαφέρεστε για ένα καλό type system, νομίζω πρέπει να δείτε και την γλώσσα Haskell, όπου χρησιμοποιείται το Hindley-Milner type system. Ένα καλό αυτού του συστήματος είναι η ικανότητα του να βρίσκει τον γενικό τύπο μιας έκφρασης χωρίς βοήθεια από τον προγραμματιστή. Η γλώσσα είναι δύσκολη και εγώ είμαι αρχάριος αλλά αν έχετε κάποια σχετική απορία, πείτε το μου και θα προσπαθίσω να σας βοηθήσω!

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

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

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

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

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

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

Σύνδεση

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

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