[comp.lang.c] Is this a valid ANSI program?

friedl@mtndew.Tustin.CA.US (Stephen J. Friedl) (06/26/91)

Hi folks,

     We're compiling our code on everything these days, and
lately a vendor is claiming that the following program is
*required* by ANSI to provoke a compiler warning:

	void foo(const char **xxx)
	{
		/* nothing */
	}

	main()
	{
	char	**p = 0;

		foo(p);
	}

The compiler claims that the argument /p/ to the function foo()
is incompatible with the prototype, and I just don't believe it.
The parameter to the foo function simply promises that it won't
modify the chars pointed to by the pointer-to-pointer passed as
an arg.  [yes, I know that "const char *const *xxx" would be an
even stronger statement: that fails the same way]

The same kind of problem is likely responsible for this code

	extern void *malloc();

	main()
	{
	const char **p;

		p = (char **)malloc(1);
	}

producing the same kind of warning in the assignment.

The vendor claims that the ANSI Standard requires these warnings.
While the assign-nonconst-to-const works for one level of pointer
defereferencing, they say that anything beyond the first level
must be identically qualified.  They said something like this was
a misfeature in the Standard (!).

I can imagine things being treated differently for qualifiers
applied to the base type and those applied to the derived types,
but I really can't find anything in my 7 Dec 1988 draft Standard
on this one.  Can anybody help me?  While this is by no means any
kind of proof, we've never seen this on any other ANSI compiler
before.

Replies via email if you don't mind, or to comp.lang.c if of
general interest.  I just had uunet turn my feed of this group,
so very fast posted responses may not make it to my machine.

     Steve "getting back into Usenet" Friedl

-- 
Steve Friedl, Resident Wizard  /  friedl@vsi.com  /  {uunet attmail}!vsi!friedl
V-Systems, the VSI*FAX people /  Santa Ana, CA  / +1 714 545-6442v 545-7653 fax

Two things for sure: death and faxes

ark@alice.att.com (Andrew Koenig) (06/27/91)

In article <609@mtndew.Tustin.CA.US> friedl@mtndew.Tustin.CA.US (Stephen J. Friedl) writes:

> 	void foo(const char **xxx) { }

> 	main()
> 	{
> 	char	**p = 0;

> 		foo(p);
> 	}

> The compiler claims that the argument /p/ to the function foo()
> is incompatible with the prototype, and I just don't believe it.

The compiler is correct: you cannot convert char ** to const char ** .
To see why, consider the following program fragment:

	main()
	{
		const char victim = '?';
		char *accomplice;
		const char **sneak = &accomplice;	/* */
		*sneak = &victim;
		*accomplice = '!';
	}

If char ** could be converted to const char **, the line with the
comment would be legal.  None of the other lines is the least bit
controversial; the declarations are surely beyond reproach,
the assignment to *sneak places the address of a "const char"
into a "const char *", and the assignment to *accomplice
assigns an "int" to a "char".  Thus allowing the commented line
would open a hole in the type system by allowing you to modify
a "const char" object without an explicit cast.
-- 
				--Andrew Koenig
				  ark@europa.att.com

datangua@watmath.waterloo.edu (David Tanguay) (06/27/91)

In article <609@mtndew.Tustin.CA.US> friedl@mtndew.Tustin.CA.US (Stephen J. Friedl) writes:
>	void foo(const char **xxx)
>	{
>		/* nothing */
>	}
>
>	main()
>	{
>	char	**p = 0;
>
>		foo(p);
>	}
>
>The compiler claims that the argument /p/ to the function foo()
>is incompatible with the prototype, and I just don't believe it.

I don't believe it, either. In 3.3.16.1, Simple Assignment, Constraints:
	both operands are pointers to qualified of unqualified versions
	of compatible types, and the type pointed to by the left has all
	the qualifications of the type pointed to by the right

