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

Image Processing C program for hough circle!!


iex

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

@imitheos ναι συμφωνω απολυτα!

 

Μεσα στα braces η μεταβλητη που οριζεις έχει τοπική εμβέλεια. Οπως ακριβως και στα braces που ξεκινουν και τερματιζουν τον ορισμο μιας συνάρτησης. Αμα δωσεις ενα printf μεσα στο block "τρωει" τις εξωτερικες με την εννοια οτι θα εκτυπωσει την τιμη που εκχωρηθηκε τελευταια ! Οπως γινεται και στις καθολικες μεταβλητες

 

Αυτο εννοουσα και εγω εξαρχης οτι τα braces δεν μπαινουν για ομορφια αλλα υπάρχει λογος οπως και οταν θελουμε να κανουμε τον μεταγλωτιστη να λαβει υποψιν του τις πολλες εντολες σαν μια.

 

@defacer σορρυ φιλε αλλα δεν σε πιανω...

 

Οσο για τα στυλ ε νταξει δεν θα δημιουργησουμε προβλημα απο το πουθενα τωρα αμα εισαι σε μια ομαδα και εχει δικο της στυλ ενστερνιζεσαι αυτο και τελειωνει το θεμα ή αμα σου ζητησει καποιος να το αλλαξεις το αλλαζεις στα γρηγορα.

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

Δημοσ. (επεξεργασμένο)

Εγώ ανέκαθεν προτιμούσα και προτιμώ το Allman indent (το βρίσκω πολύ εξυπηρετικό στην ανάγνωση), ανέχομαι όμως και το K&R (και ενίοτε το χρησιμοποιώ -για εξοικονόμηση χώρου).

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

Εγώ ανέκαθεν προτιμούσα και προτιμώ το Allman indent (το βρίσκω πολύ εξυπηρετικό στην ανάγνωση), ανέχομαι όμως και το K&R (και ενίοτε το χρησιμοποιώ -για εξοικονόμηση χώρου).

Ναι και εγώ το ίδιο (αν και εγώ αντίθετα κλείνω πιο πολύ σε K&R). Allman και K&R είναι τα πιο βολικά. Ειδικά για να κάνεις δοκιμές και να κάνεις comment κάτι το Allman βολεύει πολύ. Το K&R είναι σπαστικό σε αυτή τη περίπτωση.
Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Χρησιμοποίησα 2 image buffers κατευθείαν από προηγούμενη άσκηση που επεξεργαζόμουν την αρχική εικόνα και την αποθήκευα με νέο όνομα διατηρώντας το αρχικό αρχείο.

...

 

Και πάλι δεν βρίσκω νόημα στο να έχεις 2 Image buffers, διότι μπορείς να:

1. φορτώνεις το 1ο αρχείο στο buffer (δλδ στη μνήμη)

2. αλλάζεις την ζωγραφιά στο buffer

3. σώζεις το buffer σε άλλο αρχείο

 

Για την άσκηση που συζητάμε εδώ, δεν βλέπω να χρειάζονται ούτε τα 2 αρχεία, διότι μπορείς να:

1. ζωγραφίζεις στο buffer

2. σώζεις το buffer σε ένα αρχείο

 

Πάντως ακόμα και 2 αρχεία να χρησιμοποιήσεις, ούτε 2 image buffers χρειάζονται, ούτε 2 file pointers.

 

Όπως και να έχει, σου ξανάγραψα στα γρήγορα τον κώδικα χρησιμοποιώντας 1 μόνο image buffer, και παρόλο που δεν βλέπω την χρησιμότητά τους σου διατήρησα τα 2 αρχεία χρησιμοποιώντας όμως έναν μόνο file-pointer (από την στιγμή που δεν χρειάζεται να έχεις ταυτόχρονα ανοιχτά και τα 2 αρχεία δεν χρειάζεται 2ος file pointer, αρκεί ο ένας ;) ).

 

Εκτός από κάτι macros που τα έχω χωρίσει σε ομάδες, με την προοπτική τα user defined constants να μετατραπούν αργότερα σε ένα ή περισσότερα struct (π.χ. Settings) οπότε οι τιμές τους να μπορούν να διαβαστούν από τον χρήστη (αντί να είναι σταθερές όπως τώρα), χρησιμοποιώ το image-buffer ως μονοδιάστατο πίνακα αντί για 2-διάστατο (το είχα κάνει και στον αρχικό κώδικα που ποστάρισα).

 

Προσωπικά με εξυπηρετεί καλύτερα, ιδιαίτερα στο house-keeping όπου με αυτόν τον τρόπο αρκεί ένα loop (αντί για 2 nested). Σε άλλα σημεία είναι πιο δύστροπο όμως, συγκεκριμένα όταν χρειαζόμαστε να διαχειριστούμε τον 1-διάστο ως 2-διάστατο.

 

Το κόλπο είναι πως αντί να έχουμε 2D imgBuffer[ROWS][COLS] έχουμε 1D imgBuffer[ROWS * COLS]. Όταν χρειαστεί να τον διαχειριστούμε ως 2D, τότε αν n είναι ο indexer με τον οποίον διατρέχουμε τον 1D και θέλουμε να βγάλουμε 2D indexers i,j ισχύει το εξής:

i = n / COLS;
j = n % COLS;
Ισχύει και το ανάποδο, δηλαδή αν έχουμε π.χ. 2 φωλιασμένα loops με i και j και θέλουμε να τα μετατρέψουμε σε 1D n, τότε...
n = i * COLS + j;
Π.χ....
int buffer[ROWS * COLS];

for (int i=0; i < ROWS; i++)
  for (j=0; j < COLS; j++)
    buffer[ i * COLS + j ] = 0;
Και το ανάποδο, π.χ...
int buffer[ROWS * COLS];

for (int n=0; n < ROWS * COLS; n++)
  printf( "row = %d, col = %d\n", n/COLS, n%COLS );
Συνήθως τις πράξεις αυτές τις κάνουμε macros για να μην τις γράφουμε συνέχεια, π.χ...
#define ROWS    100
#define COLS    200
#define TOTLEN  (ROWS * COLS)

#define _ROW(n) ((n) / COLS)
#define _COL(n) ((n) % COLS)
#define _N(i,j) ( (i) * COLS + (j) )

...
int buffer[TOTLEN};
...
for (int n=0; n < TOTLEN; n++)
     printf( "row = %d, col = %d\n", _ROW(n), _COL(n) );
...
for (int i=0; i < ROWS; i++)
  for (int j=0; j < COLS; j++)
    buffer[ _N(i,j) ] = 0;
Επίσης, έχω βάλει error-checks ώστε να χτυπάει αν βάλεις invalid τιμές στα user-defined constants, κι επίσης το έχω κάνει να δέχεται το κέντρο του δίσκου όχι μόνο στο ακριβώς κέντρο της εικόνας, αλλά όπου θέλεις. Όταν ορίζεις τα CY και CX να μην είναι στο ακριβές κέντρο της εικόνας, τότε όταν σου ζητάει να βάλεις την ακτίνα, υπολογίζει και σου λέει ποια είναι η μέγιστη τιμή που θα δεχτεί (ώστε να είναι πάντα ο δίσκος εντός της εικόνας).

 

Τέλος, η εικόνα δεν χρειάζεται να είναι τετράγωνη, μπορείς να βάλεις στα IMG_H και IMG_W ότι τιμές θέλεις.

 

Ίσως μου έχει ξεφύγει κάνας έλεγχος, αλλά OK είναι και περασμένη η ώρα.

 

Αρκετά με το.. μπλα μπλα :lol: ...

 

Κώδικας

 

/**
 * Draw a disc in a .raw image format
 */

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

/* user defined constants (may be re-structured in a struct Settings, and get inputted by the user) */

#define MAXINPUT	(255 + 1)		/* max # chars to be read from stdin */

#define FNAME_IN	"_discIn.raw"		/* source file */
#define FNAME_OUT	"_discOut.raw" 		/* target file */

#define BG_COLOR	255			/* background color */
#define FG_COLOR	65			/* foreground color */

#define IMG_W		150			/* image width */
#define IMG_H		120			/* image height */

#define CX		30			/* x coord of disc's center */
#define CY		70			/* y coord of disc's center */

