fscanf 是否分配内存并在字符串末尾放置一个 NUL 字节?
Does fscanf allocate memory and place a NUL byte at the end of a string?
如果我是正确的,做这样的事情:
char *line;
那我必须分配一些内存,分配给line,是吗?如果我是对的,问题如下:
像
这样的一行
while (fscanf(fp,"%[^\n]", line) == 1) { ... }
在没有为行分配任何内存的情况下,我仍然得到正确的行,并且在这些行上有正确的 strlen 计数。
那么,fscanf
是否为我分配了那个内存并且它也放置了 '[=13=]'
?
我在 fscanf
.
的规范中没有提到这两件事
line
未初始化且未指向任何有效的内存位置,因此您看到的是未定义的行为。
在向指针写入内容之前,您需要为指针分配内存。
PS:如果您想阅读整行,那么 fgets()
比 fgets()
带有换行符更好 option.Note。
没有。你只是幸运的未定义行为。完全有可能一个小时后,程序将出现段错误而不是预期的 运行,或者不同的编译器甚至不同机器上的相同编译器将生成行为不同的程序。你不能指望没有指定的事情会发生,如果你这样做了,你也不能指望它是任何形式的可靠。
查看 C FAQ:
Q: I just tried the code
char *p;
strcpy(p, "abc");
and it worked. How? Why didn't it crash?
A: You got lucky, I guess. The memory randomly pointed to by the uninitialized pointer p happened to be writable by you, and apparently was not already in use for anything vital. See also question 11.35.
还有,here is a longer explanation, and another longer explanation.
要在 POSIX 系统上使用最新的 C 库阅读整个 行 ,您应该使用 getline(3)。它根据需要分配(并重新分配)保存该行的缓冲区。请参阅手册页上的示例。
如果你有一个没有 getline
的非 POSIX 系统,你可能会使用 fgets(3) 但是你必须自己分配线路,测试你没有阅读完整的换行符终止行,然后重复。然后你需要在调用 fgets
之前预先分配一些行缓冲区(使用例如 malloc
)(如果一行不适合你可能 realloc
它并再次调用 fgets
).类似于:
//// UNTESTED CODE
size_t linsiz=64;
char* linbuf= malloc(linsiz);
if (!linbuf) { perror("malloc"); exit(EXIT_FAILURE); };
memset(linbuf, 0, sizeof(linbuf));
bool gotentireline= false;
char* linptr= linbuf;
do {
if (!fgets(linptr, linbuf+linsiz-linptr-1, stdin))
break;
if (strchr(linptr, '\n'))
gotentireline= true;
else {
size_t newlinsiz= 3*linsiz/2+16;
char* newlinbuf= malloc(newlinsiz);
int oldlen= strlen(linbuf);
if (!newlinbuf) { perror("malloc"); exit(EXIT_FAILURE); };
memset (newlinbuf, 0, newlinsiz); // might be not needed
strncpy(newlinbuf, linbuf, linsiz);
free (linbuf);
linbuf= newlinbuf;
linsiz= newlinsiz;
linptr= newlinbuf+oldlen;
);
} while(!gotentireline);
/// here, use the linbuf, and free it later
一般规则是始终初始化指针(例如在您的情况下声明 char *line=NULL;
),并始终测试 malloc
、calloc
、realloc
的失败).此外,使用所有警告和调试信息进行编译 (gcc -Wall -Wextra -g
)。它可能会给你一个有用的警告。
顺便说一句,我喜欢清除每一个动态分配的内存,即使它不是很有用(因为行为更加确定,使程序更容易调试)。
有些系统还有 valgrind 来帮助检测内存泄漏、缓冲区溢出等。
如果您使用 m
(a
在某些较早的 POSIX 版本之前)格式,POSIX scanf()
函数族将分配内存修饰符。 注意: 当 fscanf
分配时,它需要一个 char **
指针。 (参见 man scanf
)例如:
while(fscanf(fp,"%m[^\n]", &line) == 1) { ... }
我还建议使用 newline
和 "%m[^\n]%*c"
。我同意建议使用 line-oriented
输入而不是 fscanf
的其他答案。 (例如 getline
——参见:Basile 的回答)
如果我是正确的,做这样的事情:
char *line;
那我必须分配一些内存,分配给line,是吗?如果我是对的,问题如下:
像
这样的一行while (fscanf(fp,"%[^\n]", line) == 1) { ... }
在没有为行分配任何内存的情况下,我仍然得到正确的行,并且在这些行上有正确的 strlen 计数。
那么,fscanf
是否为我分配了那个内存并且它也放置了 '[=13=]'
?
我在 fscanf
.
line
未初始化且未指向任何有效的内存位置,因此您看到的是未定义的行为。
在向指针写入内容之前,您需要为指针分配内存。
PS:如果您想阅读整行,那么 fgets()
比 fgets()
带有换行符更好 option.Note。
没有。你只是幸运的未定义行为。完全有可能一个小时后,程序将出现段错误而不是预期的 运行,或者不同的编译器甚至不同机器上的相同编译器将生成行为不同的程序。你不能指望没有指定的事情会发生,如果你这样做了,你也不能指望它是任何形式的可靠。
查看 C FAQ:
Q: I just tried the code
char *p; strcpy(p, "abc");
and it worked. How? Why didn't it crash?
A: You got lucky, I guess. The memory randomly pointed to by the uninitialized pointer p happened to be writable by you, and apparently was not already in use for anything vital. See also question 11.35.
还有,here is a longer explanation, and another longer explanation.
要在 POSIX 系统上使用最新的 C 库阅读整个 行 ,您应该使用 getline(3)。它根据需要分配(并重新分配)保存该行的缓冲区。请参阅手册页上的示例。
如果你有一个没有 getline
的非 POSIX 系统,你可能会使用 fgets(3) 但是你必须自己分配线路,测试你没有阅读完整的换行符终止行,然后重复。然后你需要在调用 fgets
之前预先分配一些行缓冲区(使用例如 malloc
)(如果一行不适合你可能 realloc
它并再次调用 fgets
).类似于:
//// UNTESTED CODE
size_t linsiz=64;
char* linbuf= malloc(linsiz);
if (!linbuf) { perror("malloc"); exit(EXIT_FAILURE); };
memset(linbuf, 0, sizeof(linbuf));
bool gotentireline= false;
char* linptr= linbuf;
do {
if (!fgets(linptr, linbuf+linsiz-linptr-1, stdin))
break;
if (strchr(linptr, '\n'))
gotentireline= true;
else {
size_t newlinsiz= 3*linsiz/2+16;
char* newlinbuf= malloc(newlinsiz);
int oldlen= strlen(linbuf);
if (!newlinbuf) { perror("malloc"); exit(EXIT_FAILURE); };
memset (newlinbuf, 0, newlinsiz); // might be not needed
strncpy(newlinbuf, linbuf, linsiz);
free (linbuf);
linbuf= newlinbuf;
linsiz= newlinsiz;
linptr= newlinbuf+oldlen;
);
} while(!gotentireline);
/// here, use the linbuf, and free it later
一般规则是始终初始化指针(例如在您的情况下声明 char *line=NULL;
),并始终测试 malloc
、calloc
、realloc
的失败).此外,使用所有警告和调试信息进行编译 (gcc -Wall -Wextra -g
)。它可能会给你一个有用的警告。
顺便说一句,我喜欢清除每一个动态分配的内存,即使它不是很有用(因为行为更加确定,使程序更容易调试)。
有些系统还有 valgrind 来帮助检测内存泄漏、缓冲区溢出等。
如果您使用 m
(a
在某些较早的 POSIX 版本之前)格式,POSIX scanf()
函数族将分配内存修饰符。 注意: 当 fscanf
分配时,它需要一个 char **
指针。 (参见 man scanf
)例如:
while(fscanf(fp,"%m[^\n]", &line) == 1) { ... }
我还建议使用 newline
和 "%m[^\n]%*c"
。我同意建议使用 line-oriented
输入而不是 fscanf
的其他答案。 (例如 getline
——参见:Basile 的回答)