【问题标题】:Reading from a file causes an error at the last line从文件中读取会导致最后一行出错
【发布时间】:2021-08-29 05:54:26
【问题描述】:

我遇到的问题是,一旦我想读取文件,程序会打印出所有内容,但它不会读取文件的最后一个元素。 所以在文件中可以说我有:

  • admin:pass:1000:off
  • test:testxx:1000:off
  • ret:passx:1000:off

它打印出前 2 行,然后显示“缓冲区结束”(它只是一个 printf,我将其放入代码中以了解问题出在哪里)。 打印出“缓冲区结束”后,它会打印出最后一行.. 我做错了什么?

#include <stdlib.h> /* exit */
#include <errno.h> /* perror */
#include <stdio.h>
#include <unistd.h> /* write, read, close*/
#include <sys/types.h> 
#include <sys/stat.h> /*open */
#include <fcntl.h> 
#include <string.h>

#define MAX 500


struct utente{
    char username[MAX];
    char password[MAX];
    int gettoni;
    char stato[MAX];
    struct utente *next;
};

void menu(int);
struct utente* lettura_file(struct utente *);
struct utente* ins_coda(struct utente *t, char username[], char password[], char gettoni[], char stato[]);
void stampa_lista(struct utente*);
struct utente* elabora(struct utente *top, char buffer[]);
int check(struct utente *, char *);
void aggiornamento_file(struct utente *top);

int main(){
    while(1){
    char scelta[MAX], stringa[MAX];
    int i=0, res=0;
    struct utente *TOP=NULL;
    
    
    
    printf("1. STAMPA LISTA\nInserisci scelta --> ");
    scanf("%s", scelta);
    
    i=atoi(scelta);
    switch(i){
        case 1:
            TOP=lettura_file(TOP);
            stampa_lista(TOP);
            /*printf("Inserisci stringa da trovare: ");
            scanf("%s", stringa);
            res=check(TOP, stringa);
            if(res==1)
                aggiornamento_file(TOP);
            */
            break;
        }
    
    }
    return 0;
}



struct utente* lettura_file(struct utente *top){
    
    int fd, nread;
    char buffer[MAX];
    
    fd=open("utenti.txt", O_RDONLY);
    if(fd==-1){
        perror("ERRORE APERTURA FILE");
        return top;
    }
    
    while((nread=read(fd, buffer, MAX)>0)){
        top=elabora(top,buffer);
        }
        close(fd);
    
    return top;
}


struct utente* elabora(struct utente *top, char buffer[]){
    char username[MAX], password[MAX], gettoni[MAX], stato[MAX];
    int i=0;    
    int j=0; //indice username
    int k=0; //indice password
    int t=0; //indice gettoni
    int x=0; //indice stato
    int count=0;
    
    printf("Inzio buffer:\n%sfine buffer\n" ,buffer);
    
    while(buffer[i]!='\0'){
    
        //ELABORO NOME
        while(buffer[i]!=':'){
            username[j]=buffer[i];
            j++;
            i++;
        }
        username[j]='\0';
        i++;
        
        //ELABORO COGNOME
        while(buffer[i]!=':'){
            password[k]=buffer[i];
            k++;
            i++;
        }
        password[k]='\0';
        i++;
        
        //ELABORO GETTONI
        while(buffer[i]!=':'){
            gettoni[t]=buffer[i];
            t++;
            i++;
        }
        gettoni[t]='\0';
        i++;
        
        while(buffer[i]!='\n' && buffer[i]!='\0'){
            stato[x]=buffer[i];
            x++;
            i++;
        }
        stato[x]='\0';
        
        
        if(buffer[i]=='\n')
            i++;
        
        if(buffer[i]=='\0'){
            printf("\nEnd of the buffer %c\n", buffer[i]);
        printf("Fine%s %s %s %s\n\n", username, password, gettoni, stato);
        
        top=ins_coda(top, username, password, gettoni, stato);
            return top;
        }
        
        
        printf("Utente %d, %s %s %s %s\n\n", count, username, password, gettoni, stato);
        
        top=ins_coda(top, username, password, gettoni, stato);
        
        bzero(username, MAX);
        bzero(password, MAX);
        bzero(gettoni, MAX);
        bzero(stato, MAX);
        j=0;
        k=0;
        t=0;
        x=0;
        
    }
    
    return top;


}


struct utente* ins_coda(struct utente *t, char username[], char password[], char gettoni[], char stato[]){
    struct utente *p, *top;
    int n_gettoni;
    p=(struct utente*)malloc(sizeof(struct utente));
    
    strcpy(p->username, username);
    strcpy(p->password, password);
    n_gettoni=atoi(gettoni);
    p->gettoni=n_gettoni;
    strcpy(p->stato, stato);
    p->next=NULL;
    
    if(t==NULL)
        return p;
        
    top=t;
    while(t->next!=NULL)
        t=t->next;
        
    t->next=p;
    return top; 
}


void stampa_lista(struct utente *TOP){
    int i=1;
    while(TOP!=NULL){
        printf("Utente %d:\n Username: %s\nPassword: %s\nGEttoni: %d\nStato: %s\n", i, TOP->username, TOP->password, TOP->gettoni, TOP->stato);
        TOP=TOP->next;
        i++;
        }
    
    return;
}



