Impossible de compiler avec GCC sur Ubuntu 12.04

9

J'essaie de compiler et d'exécuter le programme C ci-dessous sur mes machines Ubuntu et Windows avec GCC et VC9. Cependant, je suis confronté à des problèmes ci-dessous:

Sur la machine Ubuntu:

GCC compile très bien, mais lors de l'exécution, cette invite s'affiche:

Segmentation Fault (Core Dump).

Sur une machine Windows:

VC9 Compile et fonctionne bien. GCC compile bien, mais le processus se termine lorsque le programme est exécuté.

Besoin de votre assistance experte ici. Voici mon code:

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

int calc_slope(int input1,int input2)
{
    int sum=0;
    int start=input1;
    int end=input2;
    int curr=start;

    //some validation:
    if (input1>input2)
        return -1;


    while(curr<=end)
    {
        if (curr>100)
        {
            char *s="";
            int length;
            int left;
            int right;
            int cent;

            sprintf(s,"%d",curr);
            length=strlen(s);
            s++;
            do
            {
                //printf("curr=%d char=%c pointer=%d length=%d \n",curr,*s,s,length);
                left = *(s-1) - '0';
                cent = *s - '0';
                right = *(s+1) - '0';
                //printf("curr=%d l=%d c=%d r=%d\n",curr,left,cent,right);
                if ( (cent>left && cent>right) || (cent<left && cent<right) )
                {
                    sum+=1; //we have either a maxima or a minima.
                }

                s++;
            } while (*(s+1)!='\0');
        }
        curr++;
    }

    return sum;
}

int main()
{
    printf("%d",calc_slope(1,150));
    return 0;
}

Mise à jour:

Le crédit va à Eliah non seulement me aider suivre l'erreur, mais aussi me présenter à gdbet son outil de sauvegarde de traçage ( bt) qui sont si utiles dans le débogage d' un programme compilé gcc. Voici la version modifiée, j'ai travaillé après quelques essais et erreurs:

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

int calc_slope(int input1,int input2)
{
    int sum=0;
    int start=input1;
    int end=input2;
    int curr=start;

    //some validation:
    if (input1>input2)
        return -1;


    while(curr<=end)
    {
        if (curr>100)
        {
            int size=10;
            char *s=(char*)malloc((size+1) * sizeof(char));
            int left;
            int right;
            int cent;

            sprintf(s,"%d",curr);
            s++;
            do
            {
                left = *(s-1) - '0';
                cent = *s - '0';
                right = *(s+1) - '0';
                if ( (cent>left && cent>right) || (cent<left && cent<right) )
                {
                    sum+=1; //we have either a maxima or a minima.
                }

                s++;
            } while (*(s+1)!='\0');
        }
        curr++;
    }

    return sum;
}

int main()
{
    printf("%d",calc_slope(1,150));
    return 0;
}
Prahlad Yeri
la source
3
Je pense que ce n'est pas un problème de compilation, mais plutôt un problème d'exécution. Vous obtiendrez plus d'aide de StackOverflow .
oaskamay
Êtes-vous sûr que cela fonctionne vraiment bien après avoir été compilé avec VC9?
Eliah Kagan
Oui, 100%. mais pas avec gcc.
Prahlad Yeri
@PrahladYeri Cool! J'ai expliqué les raisons de cela dans ma réponse . (Cela signifie également que nous devrions probablement considérer cette question sur le sujet, car il s'agit d'un comportement spécifique à Ubuntu *. GCC dans Windows présente un comportement comparable mais il n'y a pas de message d'erreur et il est difficile de savoir exactement ce qui s'y passe - en outre, lorsque GCC sur Ubuntu et Microsoft Visual C ++ fonctionnent différemment, je pense que Ask Ubuntu est un endroit raisonnable pour demander pourquoi GCC sur Ubuntu fonctionne comme il le fait. Cela dit, d'autres questions sur la bonne façon de procéder appartiennent à Stack Overflow.)
Eliah Kagan
La modification d'un littéral de chaîne en C est un comportement non défini. N'oubliez pas cela.
jn1kk

Réponses:

15

Une erreur de segmentation se produit lorsqu'un programme tente d'accéder à la mémoire en dehors de la zone qui lui a été allouée.

Dans ce cas, un programmeur C expérimenté peut voir que le problème se produit dans la ligne où sprintfest appelé. Mais si vous ne pouvez pas dire où votre erreur de segmentation se produit, ou si vous ne voulez pas vous embêter à lire le code pour essayer de le comprendre, vous pouvez créer votre programme avec des symboles de débogage (avec gcc, le -gdrapeau fait cela ), puis exécutez-le via un débogueur.

