在保持我的代码可理解的同时格式化scanf有麻烦

时间:2015-10-22 02:40:26

标签: c

我需要一个函数来读取文件名,最大长度为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向量),这是最干净,最有效的方式吗?

1 个答案:

答案 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;
}

FORMATFMT宏是预处理器正确翻译它们所必需的。如果您直接使用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"
  

sstr中使用时会被字符串化,因此它不会首先进行宏扩展。 但是sxstr的普通参数,因此在展开xstr之前它是完全宏扩展的(参见Argument Prescan)。因此,当str到达其参数时,已经进行了宏扩展

资源