常量双指针的惯用 C

问题描述 投票:0回答:4

我知道在 C 中你不能隐式转换,例如,

char**
const char**
(参见 C-FaqSO 问题 1SO 问题 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 中惯用的解决方案是什么?

  1. 将 foo 声明为采用

    char**

    ,并仅记录它不会更改其输入的事实?这看起来有点恶心,尤其是。因为它会惩罚那些可能想要传递 
    const char**
     的用户(现在他们必须放弃 
    away const-ness)

  2. 强制用户强制输入,添加常量性。

  3. 还有什么吗?

c constants idioms
4个回答
10
投票
虽然你已经接受了答案,但我想选择 3) 即宏。您可以以这样的方式编写这些函数,即函数的用户只需编写一个调用

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

 限定数组


7
投票
2 比 1 好。不过 1 很常见,因为大量的 C 代码根本不使用 const。因此,如果您正在为新系统编写新代码,请使用 2。如果您正在为很少使用 const 的现有系统编写维护代码,请使用 1。


2
投票
选择选项 2。选项 1 具有您提到的缺点,并且类型安全性较差。

如果我看到一个带有

char **

 参数的函数,并且我有一个 
char *const *
 或类似的参数,我会复制一份并传递它,以防万一。


1
投票
使用

_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; }
    
© www.soinside.com 2019 - 2024. All rights reserved.