tags:

views:

243

answers:

4

The following code warns about incompatible type. What is the proper way to solve this code?

thanks

typedef struct a_struct struct_type;

void f(const struct_type *const data[], unsigned n);

void test(unsigned n)
{
   struct_type **v;

   assert(n);
   v = malloc(n * sizeof(struct_type *));
   /* fill v with new struct_type (with a malloc per entry) */
   f(v, n); <- WARN on v
}
A: 

Edited based on Rampion's answer. The problem is with the double const in f()'s declaration.

Code with the warning:

struct_type ** v;
v = (struct_type **)malloc(10 * sizeof(struct_type *));      
f(v);

This compiles without warning:

const struct_type *const* v;
v = (const struct_type **)malloc(10 * sizeof(struct_type *));
f(v);
Andomar
A: 

f expects to get as input an array of pointers (const struct_type* []). You pass a pointer to a pointer of struct (const struct_type**).

The best thing to do, IMO, is to change the signature of f to:

void f(const struct_type *const* data);

Why do you need to pass arrays as arguments to functions?

Cătălin Pitiș
Because that's declared like that :) (it's easier in case of static declaration of data[] I guess)
elmarco
In C, "void** myvar" and "void* myvar[]" are one and the same.
Andomar
sorry, but this is really not a proper answer to the question :) -1 for the sake of sorting the answers
elmarco
Common... punishing people so you get the right sort order?
Andomar
@Andomar: That's structuring for some people. Sort of :). I just wonder how can I edit it so it will be put on the tail of the list when sorting on response time...
Cătălin Pitiș
+4  A: 

The reason the compiler is complaining is the first const in f's declaration.

Try using

void f(struct_type *const data[], unsigned n);
/*...*/
f( v, n );

and you won't get the same warning. Alternatively, you could cast v when you call f

void f(const struct_type *const data[], unsigned n);
/*...*/
f( (const struct_type * const *) v, n );

This is a little counterintuitive, but in C, you can't pass a pointer-to-pointer-to-nonconst for a pointer-to-pointer-to-const. They made a special exception to allow you to pass a pointer-to-nonconst for a pointer-to-const.

Here's a FAQ question "Why can't I pass a char ** to a function which expects a const char **?":

You can use a pointer-to-T (for any type T) where a pointer-to-const-T is expected. However, the rule (an explicit exception) which permits slight mismatches in qualified pointer types is not applied recursively, but only at the top level. (const char ** is pointer-to-pointer-to-const-char, and the exception therefore does not apply.)

The reason that you cannot assign a char ** value to a const char ** pointer is somewhat obscure. Given that the const qualifier exists at all, the compiler would like to help you keep your promises not to modify const values. That's why you can assign a char * to a const char *, but not the other way around: it's clearly safe to add const-ness to a simple pointer, but it would be dangerous to take it away. However, suppose you performed the following more complicated series of assignments:

const char c = 'x';  /* 1 */
char *p1;   /* 2 */
const char **p2 = &p1;  /* 3 */
*p2 = &c;   /* 4 */
*p1 = 'X';   /* 5 */

In line 3, we assign a char ** to a const char **. (The compiler should complain.) In line 4, we assign a const char * to a const char *; this is clearly legal. In line 5, we modify what a char * points to--this is supposed to be legal. However, p1 ends up pointing to c, which is const. This came about in line 4, because *p2 was really p1. This was set up in line 3, which is an assignment of a form that is disallowed, and this is exactly why line 3 is disallowed.

Assigning a char ** to a const char ** (as in line 3, and in the original question) is not immediately dangerous. But it sets up a situation in which p2's promise--that the ultimately-pointed-to value won't be modified--cannot be kept.

(C++ has more complicated rules for assigning const-qualified pointers which let you make more kinds of assignments without incurring warnings, but still protect against inadvertent attempts to modify const values. C++ would still not allow assigning a char ** to a const char **, but it would let you get away with assigning a char ** to a const char * const *.)

In C, if you must assign or pass pointers which have qualifier mismatches at other than the first level of indirection, you must use explicit casts (e.g. (const char **) in this case), although as always, the need for such a cast may indicate a deeper problem which the cast doesn't really fix.

References: ISO Sec. 6.1.2.6, Sec. 6.3.16.1, Sec. 6.5.3 H&S Sec. 7.9.1 pp. 221-2

rampion
That's it, cool
Andomar
A: 

See if this would work for you:

f(struct_type *data);

void test(unsigned n)
{
    struct_type *v = malloc(n * sizeof(struct_type *));
    f(v);
}

Please let me know how you get on.

Artur
unfortunately, it wont help, because I need to modify it when filling the structs.
elmarco
You can still modify it when filling the struct. I recently was struggling with very similar problem and my code looked like:void clearScrorer(SCORER *scorerArray) { scorerArray->name = NULL; scorerArray->score = -1;}And it worked without any problems. Remember that by doing f(v) you pass the reference to v, so if you modify v inside of f function v elsewhere will be modified as well.
Artur