void*与原类型指针互转安全性及相关C指针问题咨询
void*转换与数组指针的问题 许多书籍指出,将对象类型T的指针/地址转换为
void*再转回原类型T是安全的,ISO C标准对此的规定是:
"指向void的指针可以与指向任何不完全类型或对象类型的指针相互转换。指向任何不完全类型或对象类型的指针转换为void指针后再转换回去,结果应与原指针相等。"
问题列表
- 若void指针临时被赋值为与原指针不同的对象地址,再进行来回转换是否始终安全?
- 转换为
void*是否始终安全?有哪些需要注意的事项? - 部分C资料建议"程序员在转换指针类型时应始终使用显式转换",那么对于
int a = 10;,是否应写成void* p = (void*)&a;而非void* p = &a;? - 将同类型数组名赋值给类型为T的指针是否始终正确?比如
int arr[] = {1, 2, 3}和int* p = arr这种情况?
测试程序
#include <stdio.h> #include <stdlib.h> void fx( void* p ){ void* p1 = p; p = malloc((sizeof(int))*3); void* p2 = p; int* p3 = (int*)p1; int* p4 = (int*)p2; p4[0] = 10; p4[1] = 20; p4[2] = 30; p3[0] = p4[0]; p3[1] = p4[1]; p3[2] = p4[2]; free(p2); } struct Test{ int a; char b; }; void* fx2( void* p ){ int a = 10; void* p1 = (void*)&a; p1 = p; struct Test* p2 = (struct Test*)p1; (*p2).a = 100; (*p2).b = 'G'; (*(p2+1)).a = 200; (*(p2+1)).b = 'H'; (*(p2+2)).a = 300; (*(p2+2)).b = 'I'; void* p3 = (void*)p2; return p3; } int main(){ /* case 1 */ int a = 10; int b = 30; void* pv = (void*)&a; void* pv2 = (void*)&b; pv2 = pv; int* pa = (int*)pv2; *pa = 50; /* at the end of case 1 a is 50 */ //case 2 int arr[] = { 1, 2, 3 }; void* p = (void*)arr; fx(p); int* p1 = (int*)p; /* at the end of case 2 arr[0] is 10, arr[1] is 20, arr[2] is 30*/ //case 3 struct Test t1; t1.a = 10; t1.b = 'A'; struct Test t2; t2.a = 20; t2.b = 'B'; struct Test t3; t3.a = 30; t3.b = 'C'; struct Test arrt[] = {t1, t2, t3 }; void* pvtest = (void*)arrt; pvtest = fx2(pvtest); struct Test* ptest = (struct Test*)pvtest; /* at the end of case 3 (arrt[0]).a is 100, (arrt[0]).b is G, (arrt[1]).a is 200, (arrt[1]).b is H,(arrt[2]).a is 300, (arrt[2]).b is I */ /* case 4 */ int x = 10; int* px = &x; int** pxx = &px; void* pxxv = (void*)pxx; void** pxxv2 = (void*)pxx; /* Are void* pxxv = (void*)pxx; and void** pxxv2 = (void*)pxx; equivalent ? */ /*case 5 */ int arrx[] = { 1, 2, 3}; int* parrx = arrx; *parrx = 10; *(parrx+1) = 20; void* pvarrx = (void*)parrx; int* parrx2 = (int*)pvarrx; *(parrx2+2) = 30; /* at the end of case 5 arrx[0] is 10, arrx[1] is 20 and arrx[2] is 30 */ return 0; }
问题解答
1. 若void指针临时被赋值为与原指针不同的对象地址,再进行来回转换是否始终安全?
安全的核心前提是转换回的类型必须与该void*当前指向的对象的实际类型完全匹配。比如void*当前存储的是指向struct Test的地址,转回struct Test*就是安全的;但如果转回int*,则属于未定义行为——因为实际指向的对象类型并非int。
void*只是一个通用指针容器,它的安全转换只和当前指向的对象类型有关,和它之前存储过什么指针无关。
2. 转换为void*是否始终安全?有哪些需要注意的事项?
并非所有指针都能安全转换为void*:
- 对象指针(变量、数组、结构体等的指针):完全符合标准,可以安全转换为
void*。 - 函数指针:标准未规定函数指针与
void*的转换合法性,属于未定义行为。部分编译器(如GCC)提供非标准扩展支持,但绝对不要依赖这种行为。 - 空指针:转换为
void*后仍是空指针,安全。
额外注意:void*无法直接解引用,也不能进行指针算术运算(因为void没有固定大小),必须转回具体类型后才能进行操作。
3. 对于int a = 10;,是否应写成void* p = (void*)&a;而非void* p = &a;?
不需要显式强制转换。C标准明确规定,指向对象类型的指针可以隐式转换为void*,这是合法操作。显式转换在这里属于冗余代码,不会带来任何实际收益,反而会增加代码复杂度。
注意:C++中不允许这种隐式转换,但你的问题针对C语言,遵循C标准即可。
4. 将同类型数组名赋值给类型为T的指针是否始终正确?
是的,这完全符合C标准。数组名在绝大多数表达式中会隐式转换为指向数组第一个元素的指针,类型为T*(比如int arr[]会转换为int*)。因此int* p = arr;等价于int* p = &arr[0];,是完全合法且正确的写法。
唯一例外场景:当数组名作为sizeof、_Alignof的操作数,或作为&运算符的操作数时,它不会转换为指针,而是代表整个数组。比如sizeof(arr)得到的是整个数组的字节大小,而非指针的大小。
内容的提问来源于stack exchange,提问作者alessio solari

