【问题标题】:Should I cast void** return in Visual Studio我应该在 Visual Studio 中强制转换 void** return
【发布时间】:2018-02-14 16:06:16
【问题描述】:

我用 Visual Studio 9.0 (2008) 编译了一些代码。

行为符合预期,但是当我分配一些手工制作的二维数组时 函数,Visual-Studio 会生成一些 C4133 警告:

void ** alloc_2d(int w, int h, size_t type_size);
void free_d2(void ** mem);

int main (void)
{        
    float ** data;

    /* Here is generated a C4133 warning:
       "incompatible type from void ** to float **" */
    data = alloc_2d(100, 100, sizeof **data);

    /* do things with data */

    /* free data */
    free_2d(data);

    return 0;
}

我明白为什么会产生这个警告,但我想知道我应该怎么做才能让它安静。

我该怎么办?

  • 接受警告?
  • 禁用警告(我认为很危险)?
  • 禁用围绕alloc_2d 调用的警告(使用一些特定于 视觉工作室)?
  • 强制转换函数返回(但 [Do I cast the result of malloc?

第二个问题:

  • 较新的/其他编译器是否知道这种类型的转换?

void** 后面隐藏着两个数组:一个大用于存储我需要连续的所有数据,另一个用于浏览不同的行。

实现看起来像(我删除了错误检查)

void **alloc_2D_array(int w, int h, size_t size)
{
    void ** mem = malloc(w * sizeof *mem);
    *mem = malloc(w*h*size);
    for (i = 1; i < w; ++i)
    {
        mem[i] = (void*)((char*)mem[0] + i*w*size);
    }
    return mem;
}

【问题讨论】:

  • malloc 返回一个void *,它是一个通用指针。但是你的函数返回一个void **,它不是一个通用指针,所以你必须强制转换它。
  • 为什么要用指针的指针?您可以使用指针和简单的数学计算来分配二维数组!你是如何编写 alloc_2d 函数的? (发布它可能会解决您的问题)。
  • 我认为如果不是严格有用的指针,最好避免使用指针。

标签: c visual-studio-2008 casting


【解决方案1】:

根据签名,我假设您的函数大致是这样实现的(加上错误检查,为了简洁,我在这里省略了):

void **alloc_2d(int w, int h, size_t type_size)
{
    void **pointers = malloc(h * sizeof *pointers);
    for (size_t i = 0; i < h; ++i)
    {
        pointers[i] = malloc(w * type_size);
    }
    return pointers;
}

对此的一些评论:

  • 这个函数的结果是不是一个二维数组,而是一个指向数组的指针数组。它的使用有点类似于真正的二维数组,但会带来一些开销。真正的二维数组将是一个大小为w * h * type_size 的连续块。
  • 此代码实际上是不安全的,当您将其返回的void ** 转换为float ** 时,它可能会调用未定义的行为。这是因为无法保证指向不同类型的指针具有相同的表示形式——它们甚至可以具有不同的大小。另见Are there any platforms where pointers to different types have different sizes?。通过将void ** 转换为float **,您可以将void * 的数组视为 和float * 的数组。虽然在典型的现代 PC 平台上可能还不错,但这可能会完全出错

也就是说,您可以通过简单地让函数返回 void * 而不是 void ** 来避免强制转换,但这只会隐藏问题:void * 通用指针类型并且编译器将允许隐式转换为任何其他指针类型,但您仍然会使用错误的指针类型访问您的 void * 数组。


我建议改为分配一个平面数组并手动计算偏移量,如下所示:

size_t rows = 100;
size_t cols = 100;
float *data = malloc(cols * rows * sizeof *data);

data[cols*5 + 3] = 1.41;
// ...

free(data);

作为替代方案,您可以使用可变长度数组(在 C99 中是强制性的,可选但在 C11 中几乎总是支持 - Microsoft 可能不支持 ....)来动态分配一个真实的二维数组

size_t rows = 100;
size_t cols = 100;
float (*data)[cols] = malloc(rows * sizeof *data);

data[5][3] = 1.41;
// ...
free(data);

【讨论】:

    【解决方案2】:

    C 中的通用指针类型是void*。这确实 not 意味着void** 也是通用指针类型,该规则不适用于递归。

    相反,从void**float** 的转换或相反的转换是无效的指针转换。它们不是兼容的类型 - 编译器必须发出诊断消息。忽略警告可能会导致数据未对齐或严格的别名问题 - 在任何一种情况下都会出现错误。


    我该怎么办?

    修复代码,使其不包含禁止的指针转换。


    较新的/其他编译器是否知道这种类型的转换?

    自 C 首次标准化以来,这一特定规则没有改变。


    至于你应该如何修复你的代码......你不应该。您应该从头开始重写它,以便它分配 arrays 而不是“基于指针的查找表”。没有明显的理由可以让您从这样的查找表中受益,但有很多理由可以避免使用它。

    此外,如果您使用指向 VLA 的指针,您的代码将更具可读性。

    有关如何正确执行此操作的示例,请参阅Correctly allocating multi-dimensional arrays(答案底部的代码示例)。

    【讨论】:

    • (请注意,您可能必须切换到符合标准的 C 编译器才能使其工作。)
    • 完全同意尽可能只使用一个阵列,但对于您建议的 VLA,“标准兼容”是不够的。 MSVC 至少符合 C89,但即使您严格要求符合 最新 标准,VLA 仍然是可选。因此,很高兴知道如何在没有它们的情况下实现相同的目标。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-05-16
    • 1970-01-01
    • 2012-04-12
    • 1970-01-01
    • 2013-12-25
    • 1970-01-01
    相关资源
    最近更新 更多