40 простых строк, 1 раздражающая ошибка сегментации. Я действительно не знаю, куда еще обратиться - PullRequest
2 голосов
/ 13 марта 2012

Я прошу прощения, если это пустая трата времени и / или нет того, что должно быть на этом сайте, но я отчасти из идей ... Я все еще новичок в программировании, не могу овладеть моего учителя для руководства, так что ... В ИНТЕРНЕТ!

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

void months( FILE* monthfp, char** monthGroup );

int main (void){
        FILE *monthfp; /*to be used for reading in months from months.txt*/
        char** monthGroup;
        int i;

        if (( monthfp = fopen ( "months.txt", "r" )) == NULL ){
            printf( "unable to open months.txt. \n" );
            exit ( 1 );
    }

    months( monthfp, monthGroup );

/*test so far*/
    for ( i = 0; i < 12; i++ ){
            printf( "%s", monthGroup[i] );
    }

    fclose( monthfp );


}
void months ( FILE* monthfp, char** monthGroup ){
/*****************************************
    name: months
    input: input file, data array
    returns: No return. Modifies array.
*/
    char buffer[50];
    int count = 0;

    while ( fgets( buffer, sizeof(buffer), monthfp ) != NULL ){
            count++;
            monthGroup =  malloc( count * sizeof ( char* ));
            monthGroup[count] = malloc( sizeof( buffer ) * sizeof( char ));
            strcpy(monthGroup[ count - 1 ], buffer );
    }
}

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

редактировать Спасибо всем, кто нашел время, чтобы хоть немного понять кое-что, у меня возникли проблемы с оборачиванием головы. Я чувствую себя маленьким ребенком в деревне старейшин на чужбине. Большое спасибо за вежливость и руководство.

Ответы [ 6 ]

8 голосов
/ 13 марта 2012

Боюсь, вы не понимаете, насколько вы далеки от правильного понимания. Сиди крепко, это будет долго. Добро пожаловать в C.

char** monthGroup

Все это на самом деле означает "указатель на указатель на char". Однако у C есть много причин, по которым вы хотели бы указать на что-то. В вашем случае «внутреннее» указание таково, что вы действительно можете указать на последовательность из char с в памяти (которую вы в разговорной речи воспринимаете как «строку», что C правильно делает не имеет ), а «внешнее» указание таково, что вы можете указывать на последовательность этих char* s и рассматривать эту последовательность как «массив» (даже если не ; вы собираетесь его динамически распределять).

Вот проблема: когда вы передаете это char**, которое пришло от main, оно фактически ни на что не указывает. "Хорошо", говорите вы; «Функция будет указывать на некоторую память, которую я выделю с помощью malloc()».

Нет.

C передает все по значению. char**, получаемый months, является копией блока char** в main локальных переменных. Вы перезаписываете указатель (с результатом вызова malloc), записываете некоторые указатели в указанную память (больше malloc результатов), копируете некоторые данные в эти порции указанной памяти ... и затем, в конце функции, параметр monthGroup (который является локальной переменной в months) больше не существует, и вы потеряли все эти данные, и переменная monthGroup в main по-прежнему неизменна и не указывает ни на что. Когда ты пытаешься использовать его так, как будто он на что-то указывает, бум, ты мертв.

Так как же нам обойти это? С другим уровнем наведения, конечно, C правильно не имеет «передачи по ссылке», поэтому мы должны подделать его. Мы принимаем char*** и передаем &monthGroup. Это все еще скопированное значение, но оно указывает непосредственно в хранилище локальной переменной для этого вызова main (в стеке). Это позволяет нам написать значение, которое будет видно в main. Мы присваиваем первый malloc результат *monthGroup и записываем указатели в это хранилище (*monthGroup[count]) и т. Д.

За исключением того, что мы на самом деле не хотим этого делать, потому что это невероятно уродливо и сбивает с толку, и трудно понять, как правильно. Вместо этого давайте сделаем то, что должно быть невероятно очевидной вещью, которую вы должны делать, и эта базовая инструкция подчеркивает недостаточно: используйте возвращаемое значение функции, чтобы вернуть результат вычисления - поэтому она называется возвращаемое значение .

То есть мы устанавливаем char** в months ( не принимает для него какой-либо параметр), возвращаем его и используем для инициализации значения в main.

