Void * có nghĩa là gì và làm thế nào để sử dụng nó?


147

Hôm nay khi tôi đang đọc mã của người khác, tôi đã thấy một cái gì đó như void *func(void* i);, điều này void*có nghĩa gì ở đây cho tên hàm và cho loại biến tương ứng?

Ngoài ra, khi nào chúng ta cần sử dụng loại con trỏ này và làm thế nào để sử dụng nó?


2
Bạn đang sử dụng cuốn sách C nào? Bạn đang yêu cầu phần tốt hơn của toàn bộ chương.
cnicutar




Lấy một gợi ý từ malloccalloc. Trang man tiếp tục nói: "... trả về một con trỏ tới bộ nhớ được phân bổ, được căn chỉnh phù hợp cho mọi loại dữ liệu tích hợp."
máy tự động

Câu trả lời:


175

Một con trỏ tới voidlà một loại con trỏ "chung". A void *có thể được chuyển đổi thành bất kỳ loại con trỏ nào khác mà không cần đúc rõ ràng. Bạn không thể hủy đăng ký a void *hoặc làm số học con trỏ với nó; trước tiên bạn phải chuyển đổi nó thành một con trỏ thành kiểu dữ liệu hoàn chỉnh.

void *thường được sử dụng ở những nơi bạn cần để có thể làm việc với các loại con trỏ khác nhau trong cùng một mã. Một ví dụ thường được trích dẫn là chức năng thư viện qsort:

void qsort(void *base, size_t nmemb, size_t size, 
           int (*compar)(const void *, const void *));

baselà địa chỉ của một mảng, nmemblà số phần tử trong mảng, sizelà kích thước của mỗi phần tử và comparlà con trỏ tới một hàm so sánh hai phần tử của mảng. Nó được gọi như vậy:

int iArr[10];
double dArr[30];
long lArr[50];
...
qsort(iArr, sizeof iArr/sizeof iArr[0], sizeof iArr[0], compareInt);
qsort(dArr, sizeof dArr/sizeof dArr[0], sizeof dArr[0], compareDouble);
qsort(lArr, sizeof lArr/sizeof lArr[0], sizeof lArr[0], compareLong);

Các biểu thức mảng iArr, dArrlArrđược mặc nhiên chuyển đổi từ các loại mảng với các loại con trỏ trong cuộc gọi chức năng, và mỗi được ngầm chuyển đổi từ "con trỏ đến int/ double/ long" thành "con trỏ tới void".

Các chức năng so sánh sẽ trông giống như:

int compareInt(const void *lhs, const void *rhs)
{
  const int *x = lhs;  // convert void * to int * by assignment
  const int *y = rhs;

  if (*x > *y) return 1;
  if (*x == *y) return 0;
  return -1;
}

Bằng cách chấp nhận void *, qsortcó thể làm việc với các mảng thuộc bất kỳ loại nào.

Nhược điểm của việc sử dụng void *là bạn ném loại an toàn ra khỏi cửa sổ và tham gia giao thông. Không có gì để bảo vệ bạn khỏi việc sử dụng thói quen so sánh sai:

qsort(dArr, sizeof dArr/sizeof dArr[0], sizeof dArr[0], compareInt);

compareIntđang mong đợi các đối số của nó được trỏ đến ints, nhưng thực sự đang làm việc với doubles. Không có cách nào để bắt vấn đề này vào thời gian biên dịch; bạn sẽ kết thúc với một mảng sai.


5
Nó thực sự không được đảm bảo rằng void*có thể chuyển sang một con trỏ hàm. Nhưng đối với con trỏ dữ liệu những gì bạn nói giữ.
Vatine

Trước khi con trỏ void có sẵn, "char *" đã được sử dụng thay thế. Nhưng void là tốt hơn vì nó thực sự không thể được sử dụng để thay đổi bất cứ điều gì trực tiếp.
dùng50619

22

Sử dụng một khoảng trống * có nghĩa là hàm có thể lấy một con trỏ không cần phải là một loại cụ thể. Ví dụ, trong các chức năng của ổ cắm, bạn có

send(void * pData, int nLength)

điều này có nghĩa là bạn có thể gọi nó theo nhiều cách, ví dụ

char * data = "blah";
send(data, strlen(data));

POINT p;
p.x = 1;
p.y = 2;
send(&p, sizeof(POINT));

Vì vậy, nó khá giống với thuốc generic trong các ngôn ngữ khác, nhưng không có kiểu kiểm tra, phải không?
Cầu thủ chạy cánh Sendon

3
Tôi cho rằng nó sẽ tương tự, tuy nhiên vì không có loại kiểm tra nào, việc mắc lỗi có thể gây ra kết quả rất lạ hoặc khiến chương trình bị lỗi hoàn toàn.
TheSteve

7

C là đáng chú ý về vấn đề này. Người ta có thể nói voidlà hư vô void*là tất cả (có thể là tất cả).

Chỉ là điều nhỏ bé *này tạo nên sự khác biệt.

Rene đã chỉ ra điều đó. A void *là một con trỏ đến một số vị trí. Những gì có cách "diễn giải" được để lại cho người dùng.

