C/C++ compiler ερώτηση

Giorgos Keramidas keramida at ceid.upatras.gr
Sun Jun 24 13:47:05 EEST 2012


On Sat, 23 Jun 2012 09:50:41 +0300, Theodore Lytras <thlytras at gmail.com> wrote:
> Καλησπέρα στη λίστα,
>
> θα ήθελα να κάνω μια ερώτηση, δε ξέρω αν είναι αυτονόητη, απλά ήθελα
> να είμαι σίγουρος.
>
> Σε ένα λογικό έλεγχο της μορφής: 
>
> if (A && B) { blabla(); }
>
> μπορώ να είμαι σίγουρος οτι το A θα ελεγχθεί πάντα πριν από το B, και
> οτι αν το A είναι ψευδές το B δε θα ελεγχθεί καθόλου???

Ναι μπορείς να είσαι 100% σίγουρος.

Αυτό που περιγράφεις λέγεται «short-circuit evaluation» και είναι πολύ
χρήσιμο εργαλείο.  Μάλιστα το παράδειγμα που έγραψες ακριβώς μετά:

> Το background της ερώτησης: μπορώ να είμαι σίγουρος οτι το παρακάτω δε
> θα κάνει ποτέ segfault?
> [...]
> Foo* f = 0;
> ....  // various commands processing f
> if (f && f->isSomething()) { blabla(); }

είναι από τις πιο _συχνές_ περιπτώσεις που έχει νόημα το short-circuit
evaluation.  Αν δεν έπαιζε έτσι όπως κατάλαβες ότι παίζει, θα έπρεπε να
γράφουμε πάντα κάτι σαν:

    if (f) {
      if (f->isSomething) {
        blabla();
      }
    }

Ενώ τώρα μπορείς απλά να γράψεις αυτό που σου ήρθε φυσικά:

    if (f && f->isSomething())
      blabla();

                                   --

Πάρα πολλά παραδείγματα από short-circuit evaluation έχει o networking
κώδικας π.χ. του FreeBSD.  Ορισμένα παραδείγματα από τον κώδικα του
αρχείου ip_input.c είναι παρακάτω:

http://www.freebsd.org/cgi/cvsweb.cgi/src/sys/netinet/ip_input.c?annotate=1.396

.. lines 406-410

  406:        if (m->m_len < sizeof (struct ip) &&
  407:            (m = m_pullup(m, sizeof (struct ip))) == NULL) {
  408:                IPSTAT_INC(ips_toosmall);
  409:                return;
  410:        }

Εδώ το m είναι 'struct mbuf', το οποίο αποθηκεύει τα δεδομένα ενός
πακέτου.  Η συνάρτηση m_pullup() κάνει defragmentation από τα αρχικά
sizeof(struct ip) bytes, αλλά αν την καλέσεις με mbuf που δεν έχει
αρκετά δεδομένα θα 'χει πρόβλημα.  Το short-circuit evaluation εγγυάται
ότι όταν η τιμή του m->m_len είναι μικρότερη από το σωστό μέγεθος η
m_pullup() δε θα κληθεί *ποτέ*.

.. lines 455-459

  455: #ifdef ALTQ
  456:        if (altq_input != NULL && (*altq_input)(m, AF_INET) == 0)
  457:                /* packet is dropped by traffic conditioner */
  458:                return;
  459: #endif

Αυτό είναι _ακριβώς_ το παράδειγμα που έδωσες κι εσύ.  Αν το pointer
altq_input είναι non-NULL, δείχνει σε μια συνάρτηση που πρέπει να τρέξει
με input το πακέτο το ίδιο.  Αν είναι NULL όμως το δεύτερο κομμάτι του
expression, καθώς και όλο το if-body, δεν τρέχει καν.

                                   --

Και αντίστοιχα παραδείγματα από το ip_output.c με παρόμοια λογική:

http://www.freebsd.org/cgi/cvsweb.cgi/src/sys/netinet/ip_output.c?annotate=1.331

.. lines 255-264

  255:        if (IN_MULTICAST(ntohl(ip->ip_dst.s_addr)) &&
  256:            imo != NULL && imo->imo_multicast_ifp != NULL) {
  257:                /*
  258:                 * Bypass the normal routing lookup for multicast
  259:                 * packets if the interface is specified.
  260:                 */
  261:                ifp = imo->imo_multicast_ifp;
  262:                IFP_TO_IA(ifp, ia);
  263:                isbroadcast = 0;        /* fool gcc */
  264:        }

στη γραμμή 256 πάλι το ίδιο στυλ από έλεγχο: αν το 'imo' είναι NULL το
imo->imo_multicast_ifp είναι invalid memory access.  Όμως ακριβώς λόγω
short-circuit evaluation δεν υπάρχει κανένα πρόβλημα.

.. lines 308-322

  308:        /*
  309:         * Calculate MTU.  If we have a route that is up, use that,
  310:         * otherwise use the interface's MTU.
  311:         */
  312:        if (rte != NULL && (rte->rt_flags & (RTF_UP|RTF_HOST))) {
  313:                /*
  314:                 * This case can happen if the user changed the MTU
  315:                 * of an interface after enabling IP on it.  Because
  316:                 * most netifs don't keep track of routes pointing to
  317:                 * them, there is no way for one to update all its
  318:                 * routes when the MTU is changed.
  319:                 */
  320:                if (rte->rt_rmx.rmx_mtu > ifp->if_mtu)
  321:                        rte->rt_rmx.rmx_mtu = ifp->if_mtu;
  322:                mtu = rte->rt_rmx.rmx_mtu;
  323:        }

Το 'rte' (routing table entry) είναι pointer σε πληροφορίες routing για
το network interface που χρησιμοποιείται.  Κι εδώ υπάρχει ο ίδιος
ακριβώς έλεγχος: αν το 'rte' είναι no-NULL, τότε μόνο είναι ασφαλές να
προσπελάσει κανείς τα routing entry flags 'rte->rt_flags'.

                                   --

Τα παραδείγματα σε όλο τον υπόλοιπο κώδικα είναι _πάρα_ πολλά.  Εγώ
προσωπικά το θεωρώ δείγμα «καλής γραφής» να βλέπω ότι ο κώδικας
χρησιμοποιεί με έξυπνο τρόπο το short-circuit evaluation.  Αν δω σε code
review κάτι σαν:

    if (condition1) {
        if (condition2) {
            if (condition3 || condition4) {
                kwdikas_edw();
            }
        }
    }

ζητάω πάντα να αλλάξει σε:

    if (condition1 && condition2 && (condition3 || condition4))
        kwdikas_edw();

Χωρίς _καμία_ εξαίρεση.



More information about the Linux-greek-users mailing list