C Compiler, Segmentantion fault

Giorgos Keramidas keramida at ceid.upatras.gr
Sat Mar 31 19:07:59 EEST 2007


On 2007-03-31 13:37, John Salatas <jsalatas at acn.gr> wrote:
> Χαιρετώ,
> έχω το εξής απλό προγραμματάκι

Δεν είναι ιδιαίτερα 'απλό' προγραμματάκι, κι έχει ήδη σοβαρά bugs.

Με σχετικά διορθωμένο το indentation, που είχε διαλύσει μάλλον το mailer
σου, και αριθμούς γραμμών για να ξέρουμε για τι μιλάμε κάθε φορά, είναι:

  1 | #include <stdio.h>
  2 | #include <stdlib.h>
  3 | #include <limits.h>
  4 | 
  5 | int main(int argc, char *argv[])
  6 | {
  7 |         signed short int intSignedShort;
  8 |         unsigned short int intUnsignedShort;
  9 |         signed long int intSignedLong;
 10 |         unsigned long int intUnsignedLong;
 11 |         signed int intSigned;
 12 |         unsigned int intUnsigned;
 13 | 
 14 |         printf("Δώσε τον signed short int (min: %d, max: %d):\t",
 15 |             SHRT_MIN, SHRT_MAX);
 16 |         scanf("%d", &intSignedShort);
 17 | 
 18 |         printf("Δώσε τον unsigned short int (min: %u, max: %u):\t",
 19 |             0, USHRT_MAX);
 20 |         scanf("%u", &intUnsignedShort);
 21 | 
 22 | //      intUnsignedShort=1;
 23 |         printf("Δώσε τον signed long int (min: %d, max: %d):\t",
 24 |             LONG_MIN, LONG_MAX);
 25 |         scanf("%d", &intSignedLong);
 26 | 
 27 |         printf("Δώσε τον unsigned long int (min: %u, max: %u):\t",
 28 |             0, ULONG_MAX);
 29 |         scanf("%u", &intUnsignedLong);
 30 | 
 31 |         printf("Δώσε τον signed int (min: %d, max: %d):\t",
 32 |             INT_MIN, INT_MAX);
 33 |         scanf("%d", &intSigned);
 34 | 
 35 |         printf("Δώσε τον unsigned int (min: %u, max: %u):\t", 0, UINT_MAX);
 36 |         scanf("%u", &intUnsigned);
 37 | 
 38 | //      intUnsigned=1;
 39 | 
 40 |         printf("Ο επόμενος signed short int του %d είναι ο %d\n",
 41 |             intSignedShort, intSignedShort+1);
 42 |         printf("Ο επόμενος unsigned short int του %u είναι ο %u\n",
 43 |             intUnsignedShort, intUnsignedShort+1);
 44 |         printf("Ο επόμενος signed long int του %d είναι ο %d\n",
 45 |             intSignedLong, intSignedLong+1);
 46 |         printf("Ο επόμενος unsigned long int του %u είναι ο %u\n",
 47 |             intUnsignedLong, intUnsignedLong+1);
 48 |         printf("Ο επόμενος signed int του %d είναι ο %d\n",
 49 |             intSigned, intSigned+1);
 50 |         printf("Ο επόμενος unsigned int του %u είναι ο %u\n",
 51 |             intUnsigned, intUnsigned+1);
 52 | 
 53 |         return EXIT_SUCCESS;
 54 | }

Τα προβλήματα που βλέπω εγώ είναι:

------------------------------------------------------------------------
* Warning #1.

  Στη γραμμή 5, δηλώνεις ότι η main() παίρνει (argc, argv) arguments,
  αλλά μετά δεν τα χρησιμοποιείς πουθενά.  Αυτό δεν είναι ακριβώς error,
  αλλά σίγουρα θα σου βγάλει ο compiler warning αν τον χρησιμοποιήσεις
  με πιο αυστηρό τρόπο.

------------------------------------------------------------------------
* Error #1

  Στη γραμμή 14 τυπώνεις τιμές που είναι 'signed short' με format
  specifier που ταιριάζει σε int values:

    printf("Δώσε τον signed short int (min: %d, max: %d):\t",
        SHRT_MIN, SHRT_MAX);

  Το σωστό format specifier για short int είναι "%hd" (βλ. C99 standard,
  παράγραφος 4.9.6.1, ``The fprintf function'').

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