/* general utility macros */
#define ABS(x)		( (x) < 0 ? -(x) : (x) )
#define MIN(x,y)	( (x) < (y) ? (x) : (y) )

/* auto calculated constants (do not modify them) */
#define MAX_RADIUS 							\
MIN(									\
	( ABS(CY) < ABS(IMG_H)/2 ? ABS(CY) : ABS(IMG_H) - ABS(CY) ),	\
	( ABS(CX) < ABS(IMG_W)/2 ? ABS(CX) : ABS(IMG_W) - ABS(CX) )	\
)
#define BUFSIZE		(IMG_H * IMG_W)

/* --------------------------------------------
 * print default values
 */
void print_defaults( void )
{
	printf( "Canvas size:\t\t%d x %d (%d pixels)\n", IMG_H, IMG_W, BUFSIZE );
	printf( "Background color:\t%0X (hex)\n", BG_COLOR );
	printf( "Center (y,x):\t\t(%d,%d) %d'th pixel\n", CY, CX, (CY * IMG_W + CX) );
	printf( "Source file:\t\t%s\n", FNAME_IN );
	printf( "Target file:\t\t%s\n", FNAME_OUT );
	putchar( '\n' );
}

/* --------------------------------------------
 * validate user defined constants
 */
int valid_constants( int cy, int cx, int imgHeight, int imgWidth )
{
	return	imgHeight > 1 && imgWidth > 1
		&& cy > 1 && cy < imgHeight
		&& cx > 1 && cx < imgWidth
		;
}

/* --------------------------------------------
 * validate user inputted radius (rest of the arguments MUST be already validated)
 */
int valid_radius( int radius, int cy, int cx, int imgHeight, int imgWidth )
{
	const int h = (cy < imgHeight / 2) ? cy : imgHeight - cy;
	const int w = (cx < imgWidth / 2)  ? cx : imgWidth  - cx;

	return	radius > 0 && radius <= MIN(h,w);
}

/* --------------------------------------------
 * entry point
 */
int main( void )
{
	unsigned char imgBuffer[bUFSIZE] = {0};	/* 0 = black */
	FILE *fp = NULL;			/* file pointer */
	int i=0, j=0, r;			/* r = radius */

	/* self check */
	if ( !valid_constants( CY,CX, IMG_H,IMG_W) ) {
		fputs( "*** fatal error: Invalid constants found in the sources!\n", stderr );
		fputs( "Please fix them, re-compile & re-run the program. ", stderr );
		system( "pause" );		/* Windows only */
		exit( EXIT_FAILURE );
	}

	print_defaults();

	/* init image buffer to BG_COLOR */
	memset( &imgBuffer, BG_COLOR, BUFSIZE );

	/* read data from source file to image buffer */
	fp = fopen( FNAME_IN, "rb" );
	if ( NULL == fp ) {
		fprintf( stderr, "*** error loading source file '%s'\n", FNAME_IN );
		fputs( "*** using default image buffer\n", stderr );
	}
	else {
		for (i=0; i < BUFSIZE; i++)
			imgBuffer[i] = fgetc(fp);	/* implicit casting */
		fclose( fp );
		printf( "--- source file '%s' loaded into image buffer\n", FNAME_IN );
	}
	putchar( '\n' );

	/* get disc radius from user (keep asking until valid value is entered) */
	for (;
	{
		char input[MAXINPUT] = {'\0'};	/* input string */

		printf( "Radius of disc (0 < r <= %d): ", MAX_RADIUS );
		fgets( input, MAXINPUT, stdin );/* get input from user*/

		r = atoi( input );		/* convert input from string to int */
		if ( valid_radius(r, CY,CX, IMG_H,IMG_W) )
			break;

		puts( "--- invalid radius, try again...\n" );
	}

	/* draw the disc with specifed radius into image buffer, using FG_COLOR */
	for (i=-r; i < r; i++)
	{
		const int height = sqrt(r * r - i * i);
		for (j = -height; j < height; j++) {
			int y = i + CY;
			int x = j + CX;
			imgBuffer[y*IMG_W + x] = FG_COLOR;
		}
	}

	/* save image buffer to target file  */
	fp = fopen( FNAME_OUT, "wb" );
	if ( NULL == fp ) {
		fprintf( stderr, "*** error creating target file '%s'\n", FNAME_OUT );
	}
	else {
		for (i=0; i < BUFSIZE; i++)
			fputc( imgBuffer[i], fp );
		fclose( fp );
		printf( "--- image buffer saved into target file '%s'\n", FNAME_OUT );
	}
	putchar( '\n' );

	system( "pause" );		/* Windows only */
	exit( EXIT_SUCCESS );
}

 

Εξοδος

 

Canvas size:            120 x 150 (18000 pixels)
Background color:       FF (hex)
Center (y,x):           (70,30) 10530'th pixel
Source file:            _discIn.raw
Target file:            _discOut.raw

*** error loading source file '_discIn.raw'
*** using default image buffer

Radius of disc (0 < r <= 30): 30
--- image buffer saved into target file '_discOut.raw'

Press any key to continue . . .
post-38307-0-05424800-1365027634_thumb.jpg

 

 

 

OFFTOPIC?

 

 

 

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

...
switch( whatever_int )
{
    case WHATEVER1:
    {
        int local;
        ....
        break;
    }
 
    case WHATEVER2:
        ...
       break;
 
    case WHATEVER3:
    {
        float local;
        ...
        break;
    }
 
    default:
       ...
      break;
}
 

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

 

Στα nested-loops συνήθως ανοίγω αποκάτω στο εξωτερικό, αλλά στην ίδια γραμμή στο εσωτερικό....

for (int i=0; i < ...; i++)
{
    for (int j=0; j < ...; j++) {
       ...
   }
}
εκτός αν το εσωτερικό είναι μακρινάρι, οποτε ανοίγω αποκάτω και σε αυτό.

 

Γενικώς είμαι... ασυνεπής στα άγκιστρα :P ... αν το διαβάζω καλύτερα με ασυνέπεια, τότε το αφήνω έτσι, ασυνεπές ;)

 

Θα σε συμβούλευα ως βασικό πράγμα να ξεχάσεις άμεσα ότι υπάρχει το goto :P. Προσπάθησε να κάνεις το ίδιο με μια while. Αργότερα τέτοια loops θα τα κάνεις συνέχεια για input checks οπότε καλό είναι να τα συνηθίσεις απο τώρα.

 

Το πιο straight-forward σε αυτές τις περιπτώσεις είναι ένα infinite loop με continue και break όπου χρειάζεται. Με τα while και τα do, αν θέλουμε να βγάλουμε error messages κατά το validation μέσα στο loop, τότε αναγκαζόμαστε να διπλογράψουμε τον έλεγχο: μια για να βγάλουμε το error-message (ή να επιστρέψουμε κάποιο error-code) και μια για να σταματήσουμε το loop.

 

Με τα infinite loops δεν το έχουμε αυτό το πρόβλημα.

 

Π.χ (πολύ απλοϊκό, αλλά νομίζω χαρακτηριστικό)...

 

 

do {
    int n = read_int( stdin );
    if ( n < 0 )    // <---------------
        puts( "only positives are allowed, try again... " );
} while ( n < 0 );  // <----------------
vs...

 

for ( ;; )
{
    int n = read_int( stdin );
    if ( n >=0 )
        break;
    puts( "only positives are allowed, try again... " );
}
or vs...

 

for ( ;; )
{
    int n = read_int( stdin );
    if ( n < 0 ) {
        puts( "only positives are allowed, try again... " );
        continue;
    }
    break;
}

 

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

Θα πάω και γω με τον Directx. jsiskos, παρόλο που γενικά συμφωνώ με το πνεύμα αυτού που γράφεις, όταν γράφεις ένα scratch program των 20 γραμμών το οποίο ξέρεις ότι στη συνέχεια θα το "πετάξεις", δε χρειάζεται να το κουράζεις πολύ. Αυτό βέβαια προϋποθέτει ότι μιλάμε για κάτι που αν αποφασίσεις να το κάνεις "το κάνεις και στον ύπνο σου", και δεν είναι δικαιολογία για κάποιον που βασικά δεν έχει μάθει να το κάνει.

 

Αν μου επιτρέπεις, η τοποθέτησή σου μου φέρνει στο μυαλό το "οι φοιτητές ιατρικής σε κάθε διάγνωση διαγιγνώσκουν την ασθένεια που διδάχτηκαν την προηγούμενη βδομάδα". Χωρίς παρεξήγηση, we 've all been there.

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

 

