Não é possível compilar com o GCC no Ubuntu 12.04

9

Estou tentando compilar e executar o programa C abaixo em minhas máquinas Ubuntu e Windows com GCC e VC9. No entanto, estou enfrentando problemas abaixo:

Na máquina Ubuntu:

O GCC compila bem, mas, quando executado, esse prompt é mostrado:

Segmentation Fault (Core Dump).

Na máquina Windows:

VC9 Compila e roda bem. O GCC compila bem, mas o processo termina quando o programa é executado.

Precisa da sua assistência especializada aqui. Aqui está o meu 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;
}

Atualizar:

O crédito é dado a Eliah por não apenas me ajudar a rastrear o erro, mas também me apresentar gdbe sua ferramenta de rastreamento ( bt), que são muito úteis na depuração de um programa compilado pelo gcc. Aqui está a versão modificada, trabalhei após algumas tentativas e erros:

#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
fonte
3
Eu acho que não é um problema de compilação, mas mais um problema de tempo de execução. Você obteria mais ajuda do StackOverflow .
oaskamay
Tem certeza de que isso realmente funciona bem após ser compilado com o VC9?
Eliah Kagan 28/03
Sim 100%. mas não com o gcc.
Prahlad Yeri 28/03
@PrahladYeri Cool! Eu expliquei razões para isso na minha resposta . (Isso também significa que provavelmente devemos considerar esta questão no tópico, pois é sobre o comportamento específico do Ubuntu *. O GCC no Windows exibe um comportamento comparável, mas não há mensagem de erro e é difícil saber exatamente o que está acontecendo lá - além disso, quando O GCC no Ubuntu e o Microsoft Visual C ++ funcionam de maneira diferente, acho que o Ask Ubuntu é um lugar razoável para perguntar por que o GCC no Ubuntu funciona da mesma maneira. Com isso dito, mais perguntas sobre como fazer isso corretamente pertencem ao Stack Overflow.)
Eliah Kagan
Modificar uma literal de cadeia de caracteres em C é um comportamento indefinido. Por favor lembre-se disso.
Jn1kk

Respostas:

15

Uma falha de segmentação ocorre quando um programa tenta acessar a memória fora da área que foi alocada para ele.

Nesse caso, um programador C experiente pode ver que o problema está acontecendo na linha em que sprintfé chamado. Mas se você não pode dizer onde está ocorrendo sua falha de segmentação ou se não deseja ler o código para tentar descobrir, pode criar seu programa com símbolos de depuração (com gcc, o -gsinalizador faz isso ) e execute-o através de um depurador.

Copiei seu código-fonte e colei-o em um arquivo que eu nomeei slope.c. Então eu construí assim:

gcc -Wall -g -o slope slope.c

(O -Wallé opcional. É apenas para fazer com que ele produza avisos para mais situações. Isso pode ajudar a descobrir o que também pode estar errado.)

Em seguida, executei o programa no depurador gdbexecutando primeiro gdb ./slopepara iniciar gdbo programa e, uma vez no depurador, fornecendo o runcomando ao 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

(Não se preocupe com a minha you have broken Linux kernel i386 NX... supportmensagem; ela não impede que gdbseja usada com eficácia para depurar este programa.)

Essa informação é altamente enigmática ... e se você não possui símbolos de depuração instalados no libc, receberá uma mensagem ainda mais enigmática que possui um endereço hexadecimal em vez do nome da função simbólica _IO_default_xsputn. Felizmente, isso não importa, porque o que realmente queremos saber é onde o problema está acontecendo no seu programa .

Portanto, a solução é olhar para trás e ver quais chamadas de função ocorreram antes dessa chamada de função específica em uma biblioteca do sistema onde o SIGSEGVsinal foi finalmente acionado.

gdb(e qualquer depurador) tem esse recurso embutido: é chamado de rastreamento de pilha ou backtrace . Eu uso o btcomando debugger para gerar um backtrace em 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)

Você pode ver que sua mainfunção chama a calc_slopefunção (que você pretendeu) e, em seguida , calc_slopechama sprintf, que é (neste sistema) implementada com chamadas para algumas outras funções relacionadas da biblioteca.

Em geral, o que você está interessado é a chamada de função no seu programa que chama uma função fora do seu programa . A menos que exista um erro nas próprias bibliotecas / bibliotecas que você esteja usando (nesse caso, a biblioteca C padrão libcfornecida pelo arquivo da biblioteca libc.so.6), o erro que causa a falha está no seu programa e geralmente estará no ou próximo ao última chamada no seu programa.

Nesse caso, é isso:

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

É para onde o seu programa chama sprintf. Sabemos disso porque sprintfé o próximo passo. Mas mesmo sem declarar isso, você sabe disso, porque é o que acontece na linha 26 e diz:

... at slope.c:26

No seu programa, a linha 26 contém:

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

