开发者

idiomatic C for const double-pointers

开发者 https://www.devze.com 2023-02-15 08:19 出处:网络
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).

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 idiomat开发者_开发技巧ic 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?


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.


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.


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.


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

// 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 *const *)(words),\
    char *const * : (const char *const *)(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;
}
0

精彩评论

暂无评论...
验证码 换一张
取 消