
Я пытаюсь скомпилировать и запустить следующую программу на языке 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;
}
решение1
Аошибка сегментациипроисходит, когда программа пытается получить доступ к памяти за пределами выделенной для нее области.
В этом случае опытный программист на 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
(и любой отладчик) имеет встроенную функцию: она называетсятрассировки стекаилиобратная трассировкаЯ использую 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
массива длиннее одного байта приведет к доступу к содержимому этой/этих переменных/массивов, что также может быть плохо.
В заключение, когда вы совершаете такую ошибку, этоудачливыйчтобы получить ошибку типа «Ошибка сегментации» или «Общая ошибка защиты». Когда вынеесли это так, вы можете не узнать, пока не станет слишком поздно, что ваша программанеопределенное поведение.
решение2
Привет, переполнение буфера!
char *s="";
sprintf(s,"%d",curr);
length=strlen(s);
Вы выделяете один байт для строки в стеке, а затем продолжаете записывать в него больше одного байта. И в довершение всего, вы читаете за пределами этого массива. Пожалуйста, прочтите руководство по C и особенно раздел о строках и выделении памяти для них.