Τα πάντα για την C

...του ubuntu και έργων ΕΛ/ΛΑΚ (Έργα-Οδηγοί-Προτάσεις)

Συντονιστής: konnn

Re: Τα πάντα για την C/C++

Δημοσίευσηαπό migf1 » 06 Σεπ 2011, 00:32

Πάλι τα περιπλέκεις :P

Όλοι οι πίνακες έχουν συνεχόμενα τα στοιχεία τους στη μνήμη, εννοώ δεν είναι ιδιομορφία των strings αυτό. Επίσης, οι malloc() / calloc() / realloc() πάντα δεσμεύουν συνεχόμενο κομμάτι στη μνήμη.

Τα strings δεν έχουν καμία άλλη ιδιομορφία στη φιλοσοφία τους συγκριτικά με οποιονδήποτε άλλο πίνακα, οποιουδήποτε τύπου, πλην του ότι πρέπει να έχουν ένα μηδενικό χαρακτήρα για να σηματοδοτούν το τέλος του τρέχοντος μήκους τους (αν δεν τον έχουν τότε είναι απλοί πίνακες χαρακτήρων), και έχουν μια έξτρα ειδική σύνταξη αρχικοποίησης μέσα σε διπλά εισαγωγικά.

Σε όλα τα υπόλοιπα συμπεριφέρονται όπως όλοι οι πίνακες στη C. Αν θες να τα διαχειριστείς στατικά τα ορίζεις ως πίνακες, αλλιώς ως δείκτες που πρέπει να τους κάνεις calloc() και free().

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

Τα string-literals επίσης δεν διαφέρουν σε τίποτα από μια οποιαδήποτε άλλη σταθερά οποιουδήποτε τύπου. Όπως γράφεις...
Κώδικας: Επιλογή όλων

#define MAXVALUE 100

και το διαχειρίζεσαι κατόπιν σαν να ήταν int, τον οποίον όμως δεν μπορείς να τον τροποποιήσεις (δεν μπορείς να πεις πιο κάτω στο πρόγραμμά σου: MAXVALUE = 200 ) έτσι γράφεις και...
Κώδικας: Επιλογή όλων

#define VOWELS "aeouyAEOUY"

και το διαχειρίζεσαι κατόπιν σαν ήταν string, το οποίο όμως δεν μπορείς να το τροποποιήσεις.
Go under the hood with C: Pointers, Strings, Linked Lists
Άβαταρ μέλους
migf1
powerTUX
powerTUX
 
Δημοσιεύσεις: 2082
Εγγραφή: 03 Ιουν 2011, 16:32
Εκτύπωση

Re: Τα πάντα για την C/C++

Δημοσίευσηαπό Star_Light » 06 Σεπ 2011, 01:40

αχα οκ! :)

Τωρα σχετικα με το οτι τα περιπλεκω δεν ξερω αν σε αυτο φταιει η πολυπλοκη σκεψη μου... καποιες φορες. Η πολυπλοκοτητα γενικα μεσα στην γλωσσα... ή γενικα Ολα αυτα μαζί!!!!

Π.Σ Οσο για αυτο που ειπες με τον στατικο πινακα... ναι βασικα ειναι βελτιστος απο πιθανα μελλοντικα σφαλματα και κενα ασφαλειας οπως μας τον εχεις εξηγησει εσυ... γιατι γενικα ειναι επικυνδινος!!! Μπες οπωσδηποτε αυριο να διαβασεις τον οδηγο μου που ειχα κανει πριν καιρο για το buffer overflow ισως έχει ασαφειες.... :/
Γνώσεις ⇛ Linux: Βασικές ┃ Προγραμματισμός: Δέν θέλω μεροκάματο , θέλω C και κακο θάνατο! ┃ Αγγλικά: Lower
Λειτουργικό ⇛ Ubuntu 10.10 σε Dual Boot με Windows 7
Προδιαγραφές ⇛ Επεξεργαστής : Intel(R) Core(TM) i3 CPU 540 @3.07Ghz (64bit)
RAM : Kingston 2GB
HDD : Coreshare 500GB
Κάρτα Γραφικών : Intel Corporation Core Processor Integrated Graphics Controller(rev 18) (prog-if 00 [VGA controller]) [8086:0042]
Star_Light
superbTUX
superbTUX
 
