แนวคิดของตัวชี้โมฆะในการเขียนโปรแกรม C


129

เป็นไปได้หรือไม่ที่จะตรวจสอบตัวชี้โมฆะโดยไม่ต้องใช้การพิมพ์ในภาษาโปรแกรม C?

นอกจากนี้มีวิธีการทั่วไปฟังก์ชั่นที่สามารถรับตัวชี้และเก็บไว้ในโมฆะตัวชี้และโดยใช้ตัวชี้โมฆะนั้นเราสามารถทำฟังก์ชั่นทั่วไป?

สำหรับเช่น:

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
}

ฉันต้องการทำให้ฟังก์ชั่นนี้เป็นแบบทั่วไปโดยไม่ต้องใช้คำสั่ง if-else - เป็นไปได้ไหม?

นอกจากนี้หากมีบทความทางอินเทอร์เน็ตที่ดีซึ่งอธิบายแนวคิดของตัวชี้โมฆะมันจะมีประโยชน์ถ้าคุณสามารถระบุ URL ได้

นอกจากนี้การคำนวณทางคณิตศาสตร์ด้วยพอยน์เตอร์พอยน์เตอร์จึงเป็นไปได้หรือไม่?

คำตอบ:


97

เป็นไปได้หรือไม่ที่จะตรวจสอบ void pointer โดยไม่ใช้การพิมพ์ในภาษาโปรแกรม C ...

ไม่voidบ่งชี้ว่าไม่มีประเภทไม่ใช่สิ่งที่คุณสามารถอ้างถึงหรือมอบหมายให้

มีวิธีการทั่วไปฟังก์ชั่นที่สามารถรับตัวชี้และเก็บไว้ในโมฆะตัวชี้และโดยใช้ตัวชี้โมฆะที่เราสามารถทำให้ฟังก์ชั่นทั่วไปนี้ ..

คุณไม่สามารถอ้างถึงในแบบพกพาได้เนื่องจากอาจไม่ได้รับการจัดแนวอย่างเหมาะสม มันอาจเป็นปัญหาสำหรับสถาปัตยกรรมบางอย่างเช่น ARM ซึ่งตัวชี้ไปยังชนิดข้อมูลจะต้องจัดตำแหน่งที่ขอบเขตของขนาดของชนิดข้อมูล (เช่นตัวชี้ไปยังจำนวนเต็ม 32- บิตต้องถูกจัดตำแหน่งที่ขอบเขต 4 ไบต์เพื่อยกเลิกการอ้างอิง)

ตัวอย่างเช่นการอ่านuint16_tจาก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);

นอกจากนี้การคำนวณทางคณิตศาสตร์ด้วยพอยน์เตอร์พอยน์เตอร์ก็เป็นไปได้ ...

การคำนวณตัวชี้เป็นไปไม่ได้ที่ตัวชี้voidเนื่องจากไม่มีค่าคอนกรีตภายใต้ตัวชี้และด้วยเหตุนี้ขนาด

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

2
หากฉันจำไม่ถูกต้องคุณสามารถยกเลิกช่องว่าง * ได้สองวิธี การส่งไปยังถ่าน * เป็นที่ยอมรับได้เสมอและหากคุณรู้ว่าประเภทดั้งเดิมนั้นชี้ไปที่คุณสามารถส่งไปเป็นประเภทนั้นได้ โมฆะ * สูญเสียข้อมูลประเภทดังนั้นจะต้องเก็บไว้ที่อื่น
Dan Olson

1
ใช่คุณสามารถพูดพ้องต้องกันได้หากคุณแสดงเป็นประเภทอื่น แต่คุณไม่สามารถทำแบบเดียวกันได้หากคุณไม่ได้แสดง กล่าวโดยสรุปคอมไพเลอร์จะไม่ทราบว่าคำแนะนำการประกอบที่จะใช้สำหรับการดำเนินการทางคณิตศาสตร์จนกว่าคุณจะส่ง
Zachary Hamm

20
ฉันคิดว่า GCC ปฏิบัติกับเลขคณิตในพvoid *อยน์เตอร์เหมือนกันchar *แต่มันไม่ได้มาตรฐานและคุณไม่ควรเชื่อถือ
ephemient

