warning: deprecated conversion from string constant to ‘char*’

M

Michael

$ cat str.c
#include<stdio.h>

void func(char str[])
{
//do something here
}

int main(void)
{
func("test string");
return 0;
}
$ gcc -Wall str.c -o str
$ mv str.c str.cpp
$ g++ -Wall str.cpp -o str
str.cpp: In function ‘int main()’:
str.cpp:10: warning: deprecated conversion from string constant to ‘char*’
$ vim str.cpp
$ cat str.cpp
#include<stdio.h>

template<int t>void func(const char(&str)[t])
{
//do something here
}

int main(void)
{
func("test string");
return 0;
}
$ g++ -Wall str.cpp -o str
$

*Why the first code generates a compiler in c++ but not c?*
 
S

Salt_Peter

$ cat str.c
#include<stdio.h>

void func(char str[])
{
//do something here

}

int main(void)
{
func("test string");
return 0;}

$ gcc -Wall str.c -o str
$ mv str.c str.cpp
$ g++ -Wall str.cpp -o str
str.cpp: In function ‘int main()’:
str.cpp:10: warning: deprecated conversion from string constant to ‘char*’
$ vim str.cpp
$ cat str.cpp
#include<stdio.h>

template<int t>void func(const char(&str)[t])
{
//do something here

}

template< const std::size_t Size >
void func( const char(&str)[Size] )
{
// do stuff
}
int main(void)
{
func("test string");
return 0;}

$ g++ -Wall str.cpp -o str
$

*Why the first code generates a compiler in c++ but not c?*

No warning is required as far as i know. However, in C++ its you, not
the compiler, that needs to be aware of 'undefined behaviour'. The
string literal "test string" is a constant sequence of characters. So
func is actually a lie since...

void func(char str[])
{
str[0] = 'T';
}

is not allowed. On g++ that may or may not generate a seg fault.
If you write code that looks like a parameter is mutable, you'll find
it really hard to convince a paying client that it was indeed his
fault, its not - its your fault.

Its a safe strategy to declare parameters and member functions with a
constant modifier as the default. Finally, use std::string instead of
character arrays.
 
J

James Kanze

$ cat str.c
#include<stdio.h>
void func(char str[])
{
//do something here
}
int main(void)
{
func("test string");
return 0;
}
$ gcc -Wall str.c -o str
$ mv str.c str.cpp
$ g++ -Wall str.cpp -o str
str.cpp: In function ?int main()?:
str.cpp:10: warning: deprecated conversion from string constant to ?char*?
$ vim str.cpp
$ cat str.cpp
#include<stdio.h>
*Why the first code generates a compiler in c++ but not c?*

Because in C++, the type of a string literal is char const[],
with a special case deprecated conversion to char* for reasons
of backward compatibility, whereas in C, string literals have
type char[], Since it is still undefined behavior to try and
modify them, however, your function should probably take a char
const*, rather than a char*, even in C.
 
N

Nate Eldredge

James Kanze said:
$ cat str.c
#include<stdio.h>
void func(char str[])
{
//do something here
}
int main(void)
{
func("test string");
return 0;
}
$ gcc -Wall str.c -o str
$ mv str.c str.cpp
$ g++ -Wall str.cpp -o str
str.cpp: In function ?int main()?:
str.cpp:10: warning: deprecated conversion from string constant to ?char*?
$ vim str.cpp
$ cat str.cpp
#include<stdio.h>
*Why the first code generates a compiler in c++ but not c?*

Because in C++, the type of a string literal is char const[],
with a special case deprecated conversion to char* for reasons
of backward compatibility, whereas in C, string literals have
type char[]

I presume this is for historical reasons, or to support implementations
that do allow literals to be modified? It's annoying, because most
implementations these days don't, and so it would be convenient for them
if literals were of type 'const char[]' so that this code was more
obviously wrong.
, Since it is still undefined behavior to try and
modify them, however, your function should probably take a char
const*, rather than a char*, even in C.

Unless func() modifies the string passed to it, and you've inadvertently
passed it a literal.

FWIW, gcc will warn about this if given the -Wwrite-strings option.
 
J

James Kuyper

