我需要一个函数来读取文件名,最大长度为MAX_FILE_NAME_SIZE
,这是一个符号常量,我按照以下方式执行此操作:
char * readFileName()
{
char format[6];
char * fileName = malloc(MAX_FILE_NAME_SIZE * sizeof(fileName[0]));
if(fileName== NULL)
return NULL;
sprintf(format, "%%%ds", MAX_FILE_NAME_SIZE-1);
scanf(format, fileName);
fileName= realloc(fileName, strlen(fileName)*sizeof(fileName[0]));
return fileName;
}
我真的很想阅读sprintf
部分(以及format
向量),这是最干净,最有效的方式吗?
答案 0 :(得分:3)
你可以做一点预处理器破解:
#define MAX_BUFFER 30
#define FORMAT(s) "%" #s "s"
#define FMT(s) FORMAT(s)
int main(void)
{
char buffer[MAX_BUFFER + 1];
scanf(FMT(MAX_BUFFER), buffer);
printf("string: %s\n", buffer);
printf("length: %d\n", strlen(buffer));
return 0;
}
FORMAT
和FMT
宏是预处理器正确翻译它们所必需的。如果您直接使用FORMAT
致电FORMAT(MAX_BUFFER)
,则会转换为"%" "MAX_BUFFER" "s"
这是不合适的。
您可以使用gcc -E scanf.c
验证这一点。但是,如果您通过另一个宏调用它,这将有效地为您解析宏名称并转换为"%" "30" "s"
,这是scanf
的精细格式字符串。
正如评论中@Jonathan Leffler正确指出的那样,你不能对那个宏做任何数学运算,所以你需要为NULL终止字节用加1字符声明buffer
,因为宏扩展为%30s
,它将读取30个字符加上空字节。
所以正确的缓冲区声明应该是char buffer[MAX_BUFFER + 1];
。
正如评论中所提到的,一个宏版本将无法工作,因为预处理器运算符#
将参数转换为字符串(字符串化,请参见下文)。因此,当您使用FORMAT(MAX_BUFFER)
调用它时,它只是将MAX_BUFFER
字符串化而不是宏扩展它,从而为您提供结果:"%" "MAX_BUFFER" "s"
。
C预处理器手册的3.4 Stringification部分说明了这一点:
有时您可能希望将宏参数转换为字符串常量。参数不会在字符串常量内替换,但您可以使用
‘#’
预处理运算符。当宏参数与前导‘#’
一起使用时,预处理器将其替换为实际参数的文本文本,并转换为字符串常量。 与普通参数替换不同,参数不是首先进行宏扩展。这称为字符串化。
这是gcc -E scanf.c
命令在具有一个宏版本(最后一部分)的文件上的输出:
int main(void)
{
char buffer[30 + 1];
scanf("%" "MAX_BUFFER" "s", buffer);
printf("string: %s\n", buffer);
printf("length: %d\n", strlen(buffer));
return 0;
}
正如所料。现在,对于这两个级别,我无法解释比文档本身更好,并且在其最后部分有一个这个特定情况的实际示例(两个宏):
如果要对宏参数的扩展结果进行字符串化,必须使用两个级别的宏。
#define xstr(s) str(s)
#define str(s) #s
#define foo 4
str (foo)
==> "foo"
xstr (foo)
==> xstr (4)
==> str (4)
==> "4"
s
在str
中使用时会被字符串化,因此它不会首先进行宏扩展。 但是s
是xstr
的普通参数,因此在展开xstr
之前它是完全宏扩展的(参见Argument Prescan)。因此,当str
到达其参数时,已经进行了宏扩展。