5
ฉันไม่แน่ใจว่าฉันทำตาม ในตัวอย่างที่ว่า OP ที่อยู่เหนือประเภทขาเข้านั้นรับประกันว่าถูกต้องโดยผู้ใช้ของฟังก์ชั่น คุณกำลังบอกว่าถ้าเรามีบางอย่างที่เรากำหนดค่าของชนิดที่รู้จักให้กับตัวชี้ให้โยนมันไปที่ตัวชี้เป็นโมฆะและจากนั้นก็โยนมันกลับไปที่ประเภทตัวชี้ดั้งเดิมที่มันอาจกลายเป็นไม่เป็นแนว? ฉันไม่เข้าใจว่าทำไมคอมไพเลอร์จะบ่อนทำลายคุณเช่นนั้นเพียงแค่ไม่ทำการจัดแนวแบบสุ่มเพียงเพราะคุณส่งไปยังพอยน์เตอร์โมฆะ หรือคุณเพียงแค่บอกว่าเราไม่สามารถเชื่อใจผู้ใช้ให้รู้ว่าประเภทของการขัดแย้งที่พวกเขาส่งผ่านไปยังฟังก์ชั่น?
doliver

2
@doliver ฉันหมายถึง # 2 ("เราไม่สามารถเชื่อถือได้ว่าผู้ใช้รู้ประเภทของอาร์กิวเมนต์ที่ส่งผ่านไปยังฟังก์ชัน") แต่กลับมาทบทวนคำตอบของฉันเมื่อ 6 ปีที่แล้ว (เอ่อมันนานขนาดนั้นจริงมั้ย) ฉันเห็นสิ่งที่คุณหมายถึงมันไม่ได้เป็นอย่างนั้นเสมอไป: เมื่อตัวชี้ดั้งเดิมชี้ไปที่ประเภทที่เหมาะสม ปลอดภัยที่จะส่งโดยไม่มีปัญหาการจัดตำแหน่ง
Alex B

31

ใน C void *สามารถแปลงเป็นตัวชี้ไปยังวัตถุชนิดอื่นโดยไม่ต้องใช้ตัวแปลงที่ชัดเจน:

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

สิ่งนี้ไม่ได้ช่วยในการเขียนฟังก์ชั่นของคุณในทางที่ทั่วไปมากขึ้น

คุณไม่สามารถยกเลิกการลงทะเบียนvoid *ด้วยการแปลงเป็นชนิดตัวชี้อื่นเนื่องจากการยกเลิกการอ้างอิงตัวชี้กำลังรับค่าของวัตถุแบบชี้ไปที่ การเปลือยกายvoidไม่ได้เป็นประเภทที่ถูกต้องดังนั้นจึงไม่สามารถยกเลิกการลงทะเบียนvoid *ได้

เลขคณิตของตัวชี้เป็นการเปลี่ยนแปลงค่าของตัวชี้โดยทวีคูณของsizeofวัตถุที่ชี้ไปที่ อีกครั้งเพราะvoidไม่ได้เป็นจริงชนิด, ไม่มีความหมายเพื่อคำนวณตัวชี้ไม่ถูกต้องในsizeof(void) void *(การใช้งานบางอย่างอนุญาตให้ใช้โดยใช้ตัวชี้เลขคณิตเทียบเท่าสำหรับchar *)


1
@CharlesBailey void abc(void *a, int b)ในรหัสของคุณที่คุณเขียน แต่ทำไมไม่ใช้void abc(int *a, int b)เพราะในที่สุดฟังก์ชั่นของคุณคุณจะกำหนดint *test = a;มันจะบันทึกหนึ่งตัวแปรและฉันไม่เห็นข้อได้เปรียบอื่น ๆ โดยการกำหนดตัวแปรอื่น ฉันเห็นโค้ดจำนวนมากเขียนในลักษณะนี้โดยใช้void *เป็นพารามิเตอร์และการคัดเลือกตัวแปรในภายหลังในฟังก์ชั่น แต่เนื่องจาก funtion นี้เขียนโดยผู้แต่งดังนั้นเขาต้องรู้การใช้ตัวแปรดังนั้นทำไมต้องใช้void *? ขอบคุณ
Lion Lai

15

