C中void指针的指针算法
Posted
技术标签:
【中文标题】C中void指针的指针算法【英文标题】:Pointer arithmetic for void pointer in C 【发布时间】:2011-04-01 04:34:07 【问题描述】:当指向特定类型的指针(比如int
、char
、float
、..)增加时,其值会增加该数据类型的大小。如果指向大小为x
的数据的void
指针递增,它如何指向前面的x
字节?编译器如何知道将x
添加到指针的值中?
【问题讨论】:
Error Performing Pointer Arithmetic on void * in MSVC的可能重复 这个问题听起来好像它假设编译器(/运行时)知道指针设置为什么类型的对象,并将其大小添加到指针。这是一个完全的误解:它只知道地址。 "如果指向x
大小数据的 void
指针递增,它如何指向前面的 x
字节?"它没有。为什么有这些问题的人不能在提问之前测试它们 - 你知道,至少在他们检查它是否实际编译的最低限度上,这是没有的。 -1,不敢相信这得到了 +100 和 -0。
【参考方案1】:
void*
指针上不允许使用指针运算。
【讨论】:
+1 指针算法只为指向(完整)对象类型的指针定义。void
是一个不完整类型,根据定义永远无法完成。
@schot:没错。此外,指针运算仅在指向数组对象元素的指针上定义,并且仅当操作的结果是指向同一数组中的元素的指针或该数组的最后一个元素的指针时。如果不满足这些条件,则为未定义行为。 (来自 C99 标准 6.5.6.8)
显然 gcc 7.3.0 并非如此。编译器接受 p + 1024,其中 p 为 void*。结果与 ((char *)p) + 1024 相同
@zzz777 这是一个 GCC 扩展,请参阅投票最多的答案中的链接。【参考方案2】:
您不能对 void *
类型进行指针运算,正是因为这个原因!
【讨论】:
【参考方案3】:将其转换为 char 指针,使指针向前增加 x 个字节。
【讨论】:
如果您正在编写排序函数,根据man 3 qsort
应该有void qsort(void *base, size_t nmemb, size_t size, [snip])
,那么您无法知道“正确的类型”
另外,如果你正在编写类似于 Linux 内核的 container_of 宏的东西,那么你需要一种方法来补偿不同编译器对结构的打包。例如,给定这个结构: ...typedef struct a_ x X; y Y; a;
...如果你有一个变量y *B = (something)
并且你想要一个指向B的封闭a
结构的指针(假设它存在),那么你最终需要做像这样的东西:...a *A = (a*)(((char*)B) - offsetof(a, Y));
...如果你这样做:...a *A = (a*)(((x*)B)-1);
...那么你可能会也可能不会得到一些非常令人讨厌的惊喜!【参考方案4】:
在进行指针运算之前,您必须将其转换为另一种类型的指针。
【讨论】:
【参考方案5】:最终结论:void*
的算术运算在 C 和 C++ 中都是非法。
GCC 允许它作为扩展,请参阅Arithmetic on void
- and Function-Pointers(请注意,本节是手册“C 扩展”一章的一部分)。为了与 GCC 兼容,Clang 和 ICC 可能允许 void*
算术。其他编译器(例如 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
指针执行指针运算。
注意事项
最初,由于 C 标准的这些部分,人们认为 void*
算术是允许的:
6.2.5-27:指向 void 的指针应具有相同的表示和对齐方式 需求作为一个指针 字符类型。
然而,
相同的表示和对齐方式 要求意味着 可互换性作为论据 函数,返回值来自 职能和工会成员。
所以这意味着无论x
的类型为char*
还是void*
,printf("%s", x)
的含义相同,但这并不意味着您可以对void*
进行算术运算。
【讨论】:
来自 C99 标准:(6.5.6.2) 对于加法,两个操作数都应具有算术类型,或者一个操作数应是指向对象类型的指针,而另一个应具有整数类型. (6.2.5.19) void 类型包含一组空值;这是一个无法完成的不完整类型。 我认为这清楚地表明了void*
指针算术是不允许的。 GCC 有一个 extension 允许这样做。
如果您认为您的答案不再有用,您可以将其删除。
这个答案很有用,尽管它被证明是错误的,因为它包含确凿的证据表明 void 指针不适用于算术。
这是一个很好的答案,它有正确的结论和必要的引用,但是提出这个问题的人得出了错误的结论,因为他们没有阅读到答案的底部。我已对此进行了编辑以使其更加明显。
哦,但是对于指针添加,现在“一个操作数应该是指向完整对象类型的指针,另一个应该是整数类型。”。所以我猜用 void* 指针添加指针仍然是未定义的行为。【参考方案6】:
Void 指针可以指向任何内存块。因此,当我们尝试对 void 指针进行指针运算时,编译器不知道要递增/递减多少字节。因此,void 指针必须首先被类型转换为已知类型,然后才能参与任何指针运算。
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.
【讨论】:
【参考方案7】:编译器通过类型转换知道。给定一个void *x
:
x+1
向x
添加一个字节,指针指向字节x+1
(int*)x+1
添加 sizeof(int)
字节,指针指向字节 x + sizeof(int)
(float*)x+1
地址sizeof(float)
字节,
等
虽然第一项不可移植并且违反 C/C++ 的 Galateo,但它仍然是 C 语言正确的,这意味着它将在大多数编译器上编译为可能需要适当标志的东西(如 -Wpointer-arith)
【讨论】:
Althought the first item is not portable and is against the Galateo of C/C++
是的。 it is nevertheless C-language-correct
错误。这是双重考虑! void *
上的指针运算在语法上是非法的,不应编译,并且如果编译会产生未定义的行为。如果粗心的程序员可以通过禁用某些警告使其编译,那不是借口。
@underscore_d:我认为一些编译器曾经允许它作为扩展,因为它比必须转换为 unsigned char*
更方便。将sizeof
值添加到指针。【参考方案8】:
C 标准 不允许void 指针算术。但是,考虑到 void 的大小为 1
,允许 GNU C。
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;
可能是其他编译器产生错误。
【讨论】:
【参考方案9】:void指针中不允许指针运算。
原因:指针算术与普通算术不同,因为它相对于基地址发生。
解决方案:在算术时使用类型转换运算符,这将使执行指针算术的表达式知道基本数据类型。 例如:point 是 void 指针
*point=*point +1; //Not valid
*(int *)point= *(int *)point +1; //valid
【讨论】:
以上是关于C中void指针的指针算法的主要内容,如果未能解决你的问题,请参考以下文章
UB 是不是将 void 指针与 C 中的类型化指针进行比较(是不是相等)?