Δημοσιεύσεις: 2787
Εγγραφή: 01 Μάιος 2010, 21:07
Τοποθεσία: Αθήνα
IRC: Star_Light
Εκτύπωση

Re: Τα πάντα για την C/C++

Δημοσίευσηαπό migf1 » 06 Σεπ 2011, 02:03

Θα μπω :)
Go under the hood with C: Pointers, Strings, Linked Lists
Άβαταρ μέλους
migf1
powerTUX
powerTUX
 
Δημοσιεύσεις: 2082
Εγγραφή: 03 Ιουν 2011, 16:32
Εκτύπωση

Re: Τα πάντα για την C/C++

Δημοσίευσηαπό Star_Light » 06 Σεπ 2011, 02:11

migf1 έγραψε:Θα μπω :)


Ε ναι φιλε... πιστευω η αναλυση σου σε αυτα αποτελει εγγυηση :D
Γνώσεις ⇛ Linux: Βασικές ┃ Προγραμματισμός: Δέν θέλω μεροκάματο , θέλω C και κακο θάνατο! ┃ Αγγλικά: Lower
Λειτουργικό ⇛ Ubuntu 10.10 σε Dual Boot με Windows 7
Προδιαγραφές ⇛ Επεξεργαστής : Intel(R) Core(TM) i3 CPU 540 @3.07Ghz (64bit)
RAM : Kingston 2GB
HDD : Coreshare 500GB
Κάρτα Γραφικών : Intel Corporation Core Processor Integrated Graphics Controller(rev 18) (prog-if 00 [VGA controller]) [8086:0042]
Star_Light
superbTUX
superbTUX
 
Δημοσιεύσεις: 2787
Εγγραφή: 01 Μάιος 2010, 21:07
Τοποθεσία: Αθήνα
IRC: Star_Light
Εκτύπωση

Re: Τα πάντα για την C/C++

Δημοσίευσηαπό migf1 » 06 Σεπ 2011, 15:42

Τελικά τα περισσότερα που ήθελα να γράψω σε αυτό το νήμα για τους έξτρα ελέγχους που πρέπει να κάνουμε όταν δουλεύουμε με δείκτες, τα έγραψα στο νήμα του buffer-overflow (στο τέλος εκείνης της δημοσίευσης δίνω και παραδείγματα κώδικα ειδικά για δείκτες).

Πέρα από τον έλεγχο για το αν είναι ή όχι NULL ένας δείκτης πριν τον χρησιμοποιήσουμε (που ισχύει για δείκτες οποιουδήποτε τύπου), για τα strings συχνά μας εξυπηρετεί να ελέγχουμε και για το αν ο πρώτος χαρακτήρας τους είναι ή όχι ο μηδενικός ('\0')... αν είναι δηλαδή κενά ή όχι τα strings. Αν είναι κενά ίσως δεν χρειάζεται να συνεχίσουμε τη ροή της συνάρτησης, οπότε επιστρέφουμε απευθείας την τιμή που θέλουμε, χωρίς να πάει η ροή στον βασικό αλγόριθμο της συνάρτησης.

Για παράδειγμα, ας υποθέσουμε πως θέλουμε να φτιάξουμε μια συνάρτηση:

Κώδικας: Επιλογή όλων
char s_append( char *dst, const char *src );

η οποία θα "κολλάει" στο τέλος του string dst τα περιεχόμενα του string src. Αν το src όμως είναι ανύπαρκτο ( src == NULL ) ή υπαρκτό αλλά κενό ( *src == '\0' ) τότε δεν χρειάζεται να μπούμε σε καμία διαδικασία, απλά επιστρέφουμε το dst ίδιο όπως μας περάστηκε στην συνάρτηση.

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

Οπότε για το παραπάνω παράδειγμα, μπορούμε να γράψουμε κάτι σαν το παρακάτω:
Κώδικας: Επιλογή όλων

