Khái niệm về con trỏ void trong lập trình C


129

Có thể hủy bỏ một con trỏ trống mà không cần truyền kiểu trong ngôn ngữ lập trình C không?

Ngoài ra, có cách nào để khái quát hóa một hàm có thể nhận một con trỏ và lưu nó trong một con trỏ void và bằng cách sử dụng con trỏ void đó, chúng ta có thể tạo một hàm tổng quát không?

ví dụ:

void abc(void *a, int b)
{
   if(b==1)
      printf("%d",*(int*)a);     // If integer pointer is received
   else if(b==2)
      printf("%c",*(char*)a);     // If character pointer is received
   else if(b==3)
      printf("%f",*(float*)a);     // If float pointer is received
}

Tôi muốn làm cho hàm này chung chung mà không sử dụng các câu lệnh if-other - điều này có khả thi không?

Ngoài ra nếu có các bài viết hay trên internet giải thích khái niệm về một con trỏ trống, thì sẽ có ích nếu bạn có thể cung cấp các URL.

Ngoài ra, là số học con trỏ với con trỏ void có thể?

Câu trả lời:


97

Có thể hủy bỏ con trỏ void mà không cần truyền kiểu trong ngôn ngữ lập trình C ...

Không, voidchỉ ra sự vắng mặt của loại, nó không phải là thứ bạn có thể quy định hoặc gán cho.

Có cách nào để khái quát một hàm có thể nhận con trỏ và lưu nó trong con trỏ void và bằng cách sử dụng con trỏ void đó, chúng ta có thể tạo một hàm tổng quát ..

Bạn không thể bỏ qua nó theo cách di động, vì nó có thể không được căn chỉnh chính xác. Đây có thể là một vấn đề đối với một số kiến ​​trúc như ARM, trong đó con trỏ tới kiểu dữ liệu phải được căn chỉnh theo ranh giới của kích thước của kiểu dữ liệu (ví dụ: con trỏ tới số nguyên 32 bit phải được căn chỉnh ở ranh giới 4 byte để được loại bỏ).

Ví dụ: đọc uint16_ttừ void*:

/* may receive wrong value if ptr is not 2-byte aligned */
uint16_t value = *(uint16_t*)ptr;
/* portable way of reading a little-endian value */
uint16_t value = *(uint8_t*)ptr
                | ((*((uint8_t*)ptr+1))<<8);

Ngoài ra, là số học con trỏ với con trỏ void có thể ...

Số học con trỏ là không thể trên con trỏ voiddo thiếu giá trị cụ thể bên dưới con trỏ và do đó kích thước.

void* p = ...
void *p2 = p + 1; /* what exactly is the size of void?? */

2
Trừ khi tôi nhớ lại không chính xác, bạn có thể hủy bỏ một cách an toàn một khoảng trống * theo hai cách. Truyền tới char * luôn được chấp nhận và nếu bạn biết loại ban đầu, nó trỏ đến bạn có thể chuyển sang loại đó. Khoảng trống * đã mất thông tin loại vì vậy nó sẽ phải được lưu trữ ở nơi khác.
Dan Olson

1
Có, bạn luôn có thể tham gia nếu bạn chuyển sang loại khác, nhưng bạn không thể làm như vậy nếu bạn không sử dụng. Nói tóm lại, trình biên dịch sẽ không biết hướng dẫn lắp ráp nào sẽ sử dụng cho các phép toán cho đến khi bạn sử dụng nó.
Zachary Hamm

20
Tôi nghĩ GCC đối xử với số học trên các void *con trỏ giống như char *, nhưng nó không chuẩn và bạn không nên dựa vào nó.
ephemient

