在C中从文本文件中将字符串分配给链表时,使用fscanf跳过行的问题。

huangapple go评论69阅读模式
英文:

Problems with fscanf skipping lines when assigning strings to a linked list from a txt file in C

问题

以下是您提供的代码的翻译部分:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_REG 8+1
#define MAX_OSG 2+1
#define MAX_DIO 5+1

typedef struct automobili {
    char registracija[MAX_REG];
    char osiguran[MAX_OSG];
    char dio[MAX_DIO];
    double steta;
    struct automobili *sledeci;
} AUTO;

// 打开文本文件
FILE *open(char *ime, char *tip) {
    FILE *fp = fopen(ime, tip);

    if (fp == NULL) {
        if (strcmp(tip, "r") == 0) {
            printf("Nije moguće otvoriti ulaznu datoteku %s!\n", ime);
            exit(1);
        } else if (strcmp(tip, "w") == 0) {
            printf("Nije moguće otvoriti izlaznu datoteku %s!\n", ime);
            exit(2);
        }
    }

    return fp;
}

// 创建新的链表元素
AUTO *novi_cvor(char registracija[], char osiguran[], char dio[], double steta) {
    AUTO *novi = (AUTO *)malloc(sizeof(AUTO));

    if (novi == NULL) {
        printf("Greška prilikom zauzimanja memorije!\n");
        exit(EXIT_FAILURE);
    }

    strcpy(novi->registracija, registracija);
    strcpy(novi->osiguran, osiguran);
    strcpy(novi->dio, dio);
    novi->steta = steta;

    novi->sledeci = NULL;

    return novi;
}

// 添加元素到链表的末尾
void dodaj_cvor(AUTO **glava, AUTO *novi) {
    if (*glava == NULL) {
        *glava = novi;
        return;
    } else {
        AUTO *tekuci = *glava;
        while (tekuci->sledeci != NULL) {
            tekuci = tekuci->sledeci;
        }

        tekuci->sledeci = novi;
    }
}

// 从输入文件读取并使用函数将新元素添加到链表中
void unos(FILE *in, AUTO **glava) {
    char registracija[MAX_REG];
    char osiguran[MAX_OSG];
    char dio[MAX_DIO];
    double steta;

    while (fscanf(in, "%s %s %s %lf", registracija, osiguran, dio, &steta) != EOF) {
        AUTO *novi = novi_cvor(registracija, osiguran, dio, steta);
        dodaj_cvor(glava, novi);
    }
}

void inicijalizacija(AUTO **glava) {
    *glava = NULL;
}

// 删除链表
void brisanje(AUTO **glava) {
    if (*glava != NULL) {
        brisanje(&((*glava)->sledeci));
        free(*glava);
        *glava = NULL;
    }
}

void provjera_osiguranja(AUTO *glava) {
    while (glava != NULL) {
        if (strcmp(glava->osiguran, "da") == 0) {
            glava->steta = glava->steta * 0.85;
        }
        glava = glava->sledeci;
    }
}

// 检查正确的元素并将它们写入输出文件
void provjera_registracije(FILE *out, AUTO *glava, char br_reg[]) {
    double sum = 0;
    while (glava != NULL) {
        if (strcmp(glava->registracija, br_reg) == 0) {
            fprintf(out, "%s %s %s %.2lf\n", glava->registracija, glava->osiguran, glava->dio, glava->steta);
            sum += glava->steta;
        }
        glava = glava->sledeci;
    }

    if (sum == 0) {
        printf("Registracija nije na spisku!\n");
        exit(11);
    }

    fprintf(out, "Ukupna vrijednost štete  je %.2lf din.\n", sum);
}

int main(int argc, char **argv) {

    char br_reg[MAX_REG];

    // 期望的输入格式为 "./a.out automobili.txt <exit_filename>.txt"
    if (argc != 3) {
        printf("Loš unos! Pokušajte ponovo!\n");
        exit(EXIT_FAILURE);
    }

    FILE *in = open(argv[1], "r");
    FILE *out = open(argv[2], "w");

    AUTO *glava;
    inicijalizacija(&glava);
    unos(in, &glava);

    printf("Unesite registraciju: ");
    fgets(br_reg, MAX_REG, stdin);

    provjera_osiguranja(glava);
    provjera_registracije(out, glava, br_reg);

    brisanje(&glava);
    fclose(in);
    fclose(out);

    return 0;
}

