void* vs. char* 指针算术

18
我正在翻阅我的教科书,对其中的一些代码感到有些困惑。在其中的一个部分,他们以以下方式进行指针运算:
void* bp;
...
bp = (void*)((char*)(bp)+16);
...

但后来,他们做了以下事情:
void* bp;
...
bp = bp+16;
...

我觉得它们应该是两个不同的东西,但他们却把它们当作同一件事来对待。 我这样感觉是因为,举个例子,如果你要访问一个数组(比如整数数组),你会这样做。
int* a = malloc(n*sizeof(int));
...
q = *(a+1);
...

在这种情况下,我不是访问整数数组中的下一个字节,而是下一个4个字节,对吗? 同样地,如果我有一个 void* a,那么 *(a+1) 应该是下一个4个字节... 或者这不是这种情况吗?

那个第二个例子不应该编译。 - Oliver Charlesworth
4
如果以符合模式编译,则它不会编译(或者至少会触发一个警告)。gcc默认情况下不符合规范,并将void*算术运算实现为一种扩展。 - Keith Thompson
2个回答

24

这是一个错误。标准规范中没有定义对 void * 进行算术运算,但是一些编译器将其作为扩展提供,并与 char * 的算术运算相同。第二个表达式在 C 语言中不是正式的有效表达式,但可能因为(不好的)习惯而被误解通过。


那么访问下一个16字节的正确方法是先转换为char*,然后再加上16?哈哈,现在得改好多代码了。哦,我把第一个复制错了一点,做了一个小改变,但我不认为这会对我的问题有影响。 - de1337ed
1
或者您可以强制转换为uint64_t *并加2 ;) 是的,可移植的方法是将其转换为已知大小的类型的指针并在其上执行算术运算。如果您不需要可移植性,并且编译器记录了void *算术运算以特定方式工作,那么可以使用它。但当然,某个时候您将不得不切换到另一个编译器... - Daniel Fischer

7

这个回答很好,但我想更清楚地说明为什么要使用其中之一。;)

虽然(void *)和(char *)可以等效地转换为任何其他指针类型,但只有在使用(char *)时才能进行指针算术运算,而不能使用(void *),如果您想遵守标准C。

为什么两者都用作指针?大多数指针转换到和从(void *)的转换都可以在不使用强制转换的情况下完成,但使用(char *)是旧时代的回忆。

GCC不会警告在(void *)上进行指针算术运算,因为它不是一个旨在符合标准C而是GNU C的编译器。要遵守标准C,请使用以下标志:

gcc -ansi -pedantic -Wall -Wextra -Werror <more args...>

我的底线:

  • 如果你想进行指针算术运算:使用 (char *)
  • 如果你想获取指针地址以便稍后转换为另一种类型:使用 (void *)

我不明白你为什么在暗示我们不能从char*进行转换或转换到它? - Mehdi Charife
从底线部分开始,它说如果你想要将类型转换为另一种类型,你应该使用(void*),尽管你也可以使用(char*)进行类型转换。 - Mehdi Charife
2
@AntoninGAVREL -- "要遵循标准C的行为,您可以使用以下标志...." -ansi 标志编译为 C89,但您也可以使用 -std= 来编译其他标准,例如 -std=c17 以在 C17 下编译。 - ad absurdum
1
@MehdiCharife -- 答案并没有说你不能将char *进行转换。"底线"部分提供了一些建议。malloc提供了一个很好的例子。在古老的时代(C89之前),它返回的是char *:这就需要进行转换才能将指针赋给非char指针,例如int *x = (int *)malloc(sizeof *x);。但是在C89中添加了void *,现在在C中惯用的做法是int *x = malloc(sizeof *x);,也就是说,在不需要转换的情况下不要进行转换。答案的意思是:在像这样的情况下不要使用char *,即当你需要一个通用指针类型时。 - ad absurdum
1
@MehdiCharife - 这正是答案所说的:如果你需要通过指针算术访问字节,请使用char *。但是,char *在一般的通用指针使用中是不好的,因为它需要进行强制类型转换;强制类型转换会给代码增加噪音,并且强制类型转换可能会抑制你想要看到的警告。void *在C89中被引入,恰好作为通用指针类型。 - ad absurdum
显示剩余2条评论

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