(Você sempre deve usar um editor de texto que mostre automaticamente os números de linha, pelo menos para a linha em que está atualmente. Isso é muito útil para interpretar erros em tempo de compilação e problemas de tempo de execução revelados ao usar um depurador.)

Como discutido na resposta de Dennis Kaarsemaker , sé uma matriz de um byte. (Diferente de zero, porque o valor que você atribuiu a ele ""tem um byte de comprimento, ou seja, é igual a { '\0' }, da mesma maneira que "Hello, world!\n"é igual a { 'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\n', '\0' }.)

Então, por que isso ainda funciona em alguma plataforma (e aparentemente funciona quando compilado com o VC9 para Windows)?

As pessoas costumam dizer que quando você aloca memória e tenta acessar a memória fora dela, isso gera um erro. Mas isso não é verdade. De acordo com os padrões técnicos C e C ++, o que isso realmente produz é um comportamento indefinido.

Em outras palavras, tudo pode acontecer!

Ainda assim, algumas coisas são mais prováveis ​​que outras. Por que uma pequena matriz na pilha, em algumas implementações, parece funcionar como uma matriz maior na pilha?

Isso se resume a como a alocação de pilha é implementada, o que pode variar de plataforma para plataforma. Seu executável pode alocar mais memória para sua pilha do que realmente se destina a ser usado a qualquer momento. Às vezes, isso pode permitir que você escreva na memória locais que você não reivindicou explicitamente em seu código. É muito provável que seja isso que acontece quando você cria seu programa no VC9.

No entanto, você não deve confiar nesse comportamento mesmo no VC9. Pode depender potencialmente de diferentes versões de bibliotecas que possam existir em diferentes sistemas Windows. Mas o mais provável é o problema de o espaço extra da pilha ser alocado com a intenção de que ele realmente seja usado e, portanto , possa realmente ser usado.Então você experimenta o pesadelo completo do "comportamento indefinido", onde, nesse caso, mais de uma variável pode acabar armazenada no mesmo local, onde a escrita em uma substitui a outra ... mas nem sempre, porque às vezes escreve em variáveis são armazenados em cache nos registradores e, na verdade, não são executados imediatamente (ou as leituras das variáveis ​​podem ser armazenadas em cache, ou pode-se presumir que uma variável seja a mesma de antes), porque a memória alocada a ela é conhecida pelo compilador por não ter sido gravada por meio de a própria variável).

E isso me leva à outra possibilidade provável de por que o programa funcionou quando construído com o VC9. É possível, e de certa forma, que alguma matriz ou outra variável tenha sido realmente alocada pelo seu programa (que pode incluir ser alocada por uma biblioteca que o seu programa está usando) para usar o espaço após a matriz de um byte s. Portanto, tratar scomo uma matriz com mais de um byte teria o efeito de acessar o conteúdo dessa / dessas variáveis ​​/ matrizes, o que também poderia ser ruim.

Concluindo, quando você tem um erro como esse, é uma sorte receber um erro como "Falha na segmentação" ou "Falha na proteção geral". Quando você não tem isso, pode não descobrir até que seja tarde demais que seu programa tenha um comportamento indefinido.

Eliah Kagan
fonte
11
Obrigado por uma explicação tão lúcida. Isto é precisamente o que eu precisava .. !!
Prahlad Yeri 28/03
9

Olá estouro de buffer!

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

Você aloca um byte para uma seqüência de caracteres na pilha e depois escreve mais de um byte nela. E ainda por cima, você lê além do final dessa matriz. Por favor, leia um manual em C e especialmente a seção sobre strings e alocação de memória para elas.

Dennis Kaarsemaker
fonte
Sim, eu vim a saber disso mais tarde. Mas quando eu escrevi isso, o compilador VC9 não apenas permitiu, mas também me mostrou os resultados corretamente. Eu imprimi os strlen (s) e ele me mostrou 4, não 1 !!
Prahlad Yeri 28/03
Você também pode me aconselhar sobre como devo corrigir isso? Como você deve ter imaginado a partir do código, não tenho como alocar tamanho fixo para * s antecipadamente. Seu comprimento é o número de dígitos na variável curr que não pode ser conhecido até que eu o converta em uma string !! ?
Prahlad Yeri 28/03
Eu posso, mas você realmente deve ir ao Stack Overflow para aconselhamento sobre programação, pois é bastante offtopic aqui.
Dennis Kaarsemaker 28/03
11
@DennisKaarsemaker A pergunta original aqui pode não estar fora de tópico, pois aparentemente envolve um comportamento que difere entre o Ubuntu e outra plataforma (e eu expliquei a razão mais provável para isso na minha resposta ). Concordo que as perguntas sobre como alocar corretamente as seqüências de caracteres em C pertencem ao Stack Overflow e não aqui.
Eliah Kagan 28/03