请注意,此代码翻译部分仅包括代码本身的注释和字符串,不包括任何其他内容。希望这可以帮助您解决代码问题。如果您有其他问题,请随时提出。

英文:

The following code reads the input from a file called "automobili.txt" and outputs them into a different file, however I cannot figure out why the code skips over some strings when scanning the input

#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;
#define MAX_REG 8+1
#define MAX_OSG 2+1
#define MAX_DIO 5+1
typedef struct automobili {
char registracija[MAX_REG];
char osiguran[MAX_OSG];
char dio[MAX_DIO];
double steta;
struct automobili *sledeci;
} AUTO;
//opens the txt files
FILE *open(char *ime, char *tip){
FILE *fp = fopen(ime, tip);
if (fp == NULL) {
if (strcmp(tip, &quot;r&quot;) == 0) {
printf(&quot;Nije moguće otvoriti ulaznu datoteku %s!\n&quot;, ime);
exit(1);
} else if (strcmp(tip, &quot;w&quot;) == 0) {
printf(&quot;Nije moguće otvoriti izlaznu datoteku %s!\n&quot;, ime);
exit(2);
}
}
return fp;
}
//creates new list element
AUTO *novi_cvor(char registracija[], char osiguran[], char dio[], double steta){
AUTO *novi = (AUTO*)malloc(sizeof(AUTO));
if (novi == NULL) {
printf(&quot;Greška prilikom zauzimanja memorije!\n&quot;);
exit(EXIT_FAILURE);
}
strcpy(novi-&gt;registracija, registracija);
strcpy(novi-&gt;osiguran, osiguran);
strcpy(novi-&gt;dio, dio);
novi-&gt;steta = steta;
novi-&gt;sledeci = NULL;
return novi;
}
//adds element to the end of the linked list
void dodaj_cvor(AUTO **glava, AUTO *novi){
if (*glava == NULL) {
*glava = novi;
return;
} else {
AUTO *tekuci = *glava;
while (tekuci-&gt;sledeci != NULL) {
tekuci = tekuci-&gt;sledeci;
}
tekuci-&gt;sledeci = novi;
}
}
//reads from the input file and adds new elements to the list using functions
void unos(FILE *in, AUTO **glava){
char registracija[MAX_REG];
char osiguran[MAX_OSG];
char dio[MAX_DIO];
double steta;
while (fscanf(in, &quot;%s %s %s %lf&quot;, registracija, osiguran, dio, &amp;steta) != EOF) {
AUTO *novi = novi_cvor(registracija, osiguran, dio, steta);
dodaj_cvor(glava, novi);
}
}
void inicijalizacija(AUTO **glava){
*glava = NULL;
}
//deletes the list
void brisanje(AUTO **glava){
if (*glava != NULL) {
brisanje(&amp;((*glava)-&gt;sledeci));
free(*glava);
*glava = NULL;
}
}
void provjera_osiguranja(AUTO *glava) {
while (glava != NULL) {
if (strcmp(glava-&gt;osiguran, &quot;da&quot;) == 0) {
glava-&gt;steta = glava-&gt;steta * 0.85;
}		
glava = glava-&gt;sledeci;
}
}
//checks for the right elemets and writres them down in the output file
void provjera_registracije(FILE *out, AUTO *glava, char br_reg[]){
double sum=0;
while (glava != NULL) {
if (strcmp(glava-&gt;registracija, br_reg) == 0) {
fprintf(out, &quot;%s %s %s %.2lf\n&quot;, glava-&gt;registracija, glava-&gt;osiguran, glava-&gt;dio, glava-&gt;steta);
sum += glava-&gt;steta;
}
glava = glava-&gt;sledeci;
}
if (sum == 0) {
printf(&quot;Registracija nije na spisku!\n&quot;);
exit(11);
}
fprintf(out, &quot;Ukupna vrijednost štete  je %.2lf din.\n&quot;, sum);
}
int main(int argc, char **argv){
char br_reg[MAX_REG];
//expected input is &quot;./a.out automobili.txt &lt;exit_filename&gt;.txt&quot;
if (argc != 3) {
printf(&quot;Loš unos! Pokušajte ponovo!\n&quot;);
exit(EXIT_FAILURE);
}
FILE *in = open(argv[1], &quot;r&quot;);
FILE *out = open(argv[2], &quot;w&quot;);
AUTO *glava;	
inicijalizacija(&amp;glava);
unos(in, &amp;glava);
printf(&quot;Unesite registraciju: &quot;);
fgets(br_reg, MAX_REG, stdin);
provjera_osiguranja(glava);
provjera_registracije(out, glava, br_reg);
brisanje(&amp;glava);
fclose(in);
fclose(out);
return 0;
}

