Почему возникает ошибка Invalid memory reference (SIGSEGV)?

Мне нужно получить расширения всех файлов в директории и, именно для получения расширения из строки с названием файла, я написал такую функцию:

char * get_ext(char * name) {
    int l = -1;
    for (int pos = strlen(name); pos >= 0; pos--) {
        if (name[pos] == '.') {
            l = pos;
            break;
        }
    }
    if (l == -1)
        return "";
    char result[strlen(name)];
    int pos = l;
    for (; pos < strlen(name); pos++)
        result[pos - l] = name[l];
    name[(pos < strlen(name) ? pos : pos - 1)] = '\0';
    return result;
}

Из функций main я вызываю её, с помощью strcmp сравниваю возвращаемый результат с "" и если не равно, то вывожу название файла и его расширение printf("%s: %s\n", dir[count]->d_name, get_ext(dir[count]->d_name));, но по какой-то причине мне выдаёт "Invalid memory reference (SIGSEGV)", не подскажите почему?

P.s.Если вместо return result; поставить return "."; то ошибка исчезает...


Ответы (2 шт):

Автор решения: DmitryK

Потому что вы возвращаете из функции указатель на локальную переменную. Которая уничтожается как только вы из функции выходите. И указатель в память есть, а строки там нет.

char * get_ext(char * name) {
    int l = -1;
    ....
    if (l == -1)
        return "";     // локальная переменная
    char result[strlen(name)];   // локальная переменная
    ....
    return result;
}

Вам нужно вернуть либо nullptr в случае ошибки, либо указатель на позицию в строке name. Тогда получится - если строка была file001.txt, верните указатель на 7 символ и результат будет .txt. Тогда у вас вообще не будет никакого копирования - вы работаете с исходной строкой.
Или вообще - индекс в строке name или -1 в случае ошибки. Т.к. строка char * name у вас живет за пределами функции, то всё будет корректно.

int get_ext(char * name) {
    int l = -1;
    ....
    if (l == -1)
        return l;
    ....
    return l;
}
→ Ссылка
Автор решения: Stanislav Volodarskiy

Показал ваш код компилятору:

$ gcc -std=c11 -pedantic -Wall -Wextra -Werror temp.c 
temp.c: In function ‘get_ext’:
temp.c:18:16: error: comparison of integer expressions of different signedness: ‘int’ and ‘size_t’ {aka ‘long unsigned int’} [-Werror=sign-compare]
   18 |     for (; pos < strlen(name); pos++)
      |                ^
temp.c:20:15: error: comparison of integer expressions of different signedness: ‘int’ and ‘size_t’ {aka ‘long unsigned int’} [-Werror=sign-compare]
   20 |     name[(pos < strlen(name) ? pos : pos - 1)] = '\0';
      |               ^
temp.c:21:12: error: function returns address of local variable [-Werror=return-local-addr]
   21 |     return result;
      |            ^~~~~~
cc1: all warnings being treated as errors

Первые две ошибки - не ошибки, просто компилятор ужасно строгий. Последняя - то что нам нужно: "функция возвращает адрес локальной переменной". Это плохо: локальная переменая удаляется как только функция возвращает управление. Указатель ссылается на мусор за вершиной стека. К сожалению, этот мусор часто содержит старые данные и ошибка не будет очевидна в первых тестовых прогонах. Она проявится позже и, скорее всего, в другом месте программы.

NB Это называется неопределённое поведение/undefined behavior. Если в программе UB - она может делать что угодно. Продолжение разговора о её поведении не имеет смысла.

Как поправить? Вернуть указатель на подстроку внутри строки:

char * get_ext(char * name) {
    int l = -1;
    for (int pos = strlen(name); pos >= 0; pos--) {
        if (name[pos] == '.') {
            l = pos;
            break;
        }
    }
    if (l == -1)
        return "";
    return name + l;
}

Этот код работает: локальной переменной нет, указатель ссылается на кусочек строки-аргумента. При использовании нужно описать что за указатель возвращается и всё.

Нет, не всё. strrchr ищет символ с конца строки. Обратите внимание как она устроена: если символ найден, возвращается указатель на него, иначе NULL. Вот как её можно использовать:

char * get_ext(char * name) {
    char * dot = strrchr(name, '.');
    return (dot == NULL) ? "" : dot;
}

Есть шероховатость: функция возвращает то указатель внутрь строки, то указатель на константную строку "". Лучше так не делать, тем более что функция возвращает char * что разрешает редактировать указанную память. Но редактировать байт указанный "" - неопределённое поведение. Исправляемся:

char * get_ext(char * name) {
    char * dot = strrchr(name, '.');
    return (dot == NULL) ? name + strlen(name) : dot;
}

Смысл тот же, но теперь функция возвращает указатель на старую память даже если точки в строке не было. Работать с такой проще - труднее сделать ошибку при использовании.

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

char * get_root(const char * name) {
    const char * dot = strrchr(name, '.');
    const size_t len = (dot == NULL) ? strlen(name) : (size_t)(dot - name);
    char * root = malloc(len + 1);
    if (root == NULL) {
        return NULL;
    }
    memcpy(root, name, len);
    root[len] = '\0';
    return root;
}
→ Ссылка