Мы закончили? нет Нет .

У вас все еще есть логические ошибки:

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

На самом деле, вы do хотите сделать что-то подобное, но только потому, что заранее не знаете, сколько элементов вам нужно. Проблема в том, что новое распределение - это просто новое распределение, не содержащее ранее установленных указателей.

К счастью, у C есть решение для этого: realloc. Это выделит новую память, скопирует старое содержимое (указатели на ваши выделенные «строки») и освободит старый блок. Ура! Более того, realloc будет вести себя как malloc, если мы дадим ему указатель NULL для «старой памяти». Это позволяет нам избежать использования специальной оболочки в нашем цикле.

  • Вы неправильно используете значение count.В первый раз в цикле вы увеличите count до 1, выделите некоторое пространство для monthGroup[1], чтобы указать, а затем попытаетесь записать в пространство, на которое указывает monthGroup[0], который никогда не был установлен.Вы хотите записать в то же пространство для "строки", которую вы только что выделили.(Кстати, sizeof(char) бесполезен: это всегда 1. Даже если ваша система использует более 8 бит для представления символа ! char является основной единицей храненияв вашей системе.)

За исключением случаев, когда есть более простой способ: используйте strdup, чтобы получить указатель на выделенную копию вашего буфера.

char** months(FILE* monthfp) {
    char buffer[50];
    int count = 0;
    char** monthGroup = NULL;

    while (fgets(buffer, sizeof(buffer), monthfp) != NULL) {
        // (re-)allocate the storage:
        monthGroup = realloc(monthGroup, count * sizeof(char*));
        // ask for a duplicate of the buffer contents, and put a pointer to the
        // duplicate sequence into the last element of the storage:
        monthGroup[count - 1] = strdup(buffer);
    }

    return monthGroup;
}

Настройка main для соответствия оставлено (надеюсь, тривиальным) упражнение.Пожалуйста, прочтите также документацию для realloc и strdup.

Мы закончили? Нет.

Вы все равно должны проверять NULL возвраты из realloc и strdup (так как они оба пытаются выделить память и, таким образом, могутв C) таким образом происходит сбой, и вам все еще нужен код для free выделенной памяти.

И, как отмечали другие, не следует предполагать, что будет 12 месяцев.Если бы вы могли это предположить, вы бы не выделяли динамически monthGroup во-первых;вы бы просто использовали массив .Таким образом, вам нужно как-то сообщить размер результирующего «массива» (добавление явного NULL-указателя в конец - это один из способов; другой - сделать ужасно некрасивую вещь, передать char*** и использовать возвращаемое значение для подсчетаразмер).

1 голос
/ 13 марта 2012

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

Глядя на ваш код:

monthGroup =  malloc( count * sizeof ( char* ));

Эта строка выделяет часть памяти, которая эквивалентна массиву char * размера count.

monthGroup[count] = malloc( sizeof( buffer ) * sizeof( char ));

Здесь выделяется буфер размером sizeof (buffer) (sizeof (char) не требуется). Это одна из проблем здесь: вы назначаете его на monthGroup[count]. Массивы в C имеют нулевую базу, что означает, что массив:

int array [3];

имеет элементы:

 array [0], array [1] and array [2]

array [3] находится вне памяти массива. Таким образом, monthGroup[count] также находится вне памяти массива. Вы хотите monthGroup[count-1] вместо этого. Это запишет последний элемент в массиве.

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

Чтобы исправить это, есть два подхода.

  1. При выделении массива скопируйте содержимое старого массива в новый массив:

    oldarray = monthGroup; monthGroup = malloc (count * sizeof (char *)) memcpy (monthGroup, oldarray, count-1 * sizeof (char *)); бесплатно (oldarray); monthGroup [count-1] = ....

    или используйте realloc.

  2. Использовать связанный список. Гораздо сложнее этот, но имеет то преимущество, что не требует, чтобы массивы копировались при каждом чтении нового элемента.

Кроме того, параметр monthGroup не передается вызывающей стороне. Либо измените функцию на:

char **months (FILE *fp)

или

void months (FILE *fp, char ***ugly_pointer)