To θέμα είναι ότι το παιδί είναι φοιτητής ακόμα και δεν είναι σωστό να συνηθίζει σε λανθασμένες πρακτικές όπως το goto που αναφέρατε. Εμένα για να καταλάβετε, όσο εσάς σας πειράζει η ύπαρξη goto, άλλο τόσο με πειράζει και η έλλειψη functions.

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

Δημοσ. (επεξεργασμένο)

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

 

To θέμα είναι ότι το παιδί είναι φοιτητής ακόμα και δεν είναι σωστό να συνηθίζει σε λανθασμένες πρακτικές όπως το goto που αναφέρατε. Εμένα για να καταλάβετε, όσο εσάς σας πειράζει η ύπαρξη goto, άλλο τόσο με πειράζει και η έλλειψη functions.

 

ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Ακολουθεί... έκθεση ιδεών :lol:

 

Όταν μιλάμε για παιδιά που κάνουν τα πρώτα τους βήματα, ή έστω τα δεύτερα, τότε συμφωνώ κι εγώ.

 

Από εκεί και πέρα όμως, όσο καλύτερα γνωρίζει κάποιος μια γλώσσα τόσο μεγαλύτερη ευχέρεια έχει στο να χρησιμοποιεί προς όφελός του τα ιδιαίτερα χαρακτηριστικά της κατά περίπτωση, ακόμα και ορισμένα από αυτά που η mainstream βιβλιογραφία τα αναφέρει γενικώς ως bad practice.

 

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

 

Ας πάρουμε λοιπόν για παράδειγμα μια συνάρτηση η οποία δημιουργεί δυναμικά και επιστρέφει έναν πίνακα (strTable) από strings όμοιου αλλά δυναμικά οριζόμενου μέγιστου μήκους (strLen) ...

/* --------------------------------------- */
char **new_strTable( int nelems, size_t strLen )
{
	int i = 0;
	char **ret = NULL;

	/* sanity check for nelems and strLen should be taken here */

	ret = calloc( nelems, sizeof(char *) );
	if ( NULL == ret )
		return NULL;

	for (i=0; i < nelems; i++)
	{
		ret[i] = calloc( strLen, sizeof(char) );
		if ( NULL == ret[i] )
		    goto cleanup_and_fail;
	}

	return ret;

cleanup_and_fail:
	for (int j = i-1; j > -1; j--) {
		free( ret[j] );
	}
	free( ret );
	return NULL;
}

Την έχω επίτηδες με goto σε περίπτωση που σκάσει η calloc() μέσα στο loop για οποιοδήποτε string. Σε αυτή την περίπτωση ο κώδικας πρέπει να απελευθερώσει την ήδη δεσμευμένη μνήμη για τα προηγούμενα strings, καθώς και τη μνήμη για τον πίνακα αυτόν κάθε αυτόν και να επιστρέψει αποτυχία.

 

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

 

Μια εναλλακτική που από ότι καταλαβαίνω την θεωρείς must, είναι το label να μετατραπεί σε συνάρτηση, και να κληθεί απευθείας στη θέση που τώρα υπάρχει η goto, κάτι τέτοιο δηλαδή...

 

char **new_strTable( ... )
{
	...
	for (i=0; i < nelems; i++)
	{
		ret[i] = calloc( strLen, sizeof(char) );
		if ( NULL == ret[i] ) {
		    cleanup_backwards( &strTable, i);
		    return NULL;
		}
	}

	return ret;
}

Η πρώτη παρατήρηση είναι πως ο 2ος κώδικας είναι αμφίβολο για το αν είναι πιο readable από τον αρχικό, με έναν από τους λόγους ότι συνήθως πρέπει να μετακινηθείς αλλού για να βρεις τον κώδικα της συνάρτησης, ίσως και σε διαφορετικό αρχείο

 

Η δεύτερη και πιο σημαντική παρατήρηση όμως είναι πως η υλοποίηση με συνάρτηση προσθέτει όχι μόνο extra stack overhead αλλά και sanity check overhead μέσα της, αφού στην καλύτερη περίπτωση θα πρέπει να ελέγχει το strTable ως προς NULL, και στην χειρότερη να παίρνει ως όρισμα και το nelems ώστε να κάνει validate το i.

 

Το συγκεκριμένο παράδειγμα δεν είναι κατάλληλο (εννοώ πως επειδή είναι απλό μπορούμε να την... σκαπουλάρουμε και χωρίς sanity checks μέσα στην cleanup_backwards() ή έστω να τα περιορίσουμε μονάχα στο strTable) αλλά νομίζω you get my point. Ειδικά αν αυτή η συνάρτηση αποτελεί public συνάρτηση μιας βιβλιοθήκης, τότε τα sanity checks είναι MUST.

 

Όσο μεγαλώνει το πλήθος των string tables που θέλουμε να δημιουργήσουμε (π.χ. με ένα loop που θα φτιάχνει table of string-tables), τόσο αυξάνει και το overhead του overall cleanup.

 

Μια ενδιάμεση λύση, που την υποστηρίζει η C, είναι η cleanup_backwards() αντί για συνάρτηση να γίνει macro (κάτι που επίσης καταδικάζουν πολλοί). Δεν θα μας γλίτωνε από την απομάκρυνση του κώδικα υλοποίησης (ενδεχομένως και σε άλλο αρχείο), αλλά θα μας γλίτωνε από το extra overhead (και θα εισήγαγε άλλο πρόβλημα, το type non-safeness).

 

Δυστυχώς η C δεν υποστηρίζει nested functions, κάτι που θα μετρίαζε τα "μειονεκτήματα" και των 2 παραπάνω παρατηρήσεων και άρα θα έκανε ακόμα πιο σπάνια την ανάγκη χρήσης του goto.

 

Θα αναρωτιέσαι ίσως τώρα γιατί θεωρώ την παραπάνω μινι-ανάλυση σημαντική, ακόμα και για παιδιά που κάνουν τα πρώτα τους βήματα, ή έστω τα δεύτερά τους.

 

Διότι κατά την προσωπική μου άποψη, αν εντυπώσουν στο μυαλό τους πως η goto δεν πρέπει να χρησιμοποιείται ποτέ και πουθενά ή τα macros δεν πρέπει να χρησιμοποιούνται ποτέ και πουθενά, κλπ, κλπ, τότε αυτόματα χάνουν το κίνητρο να ερευνήσουν γιατί στην ευχή υπάρχουν στη γλώσσα αυτές οι δυνατότητες αφού τα βιβλία και οι δάσκαλοι τους λένε να μην τα χρησιμοποιούν, να τα αποφεύγουν όπως ο διάβολος το λιβάνι, κλπ, κλπ.

 

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

 

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

 

Μια ωραία ιδέα για κάποιον που ενδιαφέρεται, πάντα κατά τη γνώμη μου, είναι queries στο google του στυλ...

 

valid use of goto in c

What are C macros useful for

 

κλπ, κλπ για κάθε πράγμα που διαβάζουν πως θεωρείται bad practice, ώστε κατ' ελάχιστον να μάθουν πότε και γιατί θεωρούνται έτσι, καθώς και πότε και γιατί μπορούν να φανούν χρήσιμα. Προφανώς πολλά από όσα διαβάσουν δεν θα τα καταλάβουν άμεσα, λόγω έλλειψης γνώσεων, θα είναι όμως τουλάχιστον υποψιασμένοι.

 

Να κλείσω και με ένα ακόμα παράδειγμα που προκαλεί "διαμάχες" για το αν είναι ή όχι bad practice. Η χρήση πολλών return μέσα σε μια συνάρτηση, έναντι του ενός. Δηλαδή, ο παραπάνω κώδικας μπορεί να μετατραπεί ώστε να έχει ένα μονάχα return statement, το οποίο όμως συνήθως προκαλεί μεγαλύτερο code branching...

/* --------------------------------------- */
char **new_strTable2( int nelems, size_t strLen )
{
	int i = 0;
	char **ret = NULL;

	/* sanity check for nelems and strLen should be taken here */

	ret = calloc( nelems, sizeof(char *) );
	if ( NULL == ret ) {
		ret = NULL;
	}
	else {
		for (i=0; i < nelems; i++)
		{
			ret[i] = calloc( strLen, sizeof(char) );
			if ( NULL == ret[i] ) {
				cleanup_backwards( strLen, i);
				ret = NULL;
				break;
			}
		}
	}

	return ret;
}

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