char s_append( char *dst, const char *src )
{
if ( !src || !*src ) // ισοδυναμεί με: if ( src == NULL || *src == '\0' )
return dst;
...

return dst;
}

Ένα ΒΑΣΙΚΟ σημείο που χρειάζεται ΜΕΓΑΛΗ ΠΡΟΣΟΧΗ εδώ είναι ότι ΠΡΩΤΑ ελέγχουμε για το αν ο δείκτης src είναι NULL και ΜΟΝΟ ΑΝ ΔΕΝ ΕΙΝΑΙ (και μόνο τότε) ελέγχουμε το περιεχόμενο του στοιχείου στο οποίο δείχνει!

Αν ελέγξουμε πρώτα το περιεχόμενο του δείκτη ( *src == '\0 ') τότε αν ο src είχε για παράδειγμα περαστεί με τιμή NULL στην συνάρτηση, έστω και κατά λάθος, τότε προκαλούμε segmentation-fault (και άρα κενό ασφαλείας) διότι NULL σημαίνει πως δείχνει στο ΠΟΥΘΕΝΑ (που στην πράξη σημαίνει πως δείχνει σε κάποιο τυχαίο σημείο της μνήμης ... undefined ). Οπότε, γράφοντας εμείς *src == '\0' ρισκάρουμε να μετατραπεί κάποια στιγμή στο run-time του προγράμματος σε *NULL == '\0'... κι εκεί αρχίζουν τα όργανα! :lol:

Το να περαστεί με τιμή NULL ο δείκτης src στην s_append() μπορεί να συμβεί μόνο όταν στην main() (ή σε όποια άλλη συνάρτηση καλεί την s_append() ) έχουμε επιλέξει να ορίσουμε τον src ως δείκτη και όχι ως πίνακα. Όταν ορίζουμε πίνακες, τότε όχι μόνο αποκλείεται να είναι NULL ο δείκτης που χρησιμοποιείται για όνομα του πίνακα, αλλά η γλώσσα ΔΕΝ μας αφήνει κιόλας να του αλλάξουμε απευθείας την τιμή του (μέσω άλλου δείκτη που τον βάζουμε να δείχνει στον πίνακα μας αφήνει όμως, αλλά είναι άλλη ιστορία αυτό). Επίσης, δεσμεύει αυτόματα & εγγυημένα μνήμη για τον src, την οποία μάλιστα την αποδεσμεύει κιόλας αυτόματα στο τέλος.

Οπότε αν δοκιμάσετε κάτι σαν το παρακάτω, ο compiler θα σας βγάλει σφάλμα...
Κώδικας: Επιλογή όλων