Наконец, вызывающий в данный момент предполагает, что имеется 12 записей, и пытается распечатать каждую. Что произойдет, если их меньше 12 или больше 12? Один из способов справиться с этим - использовать специальный указатель для завершения массива monthsGroup, NULL вполне подойдет. Просто выделите один дополнительный элемент в массиве и установите последний в NULL.

1 голос
/ 13 марта 2012

Завершенный код будет следующим:

int main (void) 
{
    FILE *monthfp; /*to be used for reading in months from months.txt*/
    char **monthGroup = NULL;
    char **iter;

    if ((monthfp = fopen("c:\\months.txt", "r")) == NULL){
        printf("unable to open months.txt. \n");
        exit(1);
    }

    months(monthfp, &monthGroup);

    iter = monthGroup;

    /* We know that the last element is NULL, and that element will stop the while */
    while (*iter) {
        printf("%s", *iter);
        free(*iter);
        iter++;     
    }

    /* Remember that you were modifying iter, so you have to discard it */
    free(monthGroup);

    fclose(monthfp);
}

void months(FILE *monthfp, char ***monthGroup)
{
/*****************************************
    name: months
    input: input file, data array
    returns: No return. Modifies array.
*/
    char buffer[50];
    int count = 0;

    while (fgets(buffer, sizeof(buffer), monthfp) != NULL){
        count++;

        /* We realloc the buffer */
        *monthGroup = (char**)realloc(*monthGroup, count * sizeof(char**));

        /* Here I'm allocating an exact buffer by counting the length of the line using strlen */
        (*monthGroup)[count - 1] = (char*)malloc((strlen(buffer) + 1) * sizeof( char ));
        strcpy((*monthGroup)[count - 1], buffer);
    }

    /* We add a terminating NULL element here. Other possibility would be returning count. */
    count++;
    *monthGroup = (char**)realloc(*monthGroup, count * sizeof(char**));
    (*monthGroup)[count - 1] = NULL;
}

Как говорили другие, char*** ужасен.

1 голос
/ 13 марта 2012

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

int main() {
    int a = 5;
    addOneTo(a);
    printf("%d\n", a);
    return 0;
}

будет печатать 5 независимо от того, что addOneTo () делает со своим параметром.

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

У вас есть несколько вариантов решения этой проблемы.Вы можете выполнить malloc в monthGroup вне функции months (), а затем передать его. Вы можете вернуть значение monthGroup.Или вы можете передать указатель на monthGroup для семантики передачи по ссылке (char ***).

В любом случае, я бы посоветовал вам научиться использовать отладчик (например, gdb), чтобы вы моглипосмотрим, почему это segfaults в следующий раз!

1 голос
/ 13 марта 2012

Для меня наиболее очевидной из ваших проблем является то, что вы передаете char** monthGroup в качестве параметра значением , затем malloc внутри функции months, а затем пытаетесь использовать его ввызывающая функция.Однако, поскольку вы передали его по значению, вы сохранили malloc ed-адрес только в локальной копии monthGroup, что не меняет значение исходной переменной в main.

.быстрое исправление, вам нужно передать указатель на monthGroup, а не (копию) его текущего значения:

int main (void){
  ...
  char** monthGroup;
  ...
  months( monthfp, &monthGroup );
  ...
}

void months ( FILE* monthfp, char*** monthGroup ){
  ...
  *monthGroup =  malloc( count * sizeof ( char* ));
  ...
}

Это ужасно (ИМХО не должно быть реальногопричина использовать char*** в реальном коде), но, по крайней мере, шаг в правильном направлении.

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

// read the first bunch of text from the file
count++;
// count is now 1
monthGroup =  malloc( count * sizeof ( char* ));
// you allocated an array of size 1
monthGroup[count] = malloc( sizeof( buffer ) * sizeof( char ));
// you try to write to the element at index 1 - another segfault!
// should be monthGroup[count - 1] as below
strcpy(monthGroup[ count - 1 ], buffer );

Даже с предложенным выше исправлением, после 10 итераций вы обязательно должны иметь массив из 10 элементов, первые 9 из которых являются висячими указателями итолько десятый указывает на действительный адрес.

0 голосов
/ 13 марта 2012

Основная ошибка, которую я сразу вижу, заключается в том, что ваше выделение для monthGroup никогда не вернется в ваше main.

...