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