char **new_strTable3( int nelems, size_t strLen )
{
	int i = 0;
	char **ret = NULL;

	/* sanity check for nelems and strLen should be taken here */

	ret = calloc( nelems, sizeof(char *) );
	if ( NULL == ret ) {
		ret = NULL;
	}
	else {
		int stop = 0; /* false */
		for (i=0; !stop && i < nelems; i++)
		{
			ret[i] = calloc( strLen, sizeof(char) );
			if ( NULL == ret[i] ) {
				cleanup_backwards( strLen, i );
				stop = 1; /* true */
				ret = NULL;
			}
		}
	}

	return ret;
}

Αυτός ο τελευταίος κώδικας δεν χρησιμοποιεί κανένα από τα λιγότερο ή περισσότερο "απαγορευμένα" goto & break τα οποία "χαλάνε" βίαια την ομαλή ροή ενός θεωρητικά βέλτιστα δομημένου προγράμματος.

 

Το κατά πόσο όμως είναι πιο ευανάγνωστος, πιο αποδοτικός ή πόσο κι από τα δυο, είναι αλλουνού παπά ευαγγέλιο :)

 

Σε γενικές γραμμές, εγώ προσωπικά το θεωρώ λάθος να προσπαθεί ένας προγραμματιστής να πάει κόντρα στην γλώσσα με την οποία προγραμματίζει. Λάθος και για τον ίδιον αλλά και για τους συνεργάτες του.

 

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

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

ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Ακολουθεί... έκθεση ιδεών :lol:

 

Όταν μιλάμε για παιδιά που κάνουν τα πρώτα τους βήματα, ή έστω τα δεύτερα, τότε συμφωνώ κι εγώ.

 

Από εκεί και πέρα όμως, όσο καλύτερα γνωρίζει κάποιος μια γλώσσα τόσο μεγαλύτερη ευχέρεια έχει στο να χρησιμοποιεί προς όφελός του τα ιδιαίτερα χαρακτηριστικά της κατά περίπτωση, ακόμα και ορισμένα από αυτά που η mainstream βιβλιογραφία τα αναφέρει γενικώς ως bad practice.

Φυσικά. Τα κολλήματα, ανεξαρτήτως θέματος, δεν είναι καλά. Όλα τα εργαλεία (εκτός από τον preprocessor :P) είναι για να τα χρησιμοποιούμε. Και εγώ είμαι στους αιρετικούς που τους αρέσει η goto :P.

 

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

 

 

		    goto cleanup_and_fail;
Η συγκεκριμένη υλοποίηση νομίζω θα συμφωνήσουμε οι περισσότεροι πως συνδυάζει readability & performance, χωρίς να πλήττει σημαντικά (έως και καθόλου) την δομή της συνάρτησης κατά τη γνώμη πολλών (εμού συμπεριλαμβανομένου).

 

Να κλείσω και με ένα ακόμα παράδειγμα που προκαλεί "διαμάχες" για το αν είναι ή όχι bad practice. Η χρήση πολλών return μέσα σε μια συνάρτηση, έναντι του ενός.

 

 

Για τέτοιου είδους έλεγχο και cleanup, η goto είναι πολλές φορές η πιο ευανάγνωστη λύση. Ένας μπακάλικος μπούσουλας για την goto που προτείνουν πολλοί είναι όλες οι goto να πηγαίνουν προς μία και μόνο κατεύθυνση.

 

Μόλις διάβασα το παραπάνω παράδειγμα με το goto μου ήρθε στο μυαλό να γράψω για τον κανόνα του ενός return αλλά με πρόλαβες γκρρρρ :P

 

Εκτός από απαίτηση καθηγητή μου σε ασκήσεις, το έχω δει και σε κάποια (σοβαρά) project να πρέπει να έχεις ένα μόνο return και σε όλα τα άλλα σημεία να θέτεις την τιμή που θέλεις στην μεταβλητή όπως το έκανες. Εκτός ότι σε πολλές περιπτώσεις ο κώδικας με τα πολλά return είναι πιο ευανάγνωστος μπορεί να είναι και πιο γρήγορος. Αντί να τρέχεις ολόκληρους βρόχους για ώρες ή να έχεις 10 else if μπορούν να μπουν όλες οι συνθήκες "αποτυχίας" στην αρχή ώστε να τελειώνει η συνάρτηση κατευθείαν.

 

Χαζά παραδείγματα:

int isprime(int n)
{
  int i, res = 1;

  for (i = 2; i < n; i++) {
    if ((n % i) == 0) {
      res = 0;
      /* το break όπως το goto είναι του διαβόλου 
         οπότε πρέπει να εκτελεστεί όλος ο βρόχος άσχετα αν δεν χρειάζεται */
    }
  }
  if (res) return 1; else return 0;  /* ***** */
}
Υπάρχουν πολλές συναρτήσεις που με το που πληρωθεί μία συνθήκη η δουλειά τελειώνει και δεν χρειάζεται να συνεχίσουμε το βρόχο. Για παράδειγμα ο αριθμός 273 διαιρείται με το 3 οπότε δεν χρειάζεται να ψάξουμε κανένα άλλο αριθμό αλλά το break και το goto είναι κακά οπότε όλες οι ασκήσεις θα είναι της παραπάνω μορφής και θα εξετάσουν 272 αριθμούς αντί να σταματήσουν στον 2 που εξέτασαν (το 3 δηλαδή).
int isprime(int n)
{
  int i;

  for (i = 2; i < n; i++) {
    if ((n % i) == 0)
      return 0;
  }
  return 1;
}
Με πολλαπλά return μπορεί να γίνει πολύ εύκολα έτσι και με το που θα βρει ένα αριθμό που να διαιρεί τον n αμέσως να επιστρέφει 0. Φυσικά μπορεί να βελτιωθεί πολύ περισσότερο αλλά το έθεσα σαν παράδειγμα.

 

Επίσης σημαντικό της κακής εκπαίδευσης και του γεγονότος ότι (τουλάχιστον στη δική μου σχολή) προγραμματισμός == "διδάσκω το συντακτικό μιας γλώσσας χωρίς να κάτσω να διδάξω απαραίτητες έννοιες και τρόπο σκέψης" είναι ότι στο 99% των ασκήσεων θα βρω την έκφραση που έγραψα το σχόλιο με τους αστερίσκους πριν. Σε κανένα δεν πάει το μυαλό να πει ότι η μορφή της έκφρασης είναι περιττή και μπορεί να γράψει "return res".

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

Ας πάρουμε λοιπόν για παράδειγμα μια συνάρτηση η οποία δημιουργεί δυναμικά και επιστρέφει έναν πίνακα (strTable) από strings όμοιου αλλά δυναμικά οριζόμενου μέγιστου μήκους (strLen) ...

/* --------------------------------------- */
char **new_strTable( int nelems, size_t strLen )
{
	int i = 0;
	char **ret = NULL;

	/* sanity check for nelems and strLen should be taken here */

	ret = calloc( nelems, sizeof(char *) );
	if ( NULL == ret )
		return NULL;

	for (i=0; i < nelems; i++)
	{
		ret[i] = calloc( strLen, sizeof(char) );
		if ( NULL == ret[i] )
		    goto cleanup_and_fail;
	}

	return ret;

cleanup_and_fail:
	for (int j = i-1; j > -1; j--) {
		free( ret[j] );
	}
	free( ret );
	return NULL;
}

Την έχω επίτηδες με goto σε περίπτωση που σκάσει η calloc() μέσα στο loop για οποιοδήποτε string. Σε αυτή την περίπτωση ο κώδικας πρέπει να απελευθερώσει την ήδη δεσμευμένη μνήμη για τα προηγούμενα strings, καθώς και τη μνήμη για τον πίνακα αυτόν κάθε αυτόν και να επιστρέψει αποτυχία.

 

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

 

 

 

    for (i=0; i < nelems; i++)
    {
        ret[i] = calloc( strLen, sizeof(char) );
        if ( NULL == ret[i] )
            break;
    }

    if (i != nelems)
    {
        for (int j = i-1; j > -1; j--) {
            free( ret[j] );
        }
        free( ret );
        ret = NULL;
    }

    return ret;

 

 

