На Stack Overflow существует много подобных сообщений, я внимательно просмотрел такие сообщения и связал их в конце этого сообщения. Эти существующие сообщения бесполезны, потому что они демонстрируют ошибки сегментации, возникающие в результате передачи неинициализированных участков памяти в аргумент char *dest
функции strncpy()
. Еще одна распространенная тема в этих сообщениях — это строгие рекомендации против использования strncpy()
, но единственная альтернативная рекомендация, которую я прочитал, — это использовать strlcpy()
вместо этого.
Моя программа использует статически выделенный массив структур FS_Info
для хранения информации о записях файловой системы. Идея состоит в том, чтобы вывести имена и размеры 10 самых больших файлов в указанном каталоге. Поскольку массив имеет фиксированный размер, когда обнаруживается запись файловой системы, которая больше, чем самая маленькая запись в массиве, моя программа пытается обновить структуру, описывающую эту меньшую запись, значениями, описывающими новую, большую.
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/stat.h>
#include <string.h>
#include <limits.h>
#include <unistd.h>
// size of array used to contain filesystem entries
const size_t fs_info_arr_size = 10;
// A struct to contain the name of a filesystem entry and its size in bytes.
typedef struct FS_Info
{
char name[PATH_MAX];
long long size;
} FS_Info;
// global pointer to FS_Info array
FS_Info *fs_info_arr[fs_info_arr_size];
// used to sort fs_entries array descending in terms of entry size
static int compare(const void *a, const void *b)
{
const struct FS_Info *entryA = (FS_Info *)a;
const struct FS_Info *entryB = (FS_Info *)b;
return (entryB->size - entryA->size) - (entryA->size - entryB->size);
}
/*
Iterates over an array of FS_Info structs and returns a pointer to the struct having the smallest size member
*/
FS_Info *get_smallest_entry(FS_Info **entries)
{
long long smallest = entries[0]->size;
FS_Info *target;
for (int i = 1; i < fs_info_arr_size * sizeof(FS_Info); i += sizeof(FS_Info))
{
if (entries[i]->size < smallest)
{
smallest = entries[i]->size;
target = entries[i];
}
}
return target;
}
/*
Add entires to the array. If the array is full, use the above function to find the
struct having the smallest file size, and if the current file size is larger, replace it.
*/
void update_fs_info_arr(char *path) // FIXME debug call stack shows that using strncpy here causes segfault
{
static int items_added = 0;
struct stat st;
if (stat(path, &st) == 0)
{
if (items_added < fs_info_arr_size) // if array capacity will not be exceeded
{
strncpy(fs_info_arr[items_added]->name, path, PATH_MAX);
// strlcpy(fs_info_arr[items_added]->name, path, sizeof(fs_info_arr[items_added]->name));
// strncpy(fs_info_arr[items_added]->name, path, sizeof(fs_info_arr[items_added]->name) / sizeof(fs_info_arr[items_added]->name[0]) - 1);
// fs_info_arr[items_added]->name[sizeof(fs_info_arr[items_added]->name) / sizeof(fs_info_arr[items_added]->name[0]) - 1] = 0;
fs_info_arr[items_added]->size = st.st_size;
items_added++;
}
else
// find entry having the smallest size and replace it with the current entry if it is larger
{
FS_Info *smallest = get_smallest_entry(fs_info_arr);
if (st.st_size > smallest->size)
{
strncpy(smallest->name, path, PATH_MAX);
// strlcpy(smallest->name, path, sizeof(smallest->name));
// strncpy(smallest->name, path, sizeof(smallest->name) / sizeof(smallest->name[0]) - 1);
// smallest->name[sizeof(smallest->name) / sizeof(smallest->name[0]) - 1] = 0;
smallest->size = st.st_size;
}
}
}
else
{
printf("Error getting stat for entry %s: %d\n", path, stat(path, &st));
}
}
void walk(const char *currDir)
{
DIR *dir = opendir(currDir);
struct dirent *entry;
if (dir == NULL)
{
printf("%s could not be opened", currDir);
return;
}
while ((entry = readdir(dir)) != NULL)
{
// if directory is current dir or parent dir
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
{
continue;
}
char path_to_entry[PATH_MAX];
snprintf(path_to_entry, sizeof(path_to_entry), "%s/%s", currDir, entry->d_name);
//snprintf(path_to_entry, sizeof(path_to_entry) - 1, "%s/%s", currDir, entry->d_name);
//path_to_entry[sizeof(path_to_entry) - 1] = '\0';
update_fs_info_arr(path_to_entry);
if (entry->d_type == DT_DIR)
{
walk(path_to_entry);
}
}
closedir(dir);
}
int main(int argc, char *argv[])
{
char target_dir[PATH_MAX];
strncpy(target_dir, argv[1], PATH_MAX);
printf("Finding the %zu largest files in: %s\n", fs_info_arr_size, target_dir);
// recursively visit all entries in the specified directory
walk(target_dir);
// sort the entries descending by file size
qsort(fs_info_arr, fs_info_arr_size, sizeof(*fs_info_arr), compare);
// output ten largest files found
for (int i = 0; i < fs_info_arr_size; i++)
{
printf("%s\t%lld\n", fs_info_arr[i]->name, fs_info_arr[i]->size);
}
return EXIT_SUCCESS;
}
Сообщения, указанные ниже, не могут инициализировать память, копируемую strncpy()
, или содержат неверный size_t num
аргумент, как указано здесь. В случае с последним я попытался изменить свой код, чтобы он соответствовал шаблону, описанному в сообщении, указанном первым ниже, но это не имело никакого эффекта.
strncpy приводит к ошибке сегментации
Ошибка сегментации при вызове strcpy
Ошибка сегментации: 11 (вызвана strncpy())
Почему я получаю ошибку сегментации при использовании strncpy?
Ошибка сегментации при использовании strncpy в c
Я внес исправления, как было предложено в разделе ответов, и понял, почему логика моей программы была ошибочной, но эта программа по-прежнему приводит к плохому доступу:
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/stat.h>
#include <string.h>
#include <limits.h>
#include <unistd.h>
// size of array used to contain filesystem entries
const size_t fs_info_arr_size = 10;
/*
A struct to contain the name of a filesystem entry and its size in bytes.
An array of this type will be used to catalog all filesystem entries for
the directory specified as command line argument.
*/
typedef struct FS_Info
{
char name[PATH_MAX];
long long size;
} FS_Info;
// global pointer to FS_Info array
FS_Info fs_info_arr[fs_info_arr_size];
// used to sort fs_entries array descending in terms of entry size
static int compare(const void *a, const void *b)
{
const struct FS_Info *entryA = (FS_Info *)a;
const struct FS_Info *entryB = (FS_Info *)b;
return entryB->size - entryA->size;
}
/*
Iterates over an array of FS_Info structs and returns a pointer to the struct
having the smallest size member.
*/
FS_Info *get_smallest_entry(FS_Info *entries)
{
long long smallest = entries[0].size;
FS_Info *target;
for (int i = 1; i < fs_info_arr_size; i++)
{
if (entries[i].size < smallest)
{
smallest = entries[i].size;
target = &entries[i];
}
}
return target;
}
/*
Add entires to the array. If the array is full, use the above function to find the
struct having the smallest file size, and if the current file size is larger, replace it.
*/
void update_fs_info_arr(char *path) // FIXME debug call stack shows that using strncpy here causes segfault
{
static int items_added = 0;
struct stat st;
if (stat(path, &st) == 0)
{
if (items_added < fs_info_arr_size) // if array capacity will not be exceeded
{
strncpy(fs_info_arr[items_added].name, path, PATH_MAX);
fs_info_arr[items_added].size = st.st_size;
items_added++;
}
else
// find entry having the smallest size and replace it with the current entry if it is larger
{
FS_Info *smallest = get_smallest_entry(fs_info_arr);
if (st.st_size > smallest->size)
{
strncpy(smallest->name, path, PATH_MAX);
smallest->size = st.st_size;
}
}
}
else
{
printf("Error getting stat for entry %s: %d\n", path, stat(path, &st));
}
}
void walk(const char *currDir)
{
DIR *dir = opendir(currDir);
struct dirent *entry;
if (dir == NULL)
{
printf("%s could not be opened", currDir);
return;
}
while ((entry = readdir(dir)) != NULL)
{
// if directory is current dir or parent dir
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
{
continue;
}
char path_to_entry[PATH_MAX];
snprintf(path_to_entry, sizeof(path_to_entry), "%s/%s", currDir, entry->d_name);
update_fs_info_arr(path_to_entry);
if (entry->d_type == DT_DIR)
{
walk(path_to_entry);
}
}
closedir(dir);
}
int main(int argc, char *argv[])
{
// a char array to hold a filesystem path
char target_dir[PATH_MAX];
strncpy(target_dir, argv[1], PATH_MAX);
printf("Finding the %zu largest files in: %s\n", fs_info_arr_size, target_dir);
// recursively visit all entries in the specified directory
walk(target_dir);
// sort the entries descending by file size
qsort(fs_info_arr, fs_info_arr_size, sizeof(*fs_info_arr), compare);
// output ten largest files found
for (int i = 0; i < fs_info_arr_size; i++)
{
printf("%s\t%lld\n", fs_info_arr[i].name, fs_info_arr[i].size);
}
return EXIT_SUCCESS;
}
Взгляните на это: stackoverflow.com/a/74905718/17592432
FS_Info *fs_info_arr[fs_info_arr_size];
— это массив указателей... Где вы их назначаете, чтобы они указывали на блоки памяти???
@WhozCraig, да, я не могу поверить, что написал это.... спасибо, что указали на это....
re Версия 2: get_smallest_entry()
... Попробуйте назначить target
для указания на 0-й элемент в верхней части функции ... Возможность возврата неинициализированного указателя, который немедленно разыменовывается вызывающей функцией.
@ Fe2O3, это помогло! Вы волшебник C. Далее я собираюсь работать над реализацией альтернативного подхода, который вы указали.
Рад, что вы получили желаемые результаты от своей версии... Урок из последнего исправления: "ВСЕГДА инициализируйте переменные, когда/где они определены". В противном случае спорадические симптомы работы/отказа преждевременно состарят вас... Ура! :-)
ОП: «Моя программа использует статически выделенный массив структур FS_Info для хранения информации о записях файловой системы».
Код:
FS_Info *fs_info_arr[fs_info_arr_size];
Вы выделяете указатели, но никогда не выделяете место для хранения того, на что могут указывать указатели...
Пытаться
FS_Info fs_info_arr[fs_info_arr_size];
и сделайте правильную адаптацию кода для использования массива структур (а не массива указателей, которые все равны NULL.)
И...
static int compare(const void *a, const void *b)
{
const struct FS_Info *entryA = (FS_Info *)a;
const struct FS_Info *entryB = (FS_Info *)b;
return (entryB->size - entryA->size) - (entryA->size - entryB->size);
}
является излишне сложным. Для убывания просто используйте
return entryB->size - entryA->size;
(и вы можете извиниться перед strncpy()
за неуместное оскорбление его репутации.)
Конечно, если вы используете strncpy для копирования неподходящей строки, strncpy выживет, но место назначения не содержит допустимой строки и, вероятно, позже вызовет проблемы. Плюс больше проблем, если ваши строки utf-8.
@ gnasher729 Отмечено, и требуется осторожность ... ЕСЛИ ОП доверял библиотечной функции и (вместо того, чтобы обвинять эту функцию) проверил свой собственный код на наличие недостатков, этот вопрос не возник бы ... (Кроме того, «PATH_MAX» или «MAX_PATH» обеспечивает некоторую безопасность для использования strncpy()
в этом конкретном случае... Это не дикий запад принятия пользовательского ввода с gets()
...
Спасибо за указание на ошибочную логику, связанную с использованием массива указателей на структуры. Я редактирую свой вопрос, чтобы включить обновленный код, который по-прежнему приводит к Bus Error
с Bad Access Code 2
. Я включу полную программу, чтобы вы могли попробовать ее на своей системе, если хотите.
@JohnHarrington Когда вы редактируете свой код, используйте некоторые обозначения, которые не делают недействительными текущие комментарии или ответы .... Творческое использование #if 0 // was bad
#else
и #endif
, по крайней мере, сохранит часть прошлого кода ... Вы изучили версию? по ссылке, которую я предоставил в комментарии под вашим вопросом? Гораздо меньше кода. Меньше мест, где могут спрятаться жуки...
@ Fe2O3 Fe2O3 Я вернул свои правки к исходному блоку кода, чтобы он продемонстрировал этот ужасный доступ к массиву, и включил новый блок кода, чтобы продемонстрировать внесенные мной изменения. Поскольку я новичок в C, я хотел увидеть этот подход, но смотрю вашу более раннюю реализацию, немного смущенный, чтобы сказать, что мне трудно следовать ему. Однако я попробую этот подход в следующий раз.
@JohnHarrington Когда-то мы все были новичками. Версия в этом другом вопросе была в первую очередь для того, чтобы показать, как memove()
можно использовать для хранения массива biggies[]
в отсортированном порядке (сортировка вставками... не лучшая, но демонстрирует memmove()
...). Обратите внимание, что здесь нет лишних ' вспомогательные функции» в этом коде. Иногда «разложение» кода просто означает, что читатель должен сфокусироваться, чтобы найти и прочитать удаленный код... Это балансирование, также известное как «мнение», и у всех нас есть свои личные предпочтения. :-)
Во-первых,
i += sizeof(FS_Info)
вget_smallest_entry
ужасно неправильно.i
используется как индекс массива.i
должен увеличиваться на1
на каждой итерации. Точно так жеi < fs_info_arr_size * sizeof(FS_Info)
неправильно. Так должно бытьi < fs_info_arr_size