实现dataValida函数后程序出现Segmentation Fault问题求助
问题排查:CSV解析程序触发Segmentation Fault
我实现了用于检查CSV列中日期是否符合dd/mm/yyyy格式的dataValida函数(错误格式示例:25/12/2010 15:01:56或1978|04|30),但程序运行时出现Segmentation Fault,输出如下:
File opened with success Parsing Concluded Segmentation fault
我尝试过重写该函数,但错误依旧;实现检查estado字段是否为active或inactive的函数时,也会出现同样错误。不过contemArroba和paisValido函数运行正常,恳请帮忙排查问题。
相关代码如下:
#include <stdio.h> #include <string.h> #include <stdlib.h> typedef struct { char* id; char* nome; char* email; char* tele; char* data; char sexo; char* passaporte; char* pais; char* morada; char* cria_conta; char* pagamento; char* estado; } User; void freeUser(User *user) { // Esta função libera todos os campos de string alocados dinamicamente de um User-> free(user->id); free(user->nome); free(user->email); free(user->data); free(user->passaporte); free(user->pais); free(user->morada); free(user->cria_conta); free(user->pagamento); free(user->estado); } int dataValida(const char *data) { if (strlen(data) != 10) return 0; // Verifica o comprimento da string // Verifica se os caracteres nas posições corretas são barras ('/') return data[2] == '/' && data[5] == '/'; } int contemArroba(const char *str) { return strchr(str, '@') != NULL; // Retorna 1 se contém '@', senão retorna 0 } int paisValido(const char* country) { return (strcmp(country, "PT") == 0); // Retorna 1 se o país for "PT", caso contrário 0 } // while (!feof(file)) User** organizaUser(FILE *file, int* n_user_out){ char *linha = NULL; size_t tamanho = 0; ssize_t valido; int linhas = 0; User **users = NULL; int n_user = 0; while ((valido = getline(&linha, &tamanho, file)) != -1) { if(linhas == 0) { linhas++; continue; } // user = realloc(users, (n_user + 1) * sizeof(User)); char *token = strtok(linha, ";"); if (token == NULL) continue; User *newUser = malloc(sizeof(User)); // Aloca memória para um novo User if (newUser == NULL) { // Tratar erro de alocação break; } *newUser = (User){0}; newUser->id = strdup(token); token = strtok(NULL, ";"); if (token == NULL) { free(newUser->id); continue; } newUser->nome = strdup(token); token = strtok(NULL, ";"); if (token == NULL) { free(newUser->id); free(newUser->nome); continue; } newUser->email = strdup(token); token = strtok(NULL, ";"); if (token == NULL || !contemArroba(newUser->email)) { freeUser(newUser); free(newUser); continue; } newUser->tele = strdup(token); token = strtok(NULL, ";"); if (token == NULL|| !dataValida(token) ) { freeUser(newUser); free(newUser); continue; } newUser->data = strdup(token); token = strtok(NULL, ";"); if (token == NULL) { freeUser(newUser); free(newUser); continue; } newUser->sexo = token[0]; token = strtok(NULL, ";"); if (token == NULL) { freeUser(newUser); free(newUser); continue; } newUser->passaporte = strdup(token); token = strtok(NULL, ";"); if (token == NULL) { freeUser(newUser); free(newUser); continue; } newUser->pais = strdup(token); token = strtok(NULL, ";"); if (token == NULL || !paisValido(newUser->pais)) { freeUser(newUser); free(newUser); continue; } newUser->morada = strdup(token); token = strtok(NULL, ";"); if (token == NULL) { freeUser(newUser); free(newUser); continue; } newUser->cria_conta = strdup(token); token = strtok(NULL, ";"); if (token == NULL) { freeUser(newUser); free(newUser); continue; } newUser->pagamento = strdup(token); token = strtok(NULL, ";"); if (token == NULL ) { freeUser(newUser); free(newUser); continue; } newUser->estado = strdup(token); User **temp = realloc(users, (n_user + 1) * sizeof(User*)); if (temp == NULL) { // Se realloc falhar, libere os recursos alocados e retorne // Aqui você deve liberar os campos de newUser que já foram alocados freeUser(newUser); free(newUser); break; } users = temp; // Prosseguir com a atribuição dos demais campos de newUser (nome, email, data, sexo, passaporte, pais, morada, cria_conta, pagamento, status) users[n_user] = newUser; if (n_user == 11) { printf("Usuer 12 (estado): %s\n", newUser->estado); } n_user++; linhas++; *n_user_out = n_user; } free(linha); // Liberar a memória alocada pela getline return users; // Retorna o ponteiro para o array de User } int main(void) { FILE *file = fopen("/home/rafaelsilva/Projeto/dataset/data/users.csv", "r"); if (file == NULL) { printf("Couldn't open the file.\n"); return 1; } else { printf("File opened with sucess.\n"); } int num_users; User **users = organizaUser(file, &num_users); fclose(file); printf("Parsing Concluded.\n"); printf("The id of the user 9301 : %s\n", users[9300]->id); for (int i = 0; i < num_users; i++) { freeUser(users[i]); free(users[i]); } free(users); return 0; }
核心问题分析
freeUser访问未初始化野指针
解析中途失败时(比如某个token为空),newUser的部分字段还未分配内存,直接调用freeUser会释放这些未初始化的野指针,触发内存错误。未处理
getline读取的换行符getline会将换行符\n读入到行字符串中,导致最后一个token可能包含换行符,影响dataValida的长度判断,甚至后续字符串操作出错。数组越界风险
主函数直接访问users[9300]->id,如果实际解析的用户数量不足9301个,会触发数组越界。
修复方案
1. 修复freeUser函数,只释放已初始化指针
void freeUser(User *user) { if (user->id) free(user->id); if (user->nome) free(user->nome); if (user->email) free(user->email); if (user->tele) free(user->tele); if (user->data) free(user->data); if (user->passaporte) free(user->passaporte); if (user->pais) free(user->pais); if (user->morada) free(user->morada); if (user->cria_conta) free(user->cria_conta); if (user->pagamento) free(user->pagamento); if (user->estado) free(user->estado); }
2. 处理getline的换行符
在strtok前去掉行末尾的换行符:
while ((valido = getline(&linha, &tamanho, file)) != -1) { // 移除末尾换行符 if (valido > 0 && linha[valido-1] == '\n') { linha[valido-1] = '\0'; } // 后续解析代码... }
3. 避免数组越界访问
主函数中先判断用户数量是否足够:
printf("Parsing Concluded.\n"); if (num_users > 9300) { printf("The id of the user 9301 : %s\n", users[9300]->id); } else { printf("User 9301 does not exist.\n"); }
4. 增强dataValida的日期合法性校验(可选)
原函数仅检查格式,可补充数字校验和日期范围判断:
#include <ctype.h> int dataValida(const char *data) { if (strlen(data) != 10) return 0; if (data[2] != '/' || data[5] != '/') return 0; // 检查日、月、年是否为数字 for (int i=0; i<10; i++) { if (i == 2 || i == 5) continue; if (!isdigit(data[i])) return 0; } int dia = atoi(data); int mes = atoi(data+3); int ano = atoi(data+6); // 校验月份范围 if (mes < 1 || mes > 12) return 0; // 校验日期范围(简化版,未处理闰年) int diasPorMes[] = {31,28,31,30,31,30,31,31,30,31,30,31}; if (dia < 1 || dia > diasPorMes[mes-1]) return 0; return 1; }
内容的提问来源于stack exchange,提问作者HelpMe
相关产品推荐
相关产品推荐