คุณควรทราบว่าใน C ซึ่งแตกต่างจาก Java หรือ C # ไม่มีความเป็นไปได้อย่างแน่นอนที่จะ "เดา" ชนิดของวัตถุที่void*ชี้ไปที่สำเร็จ สิ่งที่คล้ายกับgetClass()ไม่มีอยู่เนื่องจากข้อมูลนี้ไม่สามารถพบได้ ด้วยเหตุผลดังกล่าวประเภท "ทั่วไป" ที่คุณกำลังค้นหามักจะมาพร้อมกับข้อมูลที่ชัดเจนเช่นint bในตัวอย่างของคุณหรือสตริงรูปแบบในprintfตระกูลฟังก์ชัน


7

ตัวชี้โมฆะเรียกว่าตัวชี้ทั่วไปซึ่งสามารถอ้างถึงตัวแปรของชนิดข้อมูลใด ๆ


6

จนถึงตอนนี้ความเข้าใจของฉันในตัวชี้โมฆะมีดังนี้

เมื่อตัวแปรตัวชี้ถูกประกาศโดยใช้คำสำคัญโมฆะ - มันจะกลายเป็นตัวแปรตัวชี้วัตถุประสงค์ทั่วไป ที่อยู่ของตัวแปรใด ๆ ของชนิดข้อมูลใด ๆ (ถ่าน, int, ลอย ฯลฯ ) สามารถกำหนดให้กับตัวแปรโมฆะตัวชี้

main()
{
    int *p;

    void *vp;

    vp=p;
} 

เนื่องจากตัวชี้ชนิดข้อมูลอื่นสามารถกำหนดให้เป็นโมฆะตัวชี้ดังนั้นฉันใช้มันในฟังก์ชั่น absolut_value (รหัสที่แสดงด้านล่าง) เพื่อให้เป็นฟังก์ชั่นทั่วไป

ฉันพยายามเขียนรหัส C แบบง่ายซึ่งใช้จำนวนเต็มหรือลอยเป็นอาร์กิวเมนต์และพยายามทำให้มันเป็น + ถ้าลบ ฉันเขียนรหัสต่อไปนี้

#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;
}   

แต่ฉันได้รับข้อผิดพลาดดังนั้นฉันจึงได้รู้ว่าความเข้าใจของฉันเกี่ยวกับตัวชี้โมฆะนั้นไม่ถูกต้อง :( ดังนั้นตอนนี้ฉันจะย้ายไปยังการรวบรวมคะแนนว่าทำไมถึงเป็นเช่นนั้น

สิ่งที่ฉันต้องเข้าใจเพิ่มเติมเกี่ยวกับตัวชี้โมฆะคือ

เราจำเป็นต้องพิมพ์ตัวแปรตัวชี้โมฆะเพื่อยกเลิกการพิจารณา นี่เป็นเพราะตัวชี้โมฆะไม่มีชนิดข้อมูลที่เกี่ยวข้อง ไม่มีทางที่คอมไพเลอร์สามารถรู้ (หรือเดา?) ข้อมูลประเภทใดที่ชี้ไปที่ตัวชี้โมฆะ ดังนั้นในการนำข้อมูลที่ชี้ไปโดยตัวชี้โมฆะเราพิมพ์มันด้วยชนิดของข้อมูลที่ถูกเก็บไว้ภายในตำแหน่งตัวชี้โมฆะ

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) );

}

ตัวชี้โมฆะอาจมีประโยชน์จริง ๆ ถ้าโปรแกรมเมอร์ไม่แน่ใจเกี่ยวกับชนิดข้อมูลของข้อมูลที่ป้อนโดยผู้ใช้ ในกรณีเช่นนี้โปรแกรมเมอร์สามารถใช้ตัวชี้เป็นโมฆะเพื่อชี้ไปยังตำแหน่งของชนิดข้อมูลที่ไม่รู้จัก โปรแกรมสามารถตั้งค่าในลักษณะที่จะขอให้ผู้ใช้แจ้งประเภทของข้อมูลและประเภทการหล่อสามารถดำเนินการตามข้อมูลที่ป้อนโดยผู้ใช้ ข้อมูลโค้ดระบุไว้ด้านล่าง

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
}