J'ai copié votre code source et l'ai collé dans un fichier que j'ai nommé slope.c. Ensuite, je l'ai construit comme ceci:

gcc -Wall -g -o slope slope.c

(Le -Wallest facultatif. Il sert simplement à lui faire générer des avertissements pour plus de situations. Cela peut également aider à déterminer ce qui pourrait ne pas fonctionner.)

Ensuite, j'ai exécuté le programme dans le débogueur gdben exécutant d'abord gdb ./slopepour démarrer gdbavec le programme, puis, une fois dans le débogueur, en donnant la runcommande au débogueur:

ek@Kip:~/source$ gdb ./slope
GNU gdb (GDB) 7.5-ubuntu
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/ek/source/slope...done.
(gdb) run
Starting program: /home/ek/source/slope 
warning: Cannot call inferior functions, you have broken Linux kernel i386 NX (non-executable pages) support!

Program received signal SIGSEGV, Segmentation fault.
0x001a64cc in _IO_default_xsputn () from /lib/i386-linux-gnu/libc.so.6

(Ne vous inquiétez pas pour mon you have broken Linux kernel i386 NX... supportmessage; cela n'empêche pas gdbd'être utilisé efficacement pour déboguer ce programme.)

Ces informations sont hautement cryptées ... et si vous n'avez pas de symboles de débogage installés pour libc, alors vous obtiendrez un message encore plus cryptique qui a une adresse hexadécimale au lieu du nom de la fonction symbolique _IO_default_xsputn. Heureusement, cela n'a pas d'importance, car ce que nous voulons vraiment savoir, c'est se produit le problème dans votre programme .

Donc, la solution est de regarder en arrière, pour voir quels appels de fonction ont eu lieu menant à cet appel de fonction particulier dans une bibliothèque système où le SIGSEGVsignal a finalement été déclenché.

gdb(et tout débogueur) a cette fonctionnalité intégrée: elle s'appelle une trace de pile ou une trace arrière . J'utilise la btcommande debugger pour générer une trace dans gdb:

(gdb) bt
#0  0x001a64cc in _IO_default_xsputn () from /lib/i386-linux-gnu/libc.so.6
#1  0x00178e04 in vfprintf () from /lib/i386-linux-gnu/libc.so.6
#2  0x0019b234 in vsprintf () from /lib/i386-linux-gnu/libc.so.6
#3  0x0017ff7b in sprintf () from /lib/i386-linux-gnu/libc.so.6
#4  0x080484cc in calc_slope (input1=1, input2=150) at slope.c:26
#5  0x08048578 in main () at slope.c:52
(gdb)

Vous pouvez voir que votre mainfonction appelle la calc_slopefonction (que vous vouliez), puis calc_slopeappelle sprintf, qui est (sur ce système) implémentée avec des appels à quelques autres fonctions de bibliothèque associées.

Ce qui vous intéresse généralement, c'est l'appel de fonction dans votre programme qui appelle une fonction en dehors de votre programme . Sauf s'il y a un bogue dans la bibliothèque / les bibliothèques elles-mêmes que vous utilisez (dans ce cas, la bibliothèque C standard libcfournie par le fichier de bibliothèque libc.so.6), le bogue qui provoque le crash est dans votre programme et sera souvent à ou près de la dernier appel dans votre programme.

Dans ce cas, c'est:

#4  0x080484cc in calc_slope (input1=1, input2=150) at slope.c:26

C'est là que votre programme appelle sprintf. Nous le savons parce que sprintfc'est la prochaine étape. Mais même sans qu'il l'indique, vous le savez parce que c'est ce qui se passe à la ligne 26 , et il dit:

... at slope.c:26

Dans votre programme, la ligne 26 contient:

            sprintf(s,"%d",curr);

