使用 fscanf 的 C 未定义行为
C undefined behaviour with fscanf
我在下面有一个代码片段。
在 macOS 上,我在 Xcode 和 CLion 中有 运行 它,结果相同。
另一方面,在 Linux 上用 gcc 编译时它 运行 是完美的。
我想知道代码是否在任何时候产生未定义的行为。
它尝试解析的输入文件是 vigenére table,你知道,有 26 个字符的行,带有拉丁字母表,字母在第 1 行左移 1。
每行都以 CRLF 结尾。
预期的输出是 table 在控制台上打印出来的。
意外的部分是至少有 1 行在 macOS 上显示不正确。
这是输入顺便说一句:
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;
}
评论中的讨论很活跃,但 OP 似乎对许多有经验的开发人员所关心的更狭隘的问题感兴趣,所以我会 post 一个不严格针对问题的答案,但是表现出更广泛的关注。
我相信我们中的许多人都跳过了我们认为极不可能发生的情况的错误检查,但 "file not found" 或 "file is malformed" 甚至不接近该类别。这试图解决这个问题,加上它在读取后关闭文件,加上它用常量替换幻数(“26”)。
在读取每个输入行时,如果恰好有太多字符,这将溢出缓冲区,但我将把这个限制检查作为练习留给 reader。
格式错误的用户输入非常普遍,因此必须检查它。
#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;
}
这段代码中有一个错误,在这个循环中:
do table[++i] = (char*)malloc(sizeof(char) * 27);
while (fscanf(f, "%s", table[i]) > 0);
table
持有 26 个指针,但在 fscanf()
失败的迭代中,table
变量的第 27 个指针在上一步中通过 malloc
初始化。这会破坏我系统上 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;
}
我在下面有一个代码片段。 在 macOS 上,我在 Xcode 和 CLion 中有 运行 它,结果相同。 另一方面,在 Linux 上用 gcc 编译时它 运行 是完美的。 我想知道代码是否在任何时候产生未定义的行为。 它尝试解析的输入文件是 vigenére table,你知道,有 26 个字符的行,带有拉丁字母表,字母在第 1 行左移 1。 每行都以 CRLF 结尾。 预期的输出是 table 在控制台上打印出来的。 意外的部分是至少有 1 行在 macOS 上显示不正确。 这是输入顺便说一句: 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;
}
评论中的讨论很活跃,但 OP 似乎对许多有经验的开发人员所关心的更狭隘的问题感兴趣,所以我会 post 一个不严格针对问题的答案,但是表现出更广泛的关注。
我相信我们中的许多人都跳过了我们认为极不可能发生的情况的错误检查,但 "file not found" 或 "file is malformed" 甚至不接近该类别。这试图解决这个问题,加上它在读取后关闭文件,加上它用常量替换幻数(“26”)。
在读取每个输入行时,如果恰好有太多字符,这将溢出缓冲区,但我将把这个限制检查作为练习留给 reader。
格式错误的用户输入非常普遍,因此必须检查它。
#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;
}
这段代码中有一个错误,在这个循环中:
do table[++i] = (char*)malloc(sizeof(char) * 27);
while (fscanf(f, "%s", table[i]) > 0);
table
持有 26 个指针,但在 fscanf()
失败的迭代中,table
变量的第 27 个指针在上一步中通过 malloc
初始化。这会破坏我系统上 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;
}