อีกจุดสำคัญที่คุณควรทราบเกี่ยวกับตัวชี้โมฆะคือ - ตัวชี้คณิตศาสตร์ไม่สามารถทำได้ในตัวชี้โมฆะ

void *ptr;

int a;

ptr=&a;

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

ดังนั้นตอนนี้ฉันเข้าใจสิ่งที่ผิดพลาดของฉัน ฉันกำลังแก้ไขเดียวกัน

การอ้างอิง:

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

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

รหัสใหม่ดังแสดงด้านล่าง


#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;
}   

ขอบคุณ,



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

คือโปรแกรมนี้เป็นไปได้ .... โปรดตรวจสอบหากเป็นไปได้ในการเขียนโปรแกรม C ...
AGeek

2
ใช่มันเป็นไปได้ (แม้ว่ารหัสจะเป็นอันตรายโดยไม่ต้องตรวจสอบช่วง b) Printf รับจำนวนตัวแปรที่ขัดแย้งกันและ a ถูกกดลงบนสแต็กเป็นตัวชี้ใด ๆ (โดยไม่มีข้อมูลชนิดใด ๆ ) และโผล่ภายในฟังก์ชัน printf โดยใช้แมโคร va_arg โดยใช้ข้อมูลจากสตริงรูปแบบ
hlovdal

@SiegeX: โปรดอธิบายสิ่งที่ไม่ได้พกพาและสิ่งที่อาจไม่ได้กำหนด
Gauthier

@Gauthier ส่งผ่านตัวแปรที่มีตัวระบุรูปแบบไม่ใช่แบบพกพาและพฤติกรรมที่ไม่ได้กำหนดไว้ หากคุณต้องการที่จะทำอะไรแบบนั้นแล้วมองไปที่รสชาติ 'VS' printfของ คำตอบนี้มีตัวอย่างที่ดี
SiegeX

ในทางปฏิบัติฉันอาจจะแทนที่int bด้วย enum แต่การเปลี่ยนแปลงใด ๆ ในภายหลังกับ enum นั้นจะทำลายฟังก์ชันนี้
Jack Stout

1

ฉันต้องการสร้างฟังก์ชั่นนี้โดยไม่ใช้ ifs; เป็นไปได้ไหม?

วิธีง่าย ๆ เพียงอย่างเดียวที่ฉันเห็นคือการใช้การโหลดมากเกินไป ..

คุณพิจารณาการเขียนโปรแกรมภาษาซีพลัสพลัสสำหรับโปรแกรมของคุณหรือไม่? หรือมีข้อ จำกัด ใด ๆ ที่ห้ามการใช้งานหรือไม่?


ตกลงนั่นเป็นความอัปยศ จากมุมมองของฉันฉันไม่เห็นทางออกเลย ที่จริงแล้วมันเป็นเช่นเดียวกับ printf () & cout: 2 วิธีที่แตกต่างในการใช้งานการพิมพ์ printf () การใช้งาน (ถ้ามี) คำสั่งเพื่อถอดรหัสสตริงรูปแบบ (ฉันคิดหรือสิ่งที่คล้ายกัน) ในขณะที่ผู้ประกอบการสำหรับกรณีที่ศาล << เป็นฟังก์ชั่นมากเกินไป
Yves Baumes

1

นี่คือตัวชี้สั้น ๆ เกี่ยวกับพvoidอยน์เตอร์: https://www.learncpp.com/cpp-tutorial/613-void-pointers/

6.13 - ตัวชี้โมฆะ

เนื่องจากตัวชี้โมฆะไม่ทราบว่าวัตถุชนิดใดที่ชี้ไปจึงไม่สามารถอ้างถึงโดยตรงได้! แต่ตัวชี้โมฆะจะต้องส่งไปยังตัวชี้ประเภทอื่นอย่างชัดเจนก่อนที่จะถูกยกเลิกการลงทะเบียน

หากตัวชี้โมฆะไม่ทราบว่ามันชี้ไปยังอะไรเราจะรู้ได้อย่างไรว่าจะใช้มันยังไง? ท้ายที่สุดมันก็ขึ้นอยู่กับคุณที่จะติดตาม

ตัวชี้โมฆะของเรื่องจิปาถะ

