【问题标题】:Does gcc support multi-line raw string literals in macro definition?gcc 是否支持宏定义中的多行原始字符串文字?
【发布时间】:2019-08-02 11:32:43
【问题描述】:

我想在header文件中放一个常量json字符串,这样其他来源也可以使用和查看。我想在 C++11 中使用原始字符串文字,因为它看起来清晰漂亮。但是,我尝试使用 gcc 4.8.5/gcc 4.9.2 编译以下代码,使用 gcc -std=c++11 test.cpp:

#include <cstdio>

/* works, but looks ugly */
#define STR_a \
"{ \n\
    \"AAA\": \"a\", \n\
    \"BBB\": \"b\" \n\
}"

/* works with VS2017, not works with gcc */
#define STR_b \
R"({
    "AAA": "a",
    "BBB": "b"
})";

/* works, but must use 'extern const'/'static' in header files */
const char *STR_var = 1 + R"(
{
    "AAA": "a",
    "BBB": "b"
})";

int main()
{
    const char *s = STR_b;
    printf("%s\n", s);
    return 0;
}

但是,我得到编译错误:

test.cpp:16:1:错误:未终止的原始字符串 R"({ ^ test.cpp:19:3:警告:缺少终止“字符 })"; ^ test.cpp:19:1:错误:缺少终止“字符 })"; ^ test.cpp:29:2:错误:程序中出现杂散“R”

如果我添加反斜杠,gcc 可以工作:

#define STR_b \
R"({ \
    "AAA": "a", \
    "BBB": "b" \
})";

但它显示错误的字符串:

{ \ “AAA”:“一个”,\ “BBB”:“b”\ }

它是实现定义的功能吗?更高版本的 gcc 是否支持此功能?


编辑:

我下载并编译了 gcc 7.3.1 源代码,然后再次尝试了我的测试代码;但是,gcc7.3.1 报告与 gcc 4.X 相同的错误。我放弃了,决定继续使用static const char *。 @lyang 的回答也不错,让我大开眼界。

【问题讨论】:

  • 原始字符串是 C++ 编译器的一个特性,预处理器(可能)不知道该特性。然而,我在这里和那里发现了不同预处理器之间的一些细微差别,所以你可能会找到一个可以让你这样做的,但它肯定不是标准的。您展示的“丑陋”方式是唯一安全的方式。
  • 在这里使用static const char*变量有什么问题?
  • @Frank 是的,到目前为止我在头文件中使用了static const char * 变量,但我更喜欢原始字符串。如果新版本的gcc支持这个功能,我会升级gcc,然后马上替换部分代码。
  • @TaoSfqh 这是一个正交问题。您可以将原始字符串分配给static const char*。现在,如果您的意思是字符串文字,那么您就被误导了。重复的相同文字将导致相同的结果,只是编译器需要做更多的工作。
  • @Frank 抱歉我的表达不清楚。 static 使包含此标头的任何源文件都具有此变量的一个副本,我尽量避免此缺陷。如果我改用extern const char *,我必须将常量字符串放入一个源中,但我希望人们可以在头文件中看到它。

标签: c++ c++11 gcc g++


【解决方案1】:

2019 年 4 月 4 日最新更新

我最近听说Cog 并认为它可能是比 m4 更好的解决方案。它使用python进行预处理,使代码更具可读性,如下图(轻松支持字符串内的引号):

#include <cstdio>

/*[[[cog
import cog, re

def escape_raw(s):
    s = re.sub(r'\\', r'\\\\', s) # escape backslash first
    s = re.sub(r'"', r'\"', s) # escape quotes
    return re.sub(r'\n', r'\\n\n', s) # escape newline last

def cog_define(name, val):
    cog.outl("#define {name} {val}".format(name=name, val=val))

STR_test = r"""{
    "AAA": "a",
    "BBB": "b",
    "contain \" quote": "c"
}"""

cog_define("STR_test", escape_raw(STR_test))

]]]*/
//[[[end]]]

int main()
{
    const char *s = STR_test;
    printf("%s\n", s);
    return 0;
}

输出:

#include <cstdio>

/*[[[cog
import cog, re

def escape_raw(s):
    s = re.sub(r'\\', r'\\\\', s) # escape backslash first
    s = re.sub(r'"', r'\"', s) # escape quotes
    return re.sub(r'\n', r'\\n\n', s) # escape newline last

def cog_define(name, val):
    cog.outl("#define {name} {val}".format(name=name, val=val))

STR_test = r"""{
    "AAA": "a",
    "BBB": "b",
    "contain \" quote": "c"
}"""

cog_define("STR_test", escape_raw(STR_test))

]]]*/
#define STR_test {\n
    \"AAA\": \"a\",\n
    \"BBB\": \"b\",\n
    \"contain \\\" quote\": \"c\"\n
}
//[[[end]]]

int main()
{
    const char *s = STR_test;
    printf("%s\n", s);
    return 0;
}

================================================ ===============

你试过 GNU 的m4吗?

这有点骇人听闻,但想法是对丑陋的版本进行预处理(不使用原始字符串)。

你的代码会是这样的:

#include <cstdio>

m4_changecom(`/*', `*/')
m4_define(`ESCAPE_RAW', `"m4_patsubst(`m4_patsubst($1, `"', `\\"')', `
', `\\n\\
')'") /* substitute newline and double quote with escaped version */

#define STR_test ESCAPE_RAW(`{
    "AAA": "a",
    "BBB": "b"
}')

int main()
{
    const char *s = STR_test;
    printf("%s\n", s);
    return 0;
}

丑陋的ESCAPE_RAW宏只需要定义一次,后面的所有“原始字符串”都可以使用ESCAPE_RAW生成gcc可以识别的丑陋版本。

要使用 m4 进行预处理,请使用命令 m4 -P test.cpp,其中 -P 强制在定义语句上使用 m4_ 前缀。该命令产生这个:

#include <cstdio>




#define STR_test "{\n\
    \"AAA\": \"a\",\n\
    \"BBB\": \"b\"\n\
}"

int main()
{
    const char *s = STR_test;
    printf("%s\n", s);
    return 0;
}

也许将你的 m4 文件命名为 .m4 扩展名,并使用 m4 生成看起来难看的头文件。

【讨论】:

  • 谢谢,这对我来说是另一种新方法。但是,它需要 makefile 为 m4 做更多的工作,而且每个源包括这个头文件(也许其他人在我的库中使用这个头文件)也必须做这个工作。
  • 我测试过,但 m4 发出警告:excess arguments to builtin m4_patsubst ignored,并打印错误的结果。此外,我的测试代码只是一个简单的 json 字符串,实际上 json 可能很复杂,例如字段可能再次包含换行符和转义字符:"CCC":"c\"c\nc"
  • @TaoSfqh 不确定为什么会出现警告,但对于更复杂的 JSON 字符串,您还需要转义反斜杠,即m4_patsubst($1, `\', `\\')。对于您对分发的担忧,我想您可以使用一些提交钩子处理所有.m4 文件,这样最终用户只会看到带有“丑陋”宏的版本。
  • @TaoSfqh 实际上我不得不承认,使用 m4 来达到这个目的非常棘手且难以调试,特别是如果您想正确替换换行符和反斜杠。我还想不出一个正确的方法来做到这一点。对不起。
  • @TaoSfqh 我想你可能对使用名为 cog 的 python 程序进行的最新更新感兴趣。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-18
  • 2023-03-03
  • 1970-01-01
  • 2021-07-19
  • 1970-01-01
  • 2018-08-04
相关资源
最近更新 更多