【问题标题】:Read-only Pointer to Pointer指向指针的只读指针
【发布时间】:2018-07-14 17:02:20
【问题描述】:

在 C 中,const char *p 有时称为“只读”指针:指向常量对象的指针(在本例中为 char)。

好像

  1. const char **p
  2. const char *const *p

将是指向指针的只读指针的等效声明,具体取决于how many levels of indirection are immutable

但是,编译器(gcc、clang)会生成警告。

我的问题:如何将指向指针(如char **p)的指针作为“只读”指针传递给函数而不产生警告?如果需要显式转换,为什么是 char **p 而不是 char *p

更多详情

这是我正在努力实现的具体示例。

只读指针

此代码将char *ptr 视为只读指针。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void readonly(const char *ptr)
{
    // ... do something with ptr ...

    // but modifying the object it points to is forbidden
    // *ptr = 'j';  // error: read-only variable is not assignable
}

int main(void)
{
    char *ptr =  malloc(12*sizeof(char));
    strcpy(ptr, "hello world");

    printf("before: %s\n", ptr);
    readonly(ptr);
    printf("after: %s\n", ptr);

    free(ptr);
    return 0;
}

在函数调用中添加了const 限定符,没有任何抱怨。

指向指针的只读指针

我希望使用指向指针的指针应该可以进行类似的函数调用。

void readonly(const char *const *ptr)
{
    //  ... do something with ptr ...

    // but modifying the object it points to is forbidden
    // **ptr = 'j';
}

int main(void)
{
    char **ptr;

    ptr = (char **) malloc(2*sizeof(char *));

    ptr[0] = malloc(14*sizeof(char));
    strcpy(ptr[0], "hello world 0");

    ptr[1] = malloc(14*sizeof(char));
    strcpy(ptr[1], "hello world 1");

    printf("before: %s %s\n", ptr[0], ptr[1]);
    readonly(ptr);
    printf("after: %s %s\n", ptr[0], ptr[1]);

    free(ptr[1]);
    free(ptr[0]);
    free(ptr);
    return 0;
}

clang 编译器(6.0.0 版)给出了最易读的警告。

warning: passing 'char **' to parameter of type
    'const char *const *' discards qualifiers in nested pointer types
    [-Wincompatible-pointer-types-discards-qualifiers]
    readonly(ptr);
         ^~~
note: passing argument to parameter 'ptr' here
    void readonly(const char *const *ptr)

但是 gcc (8.1.1) 也会给出警告。

Aside:当我尝试添加限定符时,clang 说传递char ** 丢弃限定符似乎很奇怪?

问题

  1. 如何将指向指针(如char **p)的指针作为“只读”指针传递给函数而不产生警告?

  2. 如果需要显式转换,为什么是 char **p 而不是 char *p

【问题讨论】:

  • 指向指针的等效只读指针是char *const *ptr
  • "... 有时被称为“只读”指针:..." - 不是由懂语言的人所称。指针是读/写的。你完全被误导了。看看 cdecl 并检查你的教科书。
  • @melpomene 不是。指向指针的 const 指针是 const char ** const pointer。指针星的计数方向相反 - 请参阅我的答案和示例
  • @PeterJ_01 我不是在谈论指向指针的 const 指针。请参阅问题的第一句话:我们正在谈论“指向常量对象的指针”,即指向 char 的 const 指针的指针。 “常量对象”是指向 char 的指针。
  • @melpomene 但您声明指向 const 指针的指针,而不是指向指针的 const 指针

标签: c pointers


【解决方案1】:

您的函数readonly(const char *ptr) 承诺它不会触及指针后面的内容。
这就是调用者可以信任地传递指向 const 内存区域的指针(传递 const char *ptr)的原因。
当然,将非常量 char *ptr 传递给此函数也没有问题,因为您的函数承诺比需要的安全性更高。这就是为什么编译器会在没有任何警告或注释的情况下遵循您的意愿。

但是,这种自动性仅适用于第一个间接级别。

The Standard 在 6.5.16.1 中声明一个赋值,“两个操作数都是指向兼容类型的合格或非合格版本的指针,左边指向的类型具有所有 右边指向的类型的限定符"
这句话的最后一部分意味着给指向的类型添加一个限定符是没有问题的。
第一部分声称“兼容”类型。并且(我认为,)6.7.3 (11) 确实为合格类型描述了这一点:“对于两个兼容的合格类型,两者都应具有兼容类型的相同合格版本。

