Не могу скомпилировать с GCC на Ubuntu 12.04

9

Я пытаюсь скомпилировать и запустить приведенную ниже программу на C на компьютерах с Ubuntu и Windows, используя как GCC, так и VC9. Тем не менее, я сталкиваюсь с проблемами ниже:

На машине с Ubuntu:

GCC компилируется нормально, но при запуске мне показывается следующее приглашение:

Segmentation Fault (Core Dump).

На Windows-машине:

VC9 компилируется и работает нормально. GCC компилируется нормально, но процесс завершается при запуске программы.

Нужна ваша помощь специалиста здесь. Вот мой код:

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

Обновить:

Заслуга Илии для не только помогает мне отслеживать ошибки, но и представить меня gdbи его обратно трассировки инструмента ( bt) , которые так полезны для отладки GCC компилируется программа. Вот модифицированная версия, которую я обработал после проб и ошибок:

#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;
}
Прахлад Ери
источник
3
Я думаю, что это не проблема компиляции, а скорее проблема времени выполнения. Вы получите больше помощи от StackOverflow .
oaskamay
Вы уверены, что это действительно работает нормально после компиляции с VC9?
Элия ​​Каган,
Да, 100% но не с gcc.
Прахлад Ери
@PrahladYeri Круто! Я объяснил причины этого в своем ответе . (Это также означает, что нам, вероятно, следует рассмотреть этот вопрос по теме, поскольку речь идет о * -специфическом * поведении в Ubuntu. GCC в Windows демонстрирует сопоставимое поведение, но сообщения об ошибке нет, и трудно точно знать, что там происходит, более того, когда GCC в Ubuntu и Microsoft Visual C ++ работают по-разному, я думаю, что Ask Ubuntu - это разумное место, чтобы спросить, почему GCC в Ubuntu работает так, как он работает. С учетом сказанного, дальнейшие вопросы о том, как это сделать правильно, относятся к переполнению стека.)
Элия Каган
Изменение строкового литерала в C - неопределенное поведение. Пожалуйста, помните это.
jn1kk

Ответы:

15

Ошибка сегментации возникает, когда программа пытается получить доступ к памяти за пределами области, выделенной для нее.

В этом случае опытный программист C может увидеть, что проблема происходит в строке, где sprintfвызывается. Но если вы не можете сказать, где происходит ошибка сегментации, или если вы не хотите читать код, чтобы попытаться выяснить это, тогда вы можете построить свою программу с помощью символов отладки (с gccпомощью -gфлага это делает ), а затем запустите его через отладчик.

Я скопировал ваш исходный код и вставил его в файл, который назвал slope.c. Тогда я построил это так:

gcc -Wall -g -o slope slope.c

(Опция -Wallнеобязательна. Она просто заставляет выдавать предупреждения для большего числа ситуаций. Это также может помочь выяснить, что может быть не так.)

Затем я запустил программу в отладчике gdb, сначала запустив ее gdb ./slopeдля запуска gdb, а затем, однажды в отладчике, дал runкоманду отладчику:

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

(Не беспокойтесь о моем you have broken Linux kernel i386 NX... supportсообщении; оно не мешает gdbэффективно использоваться для отладки этой программы.)

Эта информация очень загадочная ... и если у вас не установлены отладочные символы для libc, вы получите еще более загадочное сообщение с шестнадцатеричным адресом вместо символьного имени функции _IO_default_xsputn. К счастью, это не имеет значения, потому что мы действительно хотим знать, где в вашей программе происходит проблема.

Таким образом, решение состоит в том, чтобы оглянуться назад, чтобы увидеть, какие вызовы функций имели место, приводя к тому конкретному вызову функции в системной библиотеке, где SIGSEGVсигнал был, наконец, запущен.

gdb(и любой отладчик) имеет эту встроенную функцию: она называется трассировкой стека или backtrace . Я использую команду btотладчика для генерации обратной трассировки в 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)

Вы можете видеть, что ваша mainфункция вызывает calc_slopeфункцию (которую вы намеревались), а затем calc_slopeвызывает sprintf, которая (в этой системе) реализована с помощью вызова нескольких других связанных библиотечных функций.

Что вас обычно интересует, так это вызов функции в вашей программе, который вызывает функцию вне вашей программы . Если только в самих библиотеках / библиотеках, которые вы используете (есть стандартная библиотека C, libcпредоставляемая файлом библиотеки libc.so.6), отсутствует ошибка, ошибка, вызывающая сбой, присутствует в вашей программе и часто будет на уровне или вблизи последний звонок в вашей программе.

В этом случае это:

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

