7

I'm working in a C, and C++ program. We used to be compiling without the make-strings-writable option. But that was getting a bunch of warnings, so I turned it off.

Then I got a whole bunch of errors of the form "Cannot convert const char* to char* in argmuent 3 of function foo". So, I went through and made a whole lot of changes to fix those.

However, today, the program CRASHED because the literal "" was getting passed into a function that was expecting a char*, and was setting the 0th character to 0. It wasn't doing anything bad, just trying to edit a constant, and crashing.

My question is, why wasn't that a compiler error?

In case it matters, this was on a mac compiled with gcc-4.0.

EDIT: added code:

char * host = FindArgDefault("EMailLinkHost", "");
stripCRLF(linkHost, '\n');

where:

char *FindArgDefault(char *argName, char *defVal) 
{// simplified
    char * val = defVal;
    return(val);
}

and

void stripCRLF(char *str, char delim)
{
    char *p, *q;

    for (p = q = str; *p; ++p) {
        if (*p == 0xd || *p == 0xa) {
            if (p[1] == (*p ^ 7)) ++p;
            if (delim == -1) *p = delim;
            }
        *q++ = *p;
        }
    *q = 0;  // DIES HERE
}

This compiled and ran until it tried to set *q to 0...

EDIT 2:

Most people seem to be missing the point of my question. I know why char foo[] = "bar" works. I know why char * foo = "bar"; doesn't work.

My question is mostly with respect to passing parameters. One thing that occures to me is "Is it possible that this is a C vs C++ issue?" because I have some .c files and some .cpp files, and it's quite possible that C allows it, but C++ doesn't... or vice versa...

Brian Postow
  • 11,709
  • 17
  • 81
  • 125
  • Do you have example code we can look at? – Sagar May 03 '10 at 19:05
  • 5
    The rule of thumb is that you should treat string literals always as const char *. Treating them as char * exposes you to these kind of problems. – Matteo Italia May 03 '10 at 19:09
  • @matteo: of course. My question is why did the compiler catch SOME of them, but not all... – Brian Postow May 03 '10 at 20:17
  • 1
    The behavior of the code that you added is perfectly logical. Yes, this should compile and run until you attempt to modify something. However, as I understood it, you main issue was that the compiler detected some errors and failed to detect some other errors. This in one of the "failed" ones. Now show us a successfuly "detected" one. – AnT stands with Russia May 03 '10 at 20:44
  • You've got three functions there. One calls a function to return what turns out to be a pointer to a string literal and assigns that to a `char *`, and then calls a function to modify that. You aren't making it easy for the compiler to find out what's going on. The lethal conversion is at the call to `FindArgDefault()`, and everything else is correct. There's lots of old code that's not const-correct, and it is permissible according to the standard to pass a string literal to a `char *` (although undefined behavior to attempt to change it). I think you're expecting too much from gcc. – David Thornley May 03 '10 at 21:04
  • "But that was getting a bunch of warnings, so I turned it off" Warnings are emitted for a reason. In general, getting rid of warnings by turning them off is not a good idea. – bta May 03 '10 at 21:17
  • @BTA you misunderstand. The warnings were of the form "writable strings are incompatible with [something or other]" So, to satisfy the warning, I turned off writable strings... I didn't turn off the warning. – Brian Postow May 03 '10 at 21:41
  • @Brian- Do you mean they are incompatible with another compiler option? Please post the exact error message you are seeing. In any case, the point of turning on writable strings is to generate those warnings; they help show you where your string usage is not `const`-safe. The proper solution would be to keep writable strings turned on until the string usage in your code was modified such that the "writable strings" option no longer produced warnings. – bta May 03 '10 at 21:48
  • @BTA, are you sure? It looked to me that the warning was that the writable strings option is ITSELF incompatible with the other option. When I turned it off, it made errors, not warnings. Note that "Writable strings" isn't a warning to turn off, it's a code generation option. the warning is: ld: warning: -fwritable-strings not compatible with literal CF/NSString in /Users/acordex/Documents/projects/SafeKeep/build/Safekeep.build/SafeKeep/Safekeep.build/Objects-normal/ppc/Hiar-3E34F3BE3033E97F.o – Brian Postow May 04 '10 at 13:53
  • @Brian- It appears that you are using the `CFSTR()` macro to create immutable `CFStringRef` objects from compile-time strings. That macro isn't compatible with the compiler option, since that would conflict with the `-fconstant-cfstrings` option sometimes required by the macro. Your original post didn't mention that, so I assumed that you were using only standard C strings. Given this info, you are correct about the compiler option. – bta May 04 '10 at 15:46

