Δείκτες **

Συζητήσεις για την γλώσσα C και C++

Συντονιστές: Super-Moderators, WebDev Moderators

Απάντηση
Programmer
Δημοσιεύσεις: 67
Εγγραφή: 22 Σεπ 2007 06:55

Δείκτες **

Δημοσίευση από Programmer » 02 Δεκ 2007 23:38

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

Ερώτηση:
α) που είναι τα λάθη στον παρακάτω κώδικα (δικαιολογήστε γιατί είναι λάθος αν είναι λάθος κάτι).
int matrix[5][5], **pp;
...
pp = matrix;
pp[4][3] = 100;

β)
Που βρίσκεται το λάθος στην παρακάτω συνάρτηση (αν υπάρχει λάθος) και πως μπορούμε να το αποφύγουμε;
void function(int **pp)
{
int i, j;
for(i=0; i<10; i++)
for(j=0; j<5; j++)
pp[j] = i*5 + j + 1;
}

Απαντήστε αν ξέρετε, ή έστω προβληματιστείτε...

Άβαταρ μέλους
MannyCalavera
Δημοσιεύσεις: 13
Εγγραφή: 11 Δεκ 2007 23:00
Επικοινωνία:

Δείκτες **

Δημοσίευση από MannyCalavera » 12 Δεκ 2007 01:26

Ενδιαφέροντα ερωτήματα.

Α) Στο πρώτο ερώτημα κάνεις την ανάθεση pp=matrix, που είναι σφάλμα καθώς το pp είναι δείκτης-σε-δείκτη-σε-int, ενώ το matrix είναι δείκτης-σε-int, άρα έχουμε διαφορετικούς τύπους και η ανάθεση δεν είναι επιτρεπτή.

Β) Το δεύτερο ερώτημα είναι πιο tricky.
Υποθέτωντας πως αυτό που θες να κάνεις (το λογικό) είναι:

Κώδικας: Επιλογή όλων

int array&#91;10&#93;&#91;5&#93;;
function&#40;&array&#41;;
τότε το αποτέλεσμα δε θα είναι το αναμενόμενο αφού η γραμμή:
pp[j]=100;
δεν κάνει το ίδιο με το επιθυμητό array[j]=100;

Για να έχουμε το σωστό αποτέλεσμα θα πρέπει να γράψουμε:

Κώδικας: Επιλογή όλων

&#40;*pp&#41;&#91;i&#93;&#91;j&#93; = 100;
Θεωρητικά, ο αρχικός κώδικας δεν είναι λάθος και ο compiler δε θα βγάλει μήνυμα σφάλματος. Όταν τρέξουμε όμως το πρόγραμμα το πιθανότερο είναι να πάρουμε ένα μεγαλοπρεπές Access Violation.


Βέβαια δεν έκανα compile τον κώδικα και μπορεί να κάνω και λάθος. Έχω σκουριάσει λίγο στη C :)
The living still give me the creeps

Programmer
Δημοσιεύσεις: 67
Εγγραφή: 22 Σεπ 2007 06:55

Δείκτες **

Δημοσίευση από Programmer » 12 Δεκ 2007 17:19

MannyCalavera έγραψε:Ενδιαφέροντα ερωτήματα.

Α) Στο πρώτο ερώτημα κάνεις την ανάθεση pp=matrix, που είναι σφάλμα καθώς το pp είναι δείκτης-σε-δείκτη-σε-int, ενώ το matrix είναι δείκτης-σε-int, άρα έχουμε διαφορετικούς τύπους και η ανάθεση δεν είναι επιτρεπτή.

Β) Το δεύτερο ερώτημα είναι πιο tricky.
Υποθέτωντας πως αυτό που θες να κάνεις (το λογικό) είναι:

Κώδικας: Επιλογή όλων

int array&#91;10&#93;&#91;5&#93;;
function&#40;&array&#41;;
τότε το αποτέλεσμα δε θα είναι το αναμενόμενο αφού η γραμμή:
pp[j]=100;
δεν κάνει το ίδιο με το επιθυμητό array[j]=100;

Για να έχουμε το σωστό αποτέλεσμα θα πρέπει να γράψουμε:

Κώδικας: Επιλογή όλων

