Ошибка сегментации при использовании qsort

Я работаю над программой c для чтения из txt и отсортируйте строки.

data.txt:

jk ef ab cd bc gh fg ij hi de 

Вот мой код:

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

int cmp(const void *p1, const void *p2) {
    return strcmp(*(const char **)p1,  *(const char **)p2);
}

int main() {
    FILE *f = fopen("data.txt", "r");
    char s[255][255];
    char tmp[255];
    int n = 0;

    while (!feof(f)) {
        fscanf(f, "%s", tmp);
        strcpy(s[n], tmp);
        n++;
    }

    fclose(f);

    qsort(s, n, sizeof(char *), cmp);

    int i = 0;
    for (; i < n; i++) {
        printf("%s ", s[i]);
    }

    return EXIT_SUCCESS;
} 

Я запустил код в Ubuntu, и он сломался из-за segfault. Поверьте, это segfault произошло в qsort, и я не мог понять, почему.

Кто-нибудь может дать мне несколько предложений?


person Bo Yuan    schedule 07.09.2017    source источник
comment
Я пробовал, но это не решает. Все еще вижу segfault.   -  person Bo Yuan    schedule 07.09.2017
comment
Скомпилируйте со всеми предупреждениями и отладочной информацией (например, gcc -Wall -Wextra -g с GCC....), затем используйте отладчик (например, gdb). Внимательно прочитайте документацию qsort(3). Используйте динамическое выделение памяти C   -  person Basile Starynkevitch    schedule 07.09.2017
comment
@BLUEPIXY: Re 2.: Вы уверены?   -  person alk    schedule 07.09.2017
comment
@alk Я так думаю.   -  person BLUEPIXY    schedule 07.09.2017
comment
@BLUEPIXY: qsort() передает указатели функции сравнения на элементы массива. Элементов массива char[255]. Таким образом, qsort() функция сравнения передается в двух char(*)[255].   -  person alk    schedule 07.09.2017
comment
@alk В конце концов этот главный адрес передается функции сравнения. Например, как strcmp(s[X], s[Y])   -  person BLUEPIXY    schedule 07.09.2017
comment
@alk В функцию сравнения будут переданы указатели, указывающие на начало элементов. Каждый элемент содержит строку, поэтому приведение указателей к (char*) дает правильные результаты.   -  person Klas Lindbäck    schedule 07.09.2017


Ответы (4)


Функция сравнения неверна, так как вы сортируете массив массивов char, вы можете напрямую передавать указатели на элементы в strcmp:

int cmp(const void *p1, const void *p2) {
    return strcmp(p1, p2);
}

Однако обратите внимание, что ваш цикл синтаксического анализа также неверен: feof() - неправильный способ проверки конца файла. Используйте это вместо этого:

  n = 0;
  while (n < 255 && fscanf(f, "%254s", s[n]) == 1) {
      n++;
  }

Вызов qsort должен указывать размер элемента массива:

  qsort(s, n, sizeof(*s), cmp);

Вот исправленная версия:

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

int cmp(const void *p1, const void *p2) {
    return strcmp(p1, p2);
}

int main(void) {
    FILE *f = fopen("data.txt", "r");
    char s[255][255];
    char tmp[255];
    int n = 0;

    if (f == NULL)
        return EXIT_FAILURE;

    while (n < 255 && fscanf(f, "%254s", s[n]) == 1) {
        n++;
    }
    fclose(f);

    qsort(s, n, sizeof(*s), cmp);

    for (int i = 0; i < n; i++) {
        printf("%s ", s[i]);
    }
    printf("\n");

    return EXIT_SUCCESS;
}
person chqrlie    schedule 07.09.2017

Многие люди дали хороший ответ.

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

Мы предполагаем, что исходный файл называется q.c.

Скомпилируйте с отладочными символами (обратите внимание, что здесь нет необходимости иметь Makefile):

% make CFLAGS=-g q
cc -g  q.c  -o q

Теперь запустите программу с помощью отладчика (gdb):

% gdb q
(gdb) run
Starting program: /usr/home/fenyo/tmp/qs/q
Program received signal SIGSEGV, Segmentation fault.
0x00000008009607a6 in strcmp () from /lib/libc.so.7

Теперь посмотрим на кадр стека:

