简体   繁体   中英

Why is passing a string literal into a char* argument only sometimes a compiler error?

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

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.

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.

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.

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.

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM