You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

实现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;    
}

核心问题分析

  1. freeUser访问未初始化野指针
    解析中途失败时(比如某个token为空),newUser的部分字段还未分配内存,直接调用freeUser会释放这些未初始化的野指针,触发内存错误。

  2. 未处理getline读取的换行符
    getline会将换行符\n读入到行字符串中,导致最后一个token可能包含换行符,影响dataValida的长度判断,甚至后续字符串操作出错。

  3. 数组越界风险
    主函数直接访问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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 12:02:09