5
Tôi không chắc là tôi làm theo. Trong ví dụ, OP được liệt kê ở trên các loại đến được đảm bảo là chính xác bởi người dùng chức năng. Bạn có nói rằng nếu chúng ta có một cái gì đó trong đó chúng ta gán một giá trị của loại đã biết cho một con trỏ, chuyển nó thành một con trỏ trống và sau đó đưa nó trở lại loại con trỏ ban đầu mà nó có thể trở thành không được gán? Tôi không hiểu tại sao trình biên dịch sẽ làm suy yếu bạn như thế chỉ đơn giản là sắp xếp mọi thứ đơn giản chỉ vì bạn bỏ chúng vào một con trỏ trống. Hay bạn chỉ đơn giản nói rằng chúng ta không thể tin tưởng người dùng biết loại đối số mà họ truyền cho hàm?
cá heo

2
@doliver Ý tôi là # 2 ("chúng tôi không thể tin tưởng người dùng biết loại đối số mà họ truyền cho hàm"). Nhưng xem lại câu trả lời của tôi từ 6 năm trước (ugh, nó có thực sự dài đến thế không?) Tôi có thể thấy ý của bạn là gì, không phải lúc nào cũng vậy: khi con trỏ ban đầu chỉ vào loại thích hợp, nó sẽ được căn chỉnh, vì vậy nó 'D được an toàn để đúc mà không có vấn đề liên kết.
Alex B

31

Trong C, a void *có thể được chuyển đổi thành một con trỏ thành một đối tượng thuộc loại khác mà không cần truyền rõ ràng:

