【问题标题】:idiomatic C for const double-pointers用于 const 双指针的惯用 C
【发布时间】:2011-07-08 23:40:19
【问题描述】:

我知道在 C 中你不能隐式转换,例如,char**const char**(参见C-FaqSO question 1SO Question 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** 并希望传递它的用户(现在他们必须抛弃离开 const-ness)

  2. 强制用户投射他们的输入,添加 const-ness。

  3. 还有别的吗?

【问题讨论】:

  • 1) const 在 C 中是一个无用的编译器注解。
  • @Conrad:我不知道我是否会说没用,但它看起来确实有点...... gimpy
  • @jw:你可以把它扔掉,你可以把它扔到非常量的东西上……你可以让常量指针和非常量指针指向内存中的同一个对象——这是一个懦弱的编译器注释,只会阻止最愚蠢的意外使用。更棘手、更难检测的错误完全未被发现。
  • 为了完整起见,根据您的链接之一,请注意,在 C++ 中,您应该能够强制转换 "char **" "char const * const *" 因为它不会违反这一点错误,所以这可能是理论上正确的声明函数的方法,如果你真的需要它。显然,如果您使用的是严格的 C,那将无济于事,但如果您将 C 与 C++ 混合使用,或者使用可以将其作为扩展打开的编译器(如果有的话?),这可能是一个足够的选择。

标签: c constants idioms


【解决方案1】:

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

【讨论】:

    【解决方案2】:

    使用选项 2。选项 1 具有您提到的缺点并且类型安全性较低。

    如果我看到一个接受 char ** 参数的函数并且我有一个 char *const * 或类似的,我会复制并传递它,以防万一。

    【讨论】:

    • 很好的回应 - 我给了你们两个上船并掷硬币回答(:看起来没有灵丹妙药。
    【解决方案3】:

    虽然您已经接受了答案,但我想选择 3) 即宏。您可以以这样一种方式编写这些函数,即您的函数的用户将只编写一个调用foo(x);,其中 x 可以是const-qualified 或不。这个想法是有一个宏 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 中的示例所见,这可以准确地检测到错误情况。

    很多东西都可以通过宏来实现。我最近做了一个复杂的例子with const-qualified arrays

    【讨论】:

    • 这是一个 C 如何变得模糊的例子。后来的维护者将很难理解这里发生了什么。除非名称全部大写,否则我会假设 toto() 是一个函数调用,除非在使用它的任何地方都有注释。如果后来的维护者收到警告,则需要很长时间才能找出原因,因为编译器没有告诉您有关宏的信息。
    • @atlpeg:如果打扰你的是一个人应该使用所有大写字母,这没什么大不了的,如果你愿意,把它当作TOTO。其余的我明白你的意思,但我不完全同意。无法将char** 转换为char const*const* 显然是语言的缺陷。 C++ 做得更好。强迫使用解决方案 1 或 2 的用户引入大量强制转换是维护的噩梦并且非常危险,因为 C 现在只有一种类型的强制转换。因此,如果用户将类型从 char 更改为 int,则演员只会接受。
    • 绝对有趣。不确定我会使用它,但很有趣(:
    • 报告链接断开!此外,不幸的是,这有一个问题是您不能将toto 作为函数指针传递。
    • @Shahbaz,感谢您的报告,实际上似乎 doxygen 已经改变了他们的名字。对于指针,是的,正确的,你不能拥有一切。无论如何,与此同时,我们已经用 C11 获得了_Generic,所以现在我可能会用它来完成这项任务。
    猜你喜欢
    • 2020-03-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多