Αν ήμουν αναγκασμένος να γράψω αυτό τον κώδικα μπορεί και γω να έβαζα goto, αλλά γενικά σε production κώδικα εντελώς χοντρικά τα διαστήματα ανάμεσα στις φορές που αισθάνομαι ότι εδώ θέλει goto μετριούνται σε χρόνια.

 

Και for the record είμαι της άποψης ότι όσο περισσότερα ξέρεις τόσο πιο πολύ πρέπει να αντιστέκεσαι στη χρήση του goto (από την άλλη όταν όντως κάνεις χρήση, τόσο μεγαλύτερη η πιθανότητα να "χρειαζόταν" όντως). Ακριβώς όπως όσο πιο πολύ πάρε δώσε έχεις με φονικά εργαλεία τόσο πιο πολύ προσηλωμένος είσαι στα best practices ασφαλείας κλπ κλπ.

 

Η δεύτερη και πιο σημαντική παρατήρηση όμως είναι πως η υλοποίηση με συνάρτηση προσθέτει όχι μόνο extra stack overhead αλλά και sanity check overhead μέσα της, αφού στην καλύτερη περίπτωση θα πρέπει να ελέγχει το strTable ως προς NULL, και στην χειρότερη να παίρνει ως όρισμα και το nelems ώστε να κάνει validate το i.

 

Το συγκεκριμένο παράδειγμα δεν είναι κατάλληλο (εννοώ πως επειδή είναι απλό μπορούμε να την... σκαπουλάρουμε και χωρίς sanity checks μέσα στην cleanup_backwards() ή έστω να τα περιορίσουμε μονάχα στο strTable) αλλά νομίζω you get my point. Ειδικά αν αυτή η συνάρτηση αποτελεί public συνάρτηση μιας βιβλιοθήκης, τότε τα sanity checks είναι MUST.

 

Δύο πράγματα:

  1. Overhead my ass. Το 1970 έγραφαν κώδικα προσαρμοσμένο στα γούστα του compiler γιατί ο αδύναμος κρίκος ήταν ο compiler. To 2010 ο αδύναμος κρίκος είναι ο προγραμματιστής γιατί το μέγεθος και η πολυπλοκότητα των προγραμμάτων έχει ξεφύγει (million LOC κλπ), επομένως ο κώδικας πρέπει να είναι προσαρμοσμένος στα γούστα του προγραμματιστή. Δηλαδή readability, maintainability. Δε νομίζω ότι είναι δύσκολο να το αντιληφθεί κανείς αυτό (αφήνω απέξω κάποια niches όπου χρειάζεται τρελλό optimization και αναγκαστικά κάνεις νιντζιές).
  2. Η έκφραση sanity check είναι πολύ παρεξηγήσιμη. Αν τα preconditions και postconditions της function είναι σαφώς ορισμένα και τεκμηριωμένα τότε ο έλεγχος δε νομίζω πως λέγεται sanity test, είναι τελείως "κανονικός" αφού πρόκειται για τεκμηριωμένη συμπεριφορά της function. Αν πάλι δεν είναι τεκμηριωμένα (π.χ. δεν αναφέρεται πουθενά τι πρέπει να συμβεί όταν το size < 0), τότε δεν καταλαβαίνω ποιός ο λόγος να επιστρέψεις π.χ. 0 αντί για να συνεχίσεις σα να μη τρέχει τίποτα και ο,τι γίνει. Crash early.

 

Να κλείσω και με ένα ακόμα παράδειγμα που προκαλεί "διαμάχες" για το αν είναι ή όχι bad practice. Η χρήση πολλών return μέσα σε μια συνάρτηση, έναντι του ενός. Δηλαδή, ο παραπάνω κώδικας μπορεί να μετατραπεί ώστε να έχει ένα μονάχα return statement, το οποίο όμως συνήθως προκαλεί μεγαλύτερο code branching...

 

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

 

Αυτός ο τελευταίος κώδικας δεν χρησιμοποιεί κανένα από τα λιγότερο ή περισσότερο "απαγορευμένα" goto & break τα οποία "χαλάνε" βίαια την ομαλή ροή ενός θεωρητικά βέλτιστα δομημένου προγράμματος.

 

Το κατά πόσο όμως είναι πιο ευανάγνωστος, πιο αποδοτικός ή πόσο κι από τα δυο, είναι αλλουνού παπά ευαγγέλιο :)

 

Λέγεται maximum nesting level, όχι code branching. Και δεν προκαλείται ούτε από το break (πολέμιοι του break? δεν την παλεύουν καθόλου...) ούτε από το goto, προκαλείται από τις επιλογές αυτού που γράφει τον κώδικα. Παραπάνω έδωσα variation που δέν έχει goto (θα μπορούσε πανεύκολα να μην έχει ούτε break με την ίδια ακριβώς τεχνική του stop) και δεν έχει και παραπάνω nesting.

Εκτός από απαίτηση καθηγητή μου σε ασκήσεις, το έχω δει και σε κάποια (σοβαρά) project να πρέπει να έχεις ένα μόνο return και σε όλα τα άλλα σημεία να θέτεις την τιμή που θέλεις στην μεταβλητή όπως το έκανες. Εκτός ότι σε πολλές περιπτώσεις ο κώδικας με τα πολλά return είναι πιο ευανάγνωστος μπορεί να είναι και πιο γρήγορος. Αντί να τρέχεις ολόκληρους βρόχους για ώρες ή να έχεις 10 else if μπορούν να μπουν όλες οι συνθήκες "αποτυχίας" στην αρχή ώστε να τελειώνει η συνάρτηση κατευθείαν.

 

Φαντάζομαι πως θα υπάρχουν ειδικοί λόγοι για να είναι μια συνάρτηση όπως λένε SESE ("Single entry, single exit") σε συγκεκριμένες αλλά πολύ niche περιπτώσεις (ας πούμε στρατιωτικό software όπου εκτός των άλλων η διαδικασία πιστοποίησης ψοφάει το source σε static analysis), αλλά την απαίτηση του καθηγητή σου εδώ το έχω να τη χαρακτηρίσω τραγική. Τουλάχιστον αν ήταν σε C κάτι πάει κι έρχεται, αλλά σε οποιαδήποτε γλώσσα με exceptions...

 

Από την άλλη τα πολλά exit points αν χρησιμοποιούνται με άσχημο τρόπο (το έχω στο μυαλό μου κάπως σαν "επίτηδες το έκανες τόσο χάλια?") αυξάνουν το cyclomatic complexity, κάτι που μπλα μπλα μπλα είναι πολύ κακό.

Επίσης σημαντικό της κακής εκπαίδευσης και του γεγονότος ότι (τουλάχιστον στη δική μου σχολή) προγραμματισμός == "διδάσκω το συντακτικό μιας γλώσσας χωρίς να κάτσω να διδάξω απαραίτητες έννοιες και τρόπο σκέψης" είναι ότι στο 99% των ασκήσεων θα βρω την έκφραση που έγραψα το σχόλιο με τους αστερίσκους πριν.

 

True story #1: εξετάσεις στο πολυτεχνείο, σε C (από αυτά τα ωραία που σου ζητάνε να γράψεις κώδικα στο χαρτί... ασχολίαστο). Να διαβάζεται 6ψήφιος ακέραιος X και το πρόγραμμα να αποφαίνεται αν υπάρχουν ακέραιοι Α, Β ώστε 3Α = 5Β και A + B = Χ. Σχεδόν όλος ο κόσμος το έλυσε με for.

 

True story #2: εργασία στην Αγγλία υλοποίηση πάρε δώσε SNMP σε Java. Έχουμε πάει στο lab για να επιδείξουμε τις λύσεις μας. Μας είχαν δώσει sample κώδικα που συνδέεται με τον agent, ελέγχει κάτι, αποσυνδέεται. Η εργασία θέλει να ελέγξεις Ν πράγματα αντί για 1 του sample.

 

Όλος ο κόσμος έχει κάνει copy paste τον κώδικα όπως ήταν, με αποτέλεσμα να κάνει connect => check => disconnect => connect => check => disconnect => κλπ. Εγώ με τις φανταστικές μου γνώσεις για το πώς λειτουργεί η for κάνω connect => check => check => check => κλπ => disconnect.

 