The input file:

PA454-TS da menjac 5500
BG777-OS da volan 11500
BG777-OS ne branik 7000
BG777-OS ne menjac 23500
PA454-TS da gume 16000
BG777-OS da kvacilo 33000

P.S. I apologize for non English code, the important functions have been commented on what each of them does.

The expected output when not checking for a specific registration number should have been:

PA454-TS da menjac 4675.00
BG777-OS da volan 9775.00
BG777-OS ne branik 7000.00
BG777-OS ne menjac 23500.00
PA454-TS da gume 13600.00
BG777-OS da kvacilo 28050.00
Ukupna vrijednost štete  je 86600.00 din.

However the program outputs:

 da menjac 4675.00
BG777-OS da volan 9775.00
ne branik 7000.00
ne menjac 23500.00
PA454-TS da gume 13600.00
o da kvacilo 28050.00
Ukupna vrijednost štete  je 86600.00 din.

I have tried rewriting the code however nothing changed and I cannot find a difference in how I executed the input here as compared to my previous code which works fine. I suspect the issue comes from fscanf() and not assigning the string to the element itself.

答案1

得分: 1

以下是已翻译的代码部分:

有多个问题在代码中:

`while (fscanf(in, "%s %s %s %lf", registracija, osiguran, dio, &steta) != EOF)` 很容易出错且不安全:

* 如果任何字段在 `%s` 转换中的长度超过了相应目标数组的长度,将导致缓冲区溢出和未定义行为。

* 事实上,数组 `dio` 的长度为 `5+1` 字节,而输入文件中的每一行的这个字段都有更长的字符串,这将导致未定义行为。你应该在格式字符串中指定最大长度,如 `%8s %2s %5s %lf`,并可能增加这些长度以适应实际的文件内容。

* 比较 `fscanf()` 的返回值与 `EOF` 只会检测在任何字段可以转换之前出现的意外文件结束。相反,你应该测试返回值是否为 `4`,表示所有 4 个转换已成功。

* 直接调用 `fscanf()` 使得在输入文件中检测无效行变得困难。建议使用 `fgets()` 从文件中读取一行,并使用 `sscanf()` 尝试转换,在失败时生成有信息的错误消息。

* `open` 作为函数或变量名应该避免使用,因为这个名称在 POSIX 系统上引用了一个系统调用。使用 `open_file` 既更安全又更可读。

* 还要注意,宏定义,如 `#define MAX_REG 8+1` 是有风险的:如果你在代码中写 `MAX_REG*2`,它将展开为 `8+1*2`,其值为 `10`,而不是预期的 `18`。始终要给宏表达式以及宏表达式中使用的宏参数加上括号。

以下是修改后的版本:
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_REG (8+1)
#define MAX_OSG (2+1)
#define MAX_DIO (8+1)

typedef struct automobili {
    char registracija[MAX_REG];
    char osiguran[MAX_OSG];
    char dio[MAX_DIO];
    double steta;
    struct automobili *sledeci;
} AUTO;

// 打开文本文件
FILE *open_file(const char *ime, const char *tip) {
    FILE *fp = fopen(ime, tip);
    if (fp == NULL) {
        if (strcmp(tip, "r") == 0) {
            printf("无法打开输入文件 %s!\n", ime);
            exit(1);
        } else if (strcmp(tip, "w") == 0) {
            printf("无法打开输出文件 %s!\n", ime);
            exit(2);
        } else {
            printf("无法以模式 %s 打开文件 %s!\n", tip, ime);
            exit(3);
        }
    }
    return fp;
}

// 如果没有截断,则返回字符串的长度。
// 否则截断字符串并返回目标大小。
// 如果 size > 0,则目标始终以 null 结尾
size_t pstrcpy(char *dest, size_t size, const char *src) {
    size_t len;
    for (len = 0; len < size && src[len]; len++)
        continue;
    if (len < size)
        size = len + 1;
    if (size > 0) {
        memmove(dest, src, size - 1);
        dest[size - 1] = '\0';
    }
    return len;
}