------------------------------------------------------------------------
* Στη γραμμή 7 η μεταβλητή intSignedShort ορίζεται ως αντικείμενο τύπου
  `signed short int', αλλά στη γραμμή 16 περνάς ως όρισμα στη scanf()
  ένα pointer σε αυτή την ίδια μεταβλητή με λάθος format specifier πάλι
  (%d αντί για %hd).

  7 |         signed short int intSignedShort;
  ...
 16 |         scanf("%d", &intSignedShort);

  Αυτό σημαίνει πως μπορεί η υλοποίηση της scanf() να προσπαθήσει να
  γράψει σε μια περιοχή με K = sizeof(signed short int) bytes,
  αποθηκεύοντας όμως M = sizeof(int) bytes.  Σε όλα τα μηχανήματα που
  ισχύει ότι K < M (δηλαδή σχεδόν σε όλα όσα έχω δει εγώ μέχρι σήμερα),
  αυτό μπορεί να έχει 'ενδιαφέροντα' αποτελέσματα, όπως το segmentation
  fault που είδες.

  Ειδικά στην περίπτωση του προγράμματος που γράφεις εσύ, οι automatic
  μεταβλητές της main() μπορεί να αποθηκεύονται στη runtime στοίβα της
  main() function.  Με λάθος format specifiers, η scanf() θα αρχίσει να
  γράφει στη runtime διεύθυνση που της περνάς και θα συνεχίσει να γράφει
  «και λίγο παραπάνω».  Εκεί ακριβώς, στο λίγο παραπάνω, μπορεί να είναι
  όμως το return address της main() function.  Αν όντως συμβεί αυτό, η
  main() τρέχει κανονικότατα, και μόλις τελειώνει προσπαθεί να διαβάσει
  από το stack το return address της.  Παίρνει τα random σκουπίδια που
  έγραψε η scanf() και μόλις πάει να τα «μεταφράσει» σαν διεύθυνση
  μνήμης και να προσπελάσει αυτή τη διεύθυνση μνήμης, σκάει πανηγυρικά.

------------------------------------------------------------------------
* Στις γραμμές 18-19 τυπώνεις unsigned short int τιμές με λάθος format.
  Εδώ το %u θα 'πρεπε να είναι %hu.

 18 |         printf("Δώσε τον unsigned short int (min: %u, max: %u):\t",
 19 |             0, USHRT_MAX);

------------------------------------------------------------------------
* Στη γραμμή 8 η μεταβλητή `intUnsignedShort' ορίζεται ως αντικείμενο
  τύπου `unsigned signed short int', αλλά στη γραμμή 20 περνάς ως όρισμα
  στη scanf() ένα pointer σε αυτή την ίδια μεταβλητή με λάθος format
  specifier πάλι (%u αντί για %hu).

  8 |         unsigned short int intUnsignedShort;
  ...
 20 |         scanf("%u", &intUnsignedShort);

  Και πάλι μπορεί να γράψει περισσότερα bytes από ότι έπρεπε η scanf(),
  μέσω του pointer που τις περνάς.

------------------------------------------------------------------------

κλπ.

Οι διορθώσεις στα format specifiers που χρησιμοποιείς είναι όλες στο
παρακάτω 'patch':

% --- segfault/segfault.c.orig    Sat Mar 31 19:01:31 2007
% +++ segfault/segfault.c Sat Mar 31 19:01:31 2007
% @@ -1,54 +1,54 @@
%  #include <stdio.h>
%  #include <stdlib.h>
%  #include <limits.h>
% 
% -int main(int argc, char *argv[])
% +int main(void)
%  {
%         signed short int intSignedShort;
%         unsigned short int intUnsignedShort;
%         signed long int intSignedLong;
%         unsigned long int intUnsignedLong;
%         signed int intSigned;
%         unsigned int intUnsigned;
% 
% -       printf("Δώσε τον signed short int (min: %d, max: %d):\t",
% +       printf("Δώσε τον signed short int (min: %hd, max: %hd):\t",
%             SHRT_MIN, SHRT_MAX);
% -       scanf("%d", &intSignedShort);
% +       scanf("%hd", &intSignedShort);
% 
%         printf("Δώσε τον unsigned short int (min: %u, max: %u):\t",
%             0, USHRT_MAX);
% -       scanf("%u", &intUnsignedShort);
% +       scanf("%hu", &intUnsignedShort);
% 
%  //     intUnsignedShort=1;
% -       printf("Δώσε τον signed long int (min: %d, max: %d):\t",
% +       printf("Δώσε τον signed long int (min: %ld, max: %ld):\t",
%             LONG_MIN, LONG_MAX);
% -       scanf("%d", &intSignedLong);
% +       scanf("%ld", &intSignedLong);
% 
% -       printf("Δώσε τον unsigned long int (min: %u, max: %u):\t",
% -           0, ULONG_MAX);
% -       scanf("%u", &intUnsignedLong);
% +       printf("Δώσε τον unsigned long int (min: %lu, max: %lu):\t",
% +           0UL, ULONG_MAX);
% +       scanf("%lu", &intUnsignedLong);
% 
%         printf("Δώσε τον signed int (min: %d, max: %d):\t",
%             INT_MIN, INT_MAX);
%         scanf("%d", &intSigned);
% 
%         printf("Δώσε τον unsigned int (min: %u, max: %u):\t", 0, UINT_MAX);
%         scanf("%u", &intUnsigned);
% 
%  //     intUnsigned=1;
% 
% -       printf("Ο επόμενος signed short int του %d είναι ο %d\n",
% +       printf("Ο επόμενος signed short int του %hd είναι ο %hd\n",
%             intSignedShort, intSignedShort+1);
% -       printf("Ο επόμενος unsigned short int του %u είναι ο %u\n",
% +       printf("Ο επόμενος unsigned short int του %hu είναι ο %hu\n",
%             intUnsignedShort, intUnsignedShort+1);
% -       printf("Ο επόμενος signed long int του %d είναι ο %d\n",
% +       printf("Ο επόμενος signed long int του %ld είναι ο %ld\n",
%             intSignedLong, intSignedLong+1);
% -       printf("Ο επόμενος unsigned long int του %u είναι ο %u\n",
% +       printf("Ο επόμενος unsigned long int του %lu είναι ο %lu\n",
%             intUnsignedLong, intUnsignedLong+1);
%         printf("Ο επόμενος signed int του %d είναι ο %d\n",
%             intSigned, intSigned+1);
%         printf("Ο επόμενος unsigned int του %u είναι ο %u\n",
%             intUnsigned, intUnsigned+1);
% 
%         return EXIT_SUCCESS;
%  }

> το κάνω compile 
> john at salatas:~/Projects/C/limits> cc limits.c -o limits

Το κακό είναι ότι με αυτά τα options το gcc ούτε 'standard conforming
compiler' είναι, ούτε σε προειδοποιεί για λάθη τα οποία μπορεί να έχεις.

Δοκίμασε κάτι σαν:

    cc -O2 -fno-strict-aliasing -pipe -g \
        -Wsystem-headers -Werror -Wall -Wno-format-y2k \
        -W -Wno-unused-parameter -Wstrict-prototypes \
        -Wmissing-prototypes -Wpointer-arith -Wreturn-type \
        -Wcast-qual -Wwrite-strings -Wswitch -Wshadow \
        -Wcast-align -Wunused-parameter -Wchar-subscripts \
        -Winline -Wnested-externs -Wredundant-decls \
        -Wformat=2 -Wno-format-extra-args -Werror \
        segfault.c

Αυτό είναι το default set από compiler options/flags που χρησιμοποιεί το
FreeBSD όταν γράψεις σε ένα Makefile κάτι σαν το παρακάτω:

    PROG=   segfault
    
    NO_MAN= No manpage.
    
    WARNS?= 6
    WFORMAT?= 2
    
    .include <bsd.prog.mk>

Αν ένα πρόγραμμα κάνει σωστά compile με αυτά τα options, δεν είναι
απαραίτητα bug free, αλλά τουλάχιστον μπορείς να θεωρήσεις ότι είναι σε
σχετικά καλή κατάσταση.

Για παράδειγμα, με αυτά τα warnings ενεργοποιημένα, το gcc είπε τα εξής
για το αρχικό σου πρόγραμμα:

[...]
segfault.c: In function `main':
segfault.c:16: warning: int format, different type arg (arg 2)
segfault.c:20: warning: unsigned int format, different type arg (arg 2)
segfault.c:24: warning: int format, long int arg (arg 2)
segfault.c:24: warning: int format, long int arg (arg 3)
segfault.c:25: warning: int format, long int arg (arg 2)
segfault.c:28: warning: unsigned int format, long unsigned int arg (arg 3)
segfault.c:29: warning: unsigned int format, long unsigned int arg (arg 2)
segfault.c:45: warning: int format, long int arg (arg 2)
segfault.c:45: warning: int format, long int arg (arg 3)
segfault.c:47: warning: unsigned int format, long unsigned int arg (arg 2)
segfault.c:47: warning: unsigned int format, long unsigned int arg (arg 3)
segfault.c: At top level:
segfault.c:5: warning: unused parameter 'argc'
segfault.c:5: warning: unused parameter 'argv'
*** Error code 1

Stop in /home/keramida/tmp/segfault.
$




More information about the Linux-greek-users mailing list