Αποτέλεσμα: ο lab assistant που έλεγχε τις υλοποιήσεις, αφού έχει περάσει από αρκετούς πρώτα, φτάνει σε μένα. Τρέχω το πρόγραμμα και αντί να κάνει 5-10 sec (δε θυμάμαι αλλά αργούσαν) τελειώνει σε κλάσματα επειδή δε χρειάστηκε να κάνει μερικές χιλιάδες αχρείαστα connections. Συνηθισμένος από αυτά που είχε δει νωρίτερα, ο assistant κάθεται να ελέγξει ακριβώς τι έχω κάνει (δηλαδή μη τυχόν και έκλεψα και δεν κάνει το πρόγραμμα αυτά που θα έπρεπε).

 

Γενικά όποιος νομίζει ότι εκτός USA μαθαίνεις προγραμματισμό στις σχολές έχει στο μυαλό του λάθος ερμηνεία του τι σημαίνει προγραμματισμός...

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

 

 

    for (i=0; i < nelems; i++)
    {
        ret[i] = calloc( strLen, sizeof(char) );
        if ( NULL == ret[i] )
            break;
    }

    if (i != nelems)
    {
        for (int j = i-1; j > -1; j--) {
            free( ret[j] );
        }
        free( ret );
        ret = NULL;
    }

    return ret;

 

 

 

More readable my ass! (μιας κι εισήγαγες την ορολογία αυτή στη συζήτηση υποθέτω σου είναι αποδεκτή και στην αντίθετη κατεύθυνση)

 

α) Από που ακριβώς προκύπτει πως αυτός o συγκεκριμένος κώδικας είναι πιο ευανάγνωστος από τον αρχικό με το goto;

 

β) Για προσπάθησε να γράψεις μια πιο σύνθετη συνάρτηση από το απλοϊκό παράδειγμα που παρέθεσα (και το διευκρίνισα κιόλας στο προηγούμενο ποστ μου). Για προσπάθησε για παράδειγμα σε αυτή τη συνάρτηση να προσθέσεις κώδικα ανάμεσα στα 2 for-loops που παραθέτεις. Είμαι πολύ περίεργος να δω πόσο πιο readable ή/και πιο efficiently θα διαχειριστείς την οπτική απομάκρυνση του cleanup από την πηγή του σφάλματος (να σου δώσω κι ένα hint: χωρίς χρήση συνάρτησης, macro είναι οκ).

 

Πες π.χ. ότι θες να δημιουργήσεις το string-table από tokens που παρσάρεις on the fly από ένα αρχείο και κατόπιν να τυπώσεις τα strings σε αντίστροφη σειρά. Βάλε σε παρακαλώ και τα απαραίτητα error checks όχι μόνο για επιτυχή δέσμευση της μνήμης αλλά και για επιτυχή ανάγνωση των tokens από το αρχείο, κλπ.

 

Είμαι περίεργος να δω αν και πόσο πιο ευανάγνωστα θα το κάνεις χωρίς goto . Το συγκεκριμένο παράδειγμα μόλις το έβγαλα από το κεφάλι μου, και ίσως να μην είναι και το πιο αντιπροσωπευτικό σε αυτό που θέλω να τονίσω, αλλά ελπίζω πως το point got taken (η ελπίδα πεθαίνει πάντα τελευταία).

 

γ) Άσχετο με το παραπάνω, αλλά είμαι επίσης πολύ περίεργος να δω C κώδικα που σπάει ευανάγνωστα σύνθετο deep-nesting χωρίς goto.

 

Αν ήμουν αναγκασμένος να γράψω αυτό τον κώδικα μπορεί και γω να έβαζα goto, αλλά γενικά σε production κώδικα εντελώς χοντρικά τα διαστήματα ανάμεσα στις φορές που αισθάνομαι ότι εδώ θέλει goto μετριούνται σε χρόνια.

 

Γενικά λοιπόν, θεωρείς τον εαυτό σου αντιπροσωπευτικό παράδειγμα προγραμματιστή που παράγει production κώδικα σε C; Ρωτάω επειδή έφερες τον εαυτό σου ως παράδειγμα που έχει χρόνια να χρησιμοποιήσει goto σε production κώδικα.

 

Έχεις προγραμματίσει λοιπόν για παράδειγμα σε C (ή έστω έχεις συμμετάσχει σε) production κώδικα ας πούμε για κάποιον driver? Για κάποιο OS kernel ίσως; Για κάποιον compiler? Για κάποια database μήπως; Για κάποιο graphics engine ίσως; Μήπως για κάποια CAD/3D modeling εφαρμογή/βιβλιοθήκη; Για κάποιον micro-controller; Για κάποιο περιβάλλον με συγκεκριμένους και περιορισμένους πόρους μήπως; Κάποιο σύστημα κρυπτογράφησης; Κάποιο networking API μήπως;

 

Όλα αυτά (και αρκετά ακόμα) είναι καραμπινάτα πεδία στα οποία η C χρησιμοποιείται κατά κόρον και σήμερα. Ξέρω, ξέρω... "Overhead my ass!".

 

Και for the record είμαι της άποψης ότι όσο περισσότερα ξέρεις τόσο πιο πολύ πρέπει να αντιστέκεσαι στη χρήση του goto (από την άλλη όταν όντως κάνεις χρήση, τόσο μεγαλύτερη η πιθανότητα να "χρειαζόταν" όντως). Ακριβώς όπως όσο πιο πολύ πάρε δώσε έχεις με φονικά εργαλεία τόσο πιο πολύ προσηλωμένος είσαι στα best practices ασφαλείας κλπ κλπ.

 

Μάλιστα, ο Donald Knuth ήξερε λίγα άραγε όταν έγραφε το (btw, για πολλούς C programmers "Ευαγγέλιο" ακόμα και σήμερα): Structured Programming with go το Statements; O Leavenworth; Ο (αθυρόστομος και μηδενιστής, αλλά σίγουρα όχι άσχετος) Linus Torvalds? Ο άγνωστος Χ; O Dijkstra όταν δήλωνε "Please don't fall into the trap of believing that I am terribly dogmatical about [the go to statement]. I have the uncomfortable feeling that others are making a religion out of it, as if the conceptual problems of programming could be solved by a single trick, by a simple form of coding discipline!" αρκετά καιρό μετά από την δημοσίευση του A case against goto statement, όταν είδε την αντίδραση που προκάλεσε.

 

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

 

Και όλοι τους συμφωνούν όχι μόνο πως η χρήση της goto μπορεί να αποδειχτεί ιδιαίτερα χρήσιμη κατά περιπτώσεις (και ειδικά στην πράξη) αλλά και ότι το efficiency does matter (και ειδικά στην πράξη).

 

Αλλά ξέρω, ξέρω: "Overhead my ass"!

 

Δύο πράγματα:

  • Overhead my ass. Το 1970 έγραφαν κώδικα προσαρμοσμένο στα γούστα του compiler γιατί ο αδύναμος κρίκος ήταν ο compiler. To 2010 ο αδύναμος κρίκος είναι ο προγραμματιστής γιατί το μέγεθος και η πολυπλοκότητα των προγραμμάτων έχει ξεφύγει (million LOC κλπ), επομένως ο κώδικας πρέπει να είναι προσαρμοσμένος στα γούστα του προγραμματιστή. Δηλαδή readability, maintainability. Δε νομίζω ότι είναι δύσκολο να το αντιληφθεί κανείς αυτό (αφήνω απέξω κάποια niches όπου χρειάζεται τρελλό optimization και αναγκαστικά κάνεις νιντζιές).

 

 

 

Ξανά μάλιστα. Δε ξέρω αν το γνωριζεις αλλά σε 1 μόλις παράγραφο συμπυκνώνεις αβίαστα το πόσο κολλημένος είσαι (πιθανολογώ χωρίς καν να το ξέρεις) στην, κατά Sutter "Χαμένη Δεκαετία".

 

Σου προτείνω λοιπόν ανεπιφύλακτα να αφιερώσεις όταν μπορέσεις 45 λεπτά από τη ζωή σου και να δεις αυτό το βίντεο: http://channel9.msdn.com/posts/C-and-Beyond-2011-Herb-Sutter-Why-C (βασικά το προτείνω σε όλους ανεξαιρέτως).

 

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

 

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

 

