No se puede compilar con GCC en Ubuntu 12.04

9

Estoy tratando de compilar y ejecutar el siguiente programa C en mis máquinas Ubuntu y Windows con GCC y VC9. Sin embargo, me enfrento a los siguientes problemas:

En la máquina Ubuntu:

GCC compila bien, pero cuando se ejecuta, me muestra este mensaje:

Segmentation Fault (Core Dump).

En la máquina de Windows:

VC9 Compila y funciona bien. GCC compila bien, pero el proceso termina cuando se ejecuta el programa.

Necesito su asistencia experta aquí. Aquí está mi código:

#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;
}

Actualizar:

El crédito es para Eliah por no solo ayudarme a rastrear el error, sino también presentarme gdby su herramienta de seguimiento ( bt) que es tan útil para depurar un programa compilado por gcc. Aquí está la versión modificada, trabajé después de alguna prueba y error:

#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
fuente
3
Creo que no es un problema de compilación, sino más bien un problema de tiempo de ejecución. Obtendrá más ayuda de StackOverflow .
oaskamay
¿Estás seguro de que esto realmente funciona bien después de ser compilado con VC9?
Eliah Kagan
Si, 100%. pero no con gcc.
Prahlad Yeri
@PrahladYeri Cool! He explicado las razones de esto en mi respuesta . (Esto también significa que probablemente deberíamos considerar esta pregunta sobre el tema, ya que se trata del comportamiento específico de Ubuntu *. GCC en Windows exhibe un comportamiento comparable pero no hay un mensaje de error y es difícil saber exactamente qué está sucediendo allí, además, cuándo GCC en Ubuntu y Microsoft Visual C ++ funcionan de manera diferente, creo que Ask Ubuntu es un lugar razonable para preguntar por qué GCC en Ubuntu funciona como lo hace. Dicho esto, otras preguntas sobre cómo hacerlo correctamente pertenecen a Stack Overflow.)
Eliah Kagan
Modificar un literal de cadena en C es un comportamiento indefinido. Por favor recuerda eso.
jn1kk

Respuestas:

15

Se produce un error de segmentación cuando un programa intenta acceder a la memoria fuera del área que se le ha asignado.

En este caso, un programador de C experimentado puede ver que el problema está sucediendo en la línea donde sprintfse llama. Pero si no puede saber dónde está ocurriendo su falla de segmentación, o si no quiere molestarse en leer el código para tratar de resolverlo, entonces puede construir su programa con símbolos de depuración (con gcc, la -gbandera hace esto ) y luego ejecutarlo a través de un depurador.

Copié su código fuente y lo pegué en un archivo que nombré slope.c. Luego lo construí así:

gcc -Wall -g -o slope slope.c

(El -Walles opcional. Es solo para que produzca advertencias para más situaciones. Esto también puede ayudar a descubrir qué podría estar mal).

Luego ejecuté el programa en el depurador gdbejecutando primero gdb ./slopepara comenzar gdbcon el programa, y ​​luego, una vez en el depurador, dándole el runcomando al depurador:

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

(No se preocupe por mi you have broken Linux kernel i386 NX... supportmensaje; no evita que gdbse use de manera efectiva para depurar este programa).

Esa información es muy críptica ... y si no tiene símbolos de depuración instalados para libc, obtendrá un mensaje aún más críptico que tiene una dirección hexadecimal en lugar del nombre de la función simbólica _IO_default_xsputn. Afortunadamente, no importa, porque lo que realmente queremos saber es en qué parte del programa está ocurriendo el problema.

Entonces, la solución es mirar hacia atrás, para ver qué llamadas de función se llevaron a cabo antes de esa llamada de función en particular en una biblioteca del sistema donde SIGSEGVfinalmente se activó la señal.

gdb(y cualquier depurador) tiene esta característica incorporada: se llama traza de pila o traza inversa . Utilizo el btcomando depurador para generar una traza inversa en 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)

Puede ver que su mainfunción llama a la calc_slopefunción (que ha previsto) y luego calc_slopellama sprintf, que (en este sistema) se implementa con llamadas a un par de otras funciones de biblioteca relacionadas.

Lo que generalmente le interesa es la llamada de función en su programa que llama a una función fuera de su programa . A menos que haya un error en la biblioteca / bibliotecas que esté utilizando (en este caso, la biblioteca C estándar libcprovista por el archivo de la biblioteca libc.so.6), el error que causa el bloqueo está en su programa y con frecuencia estará cerca del última llamada en su programa.

En este caso, eso es:

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

Ahí es donde llama su programa sprintf. Sabemos esto porque sprintfes el siguiente paso. Pero incluso sin que eso lo diga , usted sabe esto porque eso es lo que sucede en la línea 26 , y dice:

... at slope.c:26

