const 双指针的惯用 C

发布于 2024-10-20 02:40:32 字数 1154 浏览 1 评论 0原文

我知道在 C 中你不能隐式转换,例如,将 char** 转换为 const char** (参见 C-常见问题解答所以问题 1所以问题 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 性)

  2. 强制用户进行转换他们的输入,添加常量。

  3. 还有什么吗?

I am aware that in C you can't implicitly convert, for instance, char** to const char** (c.f. C-Faq, SO question 1, SO Question 2).

On the other hand, if I see a function declared like so:

void foo(char** ppData);

I must assume the function may change the data passed in.
Therefore, if I am writing a function that will not change the data, it is better, in my opinion, to declare:

void foo(const char** ppData);

or even:

void foo(const char * const * ppData);

But that puts the users of the function in an awkward position.
They might have:

int main(int argc, char** argv)
{
    foo(argv); // Oh no, compiler error (or warning)
    ...
}

And in order to cleanly call my function, they would need to insert a cast.

I come from a mostly C++ background, where this is less of an issue due to C++'s more in-depth const rules.

What is the idiomatic solution in C?

  1. Declare foo as taking a char**, and just document the fact that it won't change its inputs? That seems a bit gross, esp. since it punishes users who might have a const char** that they want to pass it (now they have to cast away const-ness)

  2. Force users to cast their input, adding const-ness.

  3. Something else?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

柳若烟 2024-10-27 02:40:32

尽管您已经接受了答案,但我想选择 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< 编写了一个复杂的示例 /code>-限定数组

Although you already have accepted an answer, I'd like to go for 3) namely macros. You can write these in a way that the user of your function will just write a call foo(x); where x can be const-qualified or not. The idea would to have one macro CASTIT that does the cast and checks if the argument is of a valid type, and another that is the user interface:

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
}

The CASTIT macro looks a bit complicated, but all it does is to first check if X[0] is assignment compatible with char const*. It uses a compound literal for that. This then is hidden inside a sizeof to ensure that actually the compound literal is never created and also that X is not evaluated by that test.

Then follows a plain cast, but which by itself would be too dangerous.

As you can see by the examples in the main this exactly detects the erroneous cases.

A lot of that stuff is possible with macros. I recently cooked up a complicated example with const-qualified arrays.

妄断弥空 2024-10-27 02:40:32

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

2 is better than 1. 1 is pretty common though, since huge volumes of C code don't use const at all. So if you're writing new code for a new system, use 2. If you're writing maintenance code for an existing system where const is a rarity, use 1.

纵山崖 2024-10-27 02:40:32

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

如果我看到一个带有 char ** 参数的函数,并且我有一个 char *const * 或类似的参数,我会制作一个副本并传递它,只需以防万一。

Go with option 2. Option 1 has the disadvantage that you mentioned and is less type-safe.

If I saw a function that takes a char ** argument and I've got a char *const * or similar, I'd make a copy and pass that, just in case.

不回头走下去 2024-10-27 02:40:32

现代 (C11+) 方式使用 _Generic 来保留类型安全和函数指针:

#include <stdio.h>

void print_strings (const char *const strings[])
{
    for (; *strings; strings++)
        printf("%s%s", strings[0], strings[1] ? ", " : "");
    printf("\n");
}

#define print_strings(words) print_strings(_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:
    print_strings(words_1);
    print_strings(words_2);
    print_strings(words_3);
    print_strings(words_4);

    const int *const numbers[] = { (int[]){1, 2}, (int[]){3, 4}, NULL };
// type-checking is preserved; the following call will trigger a warning:
// "warning: incompatible pointer types
// expected ‘const char * const*’ but argument is of type ‘const int * const*’"
    print_strings(numbers);

// Since the macro is defined after the function's declaration and has the
// same name, we can also get a pointer to the function:
    void (*funcptr) (const char *const *) = print_strings;
    funcptr((const char *[]){ "abc", "def", NULL });

// Minor inconvenience:  compound literals must be surrounded by parentheses;
// otherwise, the commas would be interpreted as extraneous macro arguments:
    print_strings(((char *[]) { "compound", "literal" , NULL}));
}

Modern (C11+) way using _Generic to preserve type-safety and function pointers:

#include <stdio.h>

void print_strings (const char *const strings[])
{
    for (; *strings; strings++)
        printf("%s%s", strings[0], strings[1] ? ", " : "");
    printf("\n");
}

#define print_strings(words) print_strings(_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:
    print_strings(words_1);
    print_strings(words_2);
    print_strings(words_3);
    print_strings(words_4);

    const int *const numbers[] = { (int[]){1, 2}, (int[]){3, 4}, NULL };
// type-checking is preserved; the following call will trigger a warning:
// "warning: incompatible pointer types
// expected ‘const char * const*’ but argument is of type ‘const int * const*’"
    print_strings(numbers);

// Since the macro is defined after the function's declaration and has the
// same name, we can also get a pointer to the function:
    void (*funcptr) (const char *const *) = print_strings;
    funcptr((const char *[]){ "abc", "def", NULL });

// Minor inconvenience:  compound literals must be surrounded by parentheses;
// otherwise, the commas would be interpreted as extraneous macro arguments:
    print_strings(((char *[]) { "compound", "literal" , NULL}));
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文