thing *const function parameters

N

Noob

Hello,

Consider the following program.

typedef int fun_t(int *const pTask, void *pContext);
typedef fun_t *tTaskCode;

int foo1(int *const pTask, void *const pParam) { return 1; }
int foo2(int *const pTask, void * pParam) { return 2; }

void zozo(void)
{
tTaskCode pTaskCode;
pTaskCode = foo1; /*** PROBLEM HERE ***/
pTaskCode = foo2;
}

gcc 4.3.2 seems happy with it.
$ gcc -O2 -std=c89 -Wall -Wextra -pedantic -Wno-unused -c mu2.c
/* NO OUTPUT */

but this other compiler rejects it.

$ cc -c mu2.c
E "mu2.c",L10/C15(#416):
foo1 Type `int(int *const pTask, void *const pParam)' ("mu2.c",L4/C5)
can't be converted to type `tTaskCode'
(= `int(*)(int *const pTask, void * pContext)') ("mu2.c",L2/C16).
1 user error No warnings

The 2nd param to fun_t is void *
while the 2nd param to foo1 is void *const

I assume these two types are incompatible?
Is there some constraint violation? (I assume yes.)
Is a conforming compiler allowed to refuse the code?
Is a conforming compiler required to refuse the code?

I'm generally confused about the usefulness of "thing *const p" function
parameters, which disallow code from modifying p (not the memory pointed
to by p). The "thing *const" types seem rather useless to me, as function
parameters. The function is getting a private copy of the pointer anyway.

void foob(char *const p)
{
/* p++ not allowed, use a temp var to copy p */
}

I'd drop the const, and increment p if I need to, instead of using a temp variable.

Or did I miss something important?

Regards.
 
T

Thomas Zimmermann

Hi
I assume these two types are incompatible? Is there some constraint
violation? (I assume yes.) Is a conforming compiler allowed to refuse
the code? Is a conforming compiler required to refuse the code?

Quoting "§6.5.16.1 Simple assignment" of the C99 standard

One of the following shall hold:93)

[...]

— 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;

[...]

The standard allows function pointers to implemented anyhow, so they do
not even have to be something that one would consider a pointer. And when
you convert a function pointer to another type and then call the result,
the behavior is undefined.

Thomas
 
J

James Kuyper

Noob said:
Hello,

Consider the following program.

typedef int fun_t(int *const pTask, void *pContext);
typedef fun_t *tTaskCode;

It's generally a bad idea to create typedefs of pointer types; the
special rules C has for pointers mean that code which uses such typedefs
will often be confusing.
int foo1(int *const pTask, void *const pParam) { return 1; }
int foo2(int *const pTask, void * pParam) { return 2; }

void zozo(void)
{
tTaskCode pTaskCode;
pTaskCode = foo1; /*** PROBLEM HERE ***/
pTaskCode = foo2;
}

gcc 4.3.2 seems happy with it.
$ gcc -O2 -std=c89 -Wall -Wextra -pedantic -Wno-unused -c mu2.c
/* NO OUTPUT */

but this other compiler rejects it.

$ cc -c mu2.c
E "mu2.c",L10/C15(#416):
foo1 Type `int(int *const pTask, void *const pParam)' ("mu2.c",L4/C5)
can't be converted to type `tTaskCode'
(= `int(*)(int *const pTask, void * pContext)') ("mu2.c",L2/C16).
1 user error No warnings

The 2nd param to fun_t is void *
while the 2nd param to foo1 is void *const

I assume these two types are incompatible?


According 6.7.5.3p15: "In the determination of type compatibility ...
each parameter declared with qualified type is taken as having the
unqualified version of its declared type". Therefore, const applied to
the variable itself should not matter. I can't see any reason why the
second compiler should reject it.
Is there some constraint violation? (I assume yes.)
Is a conforming compiler allowed to refuse the code?
Is a conforming compiler required to refuse the code?

I'm generally confused about the usefulness of "thing *const p" function
parameters, which disallow code from modifying p (not the memory pointed
to by p). The "thing *const" types seem rather useless to me, as function
parameters. The function is getting a private copy of the pointer anyway.

Declaring that a pointer parameter points at a const-qualified type is
far more useful than declaring that the pointer itself is const.
However, it's not completely useless.

Function parameters are local variables. Declaring them const is
something you might want to do for the same reason that you might want
to declare any other local variable const. If you know that your code
should not modify the value of the parameter, declaring it const will
make a diagnostic message mandatory if you make the mistake of trying to
change it.
void foob(char *const p)
{
/* p++ not allowed, use a temp var to copy p */
}

I'd drop the const, and increment p if I need to, instead of using a temp variable.

I'm in perfect agreement with you there; this is NOT an example of a
situation where the 'const' would be appropriate. However, jacob navia
has recently devoted an entire thread to his opinion that modifying any
parameter's value is a horrible mistake, so apparently this is at least
slightly controversial.

However, what if we had

char *foob1(char *const p)

and a requirement that foob1 return the value of p (just like strcpy()
does)? In that case you need to save the value of p somewhere, so why
not save it in p? In that case, using 'const' will help protect against
accidentally modifying that value.
 
N

Noob

James said:
It's generally a bad idea to create typedefs of pointer types; the
special rules C has for pointers mean that code which uses such typedefs
will often be confusing.

I agree with you.

I didn't write the original code, I just snipped it into a small example, in
order to elicit a specific error message from the compiler.
According 6.7.5.3p15: "In the determination of type compatibility ...
each parameter declared with qualified type is taken as having the
unqualified version of its declared type". Therefore, const applied to
the variable itself should not matter. I can't see any reason why the
second compiler should reject it.

IIUC, you think it is a compiler bug in cc?

I've written a simpler test case.

typedef int fun_t(int *p);

int foo1( int * p) { return *p; }
int foo2(const int * p) { return *p; }
int foo3( int *const p) { return *p; }
int foo4(const int *const p) { return *p; }

void zozo(void)
{
fun_t *fp;
fp = foo1;
fp = foo2; /* GCC WARNS */
fp = foo3;
fp = foo4; /* GCC WARNS */
}

$ gcc -std=c89 -pedantic -Wall -Wextra -O2 -c mu2.c
mu2.c: In function 'zozo':
mu2.c:12: warning: assignment from incompatible pointer type
mu2.c:14: warning: assignment from incompatible pointer type

gcc 4.3.2 only warns for foo2 and foo4, AFAIU because (const int *) is not
compatible with (int *).

I'll try asking on GCC's mailing list what the devs think.
(Is this an appropriate question for comp.std.c?)

Regards.
 
B

Ben Bacarisse

Noob said:
Consider the following program.

typedef int fun_t(int *const pTask, void *pContext);
typedef fun_t *tTaskCode;

int foo1(int *const pTask, void *const pParam) { return 1; }
int foo2(int *const pTask, void * pParam) { return 2; }

void zozo(void)
{
tTaskCode pTaskCode;
pTaskCode = foo1; /*** PROBLEM HERE ***/
pTaskCode = foo2;
}

gcc 4.3.2 seems happy with it.
$ gcc -O2 -std=c89 -Wall -Wextra -pedantic -Wno-unused -c mu2.c
/* NO OUTPUT */

but this other compiler rejects it.

$ cc -c mu2.c
E "mu2.c",L10/C15(#416):
foo1 Type `int(int *const pTask, void *const pParam)' ("mu2.c",L4/C5)
can't be converted to type `tTaskCode'
(= `int(*)(int *const pTask, void * pContext)') ("mu2.c",L2/C16).
1 user error No warnings

The 2nd param to fun_t is void *
while the 2nd param to foo1 is void *const

I assume these two types are incompatible?
No.

Is there some constraint violation? (I assume yes.)
No.

Is a conforming compiler allowed to refuse the code?
No.

Is a conforming compiler required to refuse the code?

No.

As already posted, assignment between pointers permits there to be
more qualifiers in the pointed-to type on the left than on the right,
but that does not apply -- both types are pointers to unqualified
types (each one a function type).

So, the question is: are the types pointed to compatible? We need to
read 6.2.7 which directs us to the rules about declarators: 6.7.5.
Here in 6.7.5.3 (function types) paragraph 15 we find:

15 For two function types to be compatible, both shall specify
compatible return types.

OK so far.

Moreover, the parameter type lists, if both are present, shall
agree in the number of parameters and in use of the ellipsis
terminator; corresponding parameters shall have compatible
types.

So the corresponding parameter types must be compatible. void * and
void * const are not compatible types so it looks like your suspicion
is right. However, after some stuff that does not apply, we read:

(In the determination of type compatibility and of a composite
type, each parameter declared with function or array type is
taken as having the adjusted type and each parameter declared
with qualified type is taken as having the unqualified version of
its declared type.)

so the qualifiers are ignored. The type are compatible and there is
no reason for a compiler to complain (though it is permitted to
complain about anything so long as it accepts the code).

The quotes are from C99 (PDF draft) but C90 has very similar wording.
I'm generally confused about the usefulness of "thing *const p"
function parameters, which disallow code from modifying p (not the
memory pointed to by p). The "thing *const" types seem rather
useless to me, as function parameters. The function is getting a
private copy of the pointer anyway.

Yes. I would never write the const in a function declaration even if
for some reason I decided to have in the definition (I don't do that
either but there is a shadow of a reason to do so).

<snip>
 
B

Ben Bacarisse

Noob said:
IIUC, you think it is a compiler bug in cc?

It's only a proper "bug" if the translation fails --s ome compilers
continue even after an error. Obviously it is a quality issue to
complain about something that is not wrong but the translation unit
should be accepted.
I've written a simpler test case.

typedef int fun_t(int *p);

int foo1( int * p) { return *p; }
int foo2(const int * p) { return *p; }
int foo3( int *const p) { return *p; }
int foo4(const int *const p) { return *p; }

void zozo(void)
{
fun_t *fp;
fp = foo1;
fp = foo2; /* GCC WARNS */
fp = foo3;
fp = foo4; /* GCC WARNS */
}

$ gcc -std=c89 -pedantic -Wall -Wextra -O2 -c mu2.c
mu2.c: In function 'zozo':
mu2.c:12: warning: assignment from incompatible pointer type
mu2.c:14: warning: assignment from incompatible pointer type

gcc 4.3.2 only warns for foo2 and foo4, AFAIU because (const int *) is not
compatible with (int *).

Looks fine to me.
I'll try asking on GCC's mailing list what the devs think.

I am not sure that would help. Do you think that gcc is at fault?
(Is this an appropriate question for comp.std.c?)

I think comp.std.c is for discussion of the standard (the text itself)
so post there if you think the text is ambiguous or could be
clarified, but it seems clear to me. I'd probably not put such a key
clause in parentheses but that is quite of a detailed complaint (don't
let that put you off -- the editors of standards like details!).
 
T

Thomas Zimmermann

Hi
(In the determination of type compatibility and of a composite
type, each parameter declared with function or array type is taken
as having the adjusted type and each parameter declared with
qualified type is taken as having the unqualified version of its
declared type.)

so the qualifiers are ignored. The type are compatible and there is no
reason for a compiler to complain (though it is permitted to complain
about anything so long as it accepts the code).

Interesting. Do you know why the qualifiers are ignored in this case?
Doesn't this conflict with the rule that function-pointer types cannot
reliably be converted among each other and still be callable?

Thomas
 
J

jameskuyper

Thomas said:
Hi


Interesting. Do you know why the qualifiers are ignored in this case?
Doesn't this conflict with the rule that function-pointer types cannot
reliably be converted among each other and still be callable?

No, that rule says: "If a converted pointer is used to call a function
whose type is not compatible with the pointed-to type, the behavior is
undefined.". The two types involved here are compatible, so that rule
doesn't apply.
 
T

Thomas Zimmermann

No, that rule says: "If a converted pointer is used to call a function
whose type is not compatible with the pointed-to type, the behavior is
undefined.". The two types involved here are compatible, so that rule
doesn't apply.

Thanks.
 
N

Noob

Ben said:
So, the question is: are the types pointed to compatible? We need to
read 6.2.7 which directs us to the rules about declarators: 6.7.5.
Here in 6.7.5.3 (function types) paragraph 15 we find:

15 For two function types to be compatible, both shall specify
compatible return types.

OK so far.

Moreover, the parameter type lists, if both are present, shall
agree in the number of parameters and in use of the ellipsis
terminator; corresponding parameters shall have compatible
types.

So the corresponding parameter types must be compatible. void * and
void * const are not compatible types so it looks like your suspicion
is right. However, after some stuff that does not apply, we read:

(In the determination of type compatibility and of a composite
type, each parameter declared with function or array type is
taken as having the adjusted type and each parameter declared
with qualified type is taken as having the unqualified version of
its declared type.)

so the qualifiers are ignored. The type are compatible and there is
no reason for a compiler to complain (though it is permitted to
complain about anything so long as it accepts the code).

I'm still confused. Consider the following code.

typedef int fun_t(int *p);
int foo2(const int *p) { return *p; }
void zozo(void)
{
fun_t *fp;
fp = foo2; /* GCC WARNS */
}

If "each parameter declared with qualified type is taken as having the
unqualified version of its declared type" then the above code should have
well-defined behavior, right?

fp points to a function which expects an (int *)
while foo2 expects a (const int *)
Is the "unqualified version of [foo2's parameter's] declared type" (int *) ?

Regards.
 
B

Ben Bacarisse

Noob said:
Ben said:
So, the question is: are the types pointed to compatible? We need to
read 6.2.7 which directs us to the rules about declarators: 6.7.5.
Here in 6.7.5.3 (function types) paragraph 15 we find:

15 For two function types to be compatible, both shall specify
compatible return types.

OK so far.

Moreover, the parameter type lists, if both are present, shall
agree in the number of parameters and in use of the ellipsis
terminator; corresponding parameters shall have compatible
types.

So the corresponding parameter types must be compatible. void * and
void * const are not compatible types so it looks like your suspicion
is right. However, after some stuff that does not apply, we read:

(In the determination of type compatibility and of a composite
type, each parameter declared with function or array type is
taken as having the adjusted type and each parameter declared
with qualified type is taken as having the unqualified version of
its declared type.)

so the qualifiers are ignored. The type are compatible and there is
no reason for a compiler to complain (though it is permitted to
complain about anything so long as it accepts the code).

I'm still confused. Consider the following code.

typedef int fun_t(int *p);
int foo2(const int *p) { return *p; }
void zozo(void)
{
fun_t *fp;
fp = foo2; /* GCC WARNS */
}

If "each parameter declared with qualified type is taken as having the
unqualified version of its declared type" then the above code should have
well-defined behavior, right?

fp points to a function which expects an (int *)
while foo2 expects a (const int *)
Is the "unqualified version of [foo2's parameter's] declared type"
(int *) ?

No. const int * is not qualified type. It is a plain old pointer
type. It happens to be a type that refers to a qualified type (const
int) but that does not make it qualified itself.

Gcc is correct to warn you about this. If the const had applied to p,
rather than to the thing p points to, then gcc would not have said
anything. I.e. the functions declared like this:

int f1(int *p);
int f2(int *const p);

can both be assigned to you fp variable. The parameter p is const
qualified and its unqualified type is int *. Had you tried with

int f3(const int *const p);

then gcc would again warn you of the incompatibility. The unqualified
version of the type of p in now const int * which is not compatible
with int *.
 
J

jameskuyper

Noob said:
Ben Bacarisse wrote: ....

I'm still confused. Consider the following code.

typedef int fun_t(int *p);
int foo2(const int *p) { return *p; }
void zozo(void)
{
fun_t *fp;
fp = foo2; /* GCC WARNS */
}

If "each parameter declared with qualified type is taken as having the
unqualified version of its declared type" then the above code should have
well-defined behavior, right?

The relevant rule only applies if the parameter's type is qualified.
In this case, the 'const' qualifies the ype of the object pointed at
by p, so that rule doesn't apply. If your code had said "int * const
p", then there would have been no problem, because in that case the
const applies to 'p', and NOT to the thing that p points at.
 
N

Noob

jameskuyper said:
The relevant rule only applies if the parameter's type is qualified.
In this case, the 'const' qualifies the type of the object pointed at
by p, so that rule doesn't apply. If your code had said "int * const
p", then there would have been no problem, because in that case the
const applies to 'p', and NOT to the thing that p points at.

James, Ben,

Thank you very much for your detailed answers.

I now understand :)

Regards.
 
D

David Thompson

No, that rule says: "If a converted pointer is used to call a function
whose type is not compatible with the pointed-to type, the behavior is
undefined.". The two types involved here are compatible, so that rule
doesn't apply.

That's the correct formal answer; but the underlying reason is that
arguments are passed (to parameters) (as-if) by rvalue, and qualifiers
don't have meaning for rvalues. I.e.

int x = 42 and const int y = 42 are different as lvalues, but:

int a; a = x+1 and a = y+1 both get 43 the same way;

void foo (int b) { } ... foo(x) ... foo(y)
both get passed the exact same 42. And

void f2 (const int c) { } ... f2(x) ... f2(y)
both get 42, but _within the function only_ treat c as const.
 

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,954
Messages
2,570,116
Members
46,704
Latest member
BernadineF

Latest Threads

Top