Вот где ваша программа вызывает sprintf. Мы знаем это, потому что sprintfэто следующий шаг. Но даже не говоря об этом, вы знаете это, потому что это то, что происходит в строке 26 , и это говорит:

... at slope.c:26

В вашей программе строка 26 содержит:

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

(Вы всегда должны использовать текстовый редактор, который автоматически показывает номера строк, по крайней мере, для той строки, в которой вы находитесь. Это очень полезно для интерпретации как ошибок во время компиляции, так и проблем во время выполнения, выявленных при использовании отладчика.)

Как обсуждалось в ответе Денниса Каарсемейкера , sэто однобайтовый массив. (Не ноль, потому что значение, которое вы ему присвоили "", имеет длину в один байт, то есть равно так { '\0' }же, "Hello, world!\n"как и { 'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\n', '\0' }.)

Итак, почему может это еще работать на какой - то платформе (и , видимо , при компиляции с VC9 для Windows)?

Люди часто говорят, что когда вы выделяете память и затем пытаетесь получить доступ к памяти вне ее, это приводит к ошибке. Но это не совсем так. В соответствии с техническими стандартами C и C ++, это действительно приводит к неопределенному поведению.

Другими словами, все может случиться!

Тем не менее, некоторые вещи более вероятны, чем другие. Почему в некоторых реализациях небольшой массив в стеке работает как большой массив в стеке?

Это сводится к тому, как реализовано распределение стека, которое может варьироваться от платформы к платформе. Ваш исполняемый файл может выделить больше памяти для своего стека, чем фактически предполагается использовать в любое время. Иногда это может позволить вам записывать в области памяти, на которые вы явно не претендовали в своем коде. Скорее всего, это то, что происходит, когда вы собираете свою программу в VC9.

Однако вы не должны полагаться на это поведение даже в VC9. Это может потенциально зависеть от разных версий библиотек, которые могут существовать в разных системах Windows. Но еще более вероятно , что проблема заключается в том, что дополнительное пространство стека выделяется с намерением, что оно будет фактически использовано, и поэтому оно может фактически использоваться.Затем вы испытываете полный кошмар "неопределенного поведения", когда в этом случае более одной переменной может в конечном итоге храниться в одном и том же месте, где запись в одну перезаписывает другую ... но не всегда, потому что иногда пишет в переменные кэшируются в регистрах и фактически не выполняются немедленно (или чтение в переменные может кэшироваться, или можно предположить, что переменная такая же, как и раньше, потому что выделенная ей память известна компилятору, что она не была записана через сама переменная).

И это подводит меня к другой вероятной возможности того, почему программа работала при сборке с VC9. Возможно, и довольно вероятно, что какой-то массив или другая переменная была фактически выделена вашей программой (которая может включать в себя выделение библиотекой, которую использует ваша программа), чтобы использовать пространство после однобайтового массива s. Таким образом, обработка sмассива длиннее одного байта будет иметь эффект доступа к содержимому этих / этих переменных / массивов, что также может быть плохо.

В заключение, если у вас есть такая ошибка, вам повезет получить ошибку типа «Ошибка сегментации» или «Ошибка общей защиты». Если у вас этого нет , вы можете не узнать, пока не станет слишком поздно, что ваша программа имеет неопределенное поведение.

Элия ​​Каган
источник
1
Спасибо за такое ясное объяснение. Это именно то, что мне нужно .. !!
Прахлад Ери
9

Привет переполнение буфера!

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

Вы выделяете один байт для строки в стеке, а затем приступаете к записи в нее более одного байта. И в довершение всего, вы читаете за пределами этого массива. Пожалуйста, прочтите руководство по C и особенно раздел о строках и распределении памяти для них.

Деннис Каарсемакер
источник
Да, я узнал об этом позже. Но когда я написал это, компилятор VC9 не только разрешил, но и показал мне результаты должным образом. Я напечатал strlen (s), и он показал мне 4, а не 1 !!
Прахлад Ери
Не могли бы вы также посоветовать мне, как мне исправить это? Как вы, наверное, догадались из кода, я не могу заранее выделить фиксированный размер для * s. Его длина - это число цифр в переменной curr, которое невозможно узнать, пока я не преобразую его в строку !! ?
Прахлад Ери
Я могу, но вам действительно стоит обратиться в Stack Overflow за советом по программированию, поскольку здесь это довольно оффтопно.
Деннис Каарсемакер
1
@DennisKaarsemaker Оригинальный вопрос здесь не может быть не по теме, поскольку он, по-видимому, связан с поведением, отличающимся между Ubuntu и другой платформой (и я объяснил наиболее вероятную причину этого в своем ответе ). Я согласен, что вопросы о том, как правильно распределить строки в C, относятся к переполнению стека, а не здесь.
Элия ​​Каган,