C fscanf的未定义行为

问题描述 投票:-2回答:2

我下面有一个代码段。在macOS上,我已经在Xcode和CLion中运行了它,结果是相同的。另一方面,在Linux上使用gcc编译时,它可以完美运行。我想知道代码在任何时候是否会产生未定义的行为。它尝试解析的输入文件是vigenére表,您知道,拉丁字母是26个字符的行,而字母在较低的1行上左移1。每行都由CRLF终止。预期的输出是控制台上打印的表格。意外的部分是在macOS上至少显示1行错误。这是输入的顺便说一句:https://pastebin.com/QnucTAFs(但是,我不知道是否保留了相应的行结尾)

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

char ** parse(char *path) {
    FILE *f = fopen(path, "r");
    char **table = (char**)malloc(sizeof(char*) * 26);
    int i = -1;

    do table[++i] = (char*)malloc(sizeof(char) * 27);
    while (fscanf(f, "%s", table[i]) > 0);

    return table;
}

int main() {
    char **table = parse("Vtabla.dat");

    for (int i = 0; i < 26; i++) {
        for (int x = 0; x < 26; x++)
            printf("%c", table[i][x]);
        printf("\n");
    }

    return 0;
}
c scanf newline undefined-behavior
2个回答
3
投票

此循环中的这段代码中有一个错误:

do table[++i] = (char*)malloc(sizeof(char) * 27);
while (fscanf(f, "%s", table[i]) > 0);

table拥有26个指针,但是在fscanf()失败的迭代中,在上一步中,通过table初始化了malloc变量的第27个指针。这会破坏我系统上table中的数据。您可以通过将该行中的26提高到27来说服自己,以查看问题是否消失:

char **table = (char**)malloc(sizeof(char*) * 26);

我对代码的返工:

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

#define LETTERS 26

char **parse(char *path) {
    char **table = calloc(LETTERS, sizeof(char *));

    FILE *f = fopen(path, "r");

    for (int i = 0; i < LETTERS; i++) {
        table[i] = (char *) calloc(LETTERS+1, sizeof(char));

        if (fscanf(f, "%s", table[i]) <= 0) {
            break;
        }
    }

    fclose(f);

    return table;
}

int main() {
    char **table = parse("Vtabla.dat");

    for (int i = 0; i < LETTERS; i++) {
        for (int j = 0; j < LETTERS; j++)
            printf("%c", table[i][j]);

        printf("\n");
        free(table[i]);
    }

    free(table);
    return 0;
}

1
投票

评论中的讨论非常活跃,但是OP似乎对许多经验丰富的开发人员所关心的问题更感兴趣,因此,我将发布的答案并非严格地指向问题,而是表现出更广泛的关注。

[我相信我们中的许多人在我们认为极不可能的情况下跳过了错误检查,但是“找不到文件”或“文件格式错误”甚至不属于该类别。这将尝试解决该问题,再加上在读取后关闭文件,再将其替换为带有常数的幻数(“ 26”)。

读取每条输入行时,如果碰巧有太多字符,这将使缓冲区溢出,但是我会将此限制检查留给读者练习。

格式错误的用户输入是如此常见,因此必须对其进行检查。

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

#define ALPHABET_SIZE  26

char ** parse(const char *path) {

    FILE *f = fopen(path, "r");

    if (f == 0)
        errx(EXIT_FAILURE, "Cannot open input file %s (err=%s)", path, strerror(errno));

    char **table = malloc(sizeof(char*) * ALPHABET_SIZE);
    int i = -1;

    do
    {
      // BUG: overflows the table - see cdlane's answer
      table[++i] = malloc(ALPHABET_SIZE + 1);

                         // TODO: what if line is too long? Or too short?
    } while (i < ALPHABET_SIZE  &&  fscanf(f, "%s", table[i]) > 0);

    if (i != ALPHABET_SIZE)
        errx(EXIT_FAILURE, "Not enough input lines");

    fclose(f);

    return table;
}

int main() {
    char **table = parse("Vtabla.dat");

    for (int i = 0; i < ALPHABET_SIZE; i++) {
        for (int x = 0; x < ALPHABET_SIZE; x++)
            printf("%c", table[i][x]);
        printf("\n");
    }

    return 0;
}
© www.soinside.com 2019 - 2024. All rights reserved.