int main( void )
{
char src[10]; // ορισμός πίνακα
src = NULL; // ΔΕΝ ΜΑΣ ΑΦΗΝΕΙ !!!
...

Άρα δεν μας αφήνει να κάνουμε έστω και κατά λάθος NULL τον src απευθείας! Και όχι μόνο αυτό, έχει δεσμεύσει κιόλας αυτόματα μνήμη για 10 στοιχεία. Με λίγα λόγια, όταν χρησιμοποιούμε σύνταξη πινάκων στους ορισμούς, μειώνουμε σημαντικά τις πιθανότητες για σφάλματα απροσεξίας.

Αντίθετα, αν επιλέξουμε να ορίσουμε τον src με σύνταξη δεικτών ώστε να διαχειριστούμε μόνοι μας τη μνήμη που θα χρησιμοποιήσει, χρειάζεται μεγαλύτερη προσοχή από μεριά μας. Π.χ. μπορεί να ξεχαστούμε και να τον περάσουμε σαν όρισμα της s_append() ή όποιας άλλης συνάρτησης (ακόμα και της στάνταρ puts() ) χωρίς να έχουμε πρώτα δεσμεύσει μνήμη.

Παράδειγμα 1ο...
Κώδικας: Επιλογή όλων

int main( void )
{
char *src = NULL;
...
s_append( ..., src );
...

Παράδειγμα 2ο...
Κώδικας: Επιλογή όλων

int main( void )
{
char *src;
...
s_append( ..., src );
...

Στο 1ο παράδειγμα περνάμε τον src στην s_append() με τιμή NULL, ενώ στο δεύτερο παράδειγμα τον περνάμε με περιεχόμενο... ΑΠΡΟΣΔΙΟΡΙΣΤΟ (που είναι ακόμα χειρότερα, διότι δείχνει σε ένα τυχαίο σημείο της μνήμης !!! ).

Επειδή μέσα στην s_append() έχουμε βάλει ειδικό έλεγχο για το αν είναι NULL ή όχι ο src πριν καν επιχειρήσουμε να εξετάσουμε το περιεχόμενό του, κι αν είναι επιστρέφουμε τη ροή πίσω στη main(), δεν έχουμε πρόβλημα :) Σε αυτή την περίπτωση λοιπόν η συνάρτησή μας λειτουργεί ΜΕ ΑΣΦΑΛΕΙΑ (κάτι που δεν ισχύει για την στανταρ συνάρτηση: strcat(), που επίσης κολλάει το string src στο string dst).

Στη δεύτερη περίπτωση όμως που περνάμε με απροσδιόριστη τιμή τον src στην s_append() έχουμε τεράστιο πρόβλημα, και μάλιστα άλυτο στο επίπεδο της συνάρτησης. Διότι θα περάσει τον έλεγχο για NULL, αλλά ο έλεγχος *src == '\0' θωρεί δεδομένο πως αφού ο src δεν είναι NULL τότε δείχνει σε έγκυρη μνήμη, δηλαδή σε μνήμη που έχει δεσμευτεί ειδικά για τον src πριν περαστεί στη συνάρτηση.... μόνο που εμείς έχουμε ΞΕΧΑΣΕΙ να κάνουμε αυτή τη δέσμευση μνήμης για τον src στην main() !!!! Οπότε, πάρε ένα ωραίο seg-fault να το ευχαριστηθεί η καρδία σου! :lol: :lol: :lol:

Αν ο src είχε οριστεί ως πίνακας στην main(), τότε θα είχε δεσμευτεί αυτόματα & εγγυημένα μνήμη για αυτόν ;) Επίσης δεν χρειάζεται να κάνουμε κάτι εμείς για την απελευθέρωση της μνήμης που έχει δεσμευτεί για τον πίνακα (γίνεται κι αυτή αυτόματα).

Επειδή όμως εμείς γουστάρουμε τα χειροκίνητα και μη-εγγυημένα :lol: ο σωστός τρόπος να ορίσουμε ένα string με σύνταξη δεικτών και να το διαχειριστούμε, είναι ο παρακάτω..
Κώδικας: Επιλογή όλων

int main( void )
{
char *src = calloc( 10, sizeof( char ) ); // ορισμός του src και δέσμευση μνήμης στην οποία δείχνει
...
s_append( ..., src );
...
if ( src ) {
free( src );
src = NULL;
}

Ελπίζω τώρα να είναι πιο σαφές γιατί στο προηγούμενο ποστ σας είπα να προτιμάτε να ορίζετε τα strings με σύνταξη πινάκων ;)

Σημειώστε επίσης πως ότι έγραψα εδώ περί NULL, απροσδιόριστων τιμών, δυναμική κι αυτόματη διαχείριση μνήμης, κλπ ισχύει ΑΤΟΦΙΟ για οποιοδήποτε data-type της γλώσσας και όχι αποκλειστικά για strings!

Αυτό που χρειάζεται ειδική μέριμνα για τα strings είναι πως για να μπορούν να διαχειριστούν με ασφάλεια με τις στάνταρ συναρτήσεις της string.h πρέπει να περιέχουν τουλάχιστον έναν μηδενικό χαρακτήρα, με τον 1ο από αυτούς να σηματοδοτεί το τέλος του τρέχοντος μήκους το. Αυτό είναι ΑΝΕΞΑΡΤΗΤΟ από ότι είπαμε σήμερα περί NULL, κλπ !!!! Είναι άλλο πράγμα δηλαδή, και προϋποθέτει πως έχει ήδη δεσμευτεί μνήμη για το string (είτε αυτόματα, είτε δυναμικά).

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

Τα έχουμε ήδη αναλύσει σε προηγούμενα ποστς :)

