在使用C語言進行開發的過程中,經常會遇到void*這樣一個特殊的指針,容易被新手忽視,實際上void*非常強大。下面舉幾個比較常見的例子。
1. 用作泛型,接收任意資料類型指針
void*用于指向特定位址,而無需關心這個位址上存放着什麼類型的資料。例如常見的memcpy等函數就用到void*,函數原型如下:
void *memcpy(void *des, void *src, size_t n)
此處的void *des和void *src可以接收任意類型的資料類型指針,既然是記憶體拷貝,入參就不應該限制傳入什麼類型的指針,邏輯上十分合理。
2. 動态記憶體申請與釋放
動态記憶體申請函數傳回值一般都是void*,如果申請成功則傳回的是申請的記憶體塊的首位址,申請失敗則傳回一個空指針NULL,NULL相當于(void *)0。C庫的malloc函數原型如下:
void *malloc(size_t size)
一般申請完記憶體之後會與指針類型強制轉換一起使用,如下所示。
typedef struct
{
char *name;
int age;
...
}animal, *animal_t;
animal_t dog = (animal_t )malloc(sizeof(animal));
if(!dog)
{
printf("malloc failed\n");
}
3. 私有資料關聯
利用void*關聯私有資料是一種常見的程式設計技巧,這種技巧在Linux中廣泛存在。以下截取了宋寶華老師的《Linux裝置驅動開發詳解》的一小段代碼,保留了核心部分。struct file結構體中有一個void* private_data,在globalmem_open中使用struct file的private_data指針記錄對應的裝置的位址,那麼後續隻要擷取到了file結構體指針,也就可以通過file結構體指針的private_data成員來間接擷取到對應的裝置了,例如globalmem_read函數。
#define DEVICE_NUM 10
struct globalmem_dev
{
struct cdev cdev;
...
};
struct globalmem_dev *global_devp;
static int __init globalmem_init(void)
{
int i;
/* 配置設定裝置号等操作 */
...
/* 為global_devp申請DEVICE_NUM個globalmem_dev記憶體大小的空間 */
global_devp = kzalloc(sizeof(struct globalmem_dev) * DEVICE_NUM, GFP_KERNEL);
if(!global_devp )
return -1;
for(i = 0; i < DEVICE_NUM; i++)
{
/* 将申請的DEVICE_NUM個裝置添加到字元裝置節點中 */
globalmem_setup_cdev(global_devp + i, i);
}
return 0;
}
static int globalmem_open(struct inode *inode,struct file *filp)
{
/* 在globalmem_setup_cdev中已經添加到字元裝置節點了 */
struct globalmem_dev *dev = container_of(inode->i_dedv,
struct globalmem_dev, cdev);
filp->private_data = dev;
}
static ssize_t globalmem_read(struct file *filp, char __user *buf,
size_t size, loff_t *ppos)
{
struct globalmem_dev *dev = filp->private_data;
}
不僅僅是Linux,在一些RTOS中也常常能見到void*的身影,例如線程或定時器在建立時,往往需要一個入口函數或逾時回調函數,而這些函數的入參往往就是一個void*,必要時對這些void*加以利用,能起到簡化代碼、減小耦合等作用。