为什么 C 编译器很难获得 -Wwrite-strings 警告?

Why do C compilers make it so hard to get -Wwrite-strings warnings?

考虑这个 C 代码:

void foo(char *);

void bar(void) {
    foo("");
}

当我用 -pedantic -Wall -Wextra 和 GCC 或 Clang 编译它时,或者用 -Weverything 和 Clang 编译它时,它编译时没有给我任何相关警告。如果我添加 -Wwrite-strings,那么 GCC 会给我这个:

<source>:4:9: warning: passing argument 1 of 'foo' discards 'const' qualifier from pointer target type [-Wdiscarded-qualifiers]
    4 |     foo("");
      |         ^~
<source>:1:10: note: expected 'char *' but argument is of type 'const char *'
    1 | void foo(char *);
      |          ^~~~~~

clang 给我这个:

<source>:4:9: warning: passing 'const char [1]' to parameter of type 'char *' discards qualifiers [-Wincompatible-pointer-types-discards-qualifiers]
    foo("");
        ^~
<source>:1:16: note: passing argument to parameter here
void foo(char *);
               ^

我觉得这里有很多问题:

这是怎么回事?为什么这个特定的警告看起来如此错误?

This seems like a pretty important warning not only to be off by default, but also to be off even with most of the ways that people enable warnings in practice.

嗯,gcc 的作者不同意你的看法,他们在手册中解释了原因:

-Wwrite-strings

When compiling C, give string constants the type const char[length] so that copying the address of one into a non-const char * pointer produces a warning. These warnings help you find at compile time code that can try to write into a string constant, but only if you have been very careful about using const in declarations and prototypes. Otherwise, it is just a nuisance. This is why we did not make -Wall request these warnings.

这也解释了你的第二个问题:

GCC's output refers to -Wdiscarded-qualifiers, but if I pass that instead of -Wwrite-strings, I don't get that warning.

因为没有 -Wwrite-strings,字符串文字只有类型 char[],所以没有限定符被丢弃。

(这也解释了为什么消息提到 -Wdiscarded-qualifiers 而不是 -Wwrite-strings;问题实际上是一个被丢弃的限定符,并且警告代码不会回溯告诉你唯一的首先有限定符的原因是因为有不同的选项。那会非常复杂。)

I thought -Weverything meant "literally every single warning the compiler knows about", but this seems to contradict that.

是的,这就是 clang 手册声称的意思:

In addition to the traditional -W flags, one can enable all diagnostics by passing -Weverything. This works as expected with -Werror, and also includes the warnings from -pedantic.

事实上,这已经被记录为错误:https://bugs.llvm.org/show_bug.cgi?id=18801。显然 -Weverything 确实在 clang 3.4 及更早版本中启用了此警告,但存在回归,或者是未记录的有意更改。 Eric Postpischil 的回答给出了一个可能是故意的合理理由:它不仅会发出警告,而且实际上会改变语言方言。

If I compile the same code as C++ instead of as C, then both GCC and Clang give me the warning I want without needing any compiler flags at all. I'm not sure why there's a difference between the languages here, since AFAIK, both C and C++ have undefined behavior if you actually write to the string literal.

在 C++ 中(显然是从 C++11 开始),字符串文字的类型实际上被语言定义为 const char[],而在标准 C 中,它一直只是一个 char []你不能写信给。参见 What is the type of string literals in C and C++?

在 C 语言中,字符串文字在 const 之前就已存在。因此 C 字符串文字不是 const 限定的(尽管尝试写入它们的结果未由 C 标准定义)。如果字符串文字是 const 限定的,许多旧软件将由于类型错误而崩溃。 C 委员会已决定此更改不值得。

开关 -Wwrite-strings 并不是真正的警告开关,尽管 -W。它将正在编译的语言更改为非标准 C,其中字符串文字是 const 限定的。 (There is a bug report that it is a mistake for GCC to categorize this as a warning switch.) 这解释了为什么 GCC 在将字符串文字分配给 char * 时显示 -Wdiscarded-qualifier,并且还解释了为什么仅 -Wdiscarded-qualifier 不会触发这些警告——因为没有 -Wwrite-strings,字符串文字不是 const 限定的,因此赋值没有丢弃任何限定符。

大概 Clang 的 -Weverything 不包括 -Wwrite-strings,因为如上所述,它不是真正的警告选项,因为它将语言更改为非标准 C。