Skip to content

Pointer to void

Generic interface and type erasure and incomplete type

正如creference Pointers to void中所言:

Pointers to void are used to pass objects of unknown type, which is common in generic interfaces

显然pointer to void是C中实现**generic programming**的方式,这种generic是通过**type erasure**来实现的:

1、void是type-less、是incomplete type,programmer无法直接操作void pointer,必须要先将它static_cast or explicit cast为complete type才能够进行操作;

关于incomplete type,参见 Incomplete-type 章节,在其中讨论了delete void pointer的内容。

2、Pointer to object of any type can be implicitly converted to pointer to void,在这个过程中,就丢失了type,显然这就是type erasure,关于type erasure,参见 Type-Erasure 章节。

cppreference Pointer to void

Pointer to object of any type can be implicitly converted to pointer to void (optionally cv-qualified); the pointer value is unchanged. The reverse conversion, which requires static_cast or explicit cast, yields the original pointer value:

NOTE: 上述的conversion,其实就是type erasure,是C中实现generic programming的重要手段。

NOTE: 下面展示的就是static_cast的用法

#include <iostream>

int main()
{
    int n = 1;
    int* p1 = &n;
    void* pv = p1;
    int* p2 = static_cast<int*>(pv);
    std::cout << *p2 << '\n'; // prints 1
}

If the original pointer is pointing to a base class subobject within an object of some polymorphic type, dynamic_cast may be used to obtain a void* that is pointing at the complete object of the most derived type.

NOTE: 这一点就是在cppreference dynamic_cast 中提及的4)

Pointers to void are used to pass objects of unknown type, which is common in C interfaces: std::malloc returns void*, std::qsort expects a user-provided callback that accepts two const void* arguments. pthread_create expects a user-provided callback that accepts and returns void*. In all cases, it is the caller's responsibility to cast the pointer to the correct type before use.

stackoverflow What does void mean in C, C++, and C#?

A

Basically it means "nothing" or "no type"

There are 3 basic ways that void is used:

1、Function argument: int myFunc(void) -- the function takes nothing.

2、Function return value: void myFunc(int) -- the function returns nothing

3、Generic data pointer: void* data -- 'data' is a pointer to data of unknown type, and cannot be dereferenced

NOTE: 所以,it is the caller's responsibility to convert the pointer to the correct type before use.

Note: the void in a function argument is optional in C++, so int myFunc() is exactly the same as int myFunc(void), and it is left out completely in C#. It is always required for a return value.

void pointer and alignment

alignment如何保证?

错误观点: void*应该具备最大的alignment,这样才能够保证它能够容纳下所有类型的pointer。

正确理解: void*类型的变量用于保存pointer的值,由于它是incomplete type,所以programmer无法直接对其进行操作(直接+*一个void*,编译器会报错的,这样的program是无法编译通过的),programmer必须先将它转换为complete type pointer。由于无法直接使用void*,所以考虑考虑它的alignment是没有意义的。需要考虑的是:void*的值是否满足它的destination type的alignment,如果不满足,则是会发生undefined behavior的。

Pointer to void VS pointer to char

前面已经描述了pointer to void了。另外一种非常常见的pointer就是pointer to char,两者存在着相似点:

通用性:

pointer type 通用性的体现 目的
void* Pointer to object of any type can be implicitly converted to pointer to void 实现generic programming
char* char*与value representation对应,并且它拥有最小的alignment requirement,所以任何类型的pointer也都可以转换为char*,从而获得所指向的object的value representation 获得value representation

最能够体现两者之间关联的例子是:std::memcpystd::memmove,解释如下:

两者的入参类型都是void*,显然这样实现了generic,这些函数内部实现中,都需要执行:

objects are reinterpreted as arrays of unsigned char.

然后按照byte( unsigned char)进行操作。

Examples

Plugin system

int FUNCTION_CALL_MODE HQInit(void* lpInstance, PFOnSetTimeOut pfOnSetTimeOut)
{
    int iRet = I_NONE;
    if (lpInstance)
        iRet = ((CHQImpl*) lpInstance)->OnInit();
    return iRet;
}

redis linked list

typedef struct listNode {
    struct listNode *prev;
    struct listNode *next;
    void *value;
} listNode;

节点的数据类型是pointer,因为redis中的所有的数据都是来自于网络,都是从接收到的数据new出一片空间的;

networking.c中有如下函数:

/* This function links the client to the global linked list of clients.
 * unlinkClient() does the opposite, among other things. */
void linkClient(client *c) {
    listAddNodeTail(server.clients,c);
    /* Note that we remember the linked list node where the client is stored,
     * this way removing the client in unlinkClient() will not require
     * a linear scan, but just a constant time operation. */
    c->client_list_node = listLast(server.clients);
    uint64_t id = htonu64(c->id);
    raxInsert(server.clients_index,(unsigned char*)&id,sizeof(id),c,NULL);
}

listAddNodeTail函数的原型如下:

list *listAddNodeTail(list *list, void *value)

显然在linkClient函数中,涉及了从client *void *类型的转换

参见Implicit conversions,其中指出:

A pointer to void can be implicitly converted to and from any pointer to object type with the following semantics:

  • If a pointer to object is converted to a pointer to void and back, its value compares equal to the original pointer.
  • No other guarantees are offered

C interfaces

正如cppreference Pointer declaration#Pointers to objects#Pointers to void中所总结的:

Pointers to void are used to pass objects of unknown type, which is common in C interfaces:

下面是例子:

example explanation
std::malloc
pthread_create
std::qsort

TO READ

https://stackoverflow.com/questions/10058234/void-vs-char-pointer-arithmetic

https://bytes.com/topic/c/answers/618725-void-vs-char

https://github.com/RIOT-OS/RIOT/issues/5497

https://codeforwin.org/2017/11/c-void-pointer-generic-pointer-use-arithmetic.html

https://www.geeksforgeeks.org/dangling-void-null-wild-pointers/

https://groups.google.com/forum/#!topic/comp.lang.c/kz6ORGo6GD8

http://computer-programming-forum.com/47-c-language/6e45270da06116ac.htm

http://computer-programming-forum.com/47-c-language/6e45270da06116ac.htm

stackoverflow Why type cast a void pointer?