C语言中void指针的指针算法


176

当一个指针到特定类型的(比如intcharfloat,..)被递增,其值增加该数据类型的大小。如果void指向大小数据的指针x增加了,它如何指向x前面的字节?编译器如何知道要增加x指针的值?



3
这个问题听起来像是假设编译器(/运行时)知道指针设置为哪种类型的对象,并将其大小添加到指针。那是一个完全的误解:它只知道地址。
PJTraill,2015年

2
“如果void指向大小数据的指针x增加了,它如何指向x前面的字节?” 没有。遇到此类问题的人为什么不能在问之前对其进行测试-知道,至少要检查他们是否真正编译时才达到最低要求,而事实并非如此。-1,简直不敢相信这有+100和-0。
underscore_d

Answers:


287

最终结论:在C和C ++中,对a进行算术void*都是非法的

GCC允许它作为扩展,请参见-和函数指针上的算术void(请注意,本节是本手册“ C扩展”一章的一部分)。Clang和ICC可能void*出于与GCC兼容的目的而允许算术。其他编译器(例如MSVC)不允许在上进行算术运算void*,并且如果-pedantic-errors指定了-Werror-pointer-arith标志或指定了标志,则GCC会禁止进行算术运算(如果您的代码库也必须使用MSVC进行编译,则此标志很有用)。

C语言标准

引文摘自n1256草案。

该标准对加法运算的描述为:

6.5.6-2:另外,两个操作数都应具有算术类型,或者一个操作数应是指向对象类型的指针,而另一个应具有整数类型。

因此,这里的问题是void*指向“对象类型”的指针,或者等效地,是否void是“对象类型” 的指针。“对象类型”的定义是:

6.2.5.1:类型分为对象类型(完全描述对象的类型),函数类型(描述函数的类型)和不完整类型(描述对象但缺少确定其大小所需的信息的类型)。

该标准定义void为:

6.2.5-19:void类型包括一组空值;它是无法完成的不完整类型。

由于void是不完整的类型,因此不是对象类型。因此,它不是加法运算的有效操作数。

因此,您不能对void指针执行指针算术。

笔记

最初,void*由于C标准的以下部分,人们认为算术是允许的:

6.2.5-27:指向void的指针应 指向字符类型的指针具有相同的表示和对齐要求。

然而,

相同的表示形式和对齐 要求旨在表示作为函数的参数,函数的返回值和并集成员的可互换性。

因此,这意味着具有类型还是printf("%s", x)具有相同的含义,但这并不意味着您可以对进行算术运算。xchar*void*void*

编者注:该答案经过编辑以反映最终结论。


7
根据C99标准:(6.5.6.2)另外,两个操作数都应具有算术类型,或者一个操作数应是指向对象类型的指针,而另一个应具有整数类型。(6.2.5.19)void类型包括一组空值;它是无法完成的不完整类型。我认为这很清楚,void*不允许使用指针算术。GCC具有允许执行此操作的扩展
Job

1
如果您不再认为自己的答案有用,则可以将其删除。
caf 2010年

1
尽管此答案被证明是错误的,但它还是有用的,因为它包含了确凿的证据,说明无效指针并非用于算术。
本·弗林

1
这是一个很好的答案,它具有正确的结论和必要的引用,但是遇到此问题的人得出了错误的结论,因为他们没有读懂答案的底部。我对此进行了编辑,使其更加明显。
Dietrich Epp

1
Clang和ICC不允许进行void*算术运算(至少默认情况下)。
谢尔盖·波多布里

61

指针不允许使用指针算术void*


16
+1指针算术仅针对(完整)对象类型的指针定义。void不完整的类型,根据定义永远无法完成。
2010年

1
@schot:是的。此外,仅在指向数组对象元素的指针上定义指针算术,并且运算的结果必须是指向同一数组中元素的指针或指向该数组最后一个元素之后的指针。如果不满足这些条件,则为未定义行为。(根据C99标准6.5.6.8)
工作

1
显然,对于gcc 7.3.0并非如此。编译器接受p + 1024,其中p为void *。结果与((char *)p)+ 1024
zzz777

@ zzz777这是一个GCC扩展名,请参阅顶部投票答案中的链接。
Ruslan

19

将其强制转换为char指针,将指针向前递增x个字节。


4
何必呢?只需将其转换为正确的类型(应该始终知道),然后将其char递增1。转换为,以递增x,然后将新值重新解释为其他类型既无意义又不确定。
underscore_d

9
如果您正在编写排序函数(根据man 3 qsort应当具有)void qsort(void *base, size_t nmemb, size_t size, [snip]),则您将无法知道“正确的类型”
alisianoi

15

C标准不允许指针运算。然而,GNU C是考虑大小允许 IS 1

C11标准§6.2.5

段落-19

void类型包括一组空值;它是无法完成的不完整对象类型

以下程序在GCC编译器中运行正常。

#include<stdio.h>

int main()
{
    int arr[2] = {1, 2};
    void *ptr = &arr;
    ptr = ptr + sizeof(int);
    printf("%d\n", *(int *)ptr);
    return 0;
}

可能是其他编译器生成错误。



8

在执行指针算术之前,必须将其强制转换为另一种类型的指针。


7

空指针可以指向任何内存块。因此,当我们尝试对空指针进行指针算术运算时,编译器不知道要增加/减少多少字节。因此,空指针必须先被类型转换为已知类型,然后才能参与任何指针算术。

void *p = malloc(sizeof(char)*10);
p++; //compiler does how many where to pint the pointer after this increment operation

char * c = (char *)p;
c++;  // compiler will increment the c by 1, since size of char is 1 byte.

-1

编译器通过类型转换知道。给定一个void *x

  • x+1向加上一个字节x,指针指向字节x+1
  • (int*)x+1添加sizeof(int)字节,指针转到字节x + sizeof(int)
  • (float*)x+1addres sizeof(float)字节等

尽管第一项不是可移植的,并且与C / C ++的Galateo相对,但它还是C语言正确的,这意味着它将在大多数编译器上编译为某种东西,可能需要适当的标记(例如-Wpointer-arith)


2
Althought the first item is not portable and is against the Galateo of C/C++真正。it is nevertheless C-language-correct假。这是双重考虑!指针算术在void *语法上是非法的,不应进行编译,并且在这样做时会产生未定义的行为。如果粗心的程序员可以通过禁用一些警告来进行编译,那不是借口。
underscore_d

@underscore_d:我认为一些编译器曾经允许它作为扩展,因为它比强制转换unsigned char*(例如,将sizeof值添加到指针)要方便得多。
超级猫
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.