C/C++ 警告或禁止字符串字面量连接。

27

有没有一种方法可以警告或禁止字面字符串连接,例如:

const char *a = "foo" " bar";

我花了数小时的时间在一个巨大的静态数组中寻找错误。

const char * a[] = {"foo" "bar"};

替代

const char * a[] = {"foo", "bar"};

24
我理解你的痛苦。我们都可能遇到过这种情况。但你不希望完全禁止字符串连接,因为很多代码会有意依赖它。 - Steve Summit
3
@MarekR 是的,它并非万无一失,但也不必如此。更重要的是不要漏掉任何一个。在实践中,你的例子可能非常罕见。另一个需要检查的是以 " 结尾的行,可能会有尾随空格。如果需要,这些可以通过 grep '"\s*$' 捕捉到。 - Tom Karzes
3
字面字符串连接是编译过程中的逻辑第6阶段,在标记化之前发生。可能无法解决这个问题。 - Richard Critten
3
一些候选人(和/或起点):*是否有GCC标志可以检测字符串字面值连接?(2015年。"最近我修复了一个bug......有人在string3后忘记了,")和为什么要允许字符串字面值的连接?(2010年。"最近我被一个微妙的bug所困扰......我忘记了two后面的,"*) - Peter Mortensen
2
@Pryftan 这只是一种捕获编译时字符串连接可能发生的情况的方法,没有其他意义。这并不意味着所有这样的匹配都应该被更改。重点是它可以让用户验证是否存在问题情况。 - Tom Karzes
显示剩余16条评论
4个回答

50

Clang有一个警告-Wstring-concatenation,专门设计用来捕捉这种错误:

warning: suspicious concatenation of string literals in an array initialization; did you mean to separate the elements with a comma? [-Wstring-concatenation]
char const *a[]  = { "ok", "foo" "bar", "ok"};
                                 ^
                                ,

这个例子不会完全适用于你展示的玩具例子,因为你需要有几个初始化器,而只有在一些地方缺少逗号,例如:

// no warning
char const *b[]  = {"foo" "bar"};
// no warning
char const *c[]  = {"ok", "foo" "bar"};
// no warning
char const *d[]  = {"foo" "bar", "ok"};

但是当您在数组中有大量的初始化程序,并且只在其中几个位置上犯了一个打字错误时,这似乎是理想的。

这里是一个演示

GCC似乎没有相应的警告,但已经有一个请求要求添加它。

请注意,这仅适用于数组初始化。您的例子为

const char *x = "foo" " bar";

不会被此警告(或我所知的任何其他警告)检测到。

还要注意,启用此警告可能会产生很多误报,但在尝试捕捉错误时可以适度使用它。


4
还发现了一个针对clang-tidy的!https://clang.llvm.org/extra/clang-tidy/checks/bugprone/suspicious-missing-comma.html - gozag
我想知道为什么它在这里不起作用:https://godbolt.org/z/W4cevbenj - Marek R
6
GCC通常会在使用-Wtraditional(仅限C语言)时警告字符串连接,但启用该选项可能不可取。 - nielsen
1
有没有GCC标志可以检测字符串字面值的连接? - phuclv

18

不完全是这样。字符串字面值的连接是 C/C++ 语法中不可或缺的一部分,有许多用例。因此需要一定的努力,这可能会破坏捕捉错误的目标。

然而,字符串连接非常严格地作用于两个字符串字面值,在它们之间只有空格时才能正常工作,因此打破空格将导致错误。例如,在这种情况下,您可以这样编写:

const *char[] = {("foo") ("bar")};  // Error

这会导致错误,而预期的语句则不会:

const *char[] = {("foo"), ("bar")};  // OK

简而言之,你无法通过某种方式明确告诉编译器两个字符串字面量可以连接,并在所有其他情况下失败,因此你必须明确告诉编译器何时一个字符串字面量可能被连接。

12
“...告诉编译器字符串字面值不能被连接时…” 他们之间加一个,怎么样?我感觉我们已经走了一圈。 - Richard Critten
4
@RichardCritten 是的,我的主要观点是,我认为原帖中寻找的解决方案并不存在于C/C++编译器服务中。 - nielsen
2
我认为大多数“必不可少”的用途涉及将字符串字面值与宏连接起来,例如在printf格式字符串中使用的宏。你真正需要连接仅仅是字符串字面值的情况比较罕见。 - Barmar
6
我经常连接裸字符串字面量,而不涉及任何宏,只是因为我喜欢换行代码。将每个单独的字符串/元素包装在括号中的建议解决方案很有趣。尽管我有相当丰富的编写和阅读C和C++代码的经验,但我不会立即知道该语法是否有效。它当然是有意义的,但它也让我感到可疑。我倾向于在代码审查中标记这一点。虽然这很有趣,也可能是解决所提问题的一种变通方法,但正如Richard所建议的那样,它使我们完全回到了原点。 - Cody Gray
3
@CodyGray: 我起初发现括号令人惊讶,但是很快就想通了它为什么有效:一个字符串字面值是 const char* 类型的对象(在 C 中可能是 char*,我忘记了)。 括号可以出现在表达式中,并计算封装的子表达式。初始化器列表需要一系列 const char* 表达式。我确实需要思考几秒钟,但是如果代码库各处都使用这种写法,我会习惯的。因此,更大的问题是阅读代码时是否应该为此额外的语法噪音而感到不适,是否比可能出现的问题更糟糕。 - Peter Cordes
显示剩余3条评论

2

以下任何一个宏都可以防止意外连接两个字符串。

C++(C预处理器)宏通常非常棒。在元素列表的末尾添加逗号也是合法的。

你可以像这样做:

#define STRINGCOMMA(a) a,

const char *x[] = {
    STRINGCOMMA("foo")
    STRINGCOMMA("bar")
};

甚至可以这样:

#define QUOTESTRINGCOMMA(a) #a,

const char *x[] = {
    QUOTESTRINGCOMMA(foo)
    QUOTESTRINGCOMMA(bar)};

逗号已经为您添加了,如果您不小心自己添加逗号将是非法的。

如果您感兴趣,还可以进一步将此概念扩展,以允许创建具有相同参数但不同处理的并行列表:

X宏

#define VARLIST \
  DO(foo) \
  DO(bar)

#define DO(a) #a,
  const char *x[] = {
VARLIST
};
#undef DO

如果您想从相同的名称列表中创建枚举列表和字符串列表,那么这将非常有用。


-2
我花了好几个小时在一个大的静态数组中找错误……

嗯,你可以这样做:

char const * a [] = 
    { "foo"
    , "bar"
    , "baz"
    , "asdf"
    , "ghjk"
    };


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