在C中动态指定scanf的最大字符串长度(如printf中的“%* s”)

我可以使用这种技术指定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动态传递缓冲区大小是一个非常有用的function。

有什么办法可以动态地将缓冲区大小传递给scanf吗?

基本答案

scanf()没有printf()格式说明符*的模拟。

在编程实践中 ,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); } 

这强调格式规范中的大小比缓冲区的大小小1(它是可以存储的非空字符数,而不计算终止空值)。 请注意,这与fgets()形成对比,其中size( int ,顺便size_t ;而不是size_t )是缓冲区的大小,而不是一个。 有多种方法可以改进function,但它说明了这一点。 (如果这是你想要的,你可以用[^\n]替换格式的s 。)

另外,正如TimČas在评论中指出的那样,如果你想要(其余的)一行输入,你通常最好使用fgets()来读取这一行,但要记住它在输出中包含换行符(而%63[^\n]使换行符被下一个I / O操作读取。 对于更一般的扫描(例如,2或3个字符串),此技术可能更好 – 特别是如果与fgets()getline() ,然后使用sscanf()来解析输入。

此外,TR 24731-1“安全”function由Microsoft(或多或少)实施并在ISO / IEC 9899-2011(C11标准)的附录K中标准化,需要明确的长度:

 if (scanf_s("%[^\n]", buffer, sizeof(buffer)) != 1) ...oops... 

这可以避免缓冲区溢出,但如果输入太长,可能会产生错误。 可以/应该在格式字符串中指定大小,如前所述:

 if (scanf_s("%63[^\n]", buffer, sizeof(buffer)) != 1) ...oops... if (scanf_s(format, buffer, sizeof(buffer)) != 1) ...oops... 

请注意,对于使用生成的格式字符串的代码,必须忽略或抑制关于’非常量格式字符串’的警告(来自某些标记集下的某些编译器)。

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_LENGHT "%10s" 

要么

 #define DOUBLE_LENGHT "%5d"