[TroubleShooting] C언어 SIGSEGV
C언어에서 발생한 Segmentation Fault
[TroubleShooting] C언어 SIGSEGV
프로젝트: MAFM에서 C언어로 파일 READ를 하는 함수를 짜던 중 오류가 발생했다.
1
Process finished with exit code 139 (interrupted by signal 11:SIGSEGV)
✏️ 코드
문제가 발생했던 코드는 아래와 같다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
// get_file_data: 주어진 파일 경로에 대한 정보를 읽고 필요한 데이터를 반환
// 입력: path (파일 경로)
// 출력: 파일 정보 배열 (파일 경로, 파일 이름, 파일 내용 조각)
/* 파일 정보 배열의 구조:
* data[0]: 파일의 전체 경로 (char*)
* data[1]: 파일의 이름 (char*)
* data[2], data[3], ...: 파일 내용을 일정 크기(chunkSize)로 나눈 조각들 (char*)
* 마지막 data[idx + 1]: NULL 포인터 (배열의 끝을 알리기 위해)
*/
char** get_file_data(const char* path) {
// 파일을 읽기 모드로 엽니다.
FILE *file = fopen(path, "rb");
if (!file) {
perror("Failed to open file"); // 파일 열기 실패 시 오류 메시지 출력
return NULL; // 실패 시 NULL 반환
}
// 파일 이름을 가져옵니다.
char *fname = get_filename(path);
if (!fname) {
perror("Failed to get filename"); // 파일 이름을 가져오는 데 실패하면 오류 메시지 출력
fclose(file); // 파일 닫기
return NULL; // 실패 시 NULL 반환
}
// 파일이 이미지 또는 비디오인지 확인합니다.
int is_image_or_video_flag = is_image_or_video(path);
// 이미지 또는 비디오 파일일 경우
if (is_image_or_video_flag) {
char **data = malloc(sizeof(char *) * 3); // data 배열에 3개의 포인터 공간을 할당합니다.
if (!data) {
perror("Failed to allocate memory for data array"); // 메모리 할당 실패 시 오류 메시지 출력
free(fname); // 파일 이름 메모리 해제
fclose(file); // 파일 닫기
return NULL; // 실패 시 NULL 반환
}
data[0] = strdup(path); // 경로 복사
if (!data[0]) {
perror("Failed to duplicate path"); // 경로 복사 실패 시 오류 메시지 출력
free(data); // data 배열 해제
free(fname); // 파일 이름 해제
fclose(file); // 파일 닫기
return NULL; // 실패 시 NULL 반환
}
data[1] = fname; // 파일 이름 저장
data[2] = NULL; // 마지막에 NULL 포인터 설정 (배열의 끝을 알리기 위해)
fclose(file); // 파일 닫기
return data; // data 배열 반환
}
// 일반 파일일 경우, 초기 배열 크기를 설정합니다.
int maxChunks = 4;
char **data = (char **)malloc(sizeof(char *) * maxChunks); // 초기 크기 4로 data 배열 할당
if (!data) {
perror("Failed to allocate memory for data array"); // 메모리 할당 실패 시 오류 메시지 출력
free(fname); // 파일 이름 해제
fclose(file); // 파일 닫기
return NULL; // 실패 시 NULL 반환
}
data[0] = strdup(path); // 파일 경로 복사
if (!data[0]) {
perror("Failed to duplicate path"); // 파일 경로 복사 실패 시 오류 메시지 출력
free(data); // data 배열 해제
free(fname); // 파일 이름 해제
fclose(file); // 파일 닫기
return NULL; // 실패 시 NULL 반환
}
data[1] = fname; // 파일 이름 저장
int idx = 2; // 데이터 조각을 저장할 인덱스 시작 (0과 1은 경로와 이름)
int chunkSize = 500; // 각 조각의 크기 (500바이트)
int bytesRead;
// 파일 내용을 500바이트씩 읽습니다.
while (1) {
if (idx >= maxChunks) {
// 현재 배열의 크기가 부족할 경우 크기를 2배로 늘립니다.
maxChunks *= 2;
char **temp = realloc(data, maxChunks * sizeof(char *));
if (temp == NULL) {
perror("Failed to reallocate memory for data array"); // 메모리 재할당 실패 시 오류 메시지 출력
// 이미 할당된 메모리 해제
for (int i = 0; i < idx; i++) {
free(data[i]);
}
free(data);
fclose(file);
return NULL; // 실패 시 NULL 반환
}
data = temp; // 재할당된 메모리 주소로 업데이트
}
// 새로운 조각을 위한 메모리 할당
data[idx] = (char *)malloc(chunkSize * sizeof(char));
if (data[idx] == NULL) {
perror("Failed to allocate memory for chunk"); // 메모리 할당 실패 시 오류 메시지 출력
// 이미 할당된 메모리 해제
for (int i = 0; i < idx; i++) {
free(data[i]);
}
free(data);
fclose(file);
return NULL; // 실패 시 NULL 반환
}
// 파일에서 chunkSize만큼 읽기
bytesRead = fread(data[idx], 1, chunkSize, file);
if (bytesRead > 0) {
if (bytesRead < chunkSize) {
// 만약 읽은 바이트가 chunkSize보다 적다면 메모리 크기 조정
char *adjusted = realloc(data[idx], bytesRead);
if (adjusted) {
data[idx] = adjusted;
}
}
idx++;
}
if (bytesRead < chunkSize) {
if (feof(file)) {
break; // 파일 끝에 도달하면 종료
} else if (ferror(file)) {
perror("Error reading file"); // 파일 읽기 중 오류 발생 시 메시지 출력
// 이미 할당된 메모리 해제
for (int i = 0; i <= idx; i++) {
free(data[i]);
}
free(data);
fclose(file);
return NULL; // 실패 시 NULL 반환
}
}
}
// 마지막에 NULL 포인터 설정 (배열의 끝을 알리기 위해)
if (idx < maxChunks) {
data[idx] = NULL;
} else {
// 추가 공간이 필요하면 재할당하여 NULL 포인터 추가
char **temp = realloc(data, (idx + 1) * sizeof(char *));
if (temp == NULL) {
perror("Failed to reallocate memory for terminating NULL pointer"); // 메모리 재할당 실패 시 오류 메시지 출력
// 이미 할당된 메모리 해제
for (int i = 0; i < idx; i++) {
free(data[i]);
}
free(data);
fclose(file);
return NULL; // 실패 시 NULL 반환
}
data = temp;
data[idx] = NULL;
}
fclose(file); // 파일 닫기
return data; // 파일 정보와 조각들을 포함한 data 배열 반환
}
// 파일 데이터를 해제하는 함수 (get_file_data로부터 반환된 메모리 해제)
void free_file_data(char** data) {
if (!data) return;
for (int i = 0; data[i] != NULL; i++) {
free(data[i]); // 각 문자열에 할당된 메모리 해제
}
free(data); // data 포인터 자체 해제
}
// 파일 데이터 배열을 해제하는 함수
void free_file_data_array(char*** file_data_array, int num_files) {
for (int i = 0; i < num_files; i++) {
free_file_data(file_data_array[i]);
}
free(file_data_array);
}
// collect_file_data_recursive: 지정된 디렉터리를 재귀적으로 탐색하며 파일 데이터를 수집
// 입력: dir_path (탐색할 디렉터리 경로), num_files (수집된 파일 개수 포인터), file_data_array (파일 데이터 배열 포인터), depth (재귀 깊이)
// 출력: 없음 (num_files와 file_data_array가 수정됨)
/* file_data_array는 파일 데이터를 가진 3차원 배열이다.
* file_data_array[0]는 첫 번째 파일의 데이터를 가리키고, file_data_array[1]는 두 번째 파일의 데이터를 가리킨다.
* file_data_array[i][j]는 i번째 파일 중 j번째 data(get_file_data의 리턴 배열)를 의미한다.
* 즉, file_data_array[0][0]는 첫 번째 파일의 전체 경로를 의미한다.
* file_data_array[i][j][k]는 단순 문자이므로 큰 의미가 없다.
*/
void collect_file_data_recursive(const char* dir_path, int* num_files, char**** file_data_array, int depth) {
if (depth > 3) {
return;
}
DIR *d = opendir(dir_path);
if (!d) {
return;
}
struct dirent *dir;
int alloc_size = 4;
// 동적 메모리 할당 후, 메모리 누수 방지를 위한 코드 추가
while ((dir = readdir(d)) != NULL) {
// 현재 디렉토리와 상위 디렉토리는 건너뛰기
if (strcmp(dir->d_name, ".") == 0 || strcmp(dir->d_name, "..") == 0) {
continue;
}
char full_path[512];
snprintf(full_path, sizeof(full_path), "%s/%s", dir_path, dir->d_name);
struct stat st;
// 파일 또는 디렉토리의 정보(stat 구조체)를 가져오기
if (stat(full_path, &st) == 0) {
if (S_ISREG(st.st_mode)) {
// 일반 파일일 경우, 파일 데이터를 수집하는 함수 호출
char **data = get_file_data(full_path);
if (data) { // 파일 데이터가 성공적으로 수집된 경우
(*num_files)++; // 파일 수 증가
// 파일 수가 할당된 크기를 초과하는 경우
if (*num_files > alloc_size) {
alloc_size *= 2;
char ***temp = realloc(*file_data_array, sizeof(char**) * alloc_size);
if (!temp) { // realloc 실패 시 메모리 해제
free_file_data(data);
closedir(d);
return;
}
*file_data_array = temp;
}
(*file_data_array)[*num_files - 1] = data;
}
} else if (S_ISDIR(st.st_mode)) {
collect_file_data_recursive(full_path, num_files, file_data_array, depth + 1);
}
}
}
closedir(d); // 디렉토리 닫기
}
char*** get_all_file_data(const char* dir_path, int* num_files) {
*num_files = 0;
char*** file_data_array = malloc(sizeof(char **) * 4);
collect_file_data_recursive(dir_path, num_files, &file_data_array, 1);
return file_data_array;
}
int main(void){
int num_files = 0;
get_all_file_data("/Users/Ruffles/Projects/MAFM/MAFM/mafm/MAFM_test", &num_files);
return 0;
}
✏️ 문제
문제는 collect_file_data_recursive에 존재했다.
collect_file_data_recursive는
file_data_array에 파일의 정보를 담는 함수이다.
- root 디렉토리 내부의 파일들을 다 읽을 때까지 끝나지 않는다.
file_data_array에는 모든 파일의 정보가 들어간다.
크기가 큰 파일이 다수 존재한다면?
➜ file_data_array 의 크기를 realloc 할 때 메모리 초과 현상이 발생한다.
예를들어 25KB 파일 10개를 file_data_array에 저장한다고 가정해보자.
file_data_array에는 모든 파일의 정보가 저장되어야 하므로 최소 250000 Byte의 메모리를 차지한다.
파일이 더 많아지면, file_data_array에는 그만큼 더 부하가 걸린다.
✏️ 해결방법
file_data_array의 내용을 일정 크기마다 다른 공간에 저장시킨다. (EX - 1000 Byte 마다 DB에 저장)- recursive를 사용하지 않는다.
필자는 2번의 방법을 선택했다.
각 파일의 내용을 읽어서 바로바로 DB에 저장하는 것이다.
디렉토리 탐색은 python에서 os.walk()를 사용해서 구현하였다.
This post is licensed under
CC BY 4.0
by the author.