【问题标题】:Proper type for a dynamic array of const strings常量字符串的动态数组的正确类型
【发布时间】:2017-08-15 13:33:12
【问题描述】:

我一直认为const char **x 是用于动态分配的 const 字符串数组的正确类型,如下所示:

#include <stdlib.h>

int main()
{
    const char **arr = malloc(10 * sizeof(const char *));
    const char *str = "Hello!";
    arr[0] = str;
    free(arr);
}

但是,当使用 VS2017 编译此代码时,我在 free 行收到此警告:

warning C4090: 'function': different 'const' qualifiers

我的代码有问题吗? FWIW,当我使用 GCC 编译时,即使使用 -Wall -Wextra -pedantic,我也没有收到任何警告。

【问题讨论】:

  • 贴出的程序完全没问题,警告是错误的。
  • 代码看起来不错。如果要删除警告,您可以键入arr 即。 free((void*)arr);
  • error C2440: 'initializing': cannot convert from 'void *' to 'const char **' VS2015 给了我上述关于 malloc 行的错误。不过,显式类型转换可以修复它。
  • 你确定你在VS中把代码编译为C吗?而不是 C++?
  • @Lundin 是的,它使用/TC 标志编译。由于 void*const char ** 的转换,编译为 C++ 会在 mallocline 上产生错误。

标签: c visual-studio constants pointer-conversion


【解决方案1】:

您的代码没有任何问题。相关规则可在此处的 C 标准中找到:

6.3.2.3 指针
指向 void 的指针可以转换为指向任何对象类型的指针或从指向任何对象类型的指针转​​换。指向任何对象类型的指针可以是 转换为指向 void 并再次返回的指针;结果应 比较等于原始指针。

对于任何限定符 q,指向非 q 限定类型的指针可能是 转换为指向该类型的 q 限定版本的指针;这 存储在原始指针和转换指针中的值应比较 相等。

意味着任何指向对象类型(指向变量)的指针都可以转换为 void 指针,除非该指针是合格的(const 或 volatile)。所以没关系

void* vp; 
char* cp; 
vp = cp;

但是这样做是不行的

void* vp; 
const char* cp; 
vp = cp; // not an allowed form of pointer conversion

到目前为止一切顺利。但是当我们混合指针到指针时,const-ness 是一个非常令人困惑的主题。

当我们有一个const char** arr 时,我们有一个指向常量 char 的指针(提示:从右到左读取表达式)。或者在 C 标准胡言乱语中:指向类型的限定指针的指针。 arr 本身不是一个合格的指针!它只是指向一个。

free() 需要一个指向 void 的指针。我们可以向它传递任何类型的指针,除非我们传递一个合格的指针。 const char** 不是限定指针,所以我们可以很好地传递它。

指向类型指针的限定指针应该是char* const*

注意当我们尝试这个时 gcc 是如何抱怨的:

char*const* arr = malloc(10 * sizeof(char*const*));
free(arr);

gcc -std=c11 -pedantic-errors -Wall - Wextra:

错误:传递 'free' 的参数 1 会丢弃 'const' 限定符 指针目标类型

显然,Visual Studio 给出了错误的诊断。或者,您将代码编译为 C++,它不允许与 void* 进行隐式转换。

【讨论】:

    【解决方案2】:

    分配有效的原因与以下分配有效的原因相同。

    const char** arr = malloc(10 * sizeof(const char *));
    void* p = arr;
    

    这条规则1说明,如果两个操作数都是指针类型,则左指针指向的类型必须与右指针指向的类型具有相同的限定符。

    右操作数是指向没有任何限定符的类型的指针。这种类型是指向 const char (const char*) 的类型指针。不要让那个 const 限定符迷惑你,那个限定符不属于指针类型。

    左操作数是一个指向同样没有任何限定符的类型的指针。类型为空。所以这个赋值是有效的。

    如果指针指向具有限定符的类型,则赋值无效:

    const char* const* arr = malloc(10 * sizeof(const char *));
    void* p = arr;    //constraint violation
    

    右操作数是指向带有限定符 const 的类型的指针,该类型是指向 const char (const char* const) 的类型 const 指针。

    左操作数是一个指针,它指向一个没有任何限定符的类型,这个类型是 void 类型。分配违反了约束1


    1(引自:ISO/IEC 9899:201x 6.5.16.1 Simple assignment Constraints 1)
    左操作数具有原子的、合格的或不合格的指针类型,并且(考虑到 左操作数在左值转换后的类型)一个操作数是一个指针 指向一个对象类型,另一个是指向一个合格或不合格版本的指针 void,并且左边指向的类型具有指向的类型的所有限定符 靠右;

    【讨论】:

    • Language-lawyer 注意:如果指针转换是以其他方式而不是简单赋值完成的,例如通过将指针作为参数传递给函数(如在 OP 中),则它不是约束违反,但未定义的行为。由于 6.3.2.3 根本没有提到将限定指针转换为非限定指针时会发生什么。
    • @Lundin - 关于将指针作为参数传递给函数。 6.5.2.2/4“在准备调用函数时,会评估参数,并为每个参数分配相应参数的值”。现在,标准可能使用具有两种不同含义的术语“赋值”,但如果不是这样,那么作为参数传递给函数也是违反约束的,不是吗?
    • @StoryTeller 实际上看起来 6.5.2.2/2 是相关部分。 “每个参数都应该有一个类型,这样它的值就可以被分配给一个对象,该对象具有其对应参数类型的非限定版本。” 所以事实证明它毕竟是违反约束的,但是违反 6.5.2.2/2 的约束。
    猜你喜欢
    • 2022-11-22
    • 2020-03-20
    • 1970-01-01
    • 2022-01-19
    • 2012-02-15
    • 1970-01-01
    • 1970-01-01
    • 2014-12-14
    • 2010-12-21
    相关资源
    最近更新 更多