(gdb) where
#0  0x00000008009607a6 in strcmp () from /lib/libc.so.7
#1  0x00000000004009b5 in cmp (p1=0x7ffffffeeb60, p2=0x7ffffffeeb88) at q.c:8
#2  0x000000080093b834 in qsort () from /lib/libc.so.7
#3  0x0000000000400af5 in main () at q.c:26

Итак, ваша проблема заключается в вызове вашей функции cmp библиотекой qsort, которая вызывает strcmp с неправильными указателями.

Итак, мы поднимаемся с одного кадра стека на уровень вашей функции cmp:

(gdb) up
#1  0x00000000004009b3 in cmp (p1=0x7ffffffeeb60, p2=0x7ffffffeeb88) at q.c:8
8           return strcmp( *(const char **) p1,  *(const char **) p2);

Смотрим на тип p1:

(gdb) ptype p1
type = void *

Поскольку p1 является указателем, мы проверяем его содержимое, отображая 10 первых байтов:

(gdb) print (*(char *) p1)@10
$43 = "jk\000\000\000\000\000\000\000"

Итак, мы обнаруживаем, что это строка с завершающим нулем, содержащая jk.

Итак, ваш слепок недействителен: *(const char **) p1.

Это должно было быть (const char*) p1.

Мы меняем актерский состав, и тогда это работает.

person Alexandre Fenyo    schedule 07.09.2017
comment
Valgrind также довольно эффективен в подобных вещах — и он не интерактивен. - person Toby Speight; 07.09.2017

qsort() передает функции сравнения два указателя на элементы массива.

Элементы массива имеют тип char[255]. Таким образом, функция сравнения qsort() передается в двух char(*)[255].

Так должно выглядеть

int cmp(const void *p1, const void *p2)
{
  const char (*ps1)[255] = p1;
  const char (*ps2)[255] = p2;

  return strcmp(*ps1, *ps2);
}
person alk    schedule 07.09.2017
comment
Было бы неприятной функцией, если бы OP захотел изменить размер массива, верно? - person machine_1; 07.09.2017
comment
предупреждение: инициализация отбрасывает квалификатор 'const' из указателя tar get type [-Wdiscarded-qualifiers] - person BLUEPIXY; 07.09.2017
comment
const char (*ps1)[255] = p1; выдает такое же предупреждение. - person BLUEPIXY; 08.09.2017
comment
Кстати, &s[X][0] передается в качестве аргумента функции сравнения. У него нет информации о типе, так как он передается как пустой указатель. Вы пытаетесь воспроизвести тип, но в итоге эта информация не используется. Вы пытаетесь воспроизвести тип, но в итоге эта информация не используется. (Перешел к strcmp как const char *) Так что это просто форма. Это может быть полезно при использовании strncmp как strncmp(*ps1, *ps2, sizeof(*ps1)). Однако, поскольку это означает, что он полезен, поскольку он разбит как C-String, он будет отличаться от желания OP. - person BLUEPIXY; 08.09.2017

char s[255][255];

Это очень плохо пахнет. Используйте динамическое выделение памяти C.

Вместо этого рассмотрим выделенный в куче указатель на массив строк, выделенных в куче.

qsort(s, n , sizeof(char *), cmp);

Внимательно прочитайте документацию qsort(3). С char s[255][255] ваш вызов qsort очень неверен.

Кто-нибудь может дать мне несколько предложений?

Прочтите более внимательно хорошую книгу по программированию на C и документацию по каждой используемой вами функции (даже strcmp(3)). Посмотрите также некоторые ссылки на C и загляните в спецификацию C11 n1570.

Скомпилируйте со всеми предупреждениями и отладочной информацией, т.е. gcc -Wall -Wextra -g с GCC (подробнее о Вызов GCC). Используйте отладчик gdb (читайте об Отладка с помощью GDB< /эм>)

PS. Мы не будем делать вашу домашнюю работу.

person Basile Starynkevitch    schedule 07.09.2017
comment
Можете ли вы порекомендовать хорошее краткое руководство для изучения основ GDB, прежде чем переходить к более важным вещам, чем Отладка с помощью GDB? - person J...S; 08.09.2017
comment
Нет. У этой штуки есть вступительная глава. Прочтите это. Кроме того, в других местах доступно множество руководств по GDB. ГИЙФ. - person Basile Starynkevitch; 08.09.2017