// 创建新的列表元素
AUTO *novi_cvor(char registracija[], char osiguran[], char dio[], double steta) {
    AUTO *novi = (AUTO *)malloc(sizeof(AUTO));

    if (novi == NULL) {
        printf("内存分配失败!\n");
        exit(EXIT_FAILURE);
    }

    pstrcpy(novi->registracija, sizeof novi->registracija, registracija);
    pstrcpy(novi->osiguran, sizeof novi->osiguran, osiguran);
    pstrcpy(novi->dio, sizeof novi->dio, dio);
    novi->steta = steta;
    novi->sledeci = NULL;
    return novi;
}

// 在链表末尾添加元素
void dodaj_cvor(AUTO **glava, AUTO *novi) {
    if (*glava == NULL) {
        *glava = novi;
        return;
    } else {
        AUTO *tekuci = *glava;
        while (tekuci->sledeci != NULL) {
            tekuci = tekuci->sledeci;
        }
        tekuci->sledeci = novi;
    }
}

// 从输入文件读取并使用函数将新元素添加到列表中
void unos(FILE *in, AUTO **glava) {
    char registracija[MAX_REG];
    char osiguran[MAX_OSG];
    char dio[MAX_DIO];
    double steta;
    char buf[100];
    char c;

    while (fgets(buf, sizeof buf, in)) {
        if (sscanf(buf, "%8s %2s %8s %lf %c", registracija, osiguran, dio, &steta, &c) == 4) {
            AUTO *novi = novi_cvor(registracija, osiguran, dio, steta);
            dodaj_cvor(glava, novi);
        } else {
            printf("无效的输入行:%s\n", buf);
        }
    }
}

void inicijalizacija(AUTO **glava) {
    *glava = NULL;
}

// 删除列表
void brisanje(AUTO **glava) {
    while (*glava != NULL) {
        AUTO *p = *glava;
        *glava = p->sledeci;
        free(p);
    }
}

void provjera_osiguranja(AUTO *glava) {
    while (glava != NULL) {
        if (strcmp(glava->osiguran, "da") == 0) {
            glava->steta = glava->steta * 0.85;
        }
        glava = glava->sledeci;
    }
}

