C语言中的void指针算术运算

5

我认为在gcc中,当涉及到指针算术时,void *和char *被视为相同的方式处理,即void *“指向”内存中的单个字节,因此以下代码:

void *p;
p = malloc(sizeof(void));
printf("%p %p\n",p,p+1);

实际上返回的是 0x984a008 0x984a009。同样地,void ** 指向一个指针,因此增加一个指针实际上意味着增加4个字节(在32位操作系统上),即

void **p;
p = (void **) malloc(sizeof(void *));
printf("%p %p\n",p,p+1);

返回结果为0x984a008 0x984a00c。然而,以下代码让我感到困惑。

void **p, *p1;
p = (void **) malloc(sizeof(void *));
p1 = (void **) p;
printf("%p %p\n",p1,p1+1);

自从它再次返回0x984a008 0x984a009,这是怎么回事?

2
也许你想输入 void **p, **p1; 而不是 void **p, *p1; - Paul R
1
sizeof(void) 的值为1或者至少会给出一个警告,这是无用的。你不需要这样做。void 类型表示什么?只有 void* - Tony The Lion
我确实是指void *p,p1。我知道这段代码可以编译,但我不明白的是为什么如果p最初被声明为void **,那么p+1对应于增加4个字节,而如果p1被声明为void *然后重新转换为void **,则算术只会为p1 + 1添加1个字节。 - Ivan
为什么会这样,将void **转换时被忽略了? - Ivan
1
@Ivan:是的,显然它被忽略了。这是一个身份转换(表达式p已经具有类型void **)。身份转换除了对值类别产生一些影响(例如,转换的结果是rvalue)之外,都会被忽略。 - Ben Voigt
5个回答

7

暂时忽略 void 指针算术的可能未定义行为...

p1 的类型是 void *

通过将不同类型的值赋给变量,不能改变变量的类型。 p1 始终保持为 void *

任何分配给它的不同类型的表达式将隐式转换为 void *(如果无法转换则会出现错误)。

因此,这与第一个示例基本相同。

编辑:

据我所知,从一个指针类型转换到另一个指针类型实际上并没有做什么,它的主要目的是进行类型检查。

指针只是一个内存地址,一个数字,因此内存看起来像:(分配后)

  p1       p2
void *   void** <- these types are fixed and known during compilation
------   ------
|1234|   |1234|         at address 1234 = the 4 bytes from malloc
------   ------
  ^
  |
this value is the only thing that will change by assigning p1 to a different value

3
在某些架构上,从一个指针类型转换为另一个指针类型会改变一些东西。然而,在您典型的x86(_64)架构上,您可以期望所有指针具有相同的表示,并且强制转换不会产生任何操作。 - Daniel Fischer

3
你应该使用char *而不是void *,因为对void指针进行算术运算是gcc扩展。
char *p1 = /* ... */;

printf("%p %p\n", p1, p1+1);

无论指向什么,对于指针 p 的算术运算都使用 char * 类型(而不是 char **)。

如果你写成:

char *p1 = /* ... */;

printf("%p %p\n", p1, (char**)p1+1);

指针算术运算使用 char **

2

当你使用void *时,增量为1。当你使用void **时,它是指针的大小。

在让你感到困惑的操作中,将你的void *强制转换为void **,然后又被隐式地转回了void *。就好像你做了这样的事情:

long a, b, c;
c = a + (int) b;

你将 b 转换为 int,但是接下来你想要操作一个 long,所以它被转换回去了。

好的,但是为什么编译器要执行回归转换呢?void *void **在堆栈上占用4个字节,据我所知,所有指针算术运算都在编译时完成。那么,为什么编译器坚持要将p1转换回去呢? - Ivan
这样想:将void **强制转换为void *是有意义的 -- 任何指针都可以成为void *。而反过来转换则不行,因为并不是所有的指针都是双重指针。编译器会对你请求的操作进行敏感处理:它只能以能理解的方式去执行。 - salezica

1

好的,但如果您将所有类型更改为 char 指针,则问题仍然存在(尽管在这种情况下 GCC 会发出警告)。 - Rup

0

我知道我发帖已经过去一年了,但我偶然看到了这个问题,它引起了我的兴趣。

我同意@Dukeling的观点,即你不能仅通过强制转换来改变变量的类型。但似乎这取决于编译器认为void是什么。看看这个示例程序,并查看生成的输出。请注意,vpvp2之间唯一的区别是malloc()中的sizeof()部分。

编译于:gcc (Debian 4.7.2-5) 4.7.2
编译命令:gcc -o void_test void_test.c

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

int main(int argc, char **argv) {
  void *vp, *vp2;

  printf("sizeof(void)   = %d\n", sizeof(void));
  printf("sizeof(void *) = %d\n", sizeof(void *));
  printf("sizeof(char)   = %d\n", sizeof(char));
  printf("sizeof(char *) = %d\n\n", sizeof(char *));

  vp = (void *) malloc(sizeof(void));
  vp2 = (void *) malloc(sizeof(void *));

  printf("vp    = %p\n", vp);
  printf("vp+1  = %p\n", vp+1);
  printf("vp2   = %p\n", vp);
  printf("vp2+1 = %p\n", vp2+1);

  return 0;
}

给出以下输出:
$ ./void_test 
sizeof(void)   = 1
sizeof(void *) = 8
sizeof(char)   = 1
sizeof(char *) = 8

vp    = 0x1ee3010
vp+1  = 0x1ee3011
vp2   = 0x1ee3010
vp2+1 = 0x1ee3031

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