En su programa, la línea 26 contiene:

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

(Siempre debe usar un editor de texto que muestre automáticamente los números de línea, al menos para la línea en la que se encuentra actualmente. Esto es muy útil para interpretar tanto los errores en tiempo de compilación como los problemas de tiempo de ejecución revelados al usar un depurador).

Como se discutió en la respuesta de Dennis Kaarsemaker , ses una matriz de un byte. (No es cero, porque el valor que le ha asignado ""es de un byte, es decir, es igual a { '\0' }, de la misma manera que "Hello, world!\n"es igual a { 'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\n', '\0' }).

Entonces, ¿por qué esto podría funcionar en alguna plataforma (y aparentemente funciona cuando se compila con VC9 para Windows)?

La gente suele decir que cuando asigna memoria y luego intenta acceder a la memoria externa, se produce un error. Pero eso no es realmente cierto. De acuerdo con los estándares técnicos C y C ++, lo que esto realmente produce es un comportamiento indefinido.

En otras palabras, ¡cualquier cosa puede pasar!

Aún así, algunas cosas son más probables que otras. ¿Por qué es que una pequeña matriz en la pila, en algunas implementaciones, parece funcionar como una matriz más grande en la pila?

Esto se reduce a cómo se implementa la asignación de la pila, que puede variar de una plataforma a otra. Su ejecutable puede asignar más memoria a su pila de la que está destinada a ser utilizada en cualquier momento. A veces esto puede permitirle escribir en ubicaciones de memoria que no ha reclamado explícitamente en su código. Es muy probable que esto sea lo que sucede cuando compila su programa en VC9.

Sin embargo, no debe confiar en este comportamiento incluso en VC9. Potencialmente podría depender de diferentes versiones de bibliotecas que podrían existir en diferentes sistemas de Windows. Pero aún más probable es el problema de que el espacio de pila adicional se asigne con la intención de que realmente se use, por lo que en realidad se puede usar.Luego experimenta la pesadilla completa del "comportamiento indefinido", donde, en este caso, más de una variable podría terminar almacenada en el mismo lugar, donde escribir en una sobrescribe a la otra ... pero no siempre, porque a veces escribe en variables se almacenan en caché en los registros y en realidad no se realizan de inmediato (o las lecturas de las variables se pueden almacenar en caché, o se puede suponer que una variable es la misma que antes porque el compilador sabe que la memoria asignada a ella no se ha escrito a través de la variable misma).

Y eso me lleva a la otra posibilidad probable de por qué el programa funcionó cuando se creó con VC9. Es posible, y algo probable, que alguna matriz u otra variable haya sido asignada realmente por su programa (lo que puede incluir ser asignado por una biblioteca que esté utilizando su programa) para usar el espacio después de la matriz de un byte s. Entonces, tratar scomo una matriz de más de un byte tendría el efecto de acceder al contenido de esas / esas variables / matrices, lo que también podría ser malo.

En conclusión, cuando tiene un error como este, es afortunado recibir un error como "Error de segmentación" o "Error de protección general". Cuando no tenga eso, es posible que no se entere hasta que sea demasiado tarde que su programa tenga un comportamiento indefinido.

Eliah Kagan
fuente
1
Gracias por tan lúcida explicación. Esto es precisamente lo que necesitaba ... !!
Prahlad Yeri
9

Hola desbordamiento de búfer!

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

Usted asigna un byte para una cadena en la pila y luego procede a escribir más de un byte en él. Y para colmo, lees más allá del final de esa matriz. Lea un manual en C y especialmente la sección sobre cadenas y asignación de memoria para ellos.

Dennis Kaarsemaker
fuente
Sí, supe más tarde sobre eso. Pero cuando escribí esto, el compilador VC9 no solo permitió, sino que también me mostró los resultados correctamente. ¡Imprimí el strlen (s) y me mostró 4, no 1!
Prahlad Yeri
¿Podrías también aconsejarme cómo debo corregir esto? Como debe haber deducido del código, no tengo forma de asignar un tamaño fijo a * s por adelantado. ¡Su longitud es el número de dígitos en la variable curr que no se puede conocer hasta que lo convierta en una cadena! ?
Prahlad Yeri
Puedo, pero realmente deberías dirigirte a Stack Overflow para obtener consejos de programación, ya que aquí es un tema bastante extraño.
Dennis Kaarsemaker
1
@DennisKaarsemaker La pregunta original aquí podría no estar fuera de tema ya que aparentemente involucra un comportamiento que difiere entre Ubuntu y otra plataforma (y he explicado la razón más probable para esto en mi respuesta ). Estoy de acuerdo en que las preguntas sobre cómo asignar cadenas correctamente en C pertenecen al Desbordamiento de pila y no aquí.
Eliah Kagan