int check(struct utente *top, char stringa[]){
    int len=strlen(stringa);
    while(top!=NULL){
        if(strncmp(top->username, stringa, len)==0){
        strcpy(top->stato, "on");
            printf("OK\n");
            return 1;
        }else{
            printf("KO\n");
            return 0;
        }
    top=top->next;  
    }
    return 0;
}




void aggiornamento_file(struct utente *top){
    char username[MAX], password[MAX], gettoni[MAX], stato[MAX]="";
    int fd;
    
    fd = open("utenti.txt", O_WRONLY |O_TRUNC, S_IRUSR | S_IWUSR);
            if(fd==-1){
                perror("ERROR apertura file utenti!");
                exit(1);
            }else{
                    while(top!=NULL){
                    strcpy(username, top->username);
                    strcpy(password, top->password);
                    sprintf(gettoni, "%d", top->gettoni);
                    strcpy(stato, top->stato);
                    
                    write(fd, username, strlen(username));
                        write(fd, ":", 1);
                        write(fd, password, strlen(password));
                        write(fd, ":", 1);
                        write(fd, gettoni, strlen(gettoni));
                        write(fd, ":", 1);
                        write(fd, stato, strlen(stato));
                        write(fd, "\n", 1);
                        
                        top=top->next;
                    }
        }       
    close(fd);
    return;

}

【问题讨论】:

  • 这种字符串拆分可能会让人困惑,那么用strtok加上“:”分隔符或者sscanf不是更方便吗?
  • 这里发生的事情太多了,无法真正给出一个连贯的答案。一方面,您在elabora 中有while(buffer[i]!='\0'){。也许我遗漏了一些东西,但我不知道你会期望在哪里找到'\0'。它可能与更大的问题无关,但这些小事可能有助于掩盖它。
  • 当然可以,但是我想了解为什么当还有一行时他会到达缓冲区的末尾?它似乎在文件的最后一行之前读取 '\0'
  • 我怀疑'\0' 正是在当前条件下发生成为char buffer[MAX]; 的基础。
  • 您正在使用open/read。虽然可以做到这一点,但您正在重新发明 fopen/fgets 已经在做的事情。

标签: c linux system-calls


【解决方案1】:

除非您的文件包含二进制数据并且您实际上在其末尾放置了一个0x00,否则它不应包含任何用于buffer[i] != '\0' 检查的零。

C 字符串以 NULL 结尾,但在写入文本文件时通常不包含此终止零。文本文件编辑器也不这样做。

一个问题是您没有初始化buffer 变量。 当程序为其分配 500 个字节时,您可能会偶然得到零。 用零初始化缓冲区的一种方法是在声明时将其第一个元素设置为零:

char buffer[MAX] = {0,};

现在,如果我正确理解了您的疑问,我看不到在程序读取最后一个条目之前文件的结尾正在发生。那里发生的事情是程序在打印文件末尾的消息后打印最后一行的消息。但是程序确实读取了文件末尾之前的最后一行,并将其存储在printf使用的变量中。

混淆通常出现在具有大功能的代码中。 elabora 函数大、复杂且难以调试。如果你把它分解成更小的函数,你可以让它更简单。我相信如果它读取行而不是整个文件会更好。我快速重写了一个例子:

void get_field(char **src, char dst[MAX])
{
    int j = 0;

    while (*(*src) && *(*src) != ':' && *(*src) != '\n') {

        dst[j++] = *(*src);
        (*src)++;
    }
}

struct utente* elabora(struct utente *top, char *line, ssize_t line_len, int *count)
{
    char username[MAX] = {0,};
    char password[MAX] = {0,};
    char gettoni[MAX]  = {0,};
    char stato[MAX]    = {0,};

    get_field(&line, username);
    get_field(&line, password);
    get_field(&line, gettoni);
    get_field(&line, stato);

    printf("Utente %d, %s %s %s %s\n\n", ++(*count), username, password, gettoni, stato);

    top = ins_coda(top, username, password, gettoni, stato);

    return top;
}

现在可以从使用geline函数逐行读取文件的函数中调用它:

struct utente* lettura_file(struct utente *top)
{
    FILE  *file        = NULL;
    char  *buffer      = NULL;
    size_t buffer_size = 0;
    int count          = 0;
    ssize_t n_read     = 0;

    if ((file = fopen("utenti.txt", "r")) == NULL) {

        perror("ERRORE APERTURA FILE");
        return top;
    }

    while ((n_read = getline(&buffer, &buffer_size, file)) > 0) {

        top = elabora(top, buffer, n_read, &count);
    }

    if (buffer != NULL)
        free(buffer);

    if (file != NULL)
        fclose(file);

    return top;
}

这些函数仍然不安全,但我相信它们具有更好的可读性并且更易于调试。 strtok 会比以这种方式解析令牌更好。正如问题的 cmets 所建议的那样,使用其他函数来打开和读取文件也会改进代码。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-09-22
    • 2023-04-07
    • 2020-11-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多