I am using a library that in early versions has a function declaration
int foo(char **);
which later evolved (without changing the internals) into
int foo(const char **);
and I want to have my program compileable with both.
However, when I do a
int bar() {
char **c;
[…] // creation of c
return foo(c);
}
I get a compilation error with newer C compilers (gcc-14):
In function ‘bar’:
error: passing argument 1 of ‘foo’ from incompatible pointer type [-Wincompatible-pointer-types]
5 | return foo(c);
| ^
| |
| char **
note: expected ‘const char **’ but argument is of type ‘char **’
1 | int foo(const char **);
| ^~~~~~~~~~~~~
Using the const
modifier for c
would prevent compilation with the old version of the library, and would also need type casts that remove the const
modifier during the initialization of c
.
What would be the way to support both old and new versions of the library, and why does the error appear at all? I do not want to just switch off the error generation, but instead be C conform.
8
Why it isn’t working:
Passing something to a function in C is done “as if by assignment” – the rules of assignment apply. And among these rules is one that allows us to assign an object of type*
to a const type*
pointer. We may add const
implicitly, but not the other way around. This is an universal rule and pointers to pointers are no different – a pointer to pointer is still a “pointer to type” like anything else.
If the pointer to pointer happens to point at a const-qualified object then that pointed-at item, a const char*
, is a different type than a char*
, because pointers are only compatible if they have exactly the same qualifiers.
In your case you have a char**
which is a pointer to char*
. And you try to assign it to a const char**
which is a pointer to const char*
. So you don’t have the previously mentioned situation of assigning a type*
to a const type*
. Rather, you have type*
(pointer to char*
) and try to assign it to some_other_type*
(pointer to const char*
).
How to fix it:
Doing a quick and dirty cast to (const char**)
would work, but it’s neither pretty nor type safe. What you could do instead is to create a macro acting as function wrapper:
int foo (const char **);
#define foo(c) foo( _Generic((c),
char**: (const char**)c,
const char**: c) )
int bar() {
char **c;
return foo(c);
}
- The function is always using
const char**
now. - If the passed argument is of type
char**
and only then, it is cast toconst char**
. - If the passed argument is
const char**
as expected, all good, pass it along. - Otherwise if the passed argument is something else, there will be a compiler error.
Modify client code to make the const char **
types or casts as required by the current API. Then add a wrapper for the older versions of the library:
#include <Xaw/XawInit.h> // or whatever the path is
#if XawVersion < ...
#define foo(s) foo((char **) ((s)))
#endif
Delete the wrapper when you no longer want to support the older versions of the library.
This is the closest thing that works, that I could throw together.
Few notes:
- it breaks a lot of variable safety standards, by forcefully bringing both variables and function to matching argument types
- the current implementation of the macro declares a function pointer before calling it – in my short time i could not manage to do it without declaring a variable
- the macro usage is ugly
It at least compiles in gcc
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int foo0(char **var)
{
printf("foo0 var: %sn", *var);
return 0;
}
int foo1(const char **var)
{
printf("foo1 var: %sn", *var);
return 1;
}
typedef int (*f_foo)(const char **var);
#define CALL_FOO(_foo, _var)
f_foo _foo##_ptr = (f_foo)_foo;
_foo##_ptr((const char **)_var);
int main()
{
char **c;
c = malloc(100);
strcpy(*c, "dynamic string");
//foo0(c);
//foo1(c);
CALL_FOO(foo0, c);
CALL_FOO(foo1, c);
free(c);
return 0;
}
EDIT: you could declare the function pointers globally, then just call them trough the macro, or have a dedicated inline function
An option for dealing with this cleanly in C semantics is simply to use either a char **
or const char **
according to the type of foo
:
int bar(void)
{
union { char **c; const char **cc; } u;
#define c _Generic(foo, int (*)(char **): u.c, default: u.cc)
… // Assignment to c.
return foo(c);
#undef c
}
The reason automatic conversion of char **
to const char **
is not allowed is given here.