Nate said:
Because in C++, the type of a string literal is char const[],
with a special case deprecated conversion to char* for reasons
of backward compatibility, whereas in C, string literals have
type char[]

I presume this is for historical reasons

Yes. Specifically, it is one of many awkward features of C++ that was
intended to ease conversion of code from C to C++.
 
S

Stephen Sprunk

Nate said:
James Kanze said:
Because in C++, the type of a string literal is char const[],
with a special case deprecated conversion to char* for reasons
of backward compatibility, whereas in C, string literals have
type char[]

I presume this is for historical reasons, or to support implementations
that do allow literals to be modified? It's annoying, because most
implementations these days don't, and so it would be convenient for them
if literals were of type 'const char[]' so that this code was more
obviously wrong.

"const" doesn't mean the same thing in C that it means in C++. Changing
the type of literals in C wouldn't have the effect you're hoping for,
plus it would potentially break a lot of code.
Unless func() modifies the string passed to it, and you've inadvertently
passed it a literal.

FWIW, gcc will warn about this if given the -Wwrite-strings option.

It may not necessarily know, if the code is convoluted enough; the first
sign something is wrong could be a segfault or other similar UB.

S
 
P

peter koch

$ cat str.c
#include<stdio.h>

void func(char str[])
{
        //do something here

}

I wonder if you understood the function signature above, as you
apparently understood that the const was needed in your next example.
The function above is equivalent to void func(char* str); the
character array is not passed by value. This is the rule for all
arrays, but by "disguising" your signature you make that (a little)
less obvious.
The correct signature for avoiding the warning would be void func(char
const str[]) or (which I prefer) void func(char const* str).

[snip]
/Peter
 
I

Ian Collins

Stephen said:
Nate said:
James Kanze said:
Because in C++, the type of a string literal is char const[],
with a special case deprecated conversion to char* for reasons
of backward compatibility, whereas in C, string literals have
type char[]

I presume this is for historical reasons, or to support implementations
that do allow literals to be modified? It's annoying, because most
implementations these days don't, and so it would be convenient for them
if literals were of type 'const char[]' so that this code was more
obviously wrong.

"const" doesn't mean the same thing in C that it means in C++. Changing
the type of literals in C wouldn't have the effect you're hoping for,
plus it would potentially break a lot of code.
s/code/disasters waiting to happen/
 
J

James Kanze

James Kanze <[email protected]> writes:

[...]
Because in C++, the type of a string literal is char const[],
with a special case deprecated conversion to char* for reasons
of backward compatibility, whereas in C, string literals have
type char[]
I presume this is for historical reasons, or to support
implementations that do allow literals to be modified?

Historical reasons. Before const was introduced, string
literals had the type char[]. In K&R C, it was even guaranteed
that each one would be unique, and that they would be in
writable memory. C++ introduced const, which was later picked
up by the C committee, and added to C. Given that all existing
code at the time did contain things like:
char* p = "abc" ;
, it was felt necessary to support this. (On the other hand,
code actually modifying the literal was considered rare enough
that it was acceptable to break it. Even in C, modifying the
literal, despite its type, is undefined behavior.)

Even today, all the C++ compilers I know have an option to put
string literals in data space, each one unique, so that such old
code will work.
It's annoying, because most implementations these days don't,
and so it would be convenient for them if literals were of
type 'const char[]' so that this code was more obviously
wrong.

Literals in C++ are of type 'char const[]'. It's just a
deprecated conversion which allows for things like:
char* p = "abc" ;
And there are a number of cases in C++ where you can easily
detect that the type is char const[]. In C, the type is char[],
but in the absence of function overloading or template argument
deduction, I don't think there is any case where you could tell.

A compiler is free to warn about anything, so presumably, a C
compiler could warn here, just as easily as the C++ could. (In
practice, declaring the conversion deprecated was an invitation
on the part of the C++ committee for compilers to warn.)
 
J

James Kanze

Nate said:
James Kanze said:
Because in C++, the type of a string literal is char const[],
with a special case deprecated conversion to char* for reasons
of backward compatibility, whereas in C, string literals have
type char[]
I presume this is for historical reasons, or to support
implementations that do allow literals to be modified?  It's
annoying, because most implementations these days don't, and
so it would be convenient for them if literals were of type
'const char[]' so that this code was more obviously wrong.
"const" doesn't mean the same thing in C that it means in C++.

