将预处理器标记转换为字符串

84

我正在寻找一种将预处理器标记转换为字符串的方法。

具体而言,我有一个这样的标记:

#define MAX_LEN 16

我想使用它来防止缓冲区溢出:

char val[MAX_LEN+1]; // room for \0
sscanf(buf, "%"MAX_LEN"s", val);

我愿意尝试其他实现方式,但只能使用标准库。


https://dev59.com/43VC5IYBdhLWcg3wvT7g - Ciro Santilli OurBigBook.com
可能是C宏创建字符串的重复问题。 - Richard Stelling
6个回答

132

请参见使用 FILELINE 报告错误

#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
#define AT __FILE__ ":" TOSTRING(__LINE__)

所以您的问题可以通过执行以下代码来解决:sscanf(buf, "%" TOSTRING(MAX_LEN) "s", val);


4
为什么要级联两个宏?一个 TOSTRING 不够吗? - feedc0de
关于双重扩展字符串化,请参见下面的答案。C/C++预处理器很脏、丑陋,我认为它没有一个标准。所以为什么它需要两个而不是一个,我无法说,因为我不想进行研究。 - Dan
19
@Daniel Brunner 一个宏将粘贴令牌本身,字面上产生"%" "MAX_LEN" "%"。第二个宏会导致令牌被粘贴,例如"16",因为TOSTRING宏使最终代码等效于STRINGIFY(16) - Tim Sylvester
1
@TimSylvester 我希望我能因为那个评论给你点赞! :) - Matthieu
@TimSylvester 那真是太令人困惑了。 - user16217248

24

我在网上找到了一个答案。(https://gcc.gnu.org/onlinedocs/gcc-3.4.3/cpp/Stringification.html)

#define VERSION_MAJOR 4
#define VERSION_MINOR 47

#define VERSION_STRING "v" #VERSION_MAJOR "." #VERSION_MINOR

The above does not work but hopefully illustrates what I would like to do, i.e. make VERSION_STRING end up as "v4.47".

To generate the proper numeric form use something like

#define VERSION_MAJOR 4
#define VERSION_MINOR 47

#define STRINGIZE2(s) #s
#define STRINGIZE(s) STRINGIZE2(s)
#define VERSION_STRING "v" STRINGIZE(VERSION_MAJOR) \
"." STRINGIZE(VERSION_MINOR)

#include <stdio.h>
int main() {
    printf ("%s\n", VERSION_STRING);
    return 0;
}

2
关于“我在网上找到了答案”的问题:你能提供一个参考吗?相关信息:《Stack Overflow 推出了抄袭标记和管理员工具!》(https://meta.stackoverflow.com/questions/423749/)。 - Peter Mortensen

10
这应该可以运行:
 sscanf(buf, "%" #MAX_LEN "s", val);

如果不行的话,就需要使用“双倍扩展”技巧:
 #define STR1(x)  #x
 #define STR(x)  STR1(x)
 sscanf(buf, "%" STR(MAX_LEN) "s", val);

3
第一个无法正常工作;#在宏展开中将宏参数转换为字符串。第二个可以正常工作。 - Jonathan Leffler
@RaviRaj — 因为 # 只将宏参数转换为字符串,而第一行不在宏的主体内。 - Jonathan Leffler

4
你应该使用双重扩展字符串宏技巧。或者只需有一个。
#define MAX_LEN    16
#define MAX_LEN_S "16"

char val[MAX_LEN+1];
sscanf(buf, "%"MAX_LEN_S"s", val);

并且保持它同步。(这有点麻烦,但只要定义紧挨着彼此,你可能会记得。)

实际上,在这种情况下,strncpy 不就足够了吗?

strncpy(val, buf, MAX_LEN);
val[MAX_LEN] = '\0';

如果是printf,那么这将更容易:
sprintf(buf, "%.*s", MAX_LEN, val);

1
是的,我希望scanf例程中有*或其他符号可用于限制长度的参数。在我看来,sprintf以那种形式比strncpy更好,因为如果strncpy超出缓冲区,则不会终止字符串,并且如果字符串太短,则会写入额外的数据。 - davenpcj

1

虽然之前的一些答案“可行”,但我个人建议使用简单的字符串API,而不是libc中提供的垃圾。

有许多便携式API,其中一些还针对易于包含在您的项目中进行了优化...而像ustr这样的API具有微小的空间开销和对堆栈变量的支持。


0
在我的例子中,要生成的格式是%16s%16s%d
#include <iostream>

#define MAX_LEN 16

#define AUX(x) #x
#define STRINGIFY(x) AUX(x)

int main() {
    char buffer[] = "Hello World 25";
    char val[MAX_LEN+1];
    char val2[MAX_LEN+1];
    int val3;

    char format[] = "%" STRINGIFY(MAX_LEN) "s" "%" STRINGIFY(MAX_LEN) "s" "%d";
    int result = sscanf(buffer, format, val, val2, &val3);
    std::cout<< val << std::endl;
    std::cout<< val2 << std::endl;
    std::cout<< val3 << std::endl;
    std::cout<<"Filled: " << result << " variables" << std::endl;
    std::cout << "Format: " << format << std::endl;
}

输出

Output


请查看 *为什么在提问时不上传代码/错误的图片?(例如,"图片应该只用于说明无法以其他方式清晰表达的问题,例如提供用户界面的截图。"*)并做正确的事情(它也涵盖了答案)。提前致谢。 - Peter Mortensen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接