在 C 中动态指定要扫描的最大字符串长度(如 printf 中的“%*s”)
Specifying the maximum string length to scanf dynamically in C (like "%*s" in printf)
我可以使用此技术指定 scanf
读取到 buffer
的最大字符数:
char buffer[64];
/* Read one line of text to buffer. */
scanf("%63[^\n]", buffer);
但是如果我们在写代码的时候不知道缓冲区的长度怎么办?如果是函数的参数呢?
void function(FILE *file, size_t n, char buffer[n])
{
/* ... */
fscanf(file, "%[^\n]", buffer); /* WHAT NOW? */
}
此代码容易受到缓冲区溢出的影响,因为fscanf
不知道缓冲区有多大。
我记得以前看过这个,开始认为这是解决问题的办法:
fscanf(file, "%*[^\n]", n, buffer);
我的第一个想法是 "%*[*^\n]"
中的 *
意味着最大字符串大小被传递给一个参数(在本例中为 n
)。这就是printf
中*
的意思。
当我查看 scanf
的文档时,我发现这意味着 scanf
应该丢弃 [^\n]
.
的结果
这让我有些失望,因为我认为能够为 scanf
.
动态传递缓冲区大小将是一个非常有用的功能
有什么方法可以动态地将缓冲区大小传递给 scanf
?
基本答案
在 scanf()
中没有与 printf()
格式说明符 *
的模拟。
在 The Practice of Programming 中,Kernighan 和 Pike 建议使用 snprintf()
创建格式字符串:
size_t sz = 64;
char format[32];
snprintf(format, sizeof(format), "%%%zus", sz);
if (scanf(format, buffer) != 1) { …oops… }
额外信息
正在将示例升级为完整功能:
int read_name(FILE *fp, char *buffer, size_t bufsiz)
{
char format[16];
snprintf(format, sizeof(format), "%%%zus", bufsiz - 1);
return fscanf(fp, format, buffer);
}
这里强调格式规范中的大小比缓冲区的大小小一(它是可以存储的非空字符数,不包括终止空字符)。请注意,这与 fgets()
形成对比,其中大小(int
,顺便说一句;不是 size_t
)是缓冲区的大小,而不是少一个。有多种改进功能的方法,但它说明了这一点。 (如果需要,您可以将格式中的 s
替换为 [^\n]
。)
此外,与 Tim Čas noted in the 一样,如果您想要(其余的)一行输入,您通常最好使用 fgets()
来阅读该行,但请记住它包括输出中的换行符(而 %63[^\n]
将换行符留给下一个 I/O 操作读取)。对于更一般的扫描(例如,2 或 3 个字符串),此技术可能更好 - 特别是如果与 fgets()
或 getline()
一起使用,然后 sscanf()
来解析输入。
此外,由 Microsoft(或多或少)实现并在 ISO/IEC 9899-2011(C11 标准)的附件 K 中标准化的 TR 24731-1 'safe' 函数需要一个长度明确地:
if (scanf_s("%[^\n]", buffer, sizeof(buffer)) != 1)
...oops...
这可以避免缓冲区溢出,但如果输入太长,可能会产生错误。大小 could/should 像以前一样在格式字符串中指定:
if (scanf_s("%63[^\n]", buffer, sizeof(buffer)) != 1)
...oops...
if (scanf_s(format, buffer, sizeof(buffer)) != 1)
...oops...
请注意,对于使用生成的格式字符串的代码,必须忽略或抑制关于 'non-constant format string' 的警告(来自某些编译器的某些标志集)。
scanf
函数族中确实没有可变宽度说明符。备选方案包括动态创建格式字符串(尽管如果宽度是编译时常量,这似乎有点愚蠢)或简单地接受幻数。一种可能性是使用预处理器宏来指定缓冲区和格式字符串宽度:
#define STR_VALUE(x) STR(x)
#define STR(x) #x
#define MAX_LEN 63
char buffer[MAX_LEN + 1];
fscanf(file, "%" STR_VALUE(MAX_LEN) "[^\n]", buffer);
另一种选择是#define
字符串的长度:
#define STRING_MAX_LENGTH "%10s"
或
#define DOUBLE_LENGTH "%5f"
我可以使用此技术指定 scanf
读取到 buffer
的最大字符数:
char buffer[64];
/* Read one line of text to buffer. */
scanf("%63[^\n]", buffer);
但是如果我们在写代码的时候不知道缓冲区的长度怎么办?如果是函数的参数呢?
void function(FILE *file, size_t n, char buffer[n])
{
/* ... */
fscanf(file, "%[^\n]", buffer); /* WHAT NOW? */
}
此代码容易受到缓冲区溢出的影响,因为fscanf
不知道缓冲区有多大。
我记得以前看过这个,开始认为这是解决问题的办法:
fscanf(file, "%*[^\n]", n, buffer);
我的第一个想法是 "%*[*^\n]"
中的 *
意味着最大字符串大小被传递给一个参数(在本例中为 n
)。这就是printf
中*
的意思。
当我查看 scanf
的文档时,我发现这意味着 scanf
应该丢弃 [^\n]
.
这让我有些失望,因为我认为能够为 scanf
.
有什么方法可以动态地将缓冲区大小传递给 scanf
?
基本答案
在 scanf()
中没有与 printf()
格式说明符 *
的模拟。
在 The Practice of Programming 中,Kernighan 和 Pike 建议使用 snprintf()
创建格式字符串:
size_t sz = 64;
char format[32];
snprintf(format, sizeof(format), "%%%zus", sz);
if (scanf(format, buffer) != 1) { …oops… }
额外信息
正在将示例升级为完整功能:
int read_name(FILE *fp, char *buffer, size_t bufsiz)
{
char format[16];
snprintf(format, sizeof(format), "%%%zus", bufsiz - 1);
return fscanf(fp, format, buffer);
}
这里强调格式规范中的大小比缓冲区的大小小一(它是可以存储的非空字符数,不包括终止空字符)。请注意,这与 fgets()
形成对比,其中大小(int
,顺便说一句;不是 size_t
)是缓冲区的大小,而不是少一个。有多种改进功能的方法,但它说明了这一点。 (如果需要,您可以将格式中的 s
替换为 [^\n]
。)
此外,与 Tim Čas noted in the fgets()
来阅读该行,但请记住它包括输出中的换行符(而 %63[^\n]
将换行符留给下一个 I/O 操作读取)。对于更一般的扫描(例如,2 或 3 个字符串),此技术可能更好 - 特别是如果与 fgets()
或 getline()
一起使用,然后 sscanf()
来解析输入。
此外,由 Microsoft(或多或少)实现并在 ISO/IEC 9899-2011(C11 标准)的附件 K 中标准化的 TR 24731-1 'safe' 函数需要一个长度明确地:
if (scanf_s("%[^\n]", buffer, sizeof(buffer)) != 1)
...oops...
这可以避免缓冲区溢出,但如果输入太长,可能会产生错误。大小 could/should 像以前一样在格式字符串中指定:
if (scanf_s("%63[^\n]", buffer, sizeof(buffer)) != 1)
...oops...
if (scanf_s(format, buffer, sizeof(buffer)) != 1)
...oops...
请注意,对于使用生成的格式字符串的代码,必须忽略或抑制关于 'non-constant format string' 的警告(来自某些编译器的某些标志集)。
scanf
函数族中确实没有可变宽度说明符。备选方案包括动态创建格式字符串(尽管如果宽度是编译时常量,这似乎有点愚蠢)或简单地接受幻数。一种可能性是使用预处理器宏来指定缓冲区和格式字符串宽度:
#define STR_VALUE(x) STR(x)
#define STR(x) #x
#define MAX_LEN 63
char buffer[MAX_LEN + 1];
fscanf(file, "%" STR_VALUE(MAX_LEN) "[^\n]", buffer);
另一种选择是#define
字符串的长度:
#define STRING_MAX_LENGTH "%10s"
或
#define DOUBLE_LENGTH "%5f"