Почему я получаю ошибку сегментации?

Я пытаюсь написать программу, которая принимает текстовый файл в качестве аргумента и анализирует его, складывая все числа вместе, а затем распечатывая сумму. Ниже приведен мой код:

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

static int sumNumbers(char filename[])
{
    int sum = 0;
    FILE *file = fopen(filename, "r");
    char *str;

    while (fgets(str, sizeof BUFSIZ, file))
    {
        while (*str != '\0')
        {
            if (isdigit(*str))
            {
                sum += atoi(str);
                str++;
                while (isdigit(*str))
                    str++;
                continue;
            }
            str++;
        }
    }

    fclose(file);

    return sum;
}

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        fprintf(stderr, "Please enter the filename as the argument.\n");
        exit(EXIT_FAILURE);
    }
    else
    {
        printf("The sum of all the numbers in the file is : %d\n", sumNumbers(argv[1]));
        exit(EXIT_SUCCESS);
    }

    return 0;
}

И текстовый файл, который я использую:

Это довольно скучный текстовый файл с разбросанными по нему случайными числами.

Вот один: 87 а вот другой: 3

и, наконец, две последние цифры: 12 19381. Готово. Фу.

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


person jon2512chua    schedule 09.10.2010    source источник


Ответы (8)


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

Тебе нужно:

char *str;
str = malloc(BUFSIZ); // this is missing..also free() the mem once done using it.

или просто:

char str[BUFSIZ]; // but then you can't do str++, you'll have to use another 
                  // pointer say char *ptr = str; and use it in place of str.

ИЗМЕНИТЬ:

Есть еще одна ошибка:

while (fgets(str, sizeof BUFSIZ, file))

Второй аргумент должен быть BUFSIZ, а не sizeof BUFSIZ.

Почему?

Потому что 2-й аргумент — это максимальное количество символов, которое нужно прочитать в буфер, включая нулевой символ. Поскольку sizeof BUFSIZ равно 4, вы можете прочитать максимум до 3 символов в буфер. Вот почему 19381 читалось как 193, а затем как 81<space>.

person codaddict    schedule 09.10.2010
comment
Эй, спасибо, теперь это работает. Но если вы не возражаете, потерпите меня еще немного. Если вы попытаетесь запустить программу после ее исправления, по какой-то причине atoi() будет анализировать число 19381 как 193 и 81 соответственно. Любая идея, почему это происходит? - person jon2512chua; 09.10.2010

Вы не выделили памяти для заполнения str. fgets принимает в качестве первого аргумента буфер, а не неназначенный указатель.

Вместо char *str; вам нужно определить буфер разумного размера, скажем, char str[BUFSIZ];

person Mark Elliot    schedule 09.10.2010

Потому что вы не выделили место для своего буфера.

person ninjalj    schedule 09.10.2010

Несколько человек уже обратились к проблеме, о которой вы спрашивали, но у меня есть ответный вопрос. Как вы думаете, что именно это дает:

        if (isdigit(*str))
        {
            if (isdigit(*str))
            {
                sum += atoi(str);
                str++;
                while (isdigit(*str))
                    str++;
                continue;
            }
        }

В чем смысл двух последовательных операторов if с одним и тем же условием? (Примечание для протокола: ни у одного из них нет пункта else).

person Jerry Coffin    schedule 09.10.2010
comment
хороший улов ... +1 за то, что выходит за рамки очевидного =) - person Mark Elliot; 09.10.2010
comment
Извините, это была опечатка. Должно быть, слишком устал. - person jon2512chua; 09.10.2010

Вы объявили char* str, но еще не выделили для нее память. Вам нужно будет выделить память для этого.

Многие ошибки, связанные с памятью, такие как эта, могут быть легко обнаружены с помощью valgrind. Я настоятельно рекомендую использовать его в качестве инструмента отладки.

person shuttle87    schedule 09.10.2010

char *str;

str не имеет выделенной для него памяти. Либо используйте malloc(), чтобы выделить для него немного памяти, либо объявите его с предопределенным размером.

char str[MAX_SIZE];
person Shamim Hafiz    schedule 09.10.2010

Ваша программа имеет несколько ошибок:

  • Он неправильно обрабатывает длинные строки. Когда вы читаете буфер некоторого размера, может случиться так, что некоторое число начинается в конце буфера и продолжается в начале следующего буфера. Например, если у вас есть буфер размером 4, это может быть вход The |numb|er 1|2345| is |larg|e., где вертикальные линии указывают содержимое буфера. Затем вы будете считать 1 и 2345 отдельно.
  • Он вызывает isdigit с аргументом char. Как только вы читаете любой "большой" символ (больше SCHAR_MAX), поведение становится неопределенным. Ваша программа может дать сбой, или выдать неверные результаты, или сделать что угодно. Чтобы исправить это, вы должны сначала привести значение к unsigned char, например isdigit((unsigned char) *str). Или, как в моем коде, вы можете передать ему значение из функции fgetc, которое гарантированно будет допустимым аргументом для isdigit.
  • Вы используете функцию, которой требуется буфер (fgets), но не можете выделить буфер. Как отмечали другие, самый простой способ получить буфер — объявить локальную переменную char buffer[BUFSIZ].
  • Вы используете переменную str для двух целей: для хранения адреса буфера (который должен оставаться постоянным в течение всего времени выполнения) и указателя для анализа текста (который изменяется во время выполнения). Сделайте эти две переменные. Я бы назвал их buffer и p (сокращение от указатель).

Вот мой код:

#include <ctype.h>
#include <stdio.h>

static int sumNumbers(const char *filename)
{
    int sum, num, c;
    FILE *f;

    if ((f = fopen(filename, "r")) == NULL) {
        /* TODO: insert error handling here. */
    }

    sum = 0;
    num = 0;
    while ((c = fgetc(f)) != EOF) {
        if (isdigit(c)) {
            num = 10 * num + (c - '0');
        } else if (num != 0) {
            sum += num;
            num = 0;
        }
    }

    if (fclose(f) != 0) {
        /* TODO: insert error handling here. */
    }

    return sum;
}

int main(int argc, char **argv) {
    int i;

    for (i = 1; i < argc; i++)
        printf("%d\t%s\n", sumNumbers(argv[i]), argv[i]);
    return 0;
}
person Roland Illig    schedule 09.10.2010
comment
Спасибо за отличный отзыв! Ценить это! :) - person jon2512chua; 09.10.2010

Вот функция, которая выполняет вашу работу:

static int sumNumbers(char* filename) {
    int sum = 0;
    FILE *file = fopen(filename, "r");
    char buf[BUFSIZ], *str;

    while (fgets(buf, BUFSIZ, file))
    {
            str=buf;
            while (*str)
            {
                    if (isdigit(*str))
                    {
                            sum += strtol(str, &str, 10);
                    }
                    str++;
            }
    }
    fclose(file);
    return sum;
}

Это не включает обработку ошибок, но работает достаточно хорошо. Для вашего файла вывод будет

Сумма всех чисел в файле: 19483

person StingX    schedule 09.10.2010