Đó là cách duy nhất để có các loại mờ trong C. Các ví dụ rất nổi bật có thể được tìm thấy, ví dụ như trong các thư viện cấu trúc dữ liệu chung hoặc glib. Nó được xử lý rất chi tiết trong "Giao diện và triển khai C".

Tôi đề nghị bạn đọc chương hoàn chỉnh và cố gắng hiểu khái niệm về một con trỏ để "lấy nó".


5
void*

là một 'con trỏ tới bộ nhớ mà không có giả định loại nào được lưu trữ'. Bạn có thể sử dụng, ví dụ, nếu bạn muốn truyền một đối số cho hàm và đối số này có thể có một số loại và trong hàm bạn sẽ xử lý từng loại.


3

Bạn có thể xem bài viết này về con trỏ http://www.cplusplus.com/doc/tutorial/pointers/ và đọc chương: void con trỏ .

Điều này cũng hoạt động cho ngôn ngữ C.

Loại khoảng trống của con trỏ là một loại con trỏ đặc biệt. Trong C ++, void đại diện cho sự vắng mặt của loại, do đó, con trỏ void là con trỏ trỏ đến một giá trị không có loại (và do đó cũng có độ dài không xác định và các thuộc tính quy định không xác định).

Điều này cho phép các con trỏ void trỏ đến bất kỳ loại dữ liệu nào, từ một giá trị nguyên hoặc dấu phẩy đến một chuỗi ký tự. Nhưng đổi lại, chúng có một hạn chế rất lớn: dữ liệu được chỉ ra bởi chúng không thể bị hủy bỏ trực tiếp (điều này hợp lý, vì chúng tôi không có loại nào để phản đối) và vì lý do đó, chúng tôi sẽ luôn phải đưa địa chỉ vào con trỏ trống để một số loại con trỏ khác trỏ đến một loại dữ liệu cụ thể trước khi hủy bỏ nó.


3

Một con trỏ void được gọi là con trỏ chung. Tôi muốn giải thích với một kịch bản pthread mẫu.

Hàm thread sẽ có nguyên mẫu là

void *(*start_routine)(void*)

Các nhà thiết kế API pthread đã xem xét các giá trị đối số và trả về của hàm luồng. Nếu những thứ đó được tạo thành chung chung, chúng ta có thể gõ cast thành void * trong khi gửi làm đối số. tương tự, giá trị trả về có thể được lấy từ void * (Nhưng tôi không bao giờ sử dụng giá trị trả về từ hàm luồng).

void *PrintHello(void *threadid)
{
   long tid;

   // ***Arg sent in main is retrieved   ***
   tid = (long)threadid;
   printf("Hello World! It's me, thread #%ld!\n", tid);
   pthread_exit(NULL);
}

int main (int argc, char *argv[])
{
   pthread_t threads[NUM_THREADS];
   int rc;
   long t;
   for(t=0; t<NUM_THREADS; t++){
      //*** t will be type cast to void* and send as argument.
      rc = pthread_create(&threads[t], NULL, PrintHello, (void *)t);   
      if (rc){
         printf("ERROR; return code from pthread_create() is %d\n", rc);
         exit(-1);
      }
   }    
   /* Last thing that main() should do */
   pthread_exit(NULL);
}

Tại sao gọi pthread_exit(NULL);thay vì return 0;ở cuối chính?
Seabass77

1
@ Seabass77 Vui lòng tham khảo stackoverflow.com/questions/3559463/ từ
Jeyaram

1

a void*là một con trỏ, nhưng loại của những gì nó trỏ đến là không xác định. Khi bạn chuyển một con trỏ void cho một hàm, bạn sẽ cần biết loại của nó là gì để đưa nó trở lại đúng loại đó sau này trong hàm để sử dụng nó. Bạn sẽ thấy các ví dụ trong pthreadsđó sử dụng các hàm với chính xác nguyên mẫu trong ví dụ của bạn được sử dụng làm hàm luồng. Sau đó, bạn có thể sử dụng void*đối số làm con trỏ cho kiểu dữ liệu chung mà bạn chọn và sau đó đưa nó trở lại kiểu đó để sử dụng trong hàm luồng của bạn. Bạn cần phải cẩn thận khi sử dụng con trỏ void mặc dù trừ khi bạn quay lại một con trỏ thuộc loại thật của nó, bạn có thể gặp phải tất cả các loại vấn đề.


1

Tiêu chuẩn C11 (n1570) §6.2.2.3 al1 p55 nói:

Một con trỏ voidcó thể được chuyển đổi thành hoặc từ một con trỏ thành bất kỳ loại đối tượng nào. Một con trỏ tới bất kỳ loại đối tượng nào có thể được chuyển đổi thành một con trỏ để bỏ trống và quay lại; kết quả sẽ so sánh bằng với con trỏ ban đầu.

Bạn có thể sử dụng con trỏ chung này để lưu trữ một con trỏ tới bất kỳ loại đối tượng nào, nhưng bạn không thể sử dụng các phép toán số học thông thường với nó và bạn không thể trì hoãn nó.


0

Hàm lấy một con trỏ tới một kiểu tùy ý và trả về một kiểu như vậy.


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.