C具有fscanf的未定义行为

C具有fscanf的未定义行为,c,scanf,newline,undefined-behavior,C,Scanf,Newline,Undefined Behavior,下面我有一段代码片段。 在macOS上,我在Xcode和CLion中运行了它,得到了相同的奇怪结果。 另一方面,在Linux上,当使用gcc编译时,它可以完美地运行。 我想知道代码在任何时候是否会产生未定义的行为。 它试图解析的输入文件是vigenére表,你知道,26个字符的拉丁字母行,字母在下一行左移1。 每一行都由CRLF终止。 预期输出是控制台上打印的表格。 意外的是,在macOS上至少有1行显示不正确。 这里是输入btw: (但我不知道相应的行尾是否被保留) #包括 #包括 字符**解

下面我有一段代码片段。 在macOS上,我在Xcode和CLion中运行了它,得到了相同的奇怪结果。 另一方面,在Linux上,当使用gcc编译时,它可以完美地运行。 我想知道代码在任何时候是否会产生未定义的行为。 它试图解析的输入文件是vigenére表,你知道,26个字符的拉丁字母行,字母在下一行左移1。 每一行都由CRLF终止。 预期输出是控制台上打印的表格。 意外的是,在macOS上至少有1行显示不正确。 这里是输入btw: (但我不知道相应的行尾是否被保留)

#包括
#包括
字符**解析(字符*路径){
文件*f=fopen(路径,“r”);
char**table=(char**)malloc(sizeof(char*)*26);
int i=-1;
do表[++i]=(字符*)malloc(大小(字符)*27);
而(fscanf(f,“%s”,表[i])>0);
返回表;
}
int main(){
char**table=parse(“Vtabla.dat”);
对于(int i=0;i<26;i++){
对于(int x=0;x<26;x++)
printf(“%c”,表[i][x]);
printf(“\n”);
}
返回0;
}

评论中的讨论非常活跃,但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;
}
#包括
#包括
#包括
#包括
#包括
#定义字母表大小26
字符**解析(常量字符*路径){
文件*f=fopen(路径,“r”);
如果(f==0)
errx(退出_失败,“无法打开输入文件%s(err=%s)”,路径,strerror(errno));
字符**表=malloc(sizeof(char*)*字母大小);
int i=-1;
做
{
//错误:溢出表格-参见cdlane的答案
表[++i]=malloc(字母表大小+1);
//TODO:若线路太长或太短怎么办?
}而(i0);
如果(i!=字母表大小)
errx(退出_故障,“输入线不足”);
fclose(f);
返回表;
}
int main(){
char**table=parse(“Vtabla.dat”);
对于(int i=0;i
此代码中有一个错误,在这个循环中:

do table[++i] = (char*)malloc(sizeof(char) * 27);
while (fscanf(f, "%s", table[i]) > 0);
包含26个指针,但在
fscanf()
失败的迭代中,
变量的第27个指针在上一步中通过
malloc
初始化。这会损坏我的系统上
中的数据。你可以让自己相信这一点,把这一行中的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;
}
#包括
#包括
#定义字母26
字符**解析(字符*路径){
char**table=calloc(字母,sizeof(char*));
文件*f=fopen(路径,“r”);
for(int i=0;i如果(fscanf(f,“%s”,表[i]),则最好在
parse
函数中
fclose(f);
,如果文件中的行数超过预期,则此代码看起来会爆炸。此代码从未被删除过compiled@SteveFriedl行数不能再多了,因为字母表是26个字符长的,这是Vigenère表中的行数。“不可能”是著名的最后一个词。在C语言中,您必须确保没有。无法使用fgets来解析它-->
char buf[80];if(fgets(buf,sizeof buf,f)){buf[strcspn(buf),\n\r”)=0;assert(strlen(buf)==26;strcpy(table[i],buffer)}
否则代码就是这样。@chux,我对
*scanf
有一种病态的厌恶,以至于我不能自己去那里;你的方法w/
fgets()
比.UV更适合于对*scanf的病态厌恶。“我不认为27会比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;
}