Your code satisfies this constraint (the others don't pertain to the example).
If you remove one level of indirection, you have a very common situation,
with the way the library routines are declared.
E.g., int printf( const char *, ... )
-- 
David Tanguay                  datanguay@watmath.waterloo.edu
Thinkage, Ltd.                 dat@Thinkage.On.CA

datangua@watmath.waterloo.edu (David Tanguay) (06/27/91)

In article <20461@alice.att.com> ark@alice.UUCP () writes:
>The compiler is correct: you cannot convert char ** to const char ** .
>To see why, consider the following program fragment:
[...]
> Thus allowing the commented line
>would open a hole in the type system by allowing you to modify
>a "const char" object without an explicit cast.

const doesn't mean that the value won't change, it only means that you
can't change it through that handle. For example, const volatile int i is
a valid and meaningful declaration (e.g., a read-only clock).
The behaviour you want from const is similar to the noalias behaviour. 
-- 
David Tanguay                  datanguay@watmath.waterloo.edu
Thinkage, Ltd.                 dat@Thinkage.On.CA

torek@elf.ee.lbl.gov (Chris Torek) (06/27/91)

>In article <609@mtndew.Tustin.CA.US> friedl@mtndew.Tustin.CA.US
(Stephen J. Friedl) asks about the assignment (via function prototype)
of `char **p' to `const char **xxx'.  In essence, this is:

	char **p; const char **xxx; ... xxx = p;

>>The compiler claims that the argument /p/ to the function foo()
>>is incompatible with the prototype, and I just don't believe it.

In article <1991Jun26.232121.29755@watmath.waterloo.edu>
datangua@watmath.waterloo.edu (David Tanguay) writes:
>I don't believe it, either. In 3.3.16.1, Simple Assignment, Constraints:
>	both operands are pointers to qualified or unqualified versions
>	of compatible types, and the type pointed to by the left has all
>	the qualifications of the type pointed to by the right
>
>Your code satisfies this constraint (the others don't pertain to the example).

I believe this is false, so let us check this out.

The thing on the left is `xxx' which is `const char **' which is really
`pointer to (pointer to const char)'.  The thing on the right is
`char **' which is really `pointer to (pointer to char)'.  Thus, both
operands are pointers to something.  The one on the left is an unqualified
pointer.  The one on the right is an unqualified pointer.  Thus

	both operands are pointers

is satisfied.  (To see that both pointers are unqualified, read page
24 and note the rather vague definition of a `top type'.  Also read
the examples.)

Now check

	to qualified or unqualified versions of compatible types

The one on the left points to `const char *' or `pointer to const
char'.  The one on the right points to `char *' or `pointer to char'.
These are both unqualified types, each of which is a pointer.  So
now we have to find the definition of compatible types.  Page 24 says:

	Two types have compatible types if their types are the same.

These are not (because they differ in qualifiers), so we must follow
its cross references to sections 3.5.2, 3.5.3, and 3.5.4.  3.5.3 is
the only one relevant; it says:

	For two qualified types to be compatible, both shall have the
	identically qualified version of a compatible type; the order
	of type qualifiers within a list of specifiers or qualifiers
	does not affect the specified type.

We still have to go back to 3.1.2.5 to figure out exactly what type
these pointers have, but we can see from the preceding paragraph
that the types `const char' and `char' are not compatible types in
terms of section 3.1.2.6 and its references.  Since the pointer types
include their subtypes (by 3.1.2.5), we have to conclude that the
two types are incompatible.

In other words, the special provisions in 3.3.16.1 never come into
effect; we are stopped by the `compatible type' phrase.

>If you remove one level of indirection, you have a very common situation,
>with the way the library routines are declared.
>E.g., int printf( const char *, ... )

These are a different case.  Here we have, in effect,

	char *p; const char *fmt; ... fmt = p;

Thus we have on the left:

  fmt  =>  const char *  =>  pointer to const char

and on the right:

  p  =>  char *  =>  pointer to char

and now the special provisions do have effect:

>	both operands are pointers to qualified or unqualified versions
>	of compatible types, and the type pointed to by the left has all
>	the qualifications of the type pointed to by the right

Here both pointers point to `qualified or unqualified versions of
compatible types' (one points to a qualified char, one to an unqualified
char; char and char are the same type, hence are compatible types).
The one on the left is the qualified one, hence it has all zero
qualifiers of the one on the right.

Incidentally, I dislike all these Special Provisions; I would be just
as happy with a `const' that merely meant `ROMable' and was not
overloaded with `unassignable' semantics, i.e., was just a storage
class.  In such a language, the question of compatibility never
arises.  Either the object itself is readonly, or not.  If it is
readonly, the result of attempting an assignment is undefined or
implementation-defined.  But that is not what we have.
-- 
In-Real-Life: Chris Torek, Lawrence Berkeley Lab CSE/EE (+1 415 486 5427)
Berkeley, CA		Domain:	torek@ee.lbl.gov

worley@compass.com (Dale Worley) (06/27/91)

In article <1991Jun26.232121.29755@watmath.waterloo.edu> datangua@watmath.waterloo.edu (David Tanguay) writes:
   > [Essentially, assigning a (char **) value to a (const char **)
   > variable.]

   I don't believe it, either. In 3.3.16.1, Simple Assignment, Constraints:
	   both operands are pointers to qualified of unqualified
	   versions of compatible types, and the type pointed to by
	   the left has all the qualifications of the type pointed to
	   by the right

   Your code satisfies this constraint (the others don't pertain to
   the example).  If you remove one level of indirection, you have a
   very common situation, with the way the library routines are
   declared.  E.g., int printf( const char *, ... )

Strange as it may seem, that's not true.  If you assign A = (char *)
to B = (const char *), then A and B are pointers to qualified versions
of compatible types (char / const char).  However, in the case of A =
(char **) and B = (const char **), A is a pointer to (char *) and B is
a pointer to (const char *), which are *not* compatible. Remember,
(const char *) is not the const-qualified version of (char *), (char *
const) is.

Dale Worley		Compass, Inc.			worley@compass.com
--
It's getting so the only place you can find a Communist is in an
American university.

steve@taumet.com (Stephen Clamage) (06/27/91)

datangua@watmath.waterloo.edu (David Tanguay) writes:

|In article <609@mtndew.Tustin.CA.US> friedl@mtndew.Tustin.CA.US (Stephen J. Friedl) writes:
|>	void foo(const char **xxx) { }
|>	main()
|>	{
|>	char	**p = 0;
|>		foo(p);
|>	}
|>
|>The compiler claims that the argument /p/ to the function foo()
|>is incompatible with the prototype, and I just don't believe it.

|I don't believe it, either. In 3.3.16.1, Simple Assignment, Constraints:
|	both operands are pointers to qualified or unqualified versions
|	of compatible types, and the type pointed to by the left has all
|	the qualifications of the type pointed to by the right

Sorry, you better believe it.  The extract from the standard you quote
explains why!  The two pointers are 'const char **xxx' and 'char **p'.
Parameter xxx points to a 'const char*', and variable p points to
a 'char *'.  These types are not compatible, so the xxx and p are not
compatible.

-- 

Steve Clamage, TauMetric Corp, steve@taumet.com

datangua@watmath.waterloo.edu (David Tanguay) (06/28/91)

In article <14737@dog.ee.lbl.gov> torek@elf.ee.lbl.gov (Chris Torek) writes:
>datangua@watmath.waterloo.edu (David Tanguay) writes:
>>I don't believe it, either. In 3.3.16.1, Simple Assignment, Constraints:
>
>I believe this is false, so let us check this out.

I got this far into Chris's excellent article when I remembered this exact
same question arising in comp.std.c about 2 years ago. I furiously raced
through the standard (then) and, not surprisingly, came to the same conclusion
Chris and others have posted. *sigh* Where's me rock?
-- 
David Tanguay                  datanguay@watmath.waterloo.edu
Thinkage, Ltd.                 dat@Thinkage.On.CA