6 Answers6

8

The standard specifies a special rule allowing the literal-to-char* conversion which quietly drops const qualification. (4.2/2):

A string literal (2.13.4) that is not a wide string literal can be converted to an rvalue of type “pointer to char”; a wide string literal can be converted to an rvalue of type “pointer to wchar_t”. In either case, the result is a pointer to the first element of the array. This conversion is considered only when there is an explicit appropriate pointer target type, and not when there is a general need to convert from an lvalue to an rvalue. [Note: this conversion is deprecated. See Annex D. ]

The C++0x standard takes that deprecation further… this nonsense rule is removed entirely from the upcoming standard.

The const char* to char* error must be a result of converting a literal to a const char* first.

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • I'm not exactly sure what that quote means. I get compiler errors when I do: char* foo = "bar"; but not when I do char foo[] = "bar"; – Brian Postow May 03 '10 at 20:44
  • 1
    @Brian: `char * foo = "bar";` creates a pointer to non-const char, and points it at const char. `char foo[] = "bar";` creates a four-character array of char, initializes with 'b', 'a', 'r', '\0'. The statements do entirely different things. – David Thornley May 03 '10 at 20:56
  • @Brian: For me, using `g++ -V 4.0.1 -Wall -ansi` on OS X 10.6.3, `char *foo = "bar";` compiles at namespace or function scope without warnings. Using the default 4.2.1 produces a warning. – Potatoswatter May 03 '10 at 21:09
  • @david, yes exactly. @Potatoswatter, is it possible that this is a differencebetween gcc and g++? (ie, between C and C++) that actually might explain evrything... – Brian Postow May 03 '10 at 21:43
  • @Brian: If you try to run C++ code through GCC rather than G++, you should see link errors. If you are using XCode, I don't think it would allow you to make that mistake, nor any other respectable IDE. – Potatoswatter May 03 '10 at 22:21
  • @Potatoswatter of course not. But if I put C code through g++ it will work, but it will also work if I put it through gcc. I have some .c files, and some .cpp files. they might be compiled with different compilers... – Brian Postow May 04 '10 at 13:42
  • This statement seems a little contradictory to me, forgive me I'm a newb and I'm trying to undersand this. "`char * foo = "bar";` creates a pointer to non-const char, and points it at const char. " – SSH This Nov 26 '12 at 17:43
  • @SSHThis: `"bar"` is a string literal, which can easily turn into a `const char *`. (It's actually a `const char[]`.) The quote from the standard is what allows the compiler to convert to a (non-`const`) `char *` as well (pre C++11, anyway). But regardless of what the pointer (or the user thereof) does, *those* `char`s *are still* const, and any attempt to modify them could set your computer on fire if it wanted. The ability to cast to `char *` was only there for backwards compatibility with old, non-`const`-correct code, and shouldn't be used in new code. – cHao May 01 '13 at 18:26
6

Using a string literal to initialize a char * pointer in C++ is a deprecated feature, but nevertheless it is legal. It is not an error. It is your responsibility to make sure that no modification attempts are made through such a pointer.

In other words, you must be misunderstanding something about the compilation errors you got earlier. I don't think you ever got any errors for such an initialization/assignment. The "Cannot convert const char* to char*" errors you mention in your question must have been produced by something else.

Note, that the fact that you can initialize char * pointer with a string literal does not mean that you can use any arbitrary const char * value to initialize a char * pointer. This code

const char *pc = "A";
char *p = pc;

will produce an error, while this

char *p = "A";

will not. The aforementioned deprecated feature applies to string literals only, not for all const char * pointers.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • My issue isn't with variables, it's with arguments. When I declare a char* foo = "bar" it actualy complains. But when I declare char foo[] = "bar" it doesn't But I think that's an unrelated issue. – Brian Postow May 03 '10 at 19:33
  • See the addendum to my answer for this issue. – Matteo Italia May 03 '10 at 20:53
  • @Brian- The reason that `char foo[] = "bar"` doesn't give you an error is because the compiler treats the string as an array initializer. Instead of declaring a pointer to a constant string (copying the address), it declared an array and initialized it with the value "bar" (copying the data). The data in `foo` should be mutable in this case. This is a subtle nuance to the array/pointer duality. – bta May 03 '10 at 21:14
2

I agree completely with the other answers, I just want to add that g++ (at least version 4.4) actually catches these deprecated conversions as warnings at any warning level (if previous versions do not do this by default, probably you have to raise the warning level):

#include <iostream>

using namespace std;

void WithConst(const char * Str)
{
    cout<<Str<<endl;
}

void WithoutConst_NoEdit(char * Str)
{
    cout<<Str<<endl;
}

void WithoutConst_Edit(char * Str)
{
    *Str='a';
    cout<<Str<<endl;
}

int main()
{
    WithConst("Test");
    WithoutConst_NoEdit("Test");
    WithoutConst_Edit("Test");
    return 0;
}

 

matteo@teoubuntu:~/cpp/test$ g++ --version
g++ (Ubuntu 4.4.3-4ubuntu5) 4.4.3
Copyright (C) 2009 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
matteo@teoubuntu:~/cpp/test$ g++ -O3 lit_const_corr.cpp -o lit_const_corr.x
lit_const_corr.cpp: In function ‘int main()’:
lit_const_corr.cpp:24: warning: deprecated conversion from string constant to ‘char*’
lit_const_corr.cpp:25: warning: deprecated conversion from string constant to ‘char*’
matteo@teoubuntu:~/cpp/test$ g++ -O3 -Wall lit_const_corr.cpp -o lit_const_corr.x
lit_const_corr.cpp: In function ‘int main()’:
lit_const_corr.cpp:24: warning: deprecated conversion from string constant to ‘char*’
lit_const_corr.cpp:25: warning: deprecated conversion from string constant to ‘char*’
matteo@teoubuntu:~/cpp/test$ g++ -O3 -Wall -Wextra -ansi -pedantic lit_const_corr.cpp -o lit_const_corr.x
lit_const_corr.cpp: In function ‘int main()’:
lit_const_corr.cpp:24: warning: deprecated conversion from string constant to ‘char*’
lit_const_corr.cpp:25: warning: deprecated conversion from string constant to ‘char*’

Moreover, there's some interesting thing going on under the hood: if I compile it without optimization, it "just does what the code says", so it crashes, since it tries to write to a read-only memory location:

matteo@teoubuntu:~/cpp/test$ g++ -Wall -Wextra -ansi -pedantic lit_const_corr.cpp -o lit_const_corr.x
lit_const_corr.cpp: In function ‘int main()’:
lit_const_corr.cpp:24: warning: deprecated conversion from string constant to ‘char*’
lit_const_corr.cpp:25: warning: deprecated conversion from string constant to ‘char*’
matteo@teoubuntu:~/cpp/test$ ./lit_const_corr.x 
Test
Test
Segmentation fault

but, if you turn on the optimizer, there's no crash:

matteo@teoubuntu:~/cpp/test$ g++ -O3 -Wall -Wextra -ansi -pedantic lit_const_corr.cpp -o lit_const_corr.x
lit_const_corr.cpp: In function ‘int main()’:
lit_const_corr.cpp:24: warning: deprecated conversion from string constant to ‘char*’
lit_const_corr.cpp:25: warning: deprecated conversion from string constant to ‘char*’
matteo@teoubuntu:~/cpp/test$ ./lit_const_corr.x 
Test
Test
Test

I suppose that this is due to some magic optimization trick, but I don't understand why is it applied; any idea?


Addendum

When I declare a char* foo = "bar" it actualy complains. But when I declare char foo[] = "bar" it doesn't

Hey, be careful not to confuse the two things: with

char * foo = "bar";

you are declaring a pointer to char, and you assign to it the address of the literal "bar", which is actually stored in some read-only memory location (usually it's a part of the executable that is mapped in memory). Instead, with

char foo[]="bar";

you are declaring and allocating RW memory (on the stack or somewhere else, depending from the context) for an array of chars, which is initialized with the "bar" value, but it is not related to the string table at all, and it's perfectly legit to change that string.

Matteo Italia
  • 123,740
  • 17
  • 206
  • 299
1

It really depends how you "went through and made a whole lot of changes to fix those."

If you just downcast the string literal to a char* then you're telling the compiler not to catch that error. You need to make a copy if you're going to modify it. Otherwise declare your function interfaces to take a const so that the compiler can check these for you.

J. Cordasco
  • 836
  • 6
  • 15
  • Well, the issue is that this was a case where I *HADN'T* fixed it, because there was no error telling me to fix something. it was just "" being passed into a char* argument. – Brian Postow May 03 '10 at 19:36
1

To answer the question why this conversion is legal (although deprecated). Well there was a time, when there was no const keyword in the C language and people managed to produce a bit of code during that time. The designers of C++ must have figured out, that it wasn't a good idea to upset so many people, by breaking their code.

Maciej Hehl
  • 7,895
  • 1
  • 22
  • 23
  • Exactly. That's one reason why gcc has the `-Wwrite-strings` option but doesn't turn it on by default or by `-Wall` (it would cause lots of warnings on older or not-const-correct code). – bta May 06 '10 at 16:44
1

Since the stripCRLF function modifies a string in place but doesn't do anything with it or return any value, passing a string literal to it is essentially a no-op and should be considered a bug. You can either solve this by having the function modify and return a copy of the string, or by setting stricter warning flags to help detect when this happens.

If you want gcc to alert you of things like this, turn on the -Wwrite-strings compiler option. This will force the compiler to warn you if a string constant is converted to a non-constant char*. Also possibly useful is the -Wcast-qual option; this should emit a warning whenever a pointer is cast in a way that removes a type qualifier (in your case, the const got removed). If you want these messages to be made more strongly, use -Werror to turn all warnings into errors.

The other point of contention is the FindArgDefault function. As provided, the function signature should more accurately use const char* instead of char* for the return and parameter types. This should cause the compiler to complain when the return value is assigned to a char* (if the -Wcast-qual option is used). Since you didn't post the complete function, this might not be a valid change to make. If either string is modified inside the function then the corresponding parameter must remain a char*, but in that event passing a string literal as the argument should generate a compiler warning (use -Wwrite-strings).

By the way, your stripCRLF function is vulnerable to problems when a NULL pointer is passed in. Also, did you mean to say if (delim == -1), or should that be !=?

Edit: After seeing more information about the error messages the OP was getting, I removed parts of the original post that were off-topic and added some additional comments.

Edit2: I tested the following simplified version of your program:

char *FindArgDefault(char *argName, char *defVal) {
    char * val = defVal;
    return(val);
}

int main (void) {
    char * host = FindArgDefault("EMailLinkHost", "");
    return (int)(host);
}

When I compiled with gcc -Wall test.c -o test.o, I got zero compiler warnings or errors.

When I compiled with gcc -Wwrite-strings -Wall test.c -o test.o, I got

test.c: In function 'main':

test.c:10: warning: passing arg 1 of 'FindArgDefault' discards qualifiers from pointer target type

test.c:10: warning: passing arg 2 of 'FindArgDefault' discards qualifiers from pointer target type

I definitely think that the -Wwrite-strings compiler option is the one you want to enable to warn you about this sort of problem.

Community
  • 1
  • 1
bta
  • 43,959
  • 6
  • 69
  • 99
  • I didn't disable -Wwrite-strings. I disabled -fwritable-stings. p isn't a null pointer. p is a pointer to the null string. q is a char* not a char, so q = "", not '\0' . yes, the function IS vulnerable to a null ptr, but I think that we're pretty clear higher up that that isn't happening... Can't hurt to protect against it though, but that is NOT what's going on here... – Brian Postow May 04 '10 at 14:04
  • @Brian- You are correct. That's the last time I try to read a complex problem and post a solution while on a conference call, I promise. I have edited my answer to more accurately address the problem. – bta May 04 '10 at 16:31
  • I'm notexactly sure what delim does. It's not originally my code... and the missing code in findargdefault calls another function (in an if branch that isn't taken in the case where it crashes) and the return is not recessarily returned to a const, so changing those char* to consts is not a good idea... – Brian Postow May 04 '10 at 17:50