c++ void*指针理解和应用

本文详细解析了 C++ 中 void* 指针的概念及其应用场景,包括其特性、限制及如何正确地使用 void* 指针进行类型转换与内存管理。

c++ void*指针理解和应用

void*说明

void指针不指向任何数据类型,它属于一种未确定类型的过渡型数据。如果要访问实际存在的数据,必须将void指针强转成为指定一个确定的数据类型的数据,如int*、string*、Person*等。

void* p=nullptr;
 
int *a=nullptr;
p=a;
 
double *b=nullptr;
p=b;

Person *per = new Person;
p = per;

char c[16]={0};
p=c;
 
void* 就像一张白纸,任何类型的指针都可以直接赋值给void*类型的指针;
 
但是反过来
 
int *a=nullptr;
a=p;         //err
a=(int *)p;//需要强制类型转换

Person *person2 = nullptr;
person2  = (Person *)p;

2.void指针只支持几种有限的操作:
a.与另一个指针进行比较;
b.向函数传递void
指针或从函数返回void指针;
c.给另一个void
指针赋值。

3.不允许使用void指针操作它所指向的对象,例如,不允许对void指针进行解引用。不允许对void*指针进行算术操作。例如:

几种常用的应用

1.函数传参时不确定类型,或者要支持多类型的传参

void function(int dataType, void* data) {
 
    // 根据dataType的不同值,进行不同的转换
    switch (dataType) {
 
        case 0:
            int* a = (int*)data;
 
        case 1:
            char* a = (char*)data;
 
        ...
    }
}

2.当函数的返回值不考虑类型指关心大小的时候

void * memcpy(void *dest, const void *src, size_t len);
void * memset ( void * buffer, int c, size_t num );
//memcpy和memset对外接收任何类型的指针,这样是合理并且必要的,因为这是内存操作函数,
//是对bit进行操作的,考虑数据类型是没有任何意义的。

*2.关于delete void指针
1.void
指向简单的系统内建类型直接只用delete void
,比如下面代码所示:

void* p=nullptr;
 
int *a=nullptr;
p=a;
 
double *b=nullptr;
p=b;

delete p;


2.void*所指向的对象在析构函数里要释放,进行此造作会丢失内存,因为它不执行析构函数,比如下面以下代码

class CEntity
{
public:
    CEntity(char flag);
    void setData(void *data);
    void setFlag(char flag);
    ~CEntity();
private:
    void * m_data;
    char   m_flag;
};

CEntity::CEntity(char flag)
{
   m_flag = flag;
   cout<<"constructing entity "<<m_flag<<endl;;
}

void CEntity::setData(void *data)
{
    m_data = data;

}

void CEntity::setFlag(char flag)
{
    m_flag = flag;
}

CEntity::~CEntity()
{
    cout<<"destructing entity "<<m_flag<<endl;
    delete m_data;
}

//以上这个类是没什么问题的,但是看以下的调用:
int main(int argc, char *argv[])
{
    CEntity * a = new CEntity();
    int ivalue = 5;
    int *pivalue = &ivalue;
    a->setFlag(true);
    a->setData(pivalue);
    delete a;

    CEntity * b = new CEntity();
    int ivalue2 = 6;
    int *pivalue2 = &ivalue2;
    b->setFlag(true);
    b->setData(pivalue2);
    void *vb = b;
    delete vb;

    return 0;
}

我们关心他的输出:

其输出为:  
           constructing entity a
           destructing entity a
           constructing entity  b                               
可见,delete b 的时候没有释放m_data所指向的内存,没有执行析构函数。
这样会造成内存泄漏。
那么如何释放指向对象类型的void*指针呢?需要强制转换类型,然后进行delete。

