我应该在 Visual Studio 中施放 void** return
Should I cast void** return in Visual Studio
我用 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
调用的警告(一些宏特定于
visual studio)?
- 转换函数return(但是[Do I cast the result of malloc?)
第二个问题:
- newer/other 编译器是否知道这种转换?
在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;
}
根据签名,我假设您的函数大致是这样实现的(加上错误检查,为简洁起见,我在这里省略):
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;
}
对此的一些评论:
- 这个函数的结果不是一个二维数组,而是一个指向数组的指针数组。它的使用有点类似于真正的二维数组,但有一些开销。一个真正的二维数组应该是 one 个大小为
w * h * type_size
. 的连续块
- 这段代码实际上是不安全的,当您将
void **
returned 从它转换为 float **
时,可能会调用未定义的行为。这是因为无法保证指向不同类型的指针具有相同的表示形式——它们甚至可以具有不同的大小。另见 Are there any platforms where pointers to different types have different sizes?。通过将 void **
转换为 float **
,您将 void *
的数组 视为 和 float *
的数组。虽然在您典型的现代 PC 平台上可能没问题,但这个 可能会完全出错。
就是说,您可以通过简单地使用函数 return 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 可能不支持 ....)动态分配一个真正的 2d 数组:
size_t rows = 100;
size_t cols = 100;
float (*data)[cols] = malloc(rows * sizeof *data);
data[5][3] = 1.41;
// ...
free(data);
C 中的通用指针类型是void*
。那确实 not 意味着 void**
也是一个通用指针类型,该规则不适用于递归。
相反,从 void**
到 float**
的转换或相反的转换是无效的指针转换。它们不是兼容类型 - 编译器必须发出诊断消息。忽略警告可能会导致数据未对齐或严重的别名问题 - 在任何一种情况下都是错误。
What should I do?
修正代码,使其不包含禁止的指针转换。
Are newer/other compilers aware of this kind of cast?
这一特定规则自 C 的第一次标准化以来就没有改变。
至于你应该如何修复你的代码……你不应该。您应该从头开始重写它,以便它分配 数组 而不是 "pointer-based look-up tables"。没有明显的理由说明为什么您会从这里的这种查找中受益 -table,但是有很多理由表明您应该避免这样做。
此外,如果您使用指向 VLA 的指针,您的代码将更具可读性。
有关如何正确执行此操作的示例,请参阅 (答案最底部的代码示例)。
我用 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
调用的警告(一些宏特定于 visual studio)? - 转换函数return(但是[Do I cast the result of malloc?)
第二个问题:
- newer/other 编译器是否知道这种转换?
在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;
}
根据签名,我假设您的函数大致是这样实现的(加上错误检查,为简洁起见,我在这里省略):
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;
}
对此的一些评论:
- 这个函数的结果不是一个二维数组,而是一个指向数组的指针数组。它的使用有点类似于真正的二维数组,但有一些开销。一个真正的二维数组应该是 one 个大小为
w * h * type_size
. 的连续块
- 这段代码实际上是不安全的,当您将
void **
returned 从它转换为float **
时,可能会调用未定义的行为。这是因为无法保证指向不同类型的指针具有相同的表示形式——它们甚至可以具有不同的大小。另见 Are there any platforms where pointers to different types have different sizes?。通过将void **
转换为float **
,您将void *
的数组 视为 和float *
的数组。虽然在您典型的现代 PC 平台上可能没问题,但这个 可能会完全出错。
就是说,您可以通过简单地使用函数 return 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 可能不支持 ....)动态分配一个真正的 2d 数组:
size_t rows = 100;
size_t cols = 100;
float (*data)[cols] = malloc(rows * sizeof *data);
data[5][3] = 1.41;
// ...
free(data);
C 中的通用指针类型是void*
。那确实 not 意味着 void**
也是一个通用指针类型,该规则不适用于递归。
相反,从 void**
到 float**
的转换或相反的转换是无效的指针转换。它们不是兼容类型 - 编译器必须发出诊断消息。忽略警告可能会导致数据未对齐或严重的别名问题 - 在任何一种情况下都是错误。
What should I do?
修正代码,使其不包含禁止的指针转换。
Are newer/other compilers aware of this kind of cast?
这一特定规则自 C 的第一次标准化以来就没有改变。
至于你应该如何修复你的代码……你不应该。您应该从头开始重写它,以便它分配 数组 而不是 "pointer-based look-up tables"。没有明显的理由说明为什么您会从这里的这种查找中受益 -table,但是有很多理由表明您应该避免这样做。
此外,如果您使用指向 VLA 的指针,您的代码将更具可读性。
有关如何正确执行此操作的示例,请参阅