views:

619

answers:

6

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

+5  A: 

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.

AndreyT
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
See the addendum to my answer for this issue.
Matteo Italia
@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
+1  A: 

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.

jcordasc
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
+7  A: 

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
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
@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
@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
@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
@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
@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
+2  A: 
Matteo Italia
+1  A: 

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
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
+1  A: 

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.

bta
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
@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
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