```cpp
    CEntity * b = new CEntity('b');
    int ivalue2 = 6;
    int *pivalue2 = &ivalue2;
    b->setData(pivalue2);
    void *vb = b;
    //delete vb;
    delete (CEntity*)vb;

其输出为: 
           constructing entity a
           destructing entity a
           constructing entity  b 
           destructing entity b

如果b对象的setData(a)这样使用,如何释放b对象内存呢、答案是b对象可以正常调用析构函数,但是其成员变量void指向的a对象内存永远释放不掉。因为delete a时会调用a的析构函数,在析构函数中本身类不知道void的真正的类型,所以delete void*时不会调用b的析构函数。代码和程序结果运行如下:

int main(int argc, char *argv[])
{
    CEntity * a = new CEntity('a');
    int ivalue = 5;
    int *pivalue = &ivalue;
    a->setData(pivalue);

    CEntity * b = new CEntity('b');
    b->setData(a);
    void *vb = b;
    delete (CEntity*)vb;

    return 0;
}

其输出为:  
           constructing entity a
           destructing entity a
           constructing entity  b 

故类中的成员尽可能避免使用void变量,否则在类对象指针传入时会造成析构时void无法正确释放。

<think>我们正在讨论C++中`void*`型在`pthread_join()`函数中的使用。根据之前引用的内容,`pthread_join`函数的第二个参数是`void** retval`,用于接收线程的返回值,这个返回值是由线程函数通过`return`语句或者`pthread_exit`返回的`void*`型的数据。 在C++中,`void*`是一种通用指针型,可以指向任意型的数据。但是,由于它没有具体的型信息,我们在使用它指向的数据时需要小心地进行型转换。 下面我将详细解释`void*`在`pthread_join`中的使用,包括如何传递接收数据,以及需要注意的问题。 ### 1. **线程函数返回`void*`** 线程函数的原型为:`void* (*start_routine)(void*)`。这意味着线程函数必须返回一个`void*`型的指针。同样,我们也可以使用`pthread_exit(void *retval)`来终止线程并返回一个值。 ### 2. **使用`pthread_join`获取返回值** - `pthread_join`的第二个参数是一个指向指针指针(`void**`),用于存放线程函数返回的`void*`值。 - 如果我们不关心返回值,可以将这个参数设置为`NULL`。 ### 3. **返回值的传递方式** 由于线程结束后,其栈上的数据会失效,因此我们不能返回指向线程栈上数据的指针(否则会导致悬垂指针)。正确的方式有两种: - **返回指向动态分配内存的指针**:在堆上分配内存,返回其地址,由调用`pthread_join`的线程负责释放。 - **返回指向静态/全局数据的指针**:静态数据或全局数据在程序运行期间一直有效,但要注意多线程同时访问的同步问题。 ### 4. **示例代码分析** 参考引用[3]中的例子: ```cpp #include <iostream> #include <pthread.h> #include <cstdlib> // for malloc and free void* thread_func(void* arg) { // 在堆上分配内存存放返回值 int* result = (int*)malloc(sizeof(int)); *result = 42; // 通过pthread_exit返回 pthread_exit(result); } int main() { pthread_t tid; pthread_create(&tid, nullptr, thread_func, NULL); void* retval; pthread_join(tid, &retval); // 等待线程结束,并获取返回值 // 将void*转换为实际的型 int* result_ptr = (int*)retval; std::cout << "Result: " << *result_ptr << std::endl; // 输出42 free(retval); // 释放动态分配的内存 return 0; } ``` 在这个例子中: - 线程函数`thread_func`动态分配了一个整数,并返回其指针。 - 主线程通过`pthread_join`获取到这个指针,然后转换为`int*`并打印值,最后释放内存。 ### 5. **注意事项** - **内存管理**:如果线程返回的是动态分配的内存,调用`pthread_join`的线程必须负责释放,否则会导致内存泄漏。 - **型安全**C++中可以使用更安全的方式,比如返回一个指向结构体的指针,或者使用`std::future`等高级特性(但这超出了`pthread`的范畴)。 - **避免返回局部变量**:绝对不要返回指向线程函数局部变量的指针,因为当线程结束时,局部变量会被销毁,导致指针无效。 ### 6. **使用静态/全局变量的示例** ```cpp void* thread_func(void* arg) { static int result = 42; // 静态变量 return &result; } // 或者 int global_result = 42; void* thread_func(void* arg) { return &global_result; } ``` 注意:使用静态或全局变量时,如果有多个线程同时修改,需要加锁保护。 ### 7. **使用`pthread_join`时常见的错误** - **忘记处理返回值**:如果线程返回了动态分配的内存,但调用者没有释放,则内存泄漏。 - **型转换错误**:将`void*`转换成错误的型,导致数据解释错误。 - **重复`join`同一个线程**:一个线程只能被`join`一次,重复调用会导致未定义行为。 ### 8. **C++中的型安全建议**C++中,我们可以使用或结构体封装多个返回值,例如: ```cpp struct ThreadResult { int value1; double value2; }; void* thread_func(void* arg) { ThreadResult* result = new ThreadResult{42, 3.14}; return result; } // 在主线程中 ThreadResult* result; pthread_join(tid, (void**)&result); // 使用result delete result; ``` 但更推荐使用C++11之后的`<thread>`库,它提供了更安全方便的线程管理机制。 ### 9. **总结** - `void*`在`pthread_join`中用于传递线程的返回值,它是一个通用指针。 - 必须确保返回的指针指向有效的内存区域(堆或静态区),并且调用者正确释放内存(如果是动态分配的)。 - 在C++中,型转换是必要的,但要注意转换的正确性。 §§相关问题§§ 1. 如何避免多线程中返回值的竞争条件? 2. C++11中的`std::thread`如何实现线程间传递返回值? 3. 除了动态分配静态变量,还有哪些安全的方式在线程间传递数据? 4. 如果线程函数需要返回多个值,应该如何设计? </think>### C++中 `void*` 型在 `pthread_join()` 中的使用详解 在 C++ 多线程编程中,`void*` 型在 `pthread_join()` 中扮演着**线程返回值传递媒介**的关键角色。以下是核心知识点: --- #### 1. `void*` 的作用机制 - **通用指针容器**: `void*` 是 C++ 中的通用指针型,可指向任意数据型(需显式型转换)。 - **线程返回值传递**: 线程函数通过 `return` 或 `pthread_exit()` 返回 `void*` 型值,该值会被 `pthread_join()` 的第二个参数捕获[^3]。 --- #### 2. 典型使用流程 ```cpp #include <pthread.h> #include <iostream> // 线程函数:返回动态分配的整数 void* thread_func(void* arg) { int* result = new int(42); // 在堆上分配返回值 return static_cast<void*>(result); // 显式转换为 void* } int main() { pthread_t tid; pthread_create(&tid, nullptr, thread_func, nullptr); void* retval_raw; // 声明 void* 接收原始返回值 pthread_join(tid, &retval_raw); // 阻塞等待并获取返回值 // 型转换 + 使用返回值 int* result_ptr = static_cast<int*>(retval_raw); std::cout << "线程返回值: " << *result_ptr << std::endl; // 输出 42 delete result_ptr; // 必须手动释放堆内存 return 0; } ``` --- #### 3. 关键注意事项 1. **内存管理责任** - ✅ **堆内存返回**:线程函数返回**堆分配指针**(`new`/`malloc`),调用方负责释放[^3] - ❌ **绝对避免栈指针**:返回局部变量指针会导致悬垂指针(Dangling Pointer) ```cpp // 危险示例:返回栈内存 void* thread_func(void* arg) { int value = 42; return &value; // 线程退出后栈帧销毁,指针失效! } ``` 2. **型安全实践** - 使用 `static_cast` 确保型转换安全(C++ 推荐方式): ```cpp // 主线程中安全的型转换 MyStruct* data = static_cast<MyStruct*>(retval_raw); ``` 3. **多数据型支持** 可通过结构体打包复杂数据: ```cpp struct Result { int status; std::string message; }; void* thread_func(void* arg) { Result* res = new Result{0, "Success"}; return res; } ``` --- #### 4. C 的区别(C++ 特性) | 特性 | C 语言实现 | C++ 推荐方式 | |---------------------|--------------------|----------------------| | 型转换 | `(int*)retval` | `static_cast<int*>` | | 内存分配 | `malloc/free` | `new/delete` | | 错误处理 | 返回值检查 | `try/catch` + RAII | --- #### 5. 最佳实践总结 1. **内存来源**:始终返回**堆分配内存**指针 2. **资源释放**:`pthread_join()` 调用方负责释放返回值内存 3. **型转换**:使用 C++ 风格 `static_cast` 确保安全 4. **NULL 处理**:检查返回值是否为 `NULL`(线程可能返回空指针) 5. **异常安全**:用 RAII 包装(如 `std::unique_ptr` 管理内存) ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值