通过 void ** 修改任何数据指针是否合法?

7

通过 void ** 访问指针类型是否合法?

我查看了关于指针别名的标准引用,但仍不确定这是否是合法的 C 语言操作:

int *array;
void **vp = (void**)&array;
*vp = malloc(sizeof(int)*10);

虽然这只是一个简单的例子,但它也适用于我所遇到的更复杂的情况。

看起来这可能不合法,因为我通过一个类型不是int *char *的变量访问了一个int *。我无法得出简单的结论。

相关:


“int *” 访问 “类型” 是什么意思? - ash
Eli Bendersky 在他的博客上发表了一篇关于这个主题的文章: http://eli.thegreenplace.net/2009/11/16/void-and-casts-in-c-and-c/ - Morten Jensen
1
@MortenJensen 这似乎只讨论了 void* 而不是 void**。 - Ryan Haining
3个回答

4

没有专门的类型指针指向“指向void的指针”,也就是说,指针的底层类型是“指向void的指针”。

当存储指向整数的指针时,不能存储类似指针值。需要强制转换的事实表明,你正在进行的操作不符合标准(并且确实如此)。有趣的是,你可以使用常规的void*来回传递,并且它将表现出定义良好的行为。换句话说,这样做:

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

int main()
{
    int *array;
    void *vp = &array;
    int **parray = vp;
    *parray = malloc(sizeof(int)*10);
}

是合法的。如果我去掉强制类型转换并使用apple llvm 4.2 (clang),你原来的例子甚至无法编译,因为存在不兼容的指针类型,也就是你问题的主题。具体的错误信息是:

"Incompatible pointer types initializing 'void **' with an expression of type 'int **'"

这是正确的。


2

不同类型的指针可以有不同的大小。

你可以将任何类型的指针存储到 void * 中,然后可以恢复它,但这意味着 void * 必须足够大以容纳所有其他指针。

通常不允许像处理保存了 int * 的变量一样来处理它,认为它确实是一个 void *

还要注意,进行强制转换(例如,将 malloc 的结果转换为 int *)与将包含 int * 的内存区域视为包含 void * 是完全不同的两件事。在第一种情况下,如果需要,编译器会被通知转换,但在第二种情况下,您向编译器提供了错误信息。

但是,在 X86 上,它们通常具有相同的大小,如果您只是使用数据指针进行操作,则是安全的(尽管函数指针可能不同)。

关于别名问题,通过 void *char * 进行的任何写操作都可以改变任何对象,因此编译器必须考虑别名作为可能的情况。然而,在您的示例中,写入 void **(另一种不同的事情)的任何操作,编译器都可以忽略对 int * 的可能别名效果。


2
@NikosC:不是的。如果你写int *x = (int *)malloc(...),编译器知道你正在进行转换并可以正确地存储值。当然,malloc需要返回一个可用于任何对象的指针。如果你说*p = malloc(...),其中pvoid **,那么编译器将执行原始写操作,认为p指向的内存是void *,即使需要转换也不会执行。 - 6502
1
当您使用malloc()时,不需要强制转换结果。您可以这样做,但C语言保证您不必这样做。这意味着如果一个int*能够容纳malloc()的返回值,即void*,那么可以推断出void*int*具有相同的大小。除非我混淆了,哈哈 :-P - Nikos C.
@NikosC - 实际上,编译器在两种情况下都是“按照指示操作”的。因此,它真的不知道正确的答案。这就是为什么“C”很容易崩溃的原因 - 因为开发人员经常犯错误,而编译器无法告诉他们。例如,将malloc转换为int *时,它告诉编译器,“将此值更改为int *”。当分配给另一个变量时,该变量是int *,它知道不再执行任何转换,因为类型相同。*p = malloc(...)会导致编译器在没有转换的情况下进行写入,正确的。如果目标大小不正确,则会出现问题。 - ash
1
@6502 没错,我搞混了 :-) 当然你是正确的;如果在某些平台上void*int*要宽,那么malloc()只需确保不会返回一个适合于int*的值。因此当截断发生时(就是一个转换的实际情况),结果仍然是正确的。 - Nikos C.
1
@Kevin:不是的。请查看n1124.pdf,第36页,第26点:“……所有指向结构类型的指针应具有相同的表示和对齐要求。所有指向联合类型的指针应具有相同的表示和对齐要求。指向其他类型的指针不需要具有相同的表示或对齐要求。”。 - 6502
显示剩余15条评论

0

你的代码可能在某些平台上可以工作,但它不具备可移植性。原因是C语言没有通用的指向指针的类型。在 void * 的情况下,标准明确允许将其与其他指向完整/不完整类型的指针进行转换,但这并不适用于 void **。这意味着在你的代码中,编译器无法知道 *vp 的值是否从除了 void * 以外的任何类型进行了转换,因此除了你自己显式强制转换之外,编译器不能执行任何转换。

考虑以下代码:

void dont_do_this(struct a_t **a, struct b_t **b)
{
    void **x = (void **) a;
    *x = *b;
}

编译器在*x = *b 行对 定义为 的隐式转换没有抱怨,即使该行试图将指向的指针放在只能放置指向的指针的地方。事实上错误出现在前一行,该行将“指向指针可以放置指向的地方”的指针转换为“指向任何东西都可以放置的指针”。这就是为什么不存在隐式转换的原因。有关指向算术类型的指针类似的例子,请参见C FAQ
因此,即使您的类型转换消除了编译器警告,但它仍然很危险,因为并非所有指针类型都具有相同的内部表示/大小(例如void **int *)。为了使您的代码适用于所有情况,您必须使用中间变量void *
int *array;
void *varray = array;
void **vp = &varray;
*vp = malloc(sizeof(int) * 10);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接