Btw, η PHP 5.3 εισήγαγε την goto το 1970; Μήπως (κατά διαβολική σύμπτωση) το εισήγαγε το 2010; Ερώτηση κάνω :P

 

 

 

  • Η έκφραση sanity check είναι πολύ παρεξηγήσιμη. Αν τα preconditions και postconditions της function είναι σαφώς ορισμένα και τεκμηριωμένα τότε ο έλεγχος δε νομίζω πως λέγεται sanity test, είναι τελείως "κανονικός" αφού πρόκειται για τεκμηριωμένη συμπεριφορά της function. Αν πάλι δεν είναι τεκμηριωμένα (π.χ. δεν αναφέρεται πουθενά τι πρέπει να συμβεί όταν το size < 0), τότε δεν καταλαβαίνω ποιός ο λόγος να επιστρέψεις π.χ. 0 αντί για να συνεχίσεις σα να μη τρέχει τίποτα και ο,τι γίνει. Crash early.

 

 

 

Διότι προσπαθείς να επιχειρηματολογήσεις βασιζόμενος σε γενικό συμπέρασμα που βγάζεις από ένα υπερ-απλουστευμένο παράδειγμα, παρόλο και που το χαρακτήρισα ως τέτοιο και έγραψα κι επί τούτου πως μια public τέτοια συνάρτηση σε μια βιβλιοθήκη θεωρείται must να κάνει sanity checks.

 

Εκτός αν δεν κατάλαβα τι εννοείς παραπάνω.

 

Με την ευκαιρία, σε function context η πιο συνηθισμένη ερμηνεία του sanity-check είναι σε αναφορά με τα ορίσματα της συνάρτησης.

 

Επίσης, τροφή για σκέψη: αν θεωρήσουμε δεδομένο πως ο programmer όντως χρησιμοποιεί ας intended τα τεκμηριωμένα pre & post conditions μια συνάρτησης, τότε έχουμε έναν λόγο παραπάνω να την μετατρέψουμε σε macro προς όφελος των επιδόσεων.

 

Λέγεται maximum nesting level, όχι code branching.

 

H Microsoft (το 1ο παράδειγμα που έπεσε μπροστά μου) το γνωρίζει άραγε, ώστε να μην γράφει ας πούμε μπούρδες και παραπληροφορεί τον κόσμο;

 

O Knuth? ...

http://pplab.snu.ac.kr/courses/adv_pl05/papers/p261-knuth.pdf (Pg #290 or 30 in pdf, and in so many other pages)

...

There is one remaining use of go to for which I have never seen a good replacement, and in fact it's a situation where I still think go to is the right idea. This situation typically occurs after a program has made a multiway branch to a rather large number of different but related cases. A little com- putation often suffices to reduce one case to another; and when we've reduced one problem to a simpler one, the most natural thing is for our program to go to the routine which solves the simpler problem. For example, consider writing an interpre- tive routine (e.g., a microprogrammed emulator), or a simulator of another com- puter. After decoding the address and fetch- ing the operand from memory, we do a multiway branch based on the operation code. Let's say the operations include no-op, add, subtract, jump on overflow, and uncon- ditional jump.

...

 

True story #1: εξετάσεις στο πολυτεχνείο, σε C (από αυτά τα ωραία που σου ζητάνε να γράψεις κώδικα στο χαρτί... ασχολίαστο). Να διαβάζεται 6ψήφιος ακέραιος X και το πρόγραμμα να αποφαίνεται αν υπάρχουν ακέραιοι Α, Β ώστε 3Α = 5Β και A + B = Χ. Σχεδόν όλος ο κόσμος το έλυσε με for.

 

True story #2: εργασία στην Αγγλία υλοποίηση πάρε δώσε SNMP σε Java. Έχουμε πάει στο lab για να επιδείξουμε τις λύσεις μας. Μας είχαν δώσει sample κώδικα που συνδέεται με τον agent, ελέγχει κάτι, αποσυνδέεται. Η εργασία θέλει να ελέγξεις Ν πράγματα αντί για 1 του sample.

 

Όλος ο κόσμος έχει κάνει copy paste τον κώδικα όπως ήταν, με αποτέλεσμα να κάνει connect => check => disconnect => connect => check => disconnect => κλπ. Εγώ με τις φανταστικές μου γνώσεις για το πώς λειτουργεί η for κάνω connect => check => check => check => κλπ => disconnect.

 

Αποτέλεσμα: ο lab assistant που έλεγχε τις υλοποιήσεις, αφού έχει περάσει από αρκετούς πρώτα, φτάνει σε μένα. Τρέχω το πρόγραμμα και αντί να κάνει 5-10 sec (δε θυμάμαι αλλά αργούσαν) τελειώνει σε κλάσματα επειδή δε χρειάστηκε να κάνει μερικές χιλιάδες αχρείαστα connections. Συνηθισμένος από αυτά που είχε δει νωρίτερα, ο assistant κάθεται να ελέγξει ακριβώς τι έχω κάνει (δηλαδή μη τυχόν και έκλεψα και δεν κάνει το πρόγραμμα αυτά που θα έπρεπε).

 

Γενικά όποιος νομίζει ότι εκτός USA μαθαίνεις προγραμματισμό στις σχολές έχει στο μυαλό του λάθος ερμηνεία του τι σημαίνει προγραμματισμός...

Ωραία! Κανένα παράδειγμα από δικό σου production code με C σε απευθείας σύγκριση με επαγγελματίες του χώρου έχεις να μας εξιστορήσεις; Θα ήταν πιο ενδιαφέρον πιστεύω (κατά προτίμηση σε σύγκριση με Αμερικάνους επαγγελματίες, μιας που ανέφερες και την Αμερική).

 

Για τα παρακάτω, σε παραπέμπω στα παραπάνω (κρίμα είναι να διπλογράφω)...

 

Και δεν προκαλείται ούτε από το break (πολέμιοι του break? δεν την παλεύουν καθόλου...) ούτε από το goto, προκαλείται από τις επιλογές αυτού που γράφει τον κώδικα. Παραπάνω έδωσα variation που δέν έχει goto (θα μπορούσε πανεύκολα να μην έχει ούτε break με την ίδια ακριβώς τεχνική του stop) και δεν έχει και παραπάνω nesting.

 

Φαντάζομαι πως θα υπάρχουν ειδικοί λόγοι για να είναι μια συνάρτηση όπως λένε SESE ("Single entry, single exit") σε συγκεκριμένες αλλά πολύ niche περιπτώσεις (ας πούμε στρατιωτικό software όπου εκτός των άλλων η διαδικασία πιστοποίησης ψοφάει το source σε static analysis), αλλά την απαίτηση του καθηγητή σου εδώ το έχω να τη χαρακτηρίσω τραγική. Τουλάχιστον αν ήταν σε C κάτι πάει κι έρχεται, αλλά σε οποιαδήποτε γλώσσα με exceptions...

 

Από την άλλη τα πολλά exit points αν χρησιμοποιούνται με άσχημο τρόπο (το έχω στο μυαλό μου κάπως σαν "επίτηδες το έκανες τόσο χάλια?") αυξάνουν το cyclomatic complexity, κάτι που μπλα μπλα μπλα είναι πολύ κακό.

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

 

More readable my ass! (μιας κι εισήγαγες την ορολογία αυτή στη συζήτηση υποθέτω σου είναι αποδεκτή και στην αντίθετη κατεύθυνση)

 

α) Από που ακριβώς προκύπτει πως αυτός o συγκεκριμένος κώδικας είναι πιο ευανάγνωστος από τον αρχικό με το goto;

 

β) Για προσπάθησε να γράψεις μια πιο σύνθετη συνάρτηση από το απλοϊκό παράδειγμα που παρέθεσα (και το διευκρίνισα κιόλας στο προηγούμενο ποστ μου). Για προσπάθησε για παράδειγμα σε αυτή τη συνάρτηση να προσθέσεις κώδικα ανάμεσα στα 2 for-loops που παραθέτεις. Είμαι πολύ περίεργος να δω πόσο πιο readable ή/και πιο efficiently θα διαχειριστείς την οπτική απομάκρυνση του cleanup από την πηγή του σφάλματος (να σου δώσω κι ένα hint: χωρίς χρήση συνάρτησης, macro είναι οκ).

 

