惯用C表示双指针

时间:2022-03-21 10:56:04

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).

我知道,在C语言中,不能隐式地将char** *转换为const char** (c.f. C- faq,问题1,问题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.

为了清晰地调用我的函数,他们需要插入一个cast。

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

我来自一个以c++为主的背景,在这里,由于c++更深入的const规则,这不是什么大问题。

What is the idiomatic solution in C?

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)

    声明foo取一个char**,并记录它不会改变它的输入?这似乎有点恶心,尤其是,因为它会惩罚那些可能有一个const char**的用户,让他们想要传递它(现在他们不得不抛弃const-ness)。

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

    强制用户投射他们的输入,增加一致性。

  3. Something else?

    别的吗?

3 个解决方案

#1


6  

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.

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

#2


7  

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:

虽然你已经接受了一个答案,但我还是选择宏。你可以这样写函数的用户会写一个foo(x)其中x可以是合格的,也可以不合格。这个想法将会有一个宏CASTIT,它会执行cast并检查参数是否为有效类型,另一个是用户界面:

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.

CASTIT宏看起来有点复杂,但它所做的只是首先检查X[0]是否赋值与char const*兼容。它使用复合文字。然后隐藏在sizeof中,以确保不会创建复合文字,也不会通过测试来评估X。

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.

很多东西都可以用宏来实现。我最近编写了一个复杂的示例,该示例使用符合const条件的数组。

#3


2  

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

选项2。选项1有您提到过的缺点,并且不太安全。

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.

如果我看到一个使用char **参数的函数,并且我有一个char *const *或类似的,我将复制并通过它,以防万一。

#1


6  

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.

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

#2


7  

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:

虽然你已经接受了一个答案,但我还是选择宏。你可以这样写函数的用户会写一个foo(x)其中x可以是合格的,也可以不合格。这个想法将会有一个宏CASTIT,它会执行cast并检查参数是否为有效类型,另一个是用户界面:

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.

CASTIT宏看起来有点复杂,但它所做的只是首先检查X[0]是否赋值与char const*兼容。它使用复合文字。然后隐藏在sizeof中,以确保不会创建复合文字,也不会通过测试来评估X。

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.

很多东西都可以用宏来实现。我最近编写了一个复杂的示例,该示例使用符合const条件的数组。

#3


2  

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

选项2。选项1有您提到过的缺点,并且不太安全。

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.

如果我看到一个使用char **参数的函数,并且我有一个char *const *或类似的,我将复制并通过它,以防万一。

相关文章