มันเป็นไปไม่ได้ที่จะทำการคำนวณทางคณิตศาสตร์บนตัวชี้โมฆะ เนื่องจากเลขคณิตของตัวชี้ต้องใช้ตัวชี้เพื่อทราบขนาดของวัตถุที่ชี้ไปเพื่อให้สามารถเพิ่มหรือลดตัวชี้อย่างเหมาะสม

สมมติว่าหน่วยความจำของเครื่องสามารถกำหนดแอดเดรสแบบไบต์ได้และไม่จำเป็นต้องเข้าถึงแบบจัดเรียงวิธีการทั่วไปที่มากที่สุดและเป็นอะตอมvoid*มิuint8_t*ก ยกตัวอย่างเช่นการส่ง a void*ไปยัง a uint8_t*ช่วยให้คุณพิมพ์ไบต์แรก 1/2/4/8 / อย่างไรก็ตามหลายคนที่คุณต้องการเริ่มต้นจากที่อยู่นั้น แต่คุณไม่สามารถทำอะไรได้มากนัก

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

0

คุณสามารถพิมพ์เครื่องพิมพ์ที่เป็นโมฆะได้อย่างง่ายดาย

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

1
ใครรับประกันvoidได้ว่ามีขนาดเท่ากันint?
Ant_222

นี่ไม่ใช่การพิมพ์voidตัวชี้ทางเทคนิคแต่เป็นการพิมพ์intที่ที่อยู่หน่วยความจำที่ตัวชี้ประกอบด้วย (ซึ่งในกรณีนี้จะเป็นค่าของp)
Marionumber1

0

เนื่องจาก C เป็นภาษาที่พิมพ์อย่างคงที่และมีการพิมพ์ที่รุนแรงคุณต้องเลือกประเภทของตัวแปรก่อนคอมไพล์ เมื่อคุณพยายามเลียนแบบยาชื่อสามัญใน C คุณจะต้องพยายามเขียน C ++ ใหม่อีกครั้งดังนั้นจึงควรใช้ C ++ แทน


0

ตัวชี้โมฆะเป็นตัวชี้ทั่วไป .. ที่อยู่ของประเภทข้อมูลของตัวแปรใด ๆ ที่สามารถกำหนดให้กับตัวชี้โมฆะ

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

คุณไม่สามารถอ้างถึงตัวชี้โดยไม่ระบุชนิดของมันเนื่องจากชนิดข้อมูลที่แตกต่างกันจะมีขนาดต่างกันในหน่วยความจำเช่น int กำลัง 4 ไบต์, อักขระถ่านเป็น 1 ไบต์


-1

พื้นฐานใน C "ประเภท" เป็นวิธีการตีความไบต์ในหน่วยความจำ ตัวอย่างเช่นรหัสอะไรต่อไปนี้

struct Point {
  int x;
  int y;
};

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

กล่าวว่า "เมื่อฉันเรียกใช้ main ฉันต้องการจัดสรร 4 (ขนาดของจำนวนเต็ม) + 4 (ขนาดของจำนวนเต็ม) = 8 (bytes ทั้งหมด) ของหน่วยความจำเมื่อฉันเขียน '.x' เป็น lvalue บนค่าที่มีป้ายกำกับประเภท ชี้เวลารวบรวมข้อมูลจากตำแหน่งหน่วยความจำของตัวชี้บวกสี่ไบต์ให้ค่าส่งคืนป้ายกำกับเวลารวบรวม "int." "

ภายในคอมพิวเตอร์ตอนรันไทม์โครงสร้าง "จุด" ของคุณจะเป็นดังนี้:

00000000 00000000 00000000 00000000 00000000 00000000 00000000

และนี่คือสิ่งvoid*ที่ประเภทข้อมูลของคุณอาจมีลักษณะ: (สมมติว่าคอมพิวเตอร์ 32 บิต)

10001010 11111001 00010010 11000101

สิ่งนี้ไม่ตอบคำถาม
MM

-2

สิ่งนี้จะไม่ทำงาน แต่ void * สามารถช่วยได้มากในการกำหนดตัวชี้ทั่วไปให้กับฟังก์ชันและส่งผ่านเป็นอาร์กิวเมนต์ให้กับฟังก์ชันอื่น (คล้ายกับ callback ใน Java) หรือกำหนดโครงสร้างคล้ายกับ oop

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.