我知道在 C 中你不能隐式转换,例如,
char**
到 const char**
(参见 C-Faq、SO 问题 1、SO 问题 2)。
另一方面,如果我看到这样声明的函数:
void foo(char** ppData);
我必须假设该函数可能会更改传入的数据。 因此,如果我正在编写一个不会更改数据的函数,那么在我看来,最好声明:
void foo(const char** ppData);
甚至:
void foo(const char * const * ppData);
但这让该功能的用户处于尴尬的境地。 他们可能有:
int main(int argc, char** argv)
{
foo(argv); // Oh no, compiler error (or warning)
...
}
为了干净地调用我的函数,他们需要插入强制转换。
我的背景主要是 C++,由于 C++ 更深入的 const 规则,这不是什么问题。
C 中惯用的解决方案是什么?
char**
,并仅记录它不会更改其输入的事实?这看起来有点恶心,尤其是。因为它会惩罚那些可能想要传递
const char**
的用户(现在他们必须放弃away const-ness)
foo(x);
,其中 x 可以是
const
限定的,也可以不是。这个想法是有一个宏
CASTIT
进行转换并检查参数是否为有效类型,另一个宏是用户界面:
void totoFunc(char const*const* x);
#define CASTIT(T, X) ( \
(void)sizeof((T const*){ (X)[0] }), \
(T const*const*)(X) \
)
#define toto(X) totoFunc(CASTIT(char, X))
int main(void) {
char * * a0 = 0;
char const* * b0 = 0;
char *const* c0 = 0;
char const*const* d0 = 0;
int * * a1 = 0;
int const* * b1 = 0;
int *const* c1 = 0;
int const*const* d1 = 0;
toto(a0);
toto(b0);
toto(c0);
toto(d0);
toto(a1); // warning: initialization from incompatible pointer type
toto(b1); // warning: initialization from incompatible pointer type
toto(c1); // warning: initialization from incompatible pointer type
toto(d1); // warning: initialization from incompatible pointer type
}
CASTIT
宏看起来有点复杂,但它所做的只是首先检查
X[0]
是否与
char const*
兼容。它使用复合文字来实现这一点。然后将其隐藏在
sizeof
内,以确保实际上永远不会创建复合文字,并且该测试不会评估
X
。然后是简单的演员阵容,但这本身就太危险了。
正如您通过
main
中的示例所看到的,这准确地检测到了错误的情况。很多事情都可以通过宏来实现。我最近编写了一个复杂的示例
,带有 const
限定数组。
如果我看到一个带有
char **
参数的函数,并且我有一个
char *const *
或类似的参数,我会复制一份并传递它,以防万一。
_Generic
来保留类型安全和函数指针的现代 (C11+) 方式:
// joins an array of words into a new string;
// mutates neither *words nor **words
char *join_words (const char *const words[])
{
// ...
}
#define join_words(words) join_words(_Generic((words),\
char ** : (const char **)(words),\
char *const * : (const char **)(words),\
default : (words)\
))
// usage :
int main (void)
{
const char *const words_1[] = {"foo", "bar", NULL};
char *const words_2[] = {"foo", "bar", NULL};
const char *words_3[] = {"foo", "bar", NULL};
char *words_4[] = {"foo", "bar", NULL};
// none of the calls generate warnings:
join_words(words_1);
join_words(words_2);
join_words(words_3);
join_words(words_4);
// type-checking is preserved:
const int *const numbers[] = { (int[]){1, 2}, (int[]){3, 4}, NULL };
join_words(numbers);
// warning: incompatible pointer types passing
// 'const int *const [2]' to parameter of type 'const char *const *'
// since the macro is defined after the function's declaration and has the same name,
// we can also get a pointer to the function
char *(*funcptr) (const char *const *) = join_words;
}