在C语言中,对于void指针的指针算术操作
指针算术是C语言中的一个重要概念,允许我们在内存中进行指针的移动和操作。然而,在使用void指针时,会出现一些问题。这篇文章将讨论使用void指针进行指针算术的原因和解决方法。
在C语言中,void指针是一种通用指针类型,可以指向任何类型的数据。它的一个常见用途是作为函数参数,允许函数接受任意类型的指针作为参数。然而,由于void指针没有具体的类型信息,因此在进行指针算术时会遇到问题。
当我们需要在void指针上进行指针算术操作时,我们需要将它强制转换为一个具体的指针类型。这通常是将它转换为char指针,因为char类型的大小为1字节。然后,我们可以通过将指针向前移动x个字节来实现指针算术。
原因之一是,在实现排序函数时,根据C库函数`qsort`的定义,我们无法知道指针的具体类型。因此,当我们在排序函数中使用void指针时,需要将其转换为char指针,并根据元素的大小来移动指针。
另一个原因是,在编写类似于Linux内核的`container_of`宏时,我们需要解决不同编译器对结构体的对齐方式的差异。例如,如果我们有以下结构体:
typedef struct a_ { x X; y Y; } a;
如果我们有一个变量`y *B = (something)`,并且我们想要得到指向B所在的`a`结构体的指针(假设它存在),那么我们需要进行如下操作:
a *A = (a*)((char*)B - offsetof(a, Y));
这里,我们将B转换为char指针,并通过`offsetof`宏获取Y在结构体a中的偏移量。然后,我们将指针向前移动偏移量的字节数,以获得指向a结构体的指针。
如果我们使用以下操作:
a *A = (a*)((x*)B - 1);
那么可能会遇到一些非常严重的问题。
为了解决这些问题,我们需要注意在使用void指针进行指针算术时的类型转换,并确保我们正确地计算了偏移量。这将确保我们能够正确地操作内存中的指针,而不会导致意外的错误。
C语言中,不允许对`void*`指针进行指针算术运算。指针算术运算只对指向(完整的)对象类型的指针定义。`void`是一个无法通过定义完成的不完整类型。
确切地说,指针算术运算只对指向数组对象的元素的指针定义,并且仅当操作的结果是指向同一数组的元素或者超过该数组的最后一个元素时才能定义。如果不满足这些条件,行为是未定义的(来自C99标准6.5.6.8)。
然而,显然在gcc 7.3.0中并非如此。编译器接受`p + 1024`这样的表达式,其中`p`是`void*`类型的指针。而且结果与`((char *)p) + 1024`相同。
这是GCC的一个扩展,可以在最高评分的答案中找到相关链接。
C语言中的void指针是一个通用指针,可以指向任何类型的数据。然而,在C和C++中,对void指针进行算术运算是不合法的。GCC允许对void指针进行算术运算作为一种扩展,但其他编译器如MSVC则不允许。
出现这个问题的原因是,根据C标准的规定,对指针进行加法运算时,要么两个操作数都是算术类型,要么一个操作数是指向对象类型的指针,另一个操作数是整数类型。然而,根据C标准对void类型的定义来看,void是一个不完整的类型,不属于对象类型,因此不能作为加法运算的操作数。所以,不能对void指针进行算术运算。
从C99标准来看,对void指针进行算术运算是不被允许的。GCC通过一个扩展来允许这种操作。
另外,根据C11标准的定义,void在C11中是一个对象类型。然而,对于指针的加法运算,仍然需要一个操作数是指向完整对象类型的指针,另一个操作数是整数类型。因此,对void指针进行算术运算仍然是未定义行为。
总结起来,无论是C还是C++中,对void指针进行算术运算都是非法的。GCC通过扩展允许这种操作,而其他编译器如MSVC不允许。为了确保代码能够在不同编译器下编译通过,可以使用编译器的特定标志,如"-Werror=pointer-arith"来检查并阻止对void指针进行算术运算。
注:本文整理自Stack Overflow上的相关讨论。