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

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

Δημοσ.

niki.txt

καλημερα,θελω να μαθω πως γινεται ο παραπανω κωδικας να λειτουργει σωστα αφου η συμβολοσειρα που υπαρχει μεσα στο αρχειο ειναι περιπου 20 χαρακτηρες και ο πινακας στον οποιο εκχωρειται ειναι n[1] . αμα γνωριζει καποιος ας βοηθησει.

ευχαριστω.

 

Δεν μπορω να καταλαβω γιατι λειτουργει σωστα.

Δημοσ.

Λειτουργεί σωστά "κατά τύχη".

 

Τι εννοώ: αυτό που συμβαίνει εδώ είναι ότι όταν καλείς τον operator >> περνώντας σαν παράμετρο το n (που είναι char[]) ο compiler αυτόματα "βλέπει" τον char[] ως char* στο πρώτο στοιχείο του πίνακα (αυτό ονομάζεται "array to pointer decay" και προβλέπεται από το στάνταρ της C++). Ο operator >> γράφει τα περιεχόμενα που διαβάζει στη διεύθυνση του n και επειδή γράφει μετά το τέλος του πίνακα έχουμε αυτό που ονομάζεται από το στάνταρ UB (undefined behavior).

 

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

 

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

 

Μία πιθανή εξήγηση: το πρόγραμμα "δουλεύει" γιατί όταν καλείται ο operator >> ο πίνακας n είναι το "τέρμα πάνω" αντικείμενο που βρίσκεται στο stack. Όταν συμβεί το buffer overrun (γιατί αυτό συμβαίνει), τα επιπλέον δεδομένα αποθηκεύονται στις επόμενες διευθύνσεις μνήμης πάνω στο stack οι οποίες από τη μία δεν χρησιμοποιούνται και από την άλλη με πολύ μεγάλη πιθανότητα βρίσκονται στην ίδια memory page με το n. H memory page έχει ήδη καταχωρηθεί για χρήση από το πρόγραμμά σου οπότε όλα καλά, και όταν η συνάρτησή σου επιστρέψει δεν έχει μείνει κάποιο κατάλοιπο από το overrun που να επηρρεάζει τη μετέπειτα λειτουργία του προγράμματος.

 

Άλλη πιθανή εξήγηση: Επειδή κάνεις compile με debug settings, ο compiler κάνει allocate περισσότερη μνήμη στο stack απ' όση ζητάς και τη χρησιμοποιεί για περιθώριο ασφαλείας (ας πούμε εσύ ζητάς 1 char και ο compiler αφήνει χώρο για 17, από τους οποίους σου λέει ότι ο ένας που ζήτησες είναι ο μεσαίος). Επομένως υπάρχει τώρα χώρος να κάνεις overrun το buffer μέχρι 8 chars προς οποιαδήποτε κατεύθυνση χωρίς να πειραχτεί κάτι που να έχει σημασία. (Αυτή την τεχνική την εφαρμόζουν σχεδόν όλοι οι compilers σε debug για να σε "βοηθήσουν" να ανακαλύψεις τυχόν buffer overruns. Το βοηθήσουν σε εισαγωγικά γιατί προσωπικά δεν είμαι πεπεισμένος ότι αυτό κάνει περισσότερο καλό απ' ότι κακό).

Δημοσ.

Και οι 2 εξηγησεις μου φαινονται λογικες.παντως δεν ηξερα οτι υπηρχε αυτος ο ορος.(UB)

ευχαριστω για τη βοηθεια . :D

Δημοσ.

παντως δεν ηξερα οτι υπηρχε αυτος ο ορος.(UB)

 

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

 

Σκόπιμα δεν επεκτάθηκα σε πολλά σχετικά θέματα όπως πρώτο και κυριότερο γιατί να υπάρχει UB στη C++, ότι μπορείς να γράψεις έναν templated operator << που να μην επιτρέπει τα buffer overrun στους πίνακες και άλλα.

 

Ρίξε και μια ματιά εδώ πάντως.

Δημοσ.

Και οι 2 εξηγησεις μου φαινονται λογικες.παντως δεν ηξερα οτι υπηρχε αυτος ο ορος.(UB)

ευχαριστω για τη βοηθεια . :D

 

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

 

Στο τέλος του προτύπου (τουλάχιστον της C αλλά υποθέτω και της C++) αναφέρονται όλες οι περιπτώσεις unspecified/undefined/implementation-defined behavior. Η διαφορά είναι ότι στις περιπτώσεις που ορίζονται ως "impementation-defined", η υλοποίηση μπορεί μεν να κάνει ό,τι θέλει αλλά πρέπει να αναφέρει ποια συμπεριφορά ακολουθεί.

 

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

 

Ένα κλασικό παράδειγμα που δίνεται για να κατανοηθεί η "βοήθεια" που παρέχει η undefined behavior είναι η υπερχείλιση ακεραίου με πρόσημο.

 

>
#include <stdio.h>

int main(void)
{
int i;

scanf("%d",&i);

return (i+1) > i;
}

 

Δες το παραπάνω χαζό παράδειγμα (Παράβλεψε την χρήση της scanf). Ζητάει ένα αριθμό και ελέγχει αν υπάρχει υπερχείλιση. Το πρότυπο αναφέρει την συμπεριφορά των unsigned τύπων όσον αφορά την υπερχείλιση αλλά δεν λέει τίποτα για τους signed οπότε ο compiler μπορεί να κάνει ό,τι θέλει.

 

Άρα ο compiler λέει για να δούμε τι περιπτώσεις έχουμε:

α) Δεν υπάρχει υπερχείλιση οπότε το i+1 θα είναι όντως μεγαλύτερο του i άρα ισχύει η συνθήκη και πρέπει να επιστρέψω 1

β) Υπάρχει υπερχείλιση οπότε μπορώ να κάνω ό,τι θέλω άρα μπορώ να επιστρέψω και πάλι 1

 

Έτσι με αυτό το σκεπτικό δεν ελέγχει καθόλου και επιστρέφει πάντα 1. Έτσι ο compiler χρησιμοποιεί την undefined behavior για να παράξει πιο γρήγορο κώδικα αλλά επίσης μπορεί να μας δημιουργήσει προβλήματα για αυτό πρέπει πάντα να αποφεύγεται.

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

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

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

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

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

Σύνδεση

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

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