&#40;*pp&#41;&#91;i&#93;&#91;j&#93; = 100;
Θεωρητικά, ο αρχικός κώδικας δεν είναι λάθος και ο compiler δε θα βγάλει μήνυμα σφάλματος. Όταν τρέξουμε όμως το πρόγραμμα το πιθανότερο είναι να πάρουμε ένα μεγαλοπρεπές Access Violation.


Βέβαια δεν έκανα compile τον κώδικα και μπορεί να κάνω και λάθος. Έχω σκουριάσει λίγο στη C :)
Μπράβο! πολύ καλή απάντηση!

Σε γενικές γραμμές όλοι έχουν ή είχαν (ή έχουν) πρόβλημα με τους δέικτες, τόσο με την έννοια τους και κυρίως με το συντακτικό τους...

Η έννοια δέικτης σημαίνει αντικείμενο τύπου διέυθυνσης μνήμης σε κάτι. Το τί θα έιναι αυτό το κάτι? ένα άλλο αντικείμενο βασικού ή σύνθετου τύπου φυσικά... Ο δείκτης είναι τυπικά απρόσημος ακέραιος, αλλά λογικά δεν έχει καμία σχέση με ακέραιο... Ο δέικτης αποθηκεύει διευθύνσεις του υπολογιστή μας (προφανώς κάποια δεδομένα θα περιέχουν οι διευθυνσεις αλλά δεν έιναι υποχρεωτικό). Ο ακέραιος αποθηκέυει τιμές δεδομένων του προγράμματος...

Το int **pp; διαβάζεται σαν δείκτης... Δείκτης σε τί? δείκτης σε δείκτη int * (*pp)... Ο τύπος του δέιτη χρειάζεται για την αποαναφορά.... Αυτή η σύνταξη είναι ένα από τα προβλήματα της C σε σχέση με τους δέικτες... Μια και πιο διαφωτιστική σύνταξη θα μπορούσε να είναι το παρακάτω

pointer name to type;
όπου pointer θα ήταν ξεχωριστός τύπος δείκτη, το name το όνομα του δέικτη, και το to type ο τύπος που θα περιείχε η διεύθυνση που θα περιείχε με την σειρά του ο δέικτης, για να ξέρει ο μεταγλωττιστής τι να κάνει στην αποαναφορά για να παρουσιάσει την τιμή (γιατί δεν έχουν όλοι οι τύποι το ίδιο μέγεθος και κωδικοποίηση)...

ίσως με το ισχύον συντακτικό το πιο ξεκάθαρο ενοιολογικά έιναι να φτιάχνουμε μόνο δέικτες void*., και να φροντιζουμε μόνοι μας για την αναφορά και την αποαναφορά.
πχ.