void abc(void *a, int b)
{
    int *test = a;
    /* ... */

Tuy nhiên, điều này không giúp viết chức năng của bạn theo cách chung chung hơn.

Bạn không thể hủy bỏ void *việc chuyển đổi nó thành một loại con trỏ khác khi hội thảo một con trỏ đang lấy giá trị của đối tượng trỏ tới. Một trần trụi voidkhông phải là một loại hợp lệ vì vậy việc hủy đăng ký a void *là không thể.

Số học con trỏ là về việc thay đổi giá trị con trỏ bằng bội số của các sizeofđối tượng được trỏ. Một lần nữa, vì voidkhông phải là một kiểu thực sự, sizeof(void)không có ý nghĩa nên số học con trỏ không hợp lệ void *. (Một số triển khai cho phép nó, sử dụng số học con trỏ tương đương cho char *.)


1
@CharlesBailey Trong mã của bạn, bạn viết void abc(void *a, int b). Nhưng tại sao không sử dụng void abc(int *a, int b), vì trong chức năng của bạn, cuối cùng bạn sẽ xác định int *test = a;, nó sẽ lưu một biến và tôi không thấy bất kỳ lợi thế nào khác bằng cách xác định một biến khác. Tôi thấy nhiều mã được viết theo cách này bằng cách sử dụng void *làm tham số và truyền các biến sau này trong hàm. Nhưng vì chức năng này được viết bởi tác giả, vì vậy anh ta phải biết cách sử dụng biến, do đó, tại sao sử dụng void *? Cảm ơn
Lion Lai

15

Bạn nên lưu ý rằng trong C, không giống như Java hay C #, hoàn toàn không có khả năng "đoán" thành công loại đối tượng mà một void*con trỏ trỏ tới. Một cái gì đó tương tự như getClass()đơn giản là không tồn tại, vì thông tin này không được tìm thấy. Vì lý do đó, loại "chung" mà bạn đang tìm kiếm luôn đi kèm với siêu dữ liệu rõ ràng, như int btrong ví dụ của bạn hoặc chuỗi định dạng trong printfhọ hàm.


7

Một con trỏ void được gọi là con trỏ chung, có thể tham chiếu đến các biến của bất kỳ loại dữ liệu nào.


6

Cho đến nay, phần gạch chân của tôi trên con trỏ void như sau.

Khi một biến con trỏ được khai báo sử dụng từ khóa void - nó sẽ trở thành biến con trỏ mục đích chung. Địa chỉ của bất kỳ biến nào của bất kỳ loại dữ liệu nào (char, int, float, v.v.) có thể được gán cho một biến con trỏ void.

main()
{
    int *p;

    void *vp;

    vp=p;
} 

Do con trỏ kiểu dữ liệu khác có thể được gán cho con trỏ void, nên tôi đã sử dụng nó trong hàm tuyệt đối_value (mã hiển thị bên dưới). Để thực hiện một chức năng chung.

Tôi đã cố gắng viết một mã C đơn giản lấy số nguyên hoặc float làm đối số và cố gắng làm cho nó + ve, nếu âm. Tôi đã viết mã sau đây,

#include<stdio.h>

void absolute_value ( void *j) // works if used float, obviously it must work but thats not my interest here.
{
    if ( *j < 0 )
        *j = *j * (-1);

}

int main()
{
    int i = 40;
    float f = -40;
    printf("print intiger i = %d \n",i);
    printf("print float f = %f \n",f);
    absolute_value(&i);
    absolute_value(&f);
    printf("print intiger i = %d \n",i);
    printf("print float f = %f \n",f);
    return 0;
}   

Nhưng tôi đã nhận được lỗi, vì vậy tôi đã biết sự hiểu biết của mình với con trỏ void là không chính xác :(. Vì vậy, bây giờ tôi sẽ chuyển sang thu thập điểm tại sao lại như vậy.

Những điều mà tôi cần hiểu thêm về con trỏ void là thế.

Chúng ta cần đánh máy biến con trỏ void để loại bỏ nó. Điều này là do một con trỏ void không có kiểu dữ liệu liên quan đến nó. Không có cách nào trình biên dịch có thể biết (hoặc đoán?) Loại dữ liệu nào được trỏ đến bởi con trỏ void. Vì vậy, để lấy dữ liệu được trỏ đến bởi một con trỏ void, chúng ta sẽ đánh máy nó với đúng loại dữ liệu được giữ bên trong vị trí con trỏ void.

void main()

{

    int a=10;

    float b=35.75;

    void *ptr; // Declaring a void pointer

    ptr=&a; // Assigning address of integer to void pointer.

    printf("The value of integer variable is= %d",*( (int*) ptr) );// (int*)ptr - is used for type casting. Where as *((int*)ptr) dereferences the typecasted void pointer variable.

    ptr=&b; // Assigning address of float to void pointer.

    printf("The value of float variable is= %f",*( (float*) ptr) );

}

Một con trỏ void có thể thực sự hữu ích nếu lập trình viên không chắc chắn về kiểu dữ liệu được nhập bởi người dùng cuối. Trong trường hợp như vậy, lập trình viên có thể sử dụng một con trỏ void để trỏ đến vị trí của kiểu dữ liệu không xác định. Chương trình có thể được thiết lập theo cách để yêu cầu người dùng thông báo loại dữ liệu và kiểu truyền có thể được thực hiện theo thông tin được nhập bởi người dùng. Một đoạn mã được đưa ra dưới đây.

void funct(void *a, int z)
{
    if(z==1)
    printf("%d",*(int*)a); // If user inputs 1, then he means the data is an integer and type casting is done accordingly.
    else if(z==2)
    printf("%c",*(char*)a); // Typecasting for character pointer.
    else if(z==3)
    printf("%f",*(float*)a); // Typecasting for float pointer
}

Một điểm quan trọng khác bạn nên ghi nhớ về con trỏ void là - số học con trỏ không thể được thực hiện trong một con trỏ void.

void *ptr;

int a;

ptr=&a;

ptr++; // This statement is invalid and will result in an error because 'ptr' is a void pointer variable.

Vì vậy, bây giờ tôi đã hiểu những gì là sai lầm của tôi. Tôi đang sửa giống nhau.

Người giới thiệu :

http://www.antoarts.com/void-pointers-in-c/

http://www.circuitstoday.com/void-pointers-in-c .

Mã mới như dưới đây.


#include<stdio.h>
#define INT 1
#define FLOAT 2

void absolute_value ( void *j, int *n)
{
    if ( *n == INT) {
        if ( *((int*)j) < 0 )
            *((int*)j) = *((int*)j) * (-1);
    }
    if ( *n == FLOAT ) {
        if ( *((float*)j) < 0 )
            *((float*)j) = *((float*)j) * (-1);
    }
}


int main()
{
    int i = 0,n=0;
    float f = 0;
    printf("Press 1 to enter integer or 2 got float then enter the value to get absolute value\n");
    scanf("%d",&n);
    printf("\n");
    if( n == 1) {
        scanf("%d",&i);
        printf("value entered before absolute function exec = %d \n",i);
        absolute_value(&i,&n);
        printf("value entered after absolute function exec = %d \n",i);
    }
    if( n == 2) {
        scanf("%f",&f);
        printf("value entered before absolute function exec = %f \n",f);
        absolute_value(&f,&n);
        printf("value entered after absolute function exec = %f \n",f);
    }
    else
    printf("unknown entry try again\n");
    return 0;
}   

Cảm ơn bạn,


2

Không, no không thể. Loại giá trị nào được quy định lại phải có?


2
void abc(void *a, int b) {
  char *format[] = {"%d", "%c", "%f"};
  printf(format[b-1], a);
}

Chương trình này có khả thi không .... Vui lòng kiểm tra, nếu điều này có thể trong lập trình C ...
AGeek 30/03/2016

2
Có, điều này là có thể (mặc dù mã rất nguy hiểm nếu không có kiểm tra phạm vi cho b). Printf lấy số lượng đối số thay đổi và a chỉ được đẩy trên ngăn xếp dưới dạng bất kỳ con trỏ nào (không có bất kỳ thông tin loại nào) và xuất hiện bên trong hàm printf bằng cách sử dụng macro va_arg sử dụng thông tin từ chuỗi định dạng.
hlovdal

@SiegeX: vui lòng mô tả những gì không thể di động và những gì có thể không được xác định.
Gauthier

@Gauthier chuyển một biến chứa định danh định dạng không thể di động và có khả năng không xác định hành vi. Nếu bạn muốn làm một cái gì đó như thế thì hãy nhìn vào hương vị 'vs' của printf. Câu trả lời này có một ví dụ tốt cho điều đó.
SiegeX

Trong thực tế, tôi có thể sẽ thay thế int bbằng một enum, nhưng thực hiện bất kỳ thay đổi nào sau đó đối với enum đó sẽ phá vỡ chức năng này.
Jack Stout

1

Tôi muốn làm cho hàm này chung chung, không sử dụng ifs; có thể không

Cách đơn giản duy nhất tôi thấy là sử dụng quá tải .. không có sẵn trong ngôn ngữ lập trình C AFAIK.

Bạn đã xem xét langage lập trình C ++ cho chương trình của bạn chưa? Hoặc có bất kỳ ràng buộc nào cấm sử dụng nó?


Ok, đó là một sự xấu hổ. Từ quan điểm của tôi, tôi thấy không có giải pháp sau đó. Trên thực tế, nó giống như printf () & cout: 2 cách khác nhau để thực hiện in. Câu lệnh printf () sử dụng câu lệnh if () để giải mã chuỗi định dạng (tôi cho là hoặc một cái gì đó tương tự), trong khi đối với toán tử trường hợp cout << là một hàm bị quá tải
yves Baume

1

Đây là một con trỏ ngắn về voidcon trỏ: https://www.learncpp.com/cpp-tutorial/613-void-pointers/

6.13 - Con trỏ trống

Bởi vì con trỏ void không biết nó đang chỉ vào loại đối tượng nào, nên nó không thể được hủy đăng ký trực tiếp! Thay vào đó, con trỏ void trước tiên phải được truyền rõ ràng sang loại con trỏ khác trước khi nó được hủy đăng ký.

Nếu một con trỏ trống không biết nó đang trỏ đến cái gì, làm thế nào để chúng ta biết nên sử dụng nó vào cái gì? Cuối cùng, đó là tùy thuộc vào bạn để theo dõi.

Void con trỏ sai

Không thể thực hiện số học con trỏ trên một con trỏ trống. Điều này là do số học con trỏ yêu cầu con trỏ biết đối tượng kích thước mà nó đang trỏ tới, vì vậy nó có thể tăng hoặc giảm con trỏ một cách thích hợp.

Giả sử bộ nhớ của máy là địa chỉ byte và không yêu cầu truy cập được căn chỉnh, cách diễn giải chung chung và nguyên tử nhất (gần nhất với mức đại diện của máy) void*như là một con trỏ đến một byte , uint8_t*. Việc tạo một void*thành một uint8_t*sẽ cho phép bạn, ví dụ, in ra các byte đầu tiên 1/2/4/8 / tuy nhiên nhiều bạn muốn bắt đầu từ địa chỉ đó, nhưng bạn không thể làm gì khác.

uint8_t* byte_p = (uint8_t*)p;
for (uint8_t* i = byte_p; i < byte_p + 8; i++) {
  printf("%x ",*i);
}

0

Bạn có thể dễ dàng in một máy in void

int p=15;
void *q;
q=&p;
printf("%d",*((int*)q));

1
Ai đảm bảo voidcó kích thước tương tự int?
Ant_222

Đây không phải là kỹ thuật in một voidcon trỏ, nó in intđịa chỉ bộ nhớ mà con trỏ chứa (trong trường hợp này sẽ là giá trị của p).
Marionumber1

0

Vì C là ngôn ngữ được gõ tĩnh, gõ mạnh, bạn phải quyết định loại biến trước khi biên dịch. Khi bạn cố gắng mô phỏng tướng trong C, cuối cùng bạn sẽ cố gắng viết lại C ++, vì vậy tốt hơn là sử dụng C ++ thay thế.


0

con trỏ void là một con trỏ chung .. Địa chỉ của bất kỳ kiểu dữ liệu nào của bất kỳ biến nào đều có thể được gán cho một con trỏ void.

int a = 10;
float b = 3.14;
void *ptr;
ptr = &a;
printf( "data is %d " , *((int *)ptr)); 
//(int *)ptr used for typecasting dereferencing as int
ptr = &b;
printf( "data is %f " , *((float *)ptr));
//(float *)ptr used for typecasting dereferencing as float

0

Bạn không thể hủy đăng ký một con trỏ mà không chỉ định loại của nó vì các loại dữ liệu khác nhau sẽ có kích thước khác nhau trong bộ nhớ, tức là int là 4 byte, char là 1 byte.


-1

Về cơ bản, trong C, "các loại" là một cách để giải thích các byte trong bộ nhớ. Ví dụ, mã nào sau đây

struct Point {
  int x;
  int y;
};

int main() {
  struct Point p;
  p.x = 0;
  p.y = 0;
}

Nói "Khi tôi chạy chính, tôi muốn phân bổ 4 (kích thước của số nguyên) + 4 (kích thước của số nguyên) = 8 (tổng số byte) của bộ nhớ. Khi tôi viết '.x' dưới dạng giá trị trên một giá trị với nhãn loại Chỉ vào thời gian biên dịch, truy xuất dữ liệu từ vị trí bộ nhớ của con trỏ cộng với bốn byte. Cung cấp giá trị trả về nhãn thời gian biên dịch "int." "

Bên trong máy tính khi chạy, cấu trúc "Điểm" của bạn trông như thế này:

00000000 00000000 00000000 00000000 00000000 00000000 00000000

Và đây là void*kiểu dữ liệu của bạn có thể trông như thế nào: (giả sử máy tính 32 bit)

10001010 11111001 00010010 11000101

Điều này không trả lời câu hỏi
MM

-2

Điều này sẽ không hoạt động, nhưng void * có thể giúp rất nhiều trong việc xác định con trỏ chung cho các hàm và chuyển nó làm đối số cho một hàm khác (tương tự như gọi lại trong Java) hoặc định nghĩa nó là một cấu trúc tương tự như oop.

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.