Bug 23087 - Misleading warning, "... differ in signedness" with the character types
Summary: Misleading warning, "... differ in signedness" with the character types
Status: NEW
Alias: None
Product: gcc
Classification: Unclassified
Component: c (show other bugs)
Version: 5.3.0
: P2 minor
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords: diagnostic, documentation, easyhack
: 71202 (view as bug list)
Depends on:
Blocks:
 
Reported: 2005-07-26 19:56 UTC by Keith Thompson
Modified: 2023-05-12 18:29 UTC (History)
13 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2008-03-31 11:03:55


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Keith Thompson 2005-07-26 19:56:19 UTC
% cat tmp.c
void foo(void)
{
    signed char   *ps = "signed?";
    unsigned char *pu = "unsigned?";
}
% gcc --version
gcc (GCC) 4.0.0
Copyright (C) 2005 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.

% gcc -c tmp.c
tmp.c: In function 'foo':
tmp.c:3: warning: pointer targets in initialization differ in signedness
tmp.c:4: warning: pointer targets in initialization differ in signedness


Plain char is either signed or unsigned, depending on the system configuration.
Both initializations call for warnings, since plain char is a distinct type
from both signed char and unsigned char, but in one of the two cases it
doesn't actually "differ in signedness".
Comment 1 Andrew Pinski 2005-07-26 20:03:17 UTC
I don't think the warning is misleading as strings are only ever "const char*"
Comment 2 Keith Thompson 2005-07-26 21:07:49 UTC
String literals in C are char*, not const char*, though writing to a
string literal invokes undefined behavior.  But that's not the point.

Assuming plain char is signed, the warning
    "pointer targets in initialization differ in signedness"
for
    signed char   *ps = "signed?";