Yes it does. At the language level, the semantics of const are
exactly the same in the two languages, at least for all of the
common uses. (In C++, const also has implications with regards
to member functions, which obviously don't apply to C.)
Changing the type of literals in C wouldn't have the effect
you're hoping for, plus it would potentially break a lot of
code.

That could only be legacy code; modern C uses const pretty
rigorously, just like C++. Just take a look at any of the API's
(Posix, for example).
It may not necessarily know, if the code is convoluted enough;
the first sign something is wrong could be a segfault or other
similar UB.

Once you've lost the const in the type information, it's lost.
I suspect that what gcc warns against is anytime you assign a
string literal to a char*. (You can't assign a char const* to a
char* in either language, so if the string literal is always
used to initialize a char const*, you'll not have any problems.)
 
J

jameskuyper

James said:
Yes it does. At the language level, the semantics of const are
exactly the same in the two languages, at least for all of the
common uses.

The uncommon uses where there are differences include:
1. 'const' gives names with file scope, that are not explicitly
declared 'extern', internal linkage.
2. pointers to const types cannot be implicitly converted to void*.
3. const objects must be initialized.

However, of course none of those apply to string literals - the
deprecated implicit conversion in C++ from char* to const char*
prevents number 2 from applying.
That could only be legacy code; modern C uses const pretty
rigorously, just like C++. Just take a look at any of the API's
(Posix, for example).

The C standard library and the POSIX library are pretty good about
this kind of thing; other libraries may not be so good. I regularly
use three different libraries for most of my code, one of which never
uses 'const' in the declarations of any of the library functions, and
one of which has only one such declaration, despite the fact that both
libraries have numerous functions where it would have been a good idea
to insert a few 'const's. Both libraries have been updated a couple of
years more recently than the C standard was.
 
S

Stephen Sprunk

James said:
Nate said:
Because in C++, the type of a string literal is char const[],
with a special case deprecated conversion to char* for reasons
of backward compatibility, whereas in C, string literals have
type char[]
I presume this is for historical reasons, or to support
implementations that do allow literals to be modified? It's
annoying, because most implementations these days don't, and
so it would be convenient for them if literals were of type
'const char[]' so that this code was more obviously wrong.
"const" doesn't mean the same thing in C that it means in C++.

Yes it does. At the language level, the semantics of const are
exactly the same in the two languages, at least for all of the
common uses. (In C++, const also has implications with regards
to member functions, which obviously don't apply to C.)

In C++, "const" results in a compile-time constant expression; in C, it
does not. A const variable cannot be used as a case in a switch
statement in C, but it can in C++. A const variable cannot be used as
an array dimension in C (except for VLAs in C99), but it can in C++.

I've been burned by both of those differences in the past, and had to
change many "static const int" variables that worked fine in C++ back to
#defines to make things work in C as well. That can be problematic
since you lose type safety and the ability to take its address...

Note that I'd prefer that C adopt C++'s meaning for const, but so far
ISO hasn't been willing to do that.
That could only be legacy code; modern C uses const pretty
rigorously, just like C++. Just take a look at any of the API's
(Posix, for example).

Requiring an error (not a warning) for the simple case of

char *foo = "foo";

is enough to break the vast majority of programs I've seen.

Yes, it is more correctly const char *foo, and I've tried to make that
change at times -- but the const then infects other expressions, and you
end up spending half the afternoon tracking down all the consts you need
to add. While library authors are generally good about such things,
it's common for C application code to not contain a single "const" --
and if you add them, other coders will be cursing you for years as they
(incorrectly) take them back out to make things "work".

There's a reason that C++ allows that specific case of assigning a const
variable to a non-const one. C "solves" the problem by throwing in the
syntactic towel and considering a string literal to be mutable -- and
then forbidding you to modify it :)

S
 
J

James Kanze

The uncommon uses where there are differences include:
1. 'const' gives names with file scope, that are not explicitly
declared 'extern', internal linkage.

Forgot about that. Yes, that is an unfortunate difference, and
I'll never figure out why C++ has this inconsistency.
2. pointers to const types cannot be implicitly converted to
void*.

Where's the difference here. Neither language allows implicit
conversions of pointer to const to pointer to non-const,
regardless of the types involved. There is a difference not
concerning const: C allows implicit conversion of void* to T*
(where T is any other type), C++ doesn't. And there is a subtle
case involving pointers to pointers: C doesn't allow char** ->
char const* const*, whereas C++ does. (Neither allow char** ->
char const**, since this would break const.)
3. const objects must be initialized.

Granted. But is there any scenario where you wouldn't
initialize them in C?
 
G

Gennaro Prota

Stephen said:
Requiring an error (not a warning) for the simple case of

char *foo = "foo";

is enough to break the vast majority of programs I've seen.

Yes, it is more correctly const char *foo, and I've tried to make that
change at times -- but the const then infects other expressions

What about char foo[] = ... ?
 
J

James Kuyper

Note: I put this list together by searching for 'const' in Annex C of
the C++ standard.
Forgot about that. Yes, that is an unfortunate difference, and
I'll never figure out why C++ has this inconsistency.

As a result of that rule, such names qualify as constant expressions in
C++. In particular, if they refer to an object with integer type, they
qualify as integral constant expressions.

That would not be the case in C, where "constant expression" doesn't
include such identifiers.
Where's the difference here. Neither language allows implicit
conversions of pointer to const to pointer to non-const,
regardless of the types involved.

Annex C of the C++ standard says that this is a difference, which rather
surprised me at first. However, when I checked it out, I found that
section 6.3.2.3p1 of the C standard says that "A pointer to any
incomplete or object type may be converted to a pointer to void...",
without imposing any restrictions on the qualification of that pointer
type. Section 6.3.2.3p2 allows conversions that increase the
qualification of pointer types other than 'void*'; there's no
corresponding clause that allows conversions that decrease the
qualification for types other than 'void*'; but for void*, conversions
in both directions are allowed.

In C++, in contrast, there is no special exception for void*, and
section 4.4 only permits pointer conversions that add qualifiers.

I think that what you're thinking of are the constraints on implicit
conversions that occur during pointer assignments involving void*, which
require the the left operand have all of the qualifiers of the right
operand. The implicit conversions that occur when passing an argument to
a prototyped function, and when returning from a function, are also
subject to those same constraints, since they are both described as
occurring "as if by assignment".

However, pointer comparisons are not subject to qualification-based
constraints. In C the comparison of an unqualified void* with a
qualified pointer to an object or incomplete type involves the allowed
implicit conversion of the qualified pointer to void*.

Such a comparison is also allowed in C++, but it does not involve the
disallowed implicit conversion to void*. Instead, it is performed by
implicit conversion of the void* pointer to the type of the other
operand. In C, it wouldn't really matter whether the pointers were
converted to void* or to the other pointer's type. However, in C++, I
think that this distinction might determine whether
MyType::eek:perator(void*)() or MyType::MyType(void*) is called. If it
doesn't the whole issue is a moot point.

It might be better for C++ Annex C to describe this as a difference in
how pointer comparisons involving void* are carried out.
> There is a difference not
concerning const: C allows implicit conversion of void* to T*
(where T is any other type), C++ doesn't. And there is a subtle
case involving pointers to pointers: C doesn't allow char** ->
char const* const*, whereas C++ does. (Neither allow char** ->
char const**, since this would break const.)


Granted. But is there any scenario where you wouldn't
initialize them in C?

In order for such constants to be of any use, they would have to have
static storage duration, and would therefore be zero-initialized, which
would only make sense if zero-initialization would give them the desired
value. This has no obvious advantage over the integer literal 0 for
scalar types.

However, I've actually used precisely this technique to create a named
const static object of struct type, which I would copy into a
dynamically allocated structure to zero-initialize it. The struct
contained both pointer and floating point members, which might, in
principle, not have been correctly zero-initialized if I had used
calloc() or memset().
 
J

James Kuyper

Hendrik said:
Stephen said:
[...]
Yes, it is more correctly const char *foo, and I've tried to make that
change at times -- but the const then infects other expressions, and
you end up spending half the afternoon tracking down all the consts
you need to add. While library authors are generally good about such
things, it's common for C application code to not contain a single
"const" -- and if you add them, other coders will be cursing you for
years as they (incorrectly) take them back out to make things "work".

When I started on a new job about a decade ago, I got to a
point where I either had to cast away one of my 'const' or
introduce more of them into the (in-house) library I needed
to use. I took the long road. Each 'const' I added required
three others and I spent several days just adding 'const'
throughout the code why the others occasionally reminded me
that I had been hired to write new code or fix old bugs
instead of performing academic exercises. In the end, just
by having the compiler constantly bark at me, I had found
a few serious bugs where some code changed data which other
code considered 'const' and thus removed some crashes from
a couple of applications which used this lib.

I've followed a similar course, with similar results, on several
different occasions. It's a real pain, but I've found enough bugs every
time I've done this that I think it's worth the trouble.
 
D

Dik T. Winter

> However, pointer comparisons are not subject to qualification-based
> constraints. In C the comparison of an unqualified void* with a
> qualified pointer to an object or incomplete type involves the allowed
> implicit conversion of the qualified pointer to void*.
>
> Such a comparison is also allowed in C++, but it does not involve the
> disallowed implicit conversion to void*. Instead, it is performed by
> implicit conversion of the void* pointer to the type of the other
> operand. In C, it wouldn't really matter whether the pointers were
> converted to void* or to the other pointer's type.

I think this is false. When a void* pointer is converted to the other
pointer's type information may get lost. The question now is whether it
is possible that those two pointers point into the same object. And I
think it is possible.
 
J

James Kuyper

Dik said:
I think this is false. When a void* pointer is converted to the other
pointer's type information may get lost.

You've got a good point there, which I missed; if the void* points to a
location that is not correctly aligned for the other pointer's type, the
behavior is undefined.
 
S

Stephen Sprunk

James said:
As a result of that rule, such names qualify as constant expressions in
C++. In particular, if they refer to an object with integer type, they
qualify as integral constant expressions.

That would not be the case in C, where "constant expression" doesn't
include such identifiers.

Why does "const" give file-scope variables internal linkage in C++, and
why is that required for the variable to be considered a constant
expression? Or, put another way, why shouldn't an extern const int
(with an initializer) be a constant expression? Why doesn't C treat it
the same way?

(This difference between C and C++ has caused me headaches, so I want to
understand the rationale...)

S
 
J

jameskuyper

Stephen said:
Why does "const" give file-scope variables internal linkage in C++, and

Are you looking for a citation of the relevant rule, or the reason for
the existence of that rule? The rule itself is C++ section 7.1.1p7.
Note: the comparison with C that occurs in Annex C says "file scope",
which is a C-specific term. Section 7.1.1p7 actually says "namespace
scope", which is the closest C++ equivalent to "file scope".

My explanation was incorrect, but it was related to the correct
explanation. Giving such identifiers internal linkage is not required
in order for them to qualify as constant expressions. All that's
actually required is that the identifier be initialized with a
constant expression. However, declaration of an object with an
initializer makes it a definition. You are only allowed one definition
of any object with external linkage in your entire program. If the
purpose of an identifier is to be used as a constant expression, it
should be initialized in every translation unit that uses it that way;
if it occurs in more than one translation unit, that requires internal
linkage. I believe that this rule about 'const' was adopted to make it
easier to define such named constants.
why is that required for the variable to be considered a constant
expression? Or, put another way, why shouldn't an extern const int
(with an initializer) be a constant expression? ...

It would be a constant expression - I explained the requirement
incorrectly. However, it would be a constant expression only in the
single translation unit in which it was initialized. It wouldn't be a
constant expression in any other translation unit.
... Why doesn't C treat it
the same way?

That I can't answer. I would expect that requiring compilers to allow
such identifiers to be used as constant expressions would complicate
the compiler somewhat, but I'm not sure whether that's the main reason
why it wasn't done.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,982
Messages
2,570,185
Members
46,736
Latest member
AdolphBig6

Latest Threads

Top