// 检查正确的元素并将其写入输出文件
void provjera_registracije(FILE *out, AUTO *glava, char br_reg[]) {
    double sum = 0;


<details>
<summary>英文:</summary>

There are multiple problems in the code:

`while (fscanf(in, &quot;%s %s %s %lf&quot;, registracija, osiguran, dio, &amp;steta) != EOF)` is very brittle and unsafe: 

* if any of the fields parsed for the `%s` conversions exceeds the length of the corresponding target array, this will cause a buffer overflow with undefined behavior.

* as a matter of fact, the array `dio` has a length of `5+1` bytes and every other line in the input file has a longer string for this field, causing undefined behavior.  You should specify the maximum length in the format string as `%8s %2s %5s %lf` and should probably increase these lengths to accommodate the actual file contents.

* comparing the return value of `fscanf()` to `EOF` will only detect an unexpected end of file occurring before any field can be converted.  You should instead test that the return value is `4`, signifying all 4 conversions have succeeded.

* calling `fscanf()` directly makes it difficult to detect invalid lines in the input file. It is recommended to use `fgets()` to read a line from the file and `sscanf()` to attempt the conversion, producing an informative error message upon failure.

* `open` as a function or variable name should be avoided as this name refers to a system call on POSIX systems. Using `open_file` is both safer and more readable.

* also note that macro definitions such as `#define MAX_REG 8+1` are risky: if you write `MAX_REG*2` in the code, it will expand to `8+1*2` which has the value `10` instead of the expected `18`.  Always parenthesize macro expressions as well as macro arguments used in the macro expression.

Here is a modified version:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_REG (8+1)
#define MAX_OSG (2+1)
#define MAX_DIO (8+1)

typedef struct automobili {
char registracija[MAX_REG];
char osiguran[MAX_OSG];
char dio[MAX_DIO];
double steta;
struct automobili *sledeci;
} AUTO;

//opens the txt files
FILE *open_file(const char *ime, const char *tip) {
FILE *fp = fopen(ime, tip);
if (fp == NULL) {
if (strcmp(tip, "r") == 0) {
printf("Nije moguće otvoriti ulaznu datoteku %s!\n", ime);
exit(1);
} else
if (strcmp(tip, "w") == 0) {
printf("Nije moguće otvoriti izlaznu datoteku %s!\n", ime);
exit(2);
} else {
printf("Nije moguće otvoriti datoteku %s za mod %s!\n", ime, tip);
exit(3);
}
}
return fp;
}

// returns the length of the string if no truncation.
// otherwise truncate the string and return the destination size.
// destination is always null terminated if size > 0
size_t pstrcpy(char *dest, size_t size, const char *src) {
//size_t len = strnlen(src, size);
size_t len;
for (len = 0; len < size && src[len]; len++)
continue;
if (len < size)
size = len + 1;
if (size > 0) {
memmove(dest, src, size - 1);
dest[size - 1] = '\0';
}
return len;
}

//creates new list element
AUTO *novi_cvor(char registracija[], char osiguran[], char dio[], double steta) {
AUTO novi = (AUTO)malloc(sizeof(AUTO));

if (novi == NULL) {
printf(&quot;Greška prilikom zauzimanja memorije!\n&quot;);
exit(EXIT_FAILURE);
}
pstrcpy(novi-&gt;registracija, sizeof novi-&gt;registracija, registracija);
pstrcpy(novi-&gt;osiguran, sizeof novi-&gt;osiguran, osiguran);
pstrcpy(novi-&gt;dio, sizeof novi-&gt;dio, dio);
novi-&gt;steta = steta;
novi-&gt;sledeci = NULL;
return novi;

}

//adds element to the end of the linked list
void dodaj_cvor(AUTO **glava, AUTO *novi) {
if (*glava == NULL) {
*glava = novi;
return;
} else {
AUTO *tekuci = *glava;
while (tekuci->sledeci != NULL) {
tekuci = tekuci->sledeci;
}
tekuci->sledeci = novi;
}
}

//reads from the input file and adds new elements to the list using functions
void unos(FILE *in, AUTO **glava) {
char registracija[MAX_REG];
char osiguran[MAX_OSG];
char dio[MAX_DIO];
double steta;
char buf[100];
char c;

while (fgets(buf, sizeof buf, in)) {
if (sscanf(buf, &quot;%8s %2s %8s %lf %c&quot;, registracija, osiguran, dio, &amp;steta, &amp;c) == 4) {
AUTO *novi = novi_cvor(registracija, osiguran, dio, steta);
dodaj_cvor(glava, novi);
} else {
printf(&quot;invalid input line: %s\n&quot;, buf);
}
}

}

void inicijalizacija(AUTO **glava) {
*glava = NULL;
}

//deletes the list
void brisanje(AUTO **glava) {
while (*glava != NULL) {
AUTO *p = *glava;
*glava = p->sledeci;
free(p);
}
}

void provjera_osiguranja(AUTO *glava) {
while (glava != NULL) {
if (strcmp(glava->osiguran, "da") == 0) {
glava->steta = glava->steta * 0.85;
}
glava = glava->sledeci;
}
}

//checks for the right elemets and writres them down in the output file
void provjera_registracije(FILE *out, AUTO *glava, char br_reg[]) {
double sum = 0;
while (glava != NULL) {
if (strcmp(glava->registracija, br_reg) == 0) {
fprintf(out, "%s %s %s %.2lf\n", glava->registracija, glava->osiguran, glava->dio, glava->steta);
sum += glava->steta;
}
glava = glava->sledeci;
}

if (sum == 0) {
printf(&quot;Registracija nije na spisku!\n&quot;);
exit(11);
}
fprintf(out, &quot;Ukupna vrijednost štete  je %.2lf din.\n&quot;, sum);

}

int main(int argc, char **argv) {

char br_reg[MAX_REG];
//expected input is &quot;./a.out automobili.txt &lt;exit_filename&gt;.txt&quot;
if (argc != 3) {
printf(&quot;Loš unos! Pokušajte ponovo!\n&quot;);
exit(EXIT_FAILURE);
}
FILE *in = open_file(argv[1], &quot;r&quot;);
FILE *out = open_file(argv[2], &quot;w&quot;);
AUTO *glava;
inicijalizacija(&amp;glava);
unos(in, &amp;glava);
printf(&quot;Unesite registraciju: &quot;);
fgets(br_reg, MAX_REG, stdin);
provjera_osiguranja(glava);
provjera_registracije(out, glava, br_reg);
brisanje(&amp;glava);
fclose(in);
fclose(out);
return 0;

}


</details>

huangapple
  • 本文由 发表于 2023年6月9日 00:04:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/76433777.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定