原文地址:http://blog.sina.com.cn/s/blog_48c95a190101ai3i.html#commonComment
在近日的项目开发中,本博主遇到了一个之前从没想过的问题,那就是在C语言中一个声明为void *类型的变量是否可以进行算术操作?为了验证该问题,俺写了一个测试程序,在测试程序中对一个void *类型的变量进行了算术操作加一,代码如下。
#include <stdio.h>
int main() {
void *test = NULL;
int int_value[] = {12, 34, 56, 67, 27};
char char_value[] = {'H', 'e', 'l', 'l', 'o'};
test = (void *)char_value;
printf("%c\n", *(char_value + 1));
printf("%c\n", *((char *)(
test + 1)));
test = (void *)int_value;
printf("%d\n", *(int_value + 1));
printf("%d\n", *((int *)(
test + 1)));
return 0;
}
使用GCC对该程序编译,编译结果是顺利通过,而且没有任务Warning,由此可知至少在语法上来看是完全可以通过的。这也是完全说得通的,学过C语言的人都知道指针本来就是可以加减的,伴随着加减指针对自动在内存空间中前后移动,所以编译通过至少在语法上也是顺理成章的。那么执行结果如何呢?执行结果如下:
从以上的执行结果可以看到一点不同,那就是当void *变量指向char *变量时,加一之后的打印结果是正确的,但是当void *变量指向int *变量时,加一之后的打印结果确实不正确的,这是为何呢?此时我们就得抛开语法的合理性来探寻更深层次的原因,也就是当指针加一时究竟发生了什么?当char *类型的指针加一时,代表的意义是将指针移动到下一个char类型数据的内存起始位置,因为char的大小为1一个字节,所以char *类型的指针加一时,指针要向后移动1个字节。当int *类型的指针加一时,代表的意义是将指针移动到下一个int类型数据的内存起始位置。但因为int的大小为4个字节,所以int *类型的指针加一时,指针要向后移动4个字节。依此类推,当void *类型的指针加一时,代表的意义是将指针移动到下一个void类型数据的内存起始位置,但是void类型的大小是多少呢?众所周知,在C语言中不能声明一个类型为void的变量,那么void的大小是否就是0呢?不然,通过打印sizeof(void)可以知道,void的大小竟然是1,这也就解释了为什么
当void *变量指向char *变量时,加一之后的打印结果是正确的,因为void的大小和char一样的!
通过以上分析可知,GNU C中在语法上支持void指针的算术操作,但在执行过程中,void指针的算术操作可能会导致错误。关于这一点我们可以在GNU的说明文档上找到官方支持
:In GNU C, addition and subtraction operations are supported on pointers to void and on pointers to functions. This is done by treating the size of a void or of a function as 1.
A consequence of this is that sizeof is also allowed on void and on function types, and returns 1.
The option -Wpointer-arith requests a warning if these extensions are used(
链接地址:
http://gcc.gnu.org/onlinedocs/gcc-4.4.2/gcc/Pointer-Arith.html#Pointer-Arith
)。也就是说不光是void指针,就连函数指针也是支持加减操作的,而void的大小和函数的大小都是1。为了理论联系实际,俺又写了一个测试程序来验证上面的说明,代码如下。
#include <stdio.h>
int echo(char *string) {
printf("%s", string);
}
int main() {
printf("%d\n", sizeof(echo));
printf("%d\n", sizeof(void));
return 0;
}
执行结果证明了以上说明的正确性,
void的大小和函数的大小确实都是1。当然,上面的说明也同时给出了避免因void指针和函数指针加减导致执行错误的方法,那就是在编译时添加编译选项:
-Wpointer-arith。俺使用该编译选项对第一个测试程序重新进行编译:gcc -Wpointer-arith test.c,而此时编译结果就不再是顺利通过而是给出了Warning信息如下。
test.c: In function 'main':
test.c:10:
warning: pointer of type 'void *' used in arithmetic
test.c:14:
warning: pointer of type 'void *' used in arithmetic