-
Ο χρήστης kyan έγραψε:
Μπα, assembly γράφαμε στα νιάτα μας. Τώρα που γίναμε άντρες γράφουμε C (σκέτη)
Σε unix, σίγουρα!
...Είμαι καλός στην COBOL (είμουν ). Υπάρχει κάτι πιο άχρηστο πλέον???
-
Ο χρήστης kyan έγραψε:
Από τότε που άρχισα να δουλεύω σε ομάδες έχουν πάψει να με συγκινούν κώδικες που παθαίνεις πονοκέφαλο για να καταλάβεις τι κάνουν. Αντίθετα συμπαθώ εκείνουν που μπορεί να τους συντηρήσει και ένας junior. Το ότι μια γλώσσα σου δίνει τη δυνατότητα να μειώσεις τον αριθμό γραμμών που γράφεις χρησιμοποιώντας συντακτικά κολπάκια όπως αλυσιδωτά ternary ifs kai ++/-- δεν την κάνει πιο γρήγορη αλλά μονάχα λιγότερο αναγνώσιμη και συντηρήσιμη. Και μην είσαι τόσο σίγουρος ότι ο αντίστοιχος κώδικας σε Object Pascal δεν μπορεί να βγάλει εξ'ίσου γρήγορο κώδικα μηχανής.Και όμως φίλε kyan, ο κώδικας:
register int i=0; while ( i++ < 10 );
είναι κατά πολύ ταχύτερος, του ισοδύναμου σε λειτουργικότητα:
int i; i=0; while ( i < 10 ) i = i+1;
Βασικά είναι η μέρα με τη νύχτα (σε computational time), όταν χρησιμοποιείς C.
Μπα, assembly γράφαμε στα νιάτα μας. Τώρα που γίναμε άντρες γράφουμε C (σκέτη)
Σε unix, σίγουρα!
Που τέτοια τύχη. VAX assembly σε VM (αν θυμάμαι καλά... έχουν περάσει και κοντά 20 χρόνια από τότε ).
Είναι όντως ασαφής σε αυτά, αλλά υπάρχει συγκεκριμένος λόγος: για να δίνει flexibility στον προγραμματιστή. Η συγκεκριμένη δήλωση είναι πολύ χρήσιμη όταν θέλεις να χρησιμοποιήσεις arrays μεταβλητού μεγέθους σε πραγματικό χρόνο.
Δεν με πείθει αυτό. Δεν μπορώ να φανταστώ πότε μία μεταβλητή μπορεί να είναι είτε pointer είτε array στον ίδιο κώδικα. Αν έβλεπα τέτοιο κώδικα από υφιστάμενό μου θα απαιτούσα να τον αλλάξει πάραυτα.
Εγώ αντίθετα θα τον επικροτούσα! Ίσως όμως του ζητούσα να προσθέσει λίγα comments
Αν θέλεις μεταβλητά arrays θα πρέπει να σου δίνει η γλώσσα τρόπο να τα δηλώνεις και να τα διαχειρίζεσαι εύκολα χωρίς runtime overhead. Η object pascal έχει τα dynamic arrays (στην ουσία είναι pointers σε arrays) τα οποία συντακτικά διαφέρουν από τους pointers ώστε ο compiler να κάνει κάποια sanity checks ενώ στο runtime είναι στην ουσία το ίδιο με έναν τύπο ** της C.
Type casting δηλαδή! Τα έχει και η C αυτά
Λάβε επίσης υπόψη σου ότι στη C, ένα array indexing: ( π.χ. array[i] ) είναι πιο αργό από το αντίστοιχο pointers arithmetic: array += i;ΥΓ. Ο κώδικας του προηγούμενου ποστ, αντιγράφει τάχιστα το string s2 στο string s1, κι επιστρέφει το s1 τερματισμένο με τον χαρακτήρα 0 (nul-terminating string). Δεν ελέγχει όμως αν το s1 ήταν nul-terminated όταν περάστηκε στη συνάρτηση.
-
Ο χρήστης maxmara έγραψε:
Μπα, assembly γράφαμε στα νιάτα μας. Τώρα που γίναμε άντρες γράφουμε C (σκέτη)
Σε unix, σίγουρα!
...Είμαι καλός στην COBOL (είμουν ). Υπάρχει κάτι πιο άχρηστο πλέον???
Πω πω, που τη θυμήθηκες αυτή;
-
Ο χρήστης migf1 έγραψε:
Και όμως φίλε kyan, ο κώδικας:register int i=0;
while ( i++ < 10 );>είναι κατά **πολύ ταχύτερος**, του ισοδύναμου σε λειτουργικότητα: >``` >int i; >i=0; >while ( i < 10 ) > i = i+1; >
Βασικά είναι η μέρα με τη νύχτα (σε computational time), όταν χρησιμοποιείς C.
Μπορεί, αλλά την 'τεράστια' διαφορά θσ τη δεις μονάχα αν γράφεις compiler, οπότε θα έχεις και αντίστοιχου επιπέδου συνεργάτες. Αν όμως γράφεις εμπορική εφαρμογή τα πράγματα είναι αλλιώς.
BTW, ο optimizer της Delphi κάνει παπάδες με κάτι τέτοια code fragments.
Type casting δηλαδή! Τα έχει και η C αυτά
Όχι. Άλλη σύνταξη:
type PChar = ^Char; PPChar = ^PChar; type PCharArray = array of PChar;
στην πρώτη περίπτωση δεν μπορείς να γράψεις p^[i] όπως στη δεύτερη αλλά μόνο p^^.
Λάβε επίσης υπόψη σου ότι στη C, ένα array indexing: ( π.χ. array[i] ) είναι πιο αργό από το αντίστοιχο pointers arithmetic: array += i;
Το ξέρω. Όμοια και στη Delphi: Inc(P, i); όπου P: PChar; (για σένα char*)
ΥΓ. Ο κώδικας του προηγούμενου ποστ, αντιγράφει τάχιστα το String s1 στο string s2, κι επιστρέφει το s1 τερματισμένο με τον χαρακτήρα 0 (nul-terminating string).
Η Delphi όμως έχει το string (και WideString) datatype με αυτόματο memory management (με κρυφό reference-counting), copy-on-write λογική και υλοποιημένες όλες τις βασικές πράξεις. Ζηλεύεις λιγάκι;
-
Ο χρήστης kyan έγραψε:
Μπορεί, αλλά την 'τεράστια' διαφορά θσ τη δεις μονάχα αν γράφεις compiler, οπότε θα έχεις και αντίστοιχου επιπέδου συνεργάτες. Αν όμως γράφεις εμπορική εφαρμογή τα πράγματα είναι αλλιώς.Χε χε... δεν είναι ακριβώς έτσι! Διότι, αν π.χ. ο πελάτης σου τρέχει την εμπορική εφραμογή στο απαραχαιωμένο του PC (π.χ. Pentium), οι διαφορές σε computational time μεγαλώνουν εκθετικά.
BTW, ο optimizer της Delphi κάνει παπάδες με κάτι τέτοια code fragments.
BTW, σε τι γλώσσα είναι γραμμένη η Delphi;
Type casting δηλαδή! Τα έχει και η C αυτά
Όχι. Άλλη σύνταξη:
type
PChar = ^Char;
PPChar = ^PChar;type
PCharArray = array of PChar;> >στην πρώτη περίπτωση δεν μπορείς να γράψεις p^[i] όπως στη δεύτερη αλλά μόνο p^^. Με μπέρδεψες! To C equivalent είναi το παρακάτω;
typedef char* PChar;
typedef PChar* PPChar; /* or typedef char** PPChar; /
typedef PChar PCharArray; /* or typedef char** PCharArray; */Τι εννοείς 1η και 2η περίπτωση (ειλικρινά με μπέρδεψες)! >>Λάβε επίσης υπόψη σου ότι στη C, ένα array indexing: ( π.χ. array[i] ) είναι πιο αργό από το αντίστοιχο pointers arithmetic: array += i; > > >Το ξέρω. Όμοια και στη Delphi: Inc(P, i); όπου P: PChar; (για σένα char*) Άρα, το readability έρχεται σε 2η μοίρα... έτσι δεν είναι; >>ΥΓ. Ο κώδικας του προηγούμενου ποστ, αντιγράφει **τάχιστα** το String s1 στο string s2, κι επιστρέφει το s1 τερματισμένο με τον χαρακτήρα 0 (nul-terminating string). > > >Η Delphi όμως έχει το string (και WideString) datatype με αυτόματο memory management (με κρυφό reference-counting), copy-on-write λογική και υλοποιημένες όλες τις βασικές πράξεις. Ζηλεύεις λιγάκι; ;) Κάντο μου λίγο πιο λιανά. Τι εστί copy-on-write λογική; Το αυτόματο memory management πάντως εμένα με παραπέμπει σε αχρείαστο overhead (όπως συμβαίνει συνήθως με οτιδήποτε 'αυτόματο'). Επίσης, τι εστί κρυφό reference-counting;
-
Αν και δεν έχω καταλάβει τι εννοείς με τα PPChar, ρίξε μια ματιά εδώ:
typedef char* String; typedef char** ArrayOfStrings; int main( void ) { String s1 = malloc( 21 ); String s2 = malloc( 21 ); ArrayOfStrings sarray = malloc( 10 * sizeof( String) ); strcpy( s1, 'string1'); strcpy( s2, 'string2'); sarray[0] = s1; sarray[1] = s2; puts( sarray[0] ); puts( sarray[1] ); free(s1); free(s2); free( sarray ); }
-
Ο χρήστης kyan έγραψε:
type
PChar = ^Char;
PPChar = ^PChar;type
PCharArray = array of PChar;> >στην πρώτη περίπτωση δεν μπορείς να γράψεις p^[i] όπως στη δεύτερη αλλά μόνο p^^. Ok, τώρα κατάλαβα τι εννοείς (sorry... όλη μέρα στη δουλειά και μετά, coding ). Εννοείς πως σε προστατεύει από buffer overruns. Δεκτόν! Αυτή είναι η μεγάλη 'πληγή της C (την οποία όμως της την συγχωρούμε, λόγω των άλλων πλεονεκτηματων της ). Η C σίγουρα δεν είναι η καταλληλότερη γλώσσα για να ξεκινήσει κανε΄ςι προγραμματισμό. Αν όμως ξεκινήσει από C, μετά όλες οι υπόλοιπες θα του φαίνονται παιχνιδάκι!
-
Ο χρήστης Raikkonen έγραψε:
Εγώ δεν 'γράφω' ακόμα. Φοιτητής είμαι. Σε όποια γλώσσα μου λένε προγραμματίζω!
LISP κάνετε; Εκεί να δεις γέλιο... χάνει η μάνα το παιδί και το παιδί τη μάνα!
Η αποθέωση του recursion... -
Μιας και μιλάμε για γλώσσες γενικότερα, διαβάστε κι αυτό...
C - You shoot yourself in the foot.
C++ - You accidentally create a dozen instances of yourself and shoot them all in the foot. Providing emergency medical care is impossible since you can't tell which are bitwise copies and which are just pointing at others and saying, 'That's me over there.'
Assembler - You try to shoot yourself in the foot, only to discover you must first invent the gun, the bullet, the trigger, and your foot.
Forth - Foot in yourself shoot.
Pascal - The compiler won't let you shoot yourself in the foot.
**Ada **- After correctly packing your foot, you attempt to concurrently load the gun, pull the trigger, scream, and shoot yourself in the foot. When you try, however, you discover you can't because your foot is of the wrong type.
Java - The gun fires just fine, but your foot can't figure out what the bullets are and ignores them.
Modula2 - After realizing that you can't actually accomplish anything in this language, you shoot yourself in the head.
-
Για τους πραγματικά φανατικούς, αφήστε τα εύκολα και δοκιμάστε την InterCal:
http://www.catb.org/~esr/intercal/Πάντως θεωρώ ότι high-level προγραμματισμός σε C είναι μαζοχισμός...
-
Ο χρήστης migf1 έγραψε:
BTW, σε τι γλώσσα είναι γραμμένη η Delphi;Απ'ό,τι ξέρω σε ...Delphi.
Κάντο μου λίγο πιο λιανά. Τι εστί copy-on-write λογική; Το αυτόματο memory management πάντως εμένα με παραπέμπει σε αχρείαστο overhead (όπως συμβαίνει συνήθως με οτιδήποτε 'αυτόματο'). Επίσης, τι εστί κρυφό reference-counting;
Λοιπόν, κατ'αρχήν το internal representation ενός pascal string είναι ένας pointer που δείχνει σε ένα null-terminated char* η διαφορά είναι ότι πριν τη θέση s[0] (για την ακρίβεια τα pascal strings είναι 1-based για λόγους legacy αλλά θα στα γράφω σε 0-based για να μη χαθούμε) υπάρχει ένα header με ένα DWORD το οποίο είναι το recerence counter του string. Δηλαδή:
bytes 0 1 2 3 4 5 6 7 ... s[0] s[1] s[2] s[3] ... s[strlen(s) - 1] 0 ^ | | s
Εκτός κι αν το string είναι κενό οπότε η τιμή του pointer s είναι 'nil' (0 δηλαδή).
Το DWORD μας δείχνει πόσοι live pointers υπάρχουν που αναφέρονται στο string. Έτσι το assignment:
s1 := s2;
είναι ένα pointer assignment και μια αύξηση κατά 1 του Ref Count. Αν τώρα γίνει μια πράξη που αλλοιώνει το περιεχόμενο ενός string, π.χ.
s[2] := 'x';
υπάρχουν δυό περιπτώσεις: Αν το Ref Count του s είναι 1 η πράξη γίνεται inline, κατευθείαν στο buffer του s. Αν όμως το Ref Count του s είναι > 1, που σημαίνει ότι υπάρχουν και άλλες μεταβλητές που δείχνουν στον ίδιο buffer τότε ο buffer αντιγράφεται, η τιμή του pointer s αλλάζει ώστε να δείχνει τον καινούργιο buffer (ο οποίος έχει Ref Count = 1) και η πράξη γίνεται στο νέο buffer. Με αυτό τον τρόπο ο buffer αντιγράφεται μονάχα όταν χρειάζεται.
Τέλος, όταν μια μεταβλητή τύπου string βγει out of scope ο compiler κάνει emit κώδικα που μειώνει το Ref Count του buffer και αν αυτός φτάσει στο 0 ο buffer απελευθερώνεται.
Με αυτή τη δυνατότητα μπορείς να χρησιμοποιείς ένα string σαν οποιοδήποτε simple data-type όπως ένα Integer χωρίς να χρειάζεται να το κάνεις allocate, copy κτλ.
Κάτι αντίστοιχο κάνει η Delphi για τα interfaces. Όταν κάνεις copy ένα interface variable ο compiler κάνει emit κώδικα που κάνει _Addref το object στο οποίο δείχνει το interface variable. Όταν ένα interface variable βγαίνει out of scope ο compiler κάνει αντίστοιχα emit κώδικα που κάνει _Release. Έτσι ακόμα και άπειροι προγραμματιστές μπορούν να χειριστούν εύκολα δύο από τα πιο error-prone data types: strings και interface pointers.
Και για να σε προλάβω, αν δεν θέλεις το overhead της διαχείρησης του string μπορείς πάντα να χρησιμοποιήσεις τον τύπο PChar (char*) που δεν έχει κανένα overhead. Πρέπει βέβαια να τον διαχειριστείς μόνος σου αν και η Delphi σου δίνει τις βασικές functions για να το κάνεις. Επίσης ξέρω ότι μπορείς στην C++ να αναπαράγεις αυτές τις δυνατότητες φτιάχνοντας 'smart pointers' αλλά τότε το compilation-time πάει κατά διαόλου.
-
Ο χρήστης kyan έγραψε:
BTW, σε τι γλώσσα είναι γραμμένη η Delphi;
Απ'ό,τι ξέρω σε ...Delphi.
Πως γίνεται αυτό; (ούτε καν με... recursion )
[snip]Με αυτή τη δυνατότητα μπορείς να χρησιμοποιείς ένα string σαν οποιοδήποτε simple data-type όπως ένα Integer χωρίς να χρειάζεται να το κάνεις allocate, copy κτλ.
Ομολογώ πως βρήκα την ανάλυση πολύ ενδιαφέρουσα, και σε ευχαριστώ για το χρόνο που διέθεσες φίλε kyan (πως είναι το όνομά σου; -αν θες το λες).
Δεν έχω καταλάβει όμως ακόμα πως μπορείς να συνδυάσεις δήλωση array μεταβλητού μεγέθους, το οποίο να περιέχει strings επίσης μεταβλητού μεγέθους, εκμεταλλευόμενος ταυτόχρονα την προστασία που σου παρέχει ο compiler της γλώσσας.Μπορείς να μου δώσεις ένα μικρό παράδειγμα κώδικα, ο οποίος να δηλώνει έναν πίνακα μεταβλητού μεγέθους (π.χ. sa), ο οποίος πίνακας να περιέχει strings επίσης μεταβλητού μεγέθους.
Κατόπιν να ορίζει δυναμικά το μέγεθος του πίνακα (sa) σε 1 string, μεγέθους 5 ορατών χαρακτήρων. Να γεμίζει μέχρι τέρμα το sa[0], κατόπιν να του ορίζει δυναμικά διαφορετικό μέγεθος, ίσο με 10 ορατούς χαρακτήρες, και να το γεμίζει ξανά. Τέλος να πρσθέτει δυναμικά δεύτερο string στον πίνακα (sa), μεγέθους 5 χαρακτήρων και να το γεμίζει.Σε C, το παραπάνω γίνεται ασφαλέστατα (με την προσθήκη μερικών error-checks για αποτυχία δέσμευσης μνημης) ως εξής:
int main( void ) { // δυναμικός ορισμός πίνακα, μεγέθους ενός string char **sa = (char **) calloc( 1, sizeof(char *) ); // δυναμικός ορισμός string 5 ορατών χαρακτήρων sa[0] = (char *) calloc( 5+1, sizeof(char) ); strncpy( sa[0], '12345', 5); // επαναπροσδιορισμός του string σε 10 ορατούς χαρακτήρες free( sa[0] ); sa[0] = (char *) calloc( 10+1, sizeof(char) ); strncpy( sa[0], '1234567890', 10); // επαναπροσδιορισμός του πίνακα σε 2 strings realloc( sa, 2 * sizeof(char *) ); // δυναμικός ορισμός του 2ου string σε 5 ορατούς χαρακτήρες sa[1] = (char *) calloc( 5+1, sizeof(char) ); strncpy( sa[1], 'abcde', 5); // εκκαθάριση κι έξοδος free( sa[0] ); free( s[1] ); free( sa ); exit (0); }
Από τη στιγμή που χρησιμοπoιείς calloc() που δεσμέυει & μηδενίζει μνήμη ( αντί για malloc() ) και strncpy() που ζητά μέγεθος ως τελευταία παράμετρο ( αντί για strcpy() ), μειώνεις στο ελάχιστο τις πιθανότητες για buffer overrun. Αν μάλιστα θυμάσαι να περνάς ως τελευταία παράμετρο της strncpy() μέγεθος μνήμης κατά ένα λιγότερο από όσο είχες δεσμεύσει, τότε οι πιθανότητες για buffer overrun τείνουν στο μηδεν!
[Edit]
Δηλαδή:s = (char *) calloc( 10+1, sizeof( char) ); strncpy( s, '12345678901234567890', 10); ... free( s );
Το παραπάνω ΔΕΝ ΚΑΝΕΙ ΠΟΤΕ buffer overrun. Aντιγράφει πάντα (μέχρι) 10 χαρακτήρες στο s, το οποίο s είναι ΠΑΝΤΑ nul-terminated (λόγω της calloc() )
[/Edit]Μηδενίζουν δε τελείως, αν αποφέυγεις (όταν χρειάζεται) την realloc() όταν θέλεις να επαναπροσδιορίσεις δυναμικά το μέγεθος ενός string και αντ' αυτής χρησιμοποιείς free(); calloc() (όπως έκανα στον επαναπροσδιορισμό του string sa[0], ΟΧΙ όμως και στον επαναπροσδιορισμό του array of strings sa).
Ειλικρινά θα με ενδιέφερε να δω κώδικα της γλώσσας που περιγράφεις (Object Pascal την είπες; ) που να συνδυάζει την παραπάνω τρομερή ευελιξία, συνδυασμένη με προστασία από τον compiler και χωρίς αχρείαστο overhead (μην το υποτιμάς το overhead... ξεχωρίζει τα πραγματικά καλά προγράμματα από εκείνα 'του κιλού'!)
Και για να σε προλάβω, αν δεν θέλεις το overhead της διαχείρησης του string μπορείς πάντα να χρησιμοποιήσεις τον τύπο PChar (char*) που δεν έχει κανένα overhead. Πρέπει βέβαια να τον διαχειριστείς μόνος σου αν και η Delphi σου δίνει τις βασικές functions για να το κάνεις.
Που πρακτικά σημαίνει να επιστρέψεις στη... C
-
Ο χρήστης TGD έγραψε:
Για τους πραγματικά φανατικούς, αφήστε τα εύκολα και δοκιμάστε την InterCal:
http://www.catb.org/~esr/intercal/Πάντως θεωρώ ότι high-level προγραμματισμός σε C είναι μαζοχισμός...
Η C είναι high-level language!
-
Ο χρήστης migf1 έγραψε:
Πως γίνεται αυτό; (ούτε καν με... recursion )Γίνεται. Γράφεις τον compiler σε ένα πρόγονο της γλώσσας που θέλεις να φτιάξεις (π.χ. Turbo Pascal στην περίπτωσή μας) ή σε μία άλλη γλώσσα που να είναι συντακτικά όμοια (π.χ. ADA) και φτιάχνεις ένα πρώτο draft του compiler το οποίο μετά χρησιμοποιείς για να αναπτύξεις και να κάνεις ...compile τον εαυτό του, αφού πρώτα κάνεις τις μικρές συντακτικές αλλαγές στον κώδικα που χρειάζονται. Τέλος φτιάχνεις το IDE.
Ομολογώ πως βρήκα την ανάλυση πολύ ενδιαφέρουσα, και σε ευχαριστώ για το χρόνο που διέθεσες φίλε kyan (πως είναι το όνομά σου; -αν θες το λες).
Κώστας.
Δεν έχω καταλάβει όμως ακόμα πως μπορείς να συνδυάσεις δήλωση array μεταβλητού μεγέθους, το οποίο να περιέχει strings επίσης μεταβλητού μεγέθους, εκμεταλλευόμενος ταυτόχρονα την προστασία που σου παρέχει ο compiler της γλώσσας.
type TDynStringArray = array of string;
Μπορείς να μου δώσεις ένα μικρό παράδειγμα κώδικα, ο οποίος να δηλώνει έναν πίνακα μεταβλητού μεγέθους (π.χ. sa), ο οποίος πίνακας να περιέχει strings επίσης μεταβλητού μεγέθους.
Κατόπιν να ορίζει δυναμικά το μέγεθος του πίνακα (sa) σε 1 string, μεγέθους 5 ορατών χαρακτήρων. Να γεμίζει μέχρι τέρμα το sa[0], κατόπιν να του ορίζει δυναμικά διαφορετικό μέγεθος, ίσο με 10 ορατούς χαρακτήρες, και να το γεμίζει ξανά. Τέλος να πρσθέτει δυναμικά δεύτερο string στον πίνακα (sa), μεγέθους 5 χαρακτήρων και να το γεμίζει.var sa: TDynStringArray; begin // δυναμικός ορισμός πίνακα, μεγέθους ενός string // Το δεύτερο όρισμα είναι το πλήθος των στοιχείων, όχι ο χώρος στη μνήμη που χρειάζεται. SetLength(sa, 1); // δυναμικός ορισμός string 5 ορατών χαρακτήρων sa[0] := '12345'; // επαναπροσδιορισμός του string σε 10 ορατούς χαρακτήρες sa[0] := sa[0] + '67890'; // ή sa[0] := '1234567890'; // επαναπροσδιορισμός του πίνακα σε 2 strings // το νέο string θα έχει την τιμή nil (null). SetLength(sa, Length(sa) + 1); // δυναμικός ορισμός του 2ου string σε 5 ορατούς χαρακτήρες sa[1] := 'abcde'; // Εδώ ο compiler κάνει emit cleanup code για τo dynamic array sa και τα στοιχεία του. end;
Ειλικρινά θα με ενδιέφερε να δω κώδικα της γλώσσας που περιγράφεις (Object Pascal την είπες;) που να συνδυάζει την παραπάνω τρομερή ευελιξία, συνδυασμένη με προστασία από τον compiler και χωρίς αχρείαστο overhead (μην το υποτιμάς το overhead... ξεχωρίζει τα πραγματικά καλά προγράμματα από εκείνα 'του κιλού'!)
Αν θέλεις κάτι περισσότερο από το απλοϊκό παράδειγμα να σου στείλω με email ό,τι θέλεις. Όλη η ιστορία είναι στην υλοποίηση του τελεστή +, του assignment := και κάποιων magic functions όπως η SetLength που διαχειρίζονται σωστά τους string/dynamic array pointers και buffers καλώντας το OS API όπου χρειάζεται memory manipulation.
-
Ο χρήστης kyan έγραψε:
Πως γίνεται αυτό; (ούτε καν με... recursion )
Γίνεται. Γράφεις τον compiler σε ένα πρόγονο της γλώσσας που θέλεις να φτιάξεις (π.χ. Turbo Pascal στην περίπτωσή μας) ή σε μία άλλη γλώσσα που να είναι συντακτικά όμοια (π.χ. ADA) και φτιάχνεις ένα πρώτο draft του compiler το οποίο μετά χρησιμοποιείς για να αναπτύξεις και να κάνεις ...compile τον εαυτό του, αφού πρώτα κάνεις τις μικρές συντακτικές αλλαγές στον κώδικα που χρειάζονται. Τέλος φτιάχνεις το IDE.
Ουσιαστικά σε Turbo Pascal είναι φτιαγμένο το παραπάνω παράδειγμα (ο compiler είναι η όλη ιστορία. Αν τον έχεις, φτιάχνεις μετά assemler, linker και IDE).
...
> >Αν θέλεις κάτι περισσότερο από το απλοϊκό παράδειγμα να σου στείλω με email ό,τι θέλεις. Όλη η ιστορία είναι στην υλοποίηση του τελεστή +, του assignment := και κάποιων magic functions όπως η SetLength που διαχειρίζονται σωστά τους string/dynamic array pointers και buffers καλώντας το OS API όπου χρειάζεται memory manipulation. Δεν χρειάζεται τίποτα παραπάνω. Δηλώνω **ΕΝΤΥΠΩΣΙΑΣΜΕΝΟΣ** !!! ![](http://www.myphone.gr/forum/image/smilies/shock.gif) Η SetLength() δεν μου φαίνεται και τόσο 'magic'. Θα μπορούσε (απολοποιημένα) να είναι κάπως έτσι:
void SetLength( void *memblock, unsigned n )
{
memblock = (void *) calloc( n, sizeof(void *) );return;
}'Magic' είναι η συνέχεια...
sa[0] := '12345';
Εδώ λογικά o compiler εξετάζει τον τελεστή εκχώρησης (assignment), αποφαίνεται πως πρόκειται για strings εκατέρωθεν και προχωρά στη διαδικασία εκχώρησης. είτε με απευθείας δείκτη της μεταβλητής στο buffer της τιμής, είτε με δημιουργία νέου buffer για τη μεταβλητή, αντιγραφή του buffer της τιμής στο νέο buffer και αποδέσμευση του buffer της τιμής (μάλλον κάνεi το πρώτο, που έχει σαφώς λιγότερο overhead). Είναι απλά εντυπωσιακό (με αρκετό βέβαια compilation overhead, το οποίο όμως λίγο μας νοιάζει μπροστά στην ευκολία του κώδικα). Το ακόμα πιο εντυπωσιακό, είναι το...
sa[0] := sa[0] + '67890';
... όπου παρατηρούμε παρόμοια συμπεριφορά και στον τελεστή πρόσθεσης, σε συνδυασμό με τον τελεστή εκχώρησης. Πηγάζουν όμως δυο εύλογες απορίες: 1. Τι γίνεται αν η απόπειρα δέσμευση μνήμης αποτύχει;;; Αν δηλαδή, στο παραπάνω παράδειγμα, ΔΕΝ υπάρχει άλλος χώρος για να προστεθεί το '67890' στο sa[0];;; Ομοίως, τι γίνεται αν η συνάρτηση SetLength(sa, 1) αποτύχει να δεσμεύσει τη μνήμη που ορίζει η 2η παράμετρός της; Διότι αν αποτύχει και εμείς αναφερθούμε στο sa[0] (όπως κάνουμε στο sa[0] := '12345' ) λογικά θα κάνουμε crash! 2. Αν οι τελεστές εκχώρησης και πρόσθεσης έχουν τη δυνατότητα να δεσμεύουν μνήμη δυναμικά, υπολογίζοντας αυτόματα το απαιτούμενο μέγεθος, τι χρειάζεται η συνάρτηση SetLength();;;;;;
-
Κατ'αρχήν Η εικόνα στη μνήμη ενός dynamic array είναι περίπου όμοια με ενός string με την μόνη διαφορά ότι στο header του buffer εκτός από το refcount υπάρχει και ένας pointer που δείχνει σε ένα compile-time-generated record (struct για σένα) το οποίο περιγράφει τον τύπο των elements του dynamic array. Αυτό το record λέγεται 'Type Information' ή 'TypeInfo' και ο compiler παράγει typeinfo records at compile-time για όλα τα data types που έχουν δηλωθεί και χρειάζονται ειδική διαχείριση (records, classes, dynamic arrays). Με αυτό τον τρόπο η 'μαγική' βιβλιοθήκη τελεστών και ρουτινών επεξεργασίας dynamic arrays ξέρει ανά πάσα στιγμή πώς να κάνει allocate, free και copy τα elements του κάθε dynamic array.
Έτσι η SetLength() είναι magic γιατί το πρώτο της όρισμα μπορεί να είναι είτε string είτε dynamic array με elements διαφορετικών τύπων. Για την ακρίβεια όταν ο compiler διαβάζει SetLength(a, n); κάνει emit _DynArraySetLength(p, a, n) όπου η _DynArraySetLength είναι η εσωτερική υλοποίηση της SetLength και η κρυφή παράμετρος 'p' είναι ο typeinfo pointer του a. Η διαφορά με τη δική σου SetLength() είναι ότι στη δική σου το n είναι πλήθος bytes ενώ στην OP SetLength() είναι πλήθος elements.
Για τις απορίες σου, η OP έχει ως lnaguage feature αυτό που λέμε 'strctured exception handling'. Έχει ως μέρος της γλώσσας τα constructs:
try ... finally CleanUpCode; end;
και
try ... except ExceptionHandingCode; end;
Στη C++ π.χ. αν θυμάμαι καλά πρόκειται για τα constructs __try{}__finally{} ή __try{}__except{} αντίστοιχα.
Αν αποτύχει λοιπόν ένα api call δημιουργείται ένα exception και η εκτέλεση του κώδικα σταματάει και πάει στον πρώτο exception handler που ακολουθεί στο current block ή σε οποιοδήποτε enclosing try/finally ή try/except block στο call stack. Αν δεν βρεθεί κανείς υπάρχει το main message loop (το οποίο έχει γράψει η Borland, δεν το γράφεις εσύ) το οποίο έχει try/except block γύρω από το κάθε DispatchMessage() και απλά δείχνει το exception σε ένα messagebox. Βέβαια σε εκτελέσιμο που δεν είναι πρόγραμμα (π.χ. dll, com library, service κτλ) δεν υπάρχει message loop, οπότε αν δεν κάνεις handle κάποιο exception μάλλον θα κρασάρει το outer program (εκτός κι αν είναι και αυτό γραμμένο σε Delphi) αλλά σε αυτό το πρόβλημα δεν υπάρχει λύση.
Η συνάρτηση SetLength() για strings χρειάζεται όταν το πλήθος των χαρακτήρων του string δεν είναι γνωστό κατά το compilation αλλά ορίζεται δυναμικά από την τιμή μιας μεταβλητής. Π.χ.
var I: Integer; S: string; begin I := Random(100); // η Random παράγει ψευδοτυχαίο ακέραιο μικρότερο από το όρισμα που έχεις περάσει. SetLength(S, I); end;
Αλλά ακόμα και αν το μήκος του string είναι γνωστό είναι πιο κομψό να γράψεις
SetLength(S, 10)
από
S := ' ';
πέραν του ότι η δεύτερη μορφή παράγει και ένα constant string literal στο code segment (DB instruction σε Pentium CPU).
Πάντως έχεις δίκιο ότι συνήθως δεν χρειάζεται να αρχικοποιήσεις με αυτό τον τρόπο ένα string, γιατί αυτό έχει την τιμή '' (ή nil, το ίδιο είναι) μόλις ορισθεί και από κει και πέρα (εκτός κι αν κάνεις C-style ταρζανιές) το μήκος του αλλάζει δυναμικά από τους τελεστές και τις συναρτήσεις διαχείρισης strings. Η μόνη περίπτωση που πρέπει να κάνεις αυτό είναι αν θέλεις να το περάσεις σε ένα api call που γράφει σε caller-allocated char* παράμετρο, όπως π.χ. η GetClassName():
var S: string; begin SetLength(s, 100); GetClassName(Wnd, PChar(s), Length(s)); end;
Α, και κάτι που ξέχασα να πω είναι πως λόγω της μορφής στη μνήμη ενός string είναι 'συμβατό' με παράμετρο τύπου PChar (char*) όπως βλέπεις στο παράδειγμα.
Μάλλον πρέπει να τα πούμε από κοντά αν ενδιαφέρεσαι να μάθεις περισσότερα για την Delphi και την Object Pascal.
-
Ο χρήστης kyan έγραψε:
Μάλλον πρέπει να τα πούμε από κοντά αν ενδιαφέρεσαι να μάθεις περισσότερα για την Delphi και την Object Pascal.
Κώστα ευχαριστώ! Το πρόβλημα είναι πως αυτό το καιρό από το πολύ πήξιμο με το ζόρι με βλέπει ακόμα και η γυναίκα μου. Θα ήθελα όμως να τα πούμε κι από κοντά κάποια στιγμή.
Έως τότε για πες μου αν υπάρχουν free υλοποιήσεις της OP (ή της Delphi) και που και αν οι γλώσσες αυτές υποστηρίζουν μεταξύ άλλων operator overloading, bit operations και casting.ΥΓ. Φαντάσου ότι είχα να ασχοληθώ με προγραμματισμό, περισσότερο από 10 χρόνια. Με πόρωσες!!
-
Έχει το url του αυτόματου συστήματος μετατροπής greeklish σε ελληνικά κάποιος?
-
Ο χρήστης migf1 έγραψε:
Μάλλον πρέπει να τα πούμε από κοντά αν ενδιαφέρεσαι να μάθεις περισσότερα για την Delphi και την Object Pascal.
Κώστα ευχαριστώ! Το πρόβλημα είναι πως αυτό το καιρό από το πολύ πήξιμο με το ζόρι με βλέπει ακόμα και η γυναίκα μου. Θα ήθελα όμως να τα πούμε κι από κοντά κάποια στιγμή.
Έχεις π.μ.
Έως τότε για πες μου αν υπάρχουν free υλοποιήσεις της OP (ή της Delphi) και που και αν οι γλώσσες αυτές υποστηρίζουν μεταξύ άλλων operator overloading, bit operations και casting.
Δυστυχώς δεν υπάρχει free υλοποίηση της OP, πάει πακέτο με τη Delphi η οποία είναι εμπορικό προϊόν και αρκετά ακριβό.
Operator overloading δεν υποστηρίζεται για οποιοδήποτε datatype (θα χρειαζόταν preprocessor και η OP δεν έχει - επίτηδες για γρήγορο compilation) αλλά μονάχα για το datatype Variant (όμοιο με το VARIANT* της C++), για το οποίο εκτός από διαχείριση παρόμοια με των strings/interfaces/dynarrays η Delphi δίνει και τη δυνατότητα να ορίσεις δικά σου variant types και τη λειτουργία των τελεστών πάνω τους. Π.χ. στα demos υπάρχει ένα implementation μιγαδικών αριθμών σε variants με όλους τους βασικούς αριθμητικούς τελεστές υλοιποημένους και functions για τις υπόλοιπες πράξεις.
Bit operations τι ακριβώς εννοείς; Έχει τους τελεστές shl, shr, div, mod, not, and, or, xor για ακέραια datatypes.
Static TypeCasting γίνεται έτσι:
AnyType(Variable) // όπως (AnyType) Variable στη C++
π.χ.
Integer(PointerVariable) := 0; // ταυτόσημο με το PointerVariable := nil;
Dynamic TypeCasting γίνεται σε class instances με τον τελεστή as:
S := (Control as TEdit).Caption; // dynamic_cast νομίζω θα γραφόταν στην C++
Αν το Control δεν είναι της κλάσης TEdit (ή descendant) δημιουργείται exception.
-
Περί Η/Υ (+ ηλεκτρ. gadgets και νέας τεχνολογίας) [#1]