4

Consider this C code:

void foo(char *);

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

When I compile that with -pedantic -Wall -Wextra with GCC or Clang, or with -Weverything with Clang, it compiles without giving me any relevant warnings. If I add -Wwrite-strings, then GCC gives me this:

<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 *);
      |          ^~~~~~

And clang gives me this:

<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 *);
               ^

Lots of things seem wrong here to me:

  • 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's output refers to -Wdiscarded-qualifiers, but if I pass that instead of -Wwrite-strings, I don't get that warning.
  • I thought -Weverything meant "literally every single warning the compiler knows about", but this seems to contradict that.
  • 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.

What's going on here? Why does this particular warning seem so buggy?

  • 1
    History and preferences seem likely, just as “-Wall” isn’t “all warnings”. – user2864740 Apr 25 '20 at 18:52
  • Compiling with `clang-cl` under Visual Studio 2019, with "Enable All Warnings" set *does* give the warning: **ISO C++11 does not allow conversion from string literal to 'char *' [-Wwritable-strings]** (as well as **no previous prototype for function 'bar' [-Wmissing-prototypes]**). – Adrian Mole Apr 25 '20 at 18:54
  • What does “Enable all Warnings” in VS *actually* translate to the compiler invocation? Seems to be including -Wwritable-strings.. each tool layer is separate. – user2864740 Apr 25 '20 at 18:55
  • 1
    @user2864740 I'm not sure - trying to figure out how to get the command line displayed. For the native MSVC compiler, it adds the `/Wall` switch. Also, MSVC gives an **error** with `/permissive-` (conformance mode) but not even a warning without it. – Adrian Mole Apr 25 '20 at 18:58
  • 1
    @user2864740 Well, according to the build log, the only "-W" switches passed to clang are `-Weverything` and `-Wno-error`. – Adrian Mole Apr 25 '20 at 19:10
  • 2
    @adrianmole in Pascal it will not compile at all. but how is it relevant to the question? C++ is not C – 0___________ Apr 25 '20 at 19:19
  • @P__J__ My previous comments were all based on compilations with C++ compilers! Where did you get Pascal (and C) from? – Adrian Mole Apr 25 '20 at 19:21
  • @AdrianMole My question is about C. – Joseph Sible-Reinstate Monica Apr 25 '20 at 19:21
  • @JosephSible-ReinstateMonica Ah, OK! Gotcha. – Adrian Mole Apr 25 '20 at 19:22
  • @AdrianMole But good catch about C++: I just added a point about it to my list in the question. – Joseph Sible-Reinstate Monica Apr 25 '20 at 19:25
  • 2
    The docs are clear what `-Wwrite-strings` do. There is no "write-strings warning", rather the warning you get is expected. You question seems to be targeted specifically to gcc developers. `why there's a difference between the languages here` I do not understand this question. C is not C++, the languages differ. There is the difference, because they are different languages. – KamilCuk Apr 25 '20 at 19:27
  • @AdrianMole instead of Pascal you can put there any language - Ada, Python, PHP ..... It will be same relevant. I – 0___________ Apr 25 '20 at 20:31
  • @P__J__ Yeah - I got the point, eventually. – Adrian Mole Apr 25 '20 at 20:46

2 Answers2

7

In C, string literals existed before const did. So C string literals were not const-qualified (although the results of attempting to write to them are not defined by the C standard). If string literals were made const-qualified, much old software would break due to type errors. The C committee has decided this change is not worthwhile.

The switch -Wwrite-strings is not really a warning switch, in spite of -W. It changes the language being compiled to a non-standard C in which string literals are const-qualified. (There is a bug report that it is a mistake for GCC to categorize this as a warning switch.) This explains why GCC shows a -Wdiscarded-qualifier when a string literal is assigned to a char *, and it also explains why -Wdiscarded-qualifier alone does not trigger these warnings—because without -Wwrite-strings, the string literal is not const-qualified, so no qualifier is being discarded by the assignment.

Presumably Clang’s -Weverything does not include -Wwrite-strings because, as noted above, it is not truly a warning option and because it changes the language to non-standard C.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • "The switch `-Wwrite-strings` is not really a warning switch, in spite of `-W`. It changes the language being compiled to a non-standard C in which string literals are const-qualified." Yep, that's the key. Those two sentences explain everything I've noticed. Thanks! – Joseph Sible-Reinstate Monica Apr 25 '20 at 20:11
  • By the way, re "In C, string literals existed before `const` did", was that K&R? Because `const` existed in C89, right? – Joseph Sible-Reinstate Monica Apr 25 '20 at 20:12
  • @JosephSible-ReinstateMonica: Right, `const` was not even a keyword in [K&R I](https://archive.org/details/TheCProgrammingLanguageFirstEdition/mode/2up) (see page 180), but in C89 it was. – Nate Eldredge Apr 25 '20 at 20:22
  • There's actually GCC problem report about this: [Bug 61579 - -Wwrite-strings does not behave as a warning option](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61579) – SamB Oct 21 '21 at 01:23
2

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.

Well, the authors of gcc disagree with you, and they have explained why in the manual:

-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.

This also explains your second question:

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

Because without -Wwrite-strings, the string literal merely has type char[], so no qualifier is being discarded.

(This also explains why the message mentions -Wdiscarded-qualifiers instead of -Wwrite-strings; the problem really is a discarded qualifier, and the warning code for that doesn't backtrack to tell you that the only reason there was a qualifier in the first place was because of a different option. That would be awfully complicated.)

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

Yes, that's what the clang manual claims that it does mean:

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.

Indeed, this has already been filed as a bug: https://bugs.llvm.org/show_bug.cgi?id=18801. Apparently -Weverything did enable this warning in clang 3.4 and earlier, but there was a regression, or else an intentional change which was not documented. Eric Postpischil's answer gives a plausible reason why it might have been intentional: it not only enables a warning but actually changes the language dialect.

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.

In C++ (apparently since C++11) the type of a string literal actually is defined by the language as being const char[], while in standard C it has always been merely a char [] that you must not write to. See What is the type of string literals in C and C++?

Nate Eldredge
  • 48,811
  • 6
  • 54
  • 82