读到这里,你指向的类型不被认为是兼容的(即使可以将一个分配给另一个)。

因此我会说关于丢弃限定符的clang警告有点误导,但它指的是非相同限定的指向类型。

【讨论】:

    【解决方案2】:

    您可以将代码简化为:

    char const ** var = (char**)0;
    

    第二个事实。这不是一个错误。以下是测试用例: https://github.com/llvm/llvm-project/blob/62ec4ac90738a5f2d209ed28c822223e58aaaeb7/clang/test/Sema/pointer-conversion.c

    让我们再举一个例子:

    char const*****  c1 = (char*****)0; // fail
    char *const****  c2 = (char*****)0; // fail
    char **const***  c3 = (char*****)0; // fail
    char ***const**  c4 = (char*****)0; // fail
    char ****const*  c5 = (char*****)0; // ok
    char *****const  c6 = (char*****)0; // ok
    

    很好。我们看到了模式。

    我猜 C/C++ 不能保证你可以修改任何深度大于 2 的东西,因为 const 正确性存在一些问题,这在 here 之前已经讨论过。

    【讨论】:

      【解决方案3】:

      在 GCC 中,您可以在代码中禁用警告:

      #pragma GCC diagnostic push
      #pragma GCC diagnostic ignored "-Wincompatible-pointer-types-discards-qualifiers"
      readonly(ptr);
      #pragma GCC diagnostic pop
      

      我想 clang 也有类似的东西。至于你的第二个问题,cmets好像有。

      【讨论】:

        【解决方案4】:

        A 我知道您想声明指向 const chat 指针的 const 指针,类似于指向 const char 的指针。声明是:

        void foo( const char ** const pointer )
        

        这有点“棘手”,因为星星是从相反的方向计数的。

        让我们考虑一个非常简单的例子

        #include <stdio.h>
        #include <stdlib.h>
        #include <string.h>
        
        void foo( const char ** const pointer )
        {
            while(**pointer)
            {
                printf("%c", **pointer);
                (*pointer)++;
            }
            printf("\n");
        }
        
        void foo1( const char *const * pointer)
        {
            while(*pointer)
            {
                printf("%s\n", *pointer++);
            }
        }
        
        
        int main(void) 
        {
            char **pointer = malloc(sizeof(char *));
            char **pointer1 = malloc(sizeof(char *) * 4);
        
            char strings_in_RAM_0[] = "one";
            char strings_in_RAM_1[] = "two";
            char strings_in_RAM_2[] = "three";
        
            pointer1[0] = strings_in_RAM_0;
            pointer1[1] = strings_in_RAM_1;
            pointer1[2] = strings_in_RAM_2;
            pointer1[3] = NULL;
        
            *pointer = malloc(50);
        
            strcpy(*pointer,"Const pointer to pointer");
        
            foo(pointer);
            foo1(pointer1);
        
            free(*pointer);
            free(pointer);
            free(pointer1);
        
            return 0;
        }
        

        它会给你一个警告,但它会按预期工作,直到免费。然后(因为指针已更改)免费失败。 为避免这种情况,您需要将原始指针保存为 char 例如:

        void foo( const char ** const pointer )
        {
            const char *ptr = *pointer;
            while(**pointer)
            {
                printf("%c", **pointer);
                (*pointer)++;
            }
            printf("\n");
            *pointer = ptr;
        }
        

        foo1 函数参数的类型是指向 const 的指针,指向 const char

        https://ideone.com/JSewN4

        【讨论】:

        • foo(pointer); 是类型错误。从char ** 转换为const char ** 是不安全的。
        • char *p; char **pp; const char **ppc; pp = &amp;p; ppc = pp; *ppc = "foo"; *p = 'x';
        • 这不仅仅是“另一个警告”;这是一个类型错误。 C 使它成为一个错误,因为它删除了const,这是一件坏事。请不要建议人们忽略程序中的类型错误。
        • 我的示例表明从char ** 转换为const char ** 是不安全的,因为它允许您将const char * 转换为char *,这会默默地剥离const,这是不安全的。跨度>
        • 您回答了关于 SO 的问题。您提供了示例代码,说“它会给您一个警告,但它会按预期工作”。如果您不建议人们这样做,为什么要发布它?
        猜你喜欢
        • 1970-01-01
        • 2011-08-04
        • 1970-01-01
        • 2018-05-16
        • 2021-12-31
        • 1970-01-01
        • 1970-01-01
        • 2016-10-30
        • 1970-01-01
        相关资源
        最近更新 更多