Απορίες;

ΥΓ. @Star_Light: Ευχαριστώ για τα καλά λόγια!
Go under the hood with C: Pointers, Strings, Linked Lists
Άβαταρ μέλους
migf1
powerTUX
powerTUX
 
Δημοσιεύσεις: 2082
Εγγραφή: 03 Ιουν 2011, 16:32
Εκτύπωση

Re: Τα πάντα για την C/C++

Δημοσίευσηαπό migf1 » 06 Σεπ 2011, 16:17

Πείτε μου επίσης αν θέλετε παιδιά αν εξακολουθεί να υπάρχει ενδιαφέρον για τα strings σε αυτό το νήμα, καθώς επίσης και για αυτό που σας πρότεινα για δημιουργία μιας δικής μας μικρής αλλά ασφαλούς βιβλιοθήκης διαχείρισής τους, για να μη γράφω τσάμπα αν είναι (εγώ βέβαια θα την φτιάξω έτσι κι αλλιώς την βιβλιοθήκη, την μισή την έχω φτιάξει ήδη, αλλά ρωτάω αν υπάρχει ακόμα ενδιαφέρον ώστε αν υπάρχει να παραθέτω και να κάνω επεξηγηματικά ποστς των συναρτήσεών της).
Go under the hood with C: Pointers, Strings, Linked Lists
Άβαταρ μέλους
migf1
powerTUX
powerTUX
 
Δημοσιεύσεις: 2082
Εγγραφή: 03 Ιουν 2011, 16:32
Εκτύπωση

Re: Τα πάντα για την C/C++

Δημοσίευσηαπό Star_Light » 06 Σεπ 2011, 18:01

ΕΛα migf1!!!! Θα διαβασω πρωτα εκεινα που γραφεις για το buffer overflow ... σε λιγο.. απλα το παω σιγα σιγα :P
ενδιαφερον σαφως και υπαρχει. Αυτο ειναι δεδομενο.
Γνώσεις ⇛ Linux: Βασικές ┃ Προγραμματισμός: Δέν θέλω μεροκάματο , θέλω C και κακο θάνατο! ┃ Αγγλικά: Lower
Λειτουργικό ⇛ Ubuntu 10.10 σε Dual Boot με Windows 7
Προδιαγραφές ⇛ Επεξεργαστής : Intel(R) Core(TM) i3 CPU 540 @3.07Ghz (64bit)
RAM : Kingston 2GB
HDD : Coreshare 500GB
Κάρτα Γραφικών : Intel Corporation Core Processor Integrated Graphics Controller(rev 18) (prog-if 00 [VGA controller]) [8086:0042]
Star_Light
superbTUX
superbTUX
 
Δημοσιεύσεις: 2787
Εγγραφή: 01 Μάιος 2010, 21:07
Τοποθεσία: Αθήνα
IRC: Star_Light
Εκτύπωση

Re: Τα πάντα για την C/C++

Δημοσίευσηαπό Star_Light » 06 Σεπ 2011, 18:28

Πριν παμε στις βιβλιοθηκες ομως... θελω να μεινω σε κατι που ειπες σχετικα με την ανω και κατω υπερχειλιση
Δηλαδη οταν εγω παω να διαχειριστω ενα στοιχειο που βρίσκεται πριν την αρχη οποιουδηποτε πινακα μου εχω κατω υπερχειλιση δηλαδη? πριν τον δεικτη 0 δηλαδη?

ενω ανω υπερχειλιση ειναι αν πχ δηλωσεις εναν

Κώδικας: Επιλογή όλων
int x[10];


να προσπαθησω να διαχειριστω τον x[11]?

εστω ο κωδικας

Κώδικας: Επιλογή όλων
#include<stdio.h>
int main(void)
{
int x1[10]={0};
int x2[3]={0,1,2};
int i,j;

while(i!=10)
{
printf("%d",x1[i]);
i++;
}


while(j!=4) //second case
{
printf("\n%d",x2[j]);
j++;
}

return 0;
}