void *ptr; /* Δείκτης που θα αποθηκέυει διευθύνσεις για τις οποίες ο μεταγλωττιστής δεν μπορεί να ξέρει τον τύπο δεδομένων που περιέχουν /*

int a = 10;
float b = 10.1F;

ptr = (void*) &a;
printf( "%d \n", *((int*) ptr) );

ptr = (void*) &b;
printf( "%f \n", *((float*) ptr) );

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

(οι δείκτες συνατακτικά είναι το ατύχημα της C...)

Για την συνάρτηση λίγα τώρα
το
int array[10][5];
function(&array);

είναι λάθος για την C++, και σωστά. (στην c που έχει χαλαρότερο έλεγχο τύπων κάποια που τα βγάζει λάθος η C++, μπορέι να περνάνε)
Το
int **pp και το int array[10][5] καμία λογική σχέση δεν έχουν σαν τύποι! Το ένα είναι δέικτης σε δέικτη ακεραίων και το άλλο πίνακας ακεραίων 10Χ5. Λογική σχεση δέν εχουν τελικά ούτε το int arrayΑ[10][5] με το int arrayΒ[2][5] που είναι λογικά διαφορετικοί παράγωγοι τύποι δεδομένων με την ομοιότητα στο ότι αποθηκέυουν μια λίστα ακεραίων αλλά κα την ουσιαστική διαφορά ότι αυτή η λίστα δεν έχει το ίδιο μέγεθος...

όταν γράφουμε
void func(int **pp)
{
...
pp[4][6] = 100;
...
}

τι λέμε στον μεταγλωτιστή? πάρε τον δέικτη σε δείκτη και μετακινήσου από την διεύθυνση αυτή 4*άγνωστο_μέγεθος μήκη σε bytes ακεραίων και μετατόπισε την διεύθυνση επιπλέον 6 μήκη ακεραίων και αποθήκευσε στην διεύθυνση την τιμή 100... Και πως θα βρεί ο μεταγλωττιστής που θα πάει αφού δεν ξέρει το μέγεθος της τελευταίας διάστασης του πίνακα? Δεν θα το βρει...

ενώ στην περίπτωση του

int array[5][10];

array[4][6] = 100;

ξέρει τι θα κάνει... έχει το μέγεθος των διαστάσεων κατα την φάση της μεταγλώττισης.

θα πάρει την διέυθυνση αρχής του πίνακα το array θα πολλαπλάσιάσει τις γραμμές με τον αριθμό στηλών που ξέρει από την δήλωση του πίνακα 4*6 και θα προσθέσει τον αριθμό στηλών για να βρει πόσα στοιχέια απο την αρχή του πίνακα απέχει το σημειο που δείχνει ο προγραμματιστής στον κώδικα.

μετά θα προσθέσει στην διεύθυση αρχής πίνακα την μετατόπιση σε bytes που είναι ίση με αριθμός_στοιχέιων επί μέγεθος_στοιχέιου, και θα καταχωρήσει σε αυτήν την διέυθυνση την τιμή...
δηλαδή θα κάνει το
*((int*)array + 4*10+6) = 100; σε στοιχέια πίνακα,
ή αν θέλετε το
*(int*)( ((char*)array) + (4*10+6)* sizeof(int)) = 100; σε bytes

και τα δύο ισοδύναμα με το array[4][6] = 100;

Στην λογική τους είναι πολύ απλά αυτά αλλά δυστυχώς όταν μπουν στην μέση οι δέικτες η σύνταξη μπερδεύει και ο αρχάριος πνίγεται...

Σημ. το συντακτικά για την C++ σωστό αλλά όχι και λογικά ορθό θα ήταν να γράψεις τα

void func(int **pp) ...
main...
...
int *p, array[1][5];

p = & array[0][0];

func( &p );
...

Το λογικά ορθό να προσπελάσεις και να τροποιήσεις δηλαδή σοιχεία δισδιάστατου πίνακα θα μπορούσε να ήταν

void other_func(int *p, int rows, int columns)
{
int i,j;

for (i = 0; i<rows; i++)
for (j = 0; j<columns; j++)
p[i*columns + j] = τιμή
....
}


main...

int array[10][5];
...
other_func(&array[0][0], 10, 5);
...

αυτά για να ξεμπερδεύονται οι αρχάριοι που ενδιαφέρονται για την γλώσσα, γιατί τα πολλά βιβλία δεν τα εξηγούν καλά, και κάποιοι καθηγητές φαίνεται να μην μπορούν να τα εξηγήσουν :wink: ...

Υ.Γ πλάκα - πλάκα μου έφαγε τρία τέτατρα να γράψω αυτό το ποστ... Α!! δεν το ξανακάνω. Ας καθήσουν οι αρχάριοι να διαβάσουν... εμείς πως μάθαμε δηλαδή? μας τα εξήγησε μήπως κανείς?

Άβαταρ μέλους
MannyCalavera
Δημοσιεύσεις: 13
Εγγραφή: 11 Δεκ 2007 23:00
Επικοινωνία:

Δείκτες **

Δημοσίευση από MannyCalavera » 12 Δεκ 2007 18:36

Γενικά η σύνταξη των δεικτών είναι ένα από τα μεγαλύτερα (κατ' εμέ) προβλήματα στη C.

Από τη μία σου δίνουν τη δυνατότητα για low level πρoγραμματισμό και τη δυνατότητα να κάνεις "κόλπα" που βελτιώνουν την απόδοση (ταχύτητα συνήθως) ενός προγράμματος.

Από την άλλη υπονομεύουν τον λεγόμενο strongly-typed προγραμματισμό, μια έννοια πολύ διαδεμομένη στις σύγχρονες γλώσσες προγραμματισμού (όπως η Java και η C++) και πολύ χρήσιμη, αφού πρακτικά εξαλείφει προβλήματα όπως τα προηγούμενα (το κουλουβάχατο που προκύπτει από τη μετατροπή ενός τύπου σε άλλο).

Εγώ είμαι υπέρ του strongly-typed προγραματισμού και θεωρώ κακή πρακτική τα προηγούμενα παραδείγματα. Ένας πίνακας είναι ένας πίνακας, και δεν πρέπει να μετατρέπεται σε pointer, όπως και ένας ακέραιος (int) είναι ένας ακέραιος, και δεν πρέπει να μετατρέπετααι σε δεκαδικό (float).

Τώρα θα μου πεις "ωραία όλα αυτά, αλλά γιατί η δημοφιλέστερη γλώσσα προγραμματισμού, η C++, αφήνει τέτοιες 'ταρζανιές' να περάσουν;".
Αυτό συμβάινει διότι υπάρχουν περιπτώσεις (λίγες, αλλά πολύ χρήσιμες) όπου τέτοιου είδους τρελές μετατροπές φτιάχνουν πολύ γρήγορα προγράμματα, λόγω της φύσης του hardware (της λειτουργίας του).

Ένα παράδειγμα είναι ο ακόλουθος κώδικας:

Κώδικας: Επιλογή όλων

float InvSqrt&#40;float x&#41;
&#123;
float xhalf = 0.5f*x;
int i = *&#40;int*&#41;&x; // get bits for floating value
i = 0x5f3759df - &#40;i>>1&#41;; // gives initial guess y0
x = *&#40;float*&#41;&i; // convert bits back to float
x = x*&#40;1.5f-xhalf*x*x&#41;; // Newton step, repeating increases accuracy
return x;
&#125;
ο οποίος εμφανίζεται στον κώδικα του Quake III και υπολογίζει το αντίστροφο της τετραγωνικής ΄ριζας ενός αριθμού.
Είναι από τους καλύτερους κώδικες που έχω δει στη ζωή μου, προϊόν ιδιοφυίας, αλλά κάνει κόλπα μετατροπής από float σε int, που ναι μεν δε θα πρεπε να είναι επιτρεπτοί, αλλά από την άλλη είναι πολλές φορές ταχύτερος από το να χρησιμοποιήσεις το απλό 1/sqrt(x).

Δεσμεύομαι κάποια στιγμή να γράψω ένα άρθρο για τον παρόντα κώδικα στα ελληνικά.
Προσπάθησε να καταλάβεις τι κάνει, πίστεψέ με δεν είναι εύκολο. Επίσης ψάξε στο google τον αριθμό 0x5f3759df για να μάθεις περισσότερες λεπτομέρειες.
The living still give me the creeps

Άβαταρ μέλους
vspartan
Δημοσιεύσεις: 57
Εγγραφή: 03 Δεκ 2007 03:18

Δείκτες **

Δημοσίευση από vspartan » 05 Φεβ 2008 20:03

Παιδιά υπάρχει κανένα βοήθημα για c και για λίγο δείκτες?Στα ελληνικά?
Ευχαριστω.
Οι Θερμοπύλες απέδειξαν ότι υπάρχουν πολλοί άνθρωποι,αλλά ολίγοι άνδρες.

Programmer
Δημοσιεύσεις: 67
Εγγραφή: 22 Σεπ 2007 06:55

Δείκτες **

Δημοσίευση από Programmer » 10 Φεβ 2008 08:49

vspartan έγραψε:Παιδιά υπάρχει κανένα βοήθημα για c και για λίγο δείκτες?Στα ελληνικά?
Ευχαριστω.
βιβλία μα κυρίως εξάσκηση. Βιβλίο μόνο για δέικτες δεν νομίζω να υπάρχει. Άρθρα πολλά στα αγγλικά. Στα ελληνικά δεν ξέρω. Αν ασχολείσαι με τον προγραμματισμό επαρκης γνώση των αγγλικών είνια απαραίτητη...
(είχα ξεκινήσει να γράφω εγώ ένα άρθρο, αλλα έιναι ημιτελές για δημοσίευση. Αν το τελέιώσω θα το δημοσιεύσω...)

Απάντηση

Επιστροφή στο “C, C++”

Μέλη σε σύνδεση

Μέλη σε αυτήν τη Δ. Συζήτηση: Δεν υπάρχουν εγγεγραμμένα μέλη και 0 επισκέπτες