Reading comprehension problems; Πουθενά δε λέω τίποτα για "more readable". Κατανοώ βέβαια την ανάγκη σου να κοντραριστείς (το πρώτο αναγκαίο βήμα στην προσπάθεια να αποδειχτείς καλύτερος λες και είμαστε νηπιαγωγείο), αλλά αυτό που δείχνω είναι το πώς γίνεται εξίσου καλά χωρίς goto και χωρίς να χρειάζεται να γίνεις Χουντίνι. Αν νομίζεις πως δε σε καλύπτει ως έχει και θέλεις more readability, βάλε πρώτα ένα int allocationFailed = i != nElems; για να μπορείς μετά να γράψεις if (allocationFailed).

 

 

Μπλα μπλα μπλα

 

More reading comprehension problems, όσον αφορά το ποιά είναι η στάση μου σχετικά με τη χρήση της goto (spoiler: αν τη χρησιμοποιήσεις δε θα σε σταυρώσω). Ίσως χρειάζεται να αφιερώνεις περισσότερο χρόνο στο προσεκτικό διάβασμα.

 

 

Γενικά λοιπόν, θεωρείς τον εαυτό σου αντιπροσωπευτικό παράδειγμα προγραμματιστή που παράγει production κώδικα σε C; Ρωτάω επειδή έφερες τον εαυτό σου ως παράδειγμα που έχει χρόνια να χρησιμοποιήσει goto σε production κώδικα.

 

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

 

 

Εκτός αν δεν κατάλαβα τι εννοείς παραπάνω.

 

Bingo!

 

  H Microsoft (το 1ο παράδειγμα που έπεσε μπροστά μου) το γνωρίζει άραγε, ώστε να μην γράφει ας πούμε μπούρδες και παραπληροφορεί τον κόσμο;

 

Φαίνεται σα να μη μπορείς να καταλάβεις ότι nesting και branching είναι δύο διαφορετικά πράγματα, τα οποία μπορεί να συμβαίνουν ξεχωριστά ή και ταυτόχρονα. Και λέω φαίνεται γιατί προφανώς μπορείς να το καταλάβεις, αλλά αν όντως επέλεγες να το κάνεις πώς θα είχαμε κόντρα; Νηπιαγωγείο.

 

Στην προκειμένη περίπτωση που σχολίασα πράγματι συμβαίνουν ταυτόχρονα αλλά το branching που είπες δε μας ενδιαφέρει καθόλου (γιατί προφανώς δεν το γλυτώνεις, ένα if ή κάτι αντίστοιχο κάπου θα υπάρχει ο,τι κι αν κάνεις) -- αυτό που μας ενδιαφέρει είναι το nesting (το οποίο είναι στην ευχέρειά σου αν θα το κάνεις ή όχι).

 

 

O Knuth? ... Ωραία! Κανένα παράδειγμα από δικό σου production code με C σε απευθείας σύγκριση με επαγγελματίες του χώρου έχεις να μας εξιστορήσεις; Θα ήταν πιο ενδιαφέρον πιστεύω (κατά προτίμηση σε σύγκριση με Αμερικάνους επαγγελματίες, μιας που ανέφερες και την Αμερική).

 

Εγώ παιδιά λέω να μαζευτείτε όλοι αυτοί που πιστεύετε ότι κάτι σας χρωστάω να ρίξετε μια ματιά αν κουνιούνται οι βάρκες στο Κάιρο. Μόνο για να μην απογοητευτείτε, το λέω από τώρα ότι production code σε C δεν έχω.

 

Αυτά. Επιπλέον, επειδή σήμερα υπάρχει δουλειά, ξεκίνα αν θέλεις να μαλώνεις μόνος σου απο κάτω και γω μπορεί να περάσω αργότερα.

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

...

Ένας μπακάλικος μπούσουλας για την goto που προτείνουν πολλοί είναι όλες οι goto να πηγαίνουν προς μία και μόνο κατεύθυνση.

...

 

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

 

Αυτό δηλαδή που έκανε ο φίλος μας και του το επισημάναμε όλοι.

 

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

 

:lol: :lol: :lol:

Καλό! Το άλλο με τον Τοτό το ξέρεις;

 

Reading comprehension problems; Πουθενά δε λέω τίποτα για "more readable". Κατανοώ βέβαια την ανάγκη σου να κοντραριστείς (το πρώτο αναγκαίο βήμα στην προσπάθεια να αποδειχτείς καλύτερος λες και είμαστε νηπιαγωγείο), αλλά αυτό που δείχνω είναι το πώς γίνεται εξίσου καλά χωρίς goto και χωρίς να χρειάζεται να γίνεις Χουντίνι. Αν νομίζεις πως δε σε καλύπτει ως έχει και θέλεις more readability, βάλε πρώτα ένα int allocationFailed = i != nElems; για να μπορείς μετά να γράψεις if (allocationFailed).

 

 

More reading comprehension problems, όσον αφορά το ποιά είναι η στάση μου σχετικά με τη χρήση της goto (spoiler: αν τη χρησιμοποιήσεις δε θα σε σταυρώσω). Ίσως χρειάζεται να αφιερώνεις περισσότερο χρόνο στο προσεκτικό διάβασμα.

 

 

Bingo!

 

 

Φαίνεται σα να μη μπορείς να καταλάβεις ότι nesting και branching είναι δύο διαφορετικά πράγματα, τα οποία μπορεί να συμβαίνουν ξεχωριστά ή και ταυτόχρονα. Και λέω φαίνεται γιατί προφανώς μπορείς να το καταλάβεις, αλλά αν όντως επέλεγες να το κάνεις πώς θα είχαμε κόντρα; Νηπιαγωγείο.

 

Στην προκειμένη περίπτωση που σχολίασα πράγματι συμβαίνουν ταυτόχρονα αλλά το branching που είπες δε μας ενδιαφέρει καθόλου (γιατί προφανώς δεν το γλυτώνεις, ένα if ή κάτι αντίστοιχο κάπου θα υπάρχει ο,τι κι αν κάνεις) -- αυτό που μας ενδιαφέρει είναι το nesting (το οποίο είναι στην ευχέρειά σου αν θα το κάνεις ή όχι).

 

 

Εγώ παιδιά λέω να μαζευτείτε όλοι αυτοί που πιστεύετε ότι κάτι σας χρωστάω να ρίξετε μια ματιά αν κουνιούνται οι βάρκες στο Κάιρο. Μόνο για να μην απογοητευτείτε, το λέω από τώρα ότι production code σε C δεν έχω.

 

Αυτά. Επιπλέον, επειδή σήμερα υπάρχει δουλειά, ξεκίνα αν θέλεις να μαλώνεις μόνος σου απο κάτω και γω μπορεί να περάσω αργότερα.

 

* Σημείωση: το strike-through στην παράθεση το προσέθεσα εγώ.

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

@defacer

 

 

 

Mην κανεις πως δεν καταλαβαινεις. Το σωστο οταν βγαινεις και λες πραγματα ειναι να περιμενεις και ερωτησεις και να μπορεις να απαντησεις κιολας. Αλλιως δεν σεβεσαι τους συνομιλητες σου.

 

Στο ειπα και τις προαλλες κουτοι δεν ειμαστε να ερχεται καποιος να μας λεει κατι και να μην τον ρωταμε μετα.

Eσυ δηλαδη τι πρεσβευεις ακριβως? να βγαινει καποιος να γραφει οτι θελει και μετα αμα τον ρωτησεις κατι σχετικα με αυτα να σου λεει "Δεν σου χρωσταω???" χαχαχαχαχαχαχχαχαχα

 

ελεος ρε φιλε.

 

 

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

@Star_Light:

 

Σε καμία περίπτωση μην επιτρέψεις στο γεγονός ότι απαντώ με αηδιαστική αν χρειαστεί λεπτομέρεια σε όλα τα μέλη του forum εκτός από του κουφού τις πόρτες να σου χαλάσει την επιχειρηματολογία. Και κάνε μου σε παρακαλώ μερικά μαθήματα ακόμα περι σεβασμού, υπάρχουν βέβαια πολλά post σου εδώ από τα οποία μπορώ να μάθω αλλά ως λίγο κουτός δυσκολεύομαι.

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

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

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

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

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

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

Σύνδεση

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

Συνδεθείτε τώρα

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