(Vous devez toujours utiliser un éditeur de texte qui affiche automatiquement les numéros de ligne, au moins pour la ligne sur laquelle vous êtes actuellement. Cela est très utile pour interpréter à la fois les erreurs de compilation et les problèmes d'exécution révélés lors de l'utilisation d'un débogueur.)

Comme discuté dans la réponse de Dennis Kaarsemaker , sest un tableau à un octet. (Pas zéro, car la valeur que vous lui avez attribuée "", est longue d'un octet, c'est-à-dire qu'elle est égale à { '\0' }, de la même manière qu'elle "Hello, world!\n"est égale à { 'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\n', '\0' }.)

Alors, pourquoi cela pourrait- il toujours fonctionner sur une plate-forme (et apparemment, cela est compilé avec VC9 pour Windows)?

Les gens disent souvent que lorsque vous allouez de la mémoire, puis essayez d'accéder à la mémoire en dehors, cela produit une erreur. Mais ce n'est pas vraiment vrai. Selon les normes techniques C et C ++, cela produit vraiment un comportement indéfini.

En d'autres termes, tout peut arriver!

Pourtant, certaines choses sont plus probables que d'autres. Pourquoi un petit tableau sur la pile semble-t-il, sur certaines implémentations, fonctionner comme un plus grand tableau sur la pile?

Cela revient à la façon dont l'allocation de pile est mise en œuvre, qui peut varier d'une plateforme à l'autre. Votre exécutable peut allouer plus de mémoire à sa pile que ce qui est réellement destiné à être utilisé à tout moment. Parfois, cela peut vous permettre d'écrire dans des emplacements de mémoire que vous n'avez pas explicitement revendiqués dans votre code. Il est très probable que c'est ce qui se passe lorsque vous créez votre programme dans VC9.

Cependant, vous ne devez pas compter sur ce comportement même dans VC9. Cela peut potentiellement dépendre de différentes versions de bibliothèques pouvant exister sur différents systèmes Windows. Mais le problème est encore plus probable que l'espace de pile supplémentaire est alloué avec l'intention qu'il sera réellement utilisé, et donc qu'il peut réellement être utilisé.Ensuite, vous vivez le cauchemar complet d'un «comportement indéfini», où, dans ce cas, plus d'une variable pourrait finir par être stockée au même endroit, où l'écriture de l'une écrase l'autre ... mais pas toujours, car parfois elle écrit dans des variables sont mis en cache dans les registres et ne sont pas réellement exécutés immédiatement (ou les lectures des variables peuvent être mises en cache, ou une variable peut être supposée être la même qu'avant car la mémoire qui lui est allouée est connue du compilateur comme n'ayant pas été écrite via la variable elle-même).

Et cela m'amène à l'autre possibilité probable pour laquelle le programme a fonctionné lorsqu'il est construit avec VC9. Il est possible, et quelque peu probable, qu'un tableau ou une autre variable ait été réellement alloué par votre programme (ce qui peut inclure l'allocation par une bibliothèque que votre programme utilise) pour utiliser l'espace après le tableau d'un octet s. Ainsi, le fait de traiter scomme un tableau de plus d'un octet aurait pour effet d'accéder au contenu de ces / ces variables / tableaux, ce qui pourrait également être mauvais.

En conclusion, lorsque vous avez une erreur comme celle-ci, vous avez de la chance d'obtenir une erreur comme "Erreur de segmentation" ou "Erreur de protection générale". Lorsque vous ne l' avez pas , vous ne découvrirez peut-être pas avant qu'il ne soit trop tard que votre programme ait un comportement indéfini.

Eliah Kagan
la source
1
Merci pour cette explication lucide. C'est précisément ce dont j'avais besoin .. !!
Prahlad Yeri
9

Bonjour débordement de tampon!

char *s="";
sprintf(s,"%d",curr);
length=strlen(s);

Vous allouez un octet à une chaîne de la pile, puis vous y écrivez plusieurs octets. Et pour couronner le tout, vous lisez au-delà de la fin de ce tableau. Veuillez lire un manuel C et en particulier la section sur les chaînes et leur allouer de la mémoire.

Dennis Kaarsemaker
la source
Oui, j'ai appris cela plus tard. Mais quand j'ai écrit cela, le compilateur VC9 a non seulement permis, mais m'a également montré les résultats correctement. J'ai imprimé les strlen (s) et cela m'a montré 4, pas 1 !!
Prahlad Yeri
Pourriez-vous également me conseiller sur la manière de corriger cela? Comme vous avez dû supposer à partir du code, je n'ai aucun moyen d'allouer une taille fixe à * s à l'avance. Sa longueur est le nombre de chiffres de la variable curr qui ne peut pas être connu jusqu'à ce que je le convertisse en chaîne !! ?
Prahlad Yeri
Je peux, mais vous devriez vraiment vous diriger vers Stack Overflow pour des conseils de programmation, car c'est assez hors sujet ici.
Dennis Kaarsemaker du
1
@DennisKaarsemaker La question d'origine ici pourrait ne pas être hors sujet car elle implique apparemment un comportement qui diffère entre Ubuntu et une autre plate-forme (et j'ai expliqué la raison la plus probable de cela dans ma réponse ). Je suis d'accord que les questions sur la façon d'allouer correctement les chaînes en C appartiennent à Stack Overflow et non ici.
Eliah Kagan