is misleading because the pointer targets don't differ in signedness.
They do differ in type, so a warning is appropriate, but not that warning.
Comment 3 Paul Schlie 2005-07-26 23:58:33 UTC
(In reply to comment #2)
> String literals in C are char*, not const char*, though writing to a
> string literal invokes undefined behavior.  But that's not the point.

Actually as string literals are defined as specifying an array of character constants,
a "const char *" type seems most appropriate, which is why a write to a string literal
is undefined.

But agree that GCC should not generate a warning that the types differ in signness
when initializing a signed or unsigned char * with a string literal, as the types are
defined as being compatible; but should generate a warning that a "const char *" is
being assigned to a non-const "char *".
Comment 4 Keith Thompson 2005-07-27 01:54:37 UTC
Oh, I agree completely that making string literals const
(as they are in C++) would make more sense.  The reason they
aren't defined that way in C is that by the time "const" was
added to the language, there was too much code that would be
broken by the change.  For example, given
    void func(char *s);
the call
    func("hello");
would be a constraint violation if string literals were const.
(As it is, it's merely dangerous, causing undiagnosed undefined
behavior if func attempts to modify the string.)

But all this is beside the point.  I get the same warning
messages with this:

void foo(void)
{
    const signed char   *ps = "signed?";
    const unsigned char *pu = "unsigned?";
}

Plain char, unsigned char, and signed char are all compatible,
in the sense that you can use a value of any of the three
types to initialize an object of any of the three types
(possibly with an implicit conversion).

But because they are all distinct types, pointers to those types
are distinct and incompatible types.

The correct warning message should be
    "initialization from incompatible pointer type",
the same message produced for the initialization of lptr in
the following:

void bar(void)
{
    int i = 42;
    long l = i;         /* ok, implicit conversion */
    int *iptr = &i;
    long *lptr = iptr;  /* incompatible types */
}

even though int and long happen to have the same representation
on the system I'm using, just as char and signed char happen
to have the same representation.

The initializations of ps and pu in foo() are invalid,
but this has nothing to do with signedness or constness;
it's just because the types are incompatible.
Comment 5 Paul Schlie 2005-07-27 16:22:20 UTC
(In reply to comment #4)
> Oh, I agree completely that making string literals const
> (as they are in C++) would make more sense.  The reason they
> aren't defined that way in C is that by the time "const" was
> added to the language, there was too much code that would be
> broken by the change.  For example, given
>     void func(char *s);
> the call
>     func("hello");
> would be a constraint violation if string literals were const.

- yes, and would have been at least been made visable if a
   warning that a "const char *" was being assigned to a "char *"
   were arguably properly generated when passed as an argument.
   (although I may be blind,  I can find reference to the elements
   of a string literal being const char's, I can't find any specifying
   albeit this, a string literal is defined as a "char *", as opposed
   to more properly a "const char *"?)

> But all this is beside the point.  I get the same warning
> messages with this:
> 
> void foo(void)
> {
>     const signed char   *ps = "signed?";
>     const unsigned char *pu = "unsigned?";
> }
> 
> Plain char, unsigned char, and signed char are all compatible,
> in the sense that you can use a value of any of the three
> types to initialize an object of any of the three types
> (possibly with an implicit conversion).
> 
> But because they are all distinct types, pointers to those types
> are distinct and incompatible types.

- understood, but as the types pointed to are "compatible" is seem
  like a mistake to interpret thier respective pointers as being otherwise.
  (as their referenced object's values may be assigned freely to each other,
  so it would seem that arguably pointers to them should be treated similarly.
  Which would also be consistent with the standard's apparent aliasing guidline:

  6.3  Expressions

       [#7] An object shall have its stored value accessed only  by
       an lvalue expression that has one of the following types:59

          - a type  compatible  with  the  effective  type  of  the
            object,

          - a qualified version  of  a  type  compatible  with  the
            effective type of the object,

           - a  type  that  is  the   signed   or   unsigned   type
            corresponding to the effective type of the object,

           - a  type  that  is  the   signed   or   unsigned   type
            corresponding  to  a qualified version of the effective
       __________

       59. The   intent   of   this   list   is  to  specify  those
           circumstances in which an  object  may  or  may  not  be
           aliased.

  Which seems to imply stongly that compatible types which only
  differ in qualification and/or signness should be considred as being
  potentially aliased by such coresponding pointers, as they reference
  strinctly compaible types?

Comment 6 Keith Thompson 2005-07-27 19:34:53 UTC
I misused the term "compatible" above (and I think the standard itself
is sometimes a bit loose about the term). 

All references are to the C99 standard.  I think the C90 rules are the
same or very similar.

6.7.8p11:
    
    The initializer for a scalar shall be a single expression,
    optionally enclosed in braces. The initial value of the object
    is that of the expression (after conversion); the same type
    constraints and conversions as for simple assignment apply, 
    taking the type of the scalar to be the unqualified version of
    its declared type.

6.5.16.1p1 (Simple assignment):

    One of the following shall hold:
    [...]
    -- both operands are pointers to qualified or unqualified versions
       of compatible types, and the type pointed to by the left has 
       all the qualifiers of the type pointed to by the right;

6.7.5.1p2:

    For two pointer types to be compatible, both shall be identically 
    qualified and both shall be pointers to compatible types.
    
6.2.5p14:

    The type char, the signed and unsigned integer types, and the 
    floating types are collectively called the basic types. Even if
    the implementation defines two or more basic types to have the
    same representation, they are nevertheless different types. (34)

6.2.5p15:

    The three types char, signed char, and unsigned char are 
    collectively called the _character types_. The implementation shall
    define char to have the same range, representation, and behavior
    as either signed char or unsigned char. (35)
  
Footnote 35:

    CHAR_MIN, defined in <limits.h>, will have one of the values 
    0 or SCHAR_MIN, and this can be used to distinguish the two
    options. Irrespective of the choice made, char is a separate type
    from the other two and is not compatible with either.
    
For an initialization of a char object, there's an implicit conversion,
even though types char and signed char are not compatible.

    signed char sc = 'x';
    char c = sc;     /* implicit conversion */
    
There is no implicit conversion for pointer types other than void*:

    signed char *psc = &sc;
    char *pc = psc;   /* illegal, incompatible types */
Comment 7 Eric Botcazou 2005-10-09 17:47:04 UTC
But it's platform-independent.
Comment 8 Axel Andersson 2006-01-16 17:14:34 UTC
There's also the following issue, which seem related.

$ cat test.c
void nil_uch(unsigned char *uch) {
    *uch = 0;
}

void nil_sch(signed char *sch) {
    *sch = 0;
}

int main(void) {
    char ch = 0;

    nil_uch(&ch);
    nil_sch(&ch);

    return 0;
}

$ gcc --version
powerpc-apple-darwin8-gcc-4.0.1 (GCC) 4.0.1 (Apple Computer, Inc. build 5250)
Copyright (C) 2005 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.

$ gcc -o test test.c
test.c: In function 'main':
test.c:14: warning: pointer targets in passing argument 1 of 'nil_uch' differ in signedness
test.c:15: warning: pointer targets in passing argument 1 of 'nil_sch' differ in signedness

I'd expect the warning to be muted in one of the calls, depending on -f{un}signed-char.
Comment 9 Andrew Pinski 2008-03-30 20:42:07 UTC
>I'd expect the warning to be muted in one of the calls, depending on
-f{un}signed-char.

No, char is a seperate type from signed char and unsigned char so they are always incompatiable when it comes to pointers to them.

Closing as invalid.
Comment 10 Keith Thompson 2008-03-30 21:49:28 UTC
(In reply to comment #9)
> >I'd expect the warning to be muted in one of the calls, depending on
> -f{un}signed-char.
>
> No, char is a seperate type from signed char and unsigned char so they are
> always incompatiable when it comes to pointers to them.
>
> Closing as invalid.

Yes, they're incompatible -- but that's not what the warning says.

I'm now using gcc 4.1.3.  (Note that the warning doesn't appear without
"-pedantic".)  Here's the current behavior:
========================================
% cat tmp.c
void foo(void)
{
    signed char   *ps = "signed?";
    unsigned char *pu = "unsigned?";
}
% gcc --version
gcc (GCC) 4.1.3 20070929 (prerelease) (Ubuntu 4.1.2-16ubuntu2)
Copyright (C) 2006 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.

% gcc -c -pedantic tmp.c
tmp.c: In function `foo':
tmp.c:3: warning: pointer targets in initialization differ in signedness
tmp.c:4: warning: pointer targets in initialization differ in signedness
========================================

On my system plain char is signed, so for the initialization of ps,
the pointer targets *don't* differ in signedness.  A warning is
necessary; the warning that's currently printed is incorrect.

Here's what it should do:
========================================
[...]
% gcc -c -pedantic -c tmp.c
tmp.c: In function `foo':
tmp.c:3: warning: initialization from incompatible pointer type
tmp.c:4: warning: initialization from incompatible pointer type
========================================
Comment 11 Manuel López-Ibáñez 2008-03-31 11:03:54 UTC
Actually as a user I would find clearer a warning such:

warning: initialization of 'signed char *' from incompatible pointer type 'char *'

so CONFIRMED.
Comment 12 Jackie Rosen 2014-02-16 13:15:56 UTC Comment hidden (spam)
Comment 13 Keith Thompson 2016-01-06 02:02:00 UTC
This problem still exists in gcc 5.3.0.

Here's a perhaps clearer example that doesn't depend on string literals,
and that demonstrates the problem both when plain char is signed
and when it's unsigned.

$ cat tmp.c
#include <limits.h>
void foo(void) {
    char *pc = 0;
#if CHAR_MIN < 0
    /* plain char is signed but incompatible with signed char */
    signed char *psc = pc;
#else
    /* plain char is unsigned but incompatible with unsigned char */
    unsigned char *puc = pc;
#endif
}
$ gcc --version
gcc (GCC) 5.3.0
Copyright (C) 2015 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.

$ gcc -std=c11 -pedantic-errors -c tmp.c
tmp.c: In function 'foo':
tmp.c:6:24: error: pointer targets in initialization differ in signedness [-Wpointer-sign]
     signed char *psc = pc;
                        ^
$ gcc -std=c11 -pedantic-errors -c -funsigned-char tmp.c
tmp.c: In function 'foo':
tmp.c:9:26: error: pointer targets in initialization differ in signedness [-Wpointer-sign]
     unsigned char *puc = pc;
                          ^
$
Comment 14 Marek Polacek 2016-01-06 17:02:54 UTC
I think I might have a fix, but there's this thing about e.g.

void
foo (void)
{
  char *p = "";
  signed char *ps = "";
  unsigned char *pu = "";
}

with this issue fixed, we'd warn like this
$ xgcc z.c -c
z.c: In function ‘foo’:
z.c:5:21: warning: initialization from incompatible pointer type [-Wincompatible-pointer-types]
   signed char *ps = "";
                     ^~

z.c:6:23: warning: initialization from incompatible pointer type [-Wincompatible-pointer-types]
   unsigned char *pu = "";
                       ^~
whereas older gcc would be silent on that.  I.e., -Wincompatible-pointer-types is a warning that is enabled by default, but -Wpointer-sign is enabled by -Wpedantic || -Wall.  I.e., we'd be more verbose.  Maybe this is a non-issue as most of the people are using -Wall anyway.

In any case I agree that the message about signedness is wrong.
Comment 15 Bernd Schmidt 2016-01-09 14:02:45 UTC
Joseph, based on gcc-patches discussion, should we close this as invalid, or do you think there is a beneficial change that could be made?
Comment 16 jsm-csl@polyomino.org.uk 2016-01-11 22:44:29 UTC
Well, an additional

note: 'char' and 'signed char' are different types

(or similar in the unsigned case) could be added in the case where the 
types have the same representation, one is char and the other is signed or 
unsigned char.  Or the wording of the original warning could be refined in 
that case.  But I don't think the less-specific wording about incompatible 
types would be appropriate here, or that the warning should be enabled 
more widely than the general signedness case (if anything, this case is 
less serious than that one).
Comment 17 Keith Thompson 2016-01-12 01:09:59 UTC
I just took a quick look at the discussion on the gcc-patches mailing
list.

It's true that the standard doesn't classify plain "char" either as a
signed integer type or as an unsigned integer type.

But I think that 99% of users think of plain "char" as either signed
or unsigned, not some third kind of signedness. If plain "char" has the
same range as "signed char", saying that they "differ in signedness" is
just confusing.  (Even to me, and I read ISO language standards for fun.)

And really, the signedness is not the issue.  The issue is that they're
incompatible types.

I'll also note the documentation of the "-fsigned-char" option:

    Let the type 'char' be signed, like 'signed char'.

It's difficult to tell uses that plain char is not signed when gcc's
own documentation says it is.

I understand that you might not want to warn about assigning a "signed
char*" value to a "char* object in the same circumstances where you'd warn
about assigning an "int*" to a "long*".  (Both are constraint violations,
but that's another kettle of fish.)

Perhaps char* vs. signed char* or char* vs. unsigned char* needs to be
treated as a special case. But I still say that the existing warning is
misleading and at least needs to be rephrased.
Comment 18 Eric Gallager 2018-01-27 19:50:02 UTC
(In reply to Bernd Schmidt from comment #15)
> Joseph, based on gcc-patches discussion, should we close this as invalid, or
> do you think there is a beneficial change that could be made?

(In reply to Keith Thompson from comment #17)
> I just took a quick look at the discussion on the gcc-patches mailing
> list.
> 

Link to the gcc-patches archives where this discussion took place?
Comment 19 Eric Gallager 2018-04-27 16:01:50 UTC
(In reply to Keith Thompson from comment #17)
> 
> I'll also note the documentation of the "-fsigned-char" option:
> 
>     Let the type 'char' be signed, like 'signed char'.
> 
> It's difficult to tell uses that plain char is not signed when gcc's
> own documentation says it is.
> 

I guess this is a documentation issue, too, then
Comment 20 Andrew Pinski 2023-05-12 18:27:50 UTC
*** Bug 71202 has been marked as a duplicate of this bug. ***