Στην 2η περιπτωση εγω προκαλω ανω υπερχείλιση?

Π.Σ Εχει αρχισει να μου αρεσει και η while :D
Γνώσεις ⇛ Linux: Βασικές ┃ Προγραμματισμός: Δέν θέλω μεροκάματο , θέλω C και κακο θάνατο! ┃ Αγγλικά: Lower
Λειτουργικό ⇛ Ubuntu 10.10 σε Dual Boot με Windows 7
Προδιαγραφές ⇛ Επεξεργαστής : Intel(R) Core(TM) i3 CPU 540 @3.07Ghz (64bit)
RAM : Kingston 2GB
HDD : Coreshare 500GB
Κάρτα Γραφικών : Intel Corporation Core Processor Integrated Graphics Controller(rev 18) (prog-if 00 [VGA controller]) [8086:0042]
Star_Light
superbTUX
superbTUX
 
Δημοσιεύσεις: 2787
Εγγραφή: 01 Μάιος 2010, 21:07
Τοποθεσία: Αθήνα
IRC: Star_Light
Εκτύπωση

Re: Τα πάντα για την C/C++

Δημοσίευσηαπό migf1 » 06 Σεπ 2011, 19:05

Ακριβώς όπως τα έγραψες :)
Στη 2η περίπτωση προκαλείς άνω υπερχείλιση.

Κι εδώ ένα παράδειγμα επιρρεπές σε κάτω υπερχείλιση...

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

#include <stdio.h>
#define MAXNUMS 5
// -------------------------------------------------------------------------
int num_find_position( int *nums, int n )
{
int i = 0;

for (i=0; i < MAXNUMS; i++ )
if ( nums[ i ] == n )
return i;

return -1;
}
// -------------------------------------------------------------------------
int main( void )
{
int nums[ MAXNUMS ] = { 10, 20, 30, 40, 50 };
int n, pos;

printf("Dwste enan arithmo na sas pw poios einai dipla toy apo aristera ston pinaka: ");
scanf( "%d", &n);

pos = num_find_position( nums, n );
printf("O arithmos poy brisketai aristera apo ton %d einai o %d\n", n, nums[ pos-1 ] );

return 0;
}

Μπορείτε να βρείτε γιατί και πότε προκαλεί κάτω υπερχείλιση στον nums ο παραπάνω κώδικας;
Go under the hood with C: Pointers, Strings, Linked Lists
Άβαταρ μέλους
migf1
powerTUX
powerTUX
 
Δημοσιεύσεις: 2082
Εγγραφή: 03 Ιουν 2011, 16:32
Εκτύπωση

Re: Τα πάντα για την C/C++

Δημοσίευσηαπό Star_Light » 06 Σεπ 2011, 19:37

Στην περιπτωση του n=10 δημιουργει κατω υπερχειλιση γιατι δεν υπαρχει pos-1 μεσα στον nums σε αυτη την περιπτωση.

Ή καλυτερα δεν υπάρχει εγκυρο pos-1 για αυτη την περιπτωση μεσα στον nums.
Γνώσεις ⇛ Linux: Βασικές ┃ Προγραμματισμός: Δέν θέλω μεροκάματο , θέλω C και κακο θάνατο! ┃ Αγγλικά: Lower
Λειτουργικό ⇛ Ubuntu 10.10 σε Dual Boot με Windows 7
Προδιαγραφές ⇛ Επεξεργαστής : Intel(R) Core(TM) i3 CPU 540 @3.07Ghz (64bit)
RAM : Kingston 2GB
HDD : Coreshare 500GB
Κάρτα Γραφικών : Intel Corporation Core Processor Integrated Graphics Controller(rev 18) (prog-if 00 [VGA controller]) [8086:0042]
Star_Light
superbTUX
superbTUX
 
Δημοσιεύσεις: 2787
Εγγραφή: 01 Μάιος 2010, 21:07
Τοποθεσία: Αθήνα
IRC: Star_Light
Εκτύπωση

ΠροηγούμενηΕπόμενο

Επιστροφή στο Ανάπτυξη Λογισμικού / Αλγόριθμοι