Почему возникает ошибка 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 шт):
Потому что вы возвращаете из функции указатель на локальную переменную. Которая уничтожается как только вы из функции выходите. И указатель в память есть, а строки там нет.
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;
}
Показал ваш код компилятору:
$ 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;
}