วิธีการตรวจสอบว่าเป็นโมฆะตัวชี้ (void *) เป็นหนึ่งในสองชนิดข้อมูล?


10

ฉันกำลังเขียนฟังก์ชันที่ฉันต้องการยอมรับtypeพารามิเตอร์2 วินาที

  • A string(อักขระ *)
  • structureที่จะมี n จำนวนขององค์ประกอบ

และเพื่อให้บรรลุนี้ฉันคิดว่าใช้ง่ายvoid *เป็นพารามิเตอร์ประเภท แต่ฉันไม่ทราบวิธีการตรวจสอบว่าพารามิเตอร์เป็นประเภทหนึ่งหรืออื่น ๆ อย่างปลอดภัย


10
คุณทำไม่ได้! อย่างน้อยที่สุดคุณจะต้องเพิ่มพารามิเตอร์ตัวที่สองในฟังก์ชั่นซึ่งระบุถึงสิ่งที่void*ชี้ไป
Adrian Mole

4
... และถ้าคุณมีการเพิ่มพารามิเตอร์ที่สองอย่างไรก็ตามคุณสามารถเพียงเช่นกันเขียนสองฟังก์ชั่นที่แยกต่างหากfunc_strและfunc_structและได้รับการตรวจสอบชนิดที่รวบรวมเวลา
M Oehm

ใช่นั่นคือเหตุผลที่ฉันคิดว่ามันเป็นไปได้ในฟังก์ชั่นเดียวเท่านั้น
localhost

1
คุณไม่สามารถปลอดภัยและพกพาได้ หากคุณมีความกล้าหาญพอที่คุณสามารถพยายามที่จะใช้การวิเคราะห์พฤติกรรมและพยายามเดาว่าไบต์แรกของหน่วยความจำลักษณะเหมือนสิ่งที่คุณสามารถคาดหวังสำหรับตัวอักษร แต่ฉันจะไม่เรียกว่าปลอดภัย
Serge Ballesta

หากคุณต้องการชื่อสามัญสำหรับฟังก์ชันสตริงและ struct คุณสามารถใช้_Genericแมโครได้ คุณสามารถสร้างประเภทการระบุตนเองเช่นสหภาพที่ติดแท็กซึ่งหมายความว่าคุณไม่สามารถส่งchar *สตริงดิบได้ สิ่งที่อาจเป็นปัญหามากกว่าที่ควรจะเป็น
M Oehm

คำตอบ:


12

คำแปลของvoid*คือ
"Dear compiler นี่คือตัวชี้และไม่มีข้อมูลเพิ่มเติมสำหรับคุณในเรื่องนี้"

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

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


นอกจากนี้คอมไพเลอร์รู้จริงว่าข้อมูลประเภทใดชี้ไปที่ ดังนั้นหากคุณกระโดดเข้าไปในvoid*ฟังก์ชั่นบางตัวให้ส่งไปยังประเภทที่ไม่ถูกต้องแล้วยกเลิกการอ้างอิงข้อมูล ... ดังนั้นพฤติกรรมที่ไม่ได้กำหนดจะถูกเรียกใช้
Lundin

5

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

คุณควรใช้ C11 _Genericซึ่งสามารถตรวจสอบประเภทของเวลาคอมไพล์และเพิ่มความปลอดภัยของประเภท ตัวอย่าง:

#include <stdio.h>

typedef struct
{
  int n;
} s_t; // some struct

void func_str (const char* str)
{
  printf("Doing string stuff: %s\n", str);
}

void func_s (const s_t* s)
{
  printf("Doing struct stuff: %d\n", s->n);
}

#define func(x) _Generic((x),              \
  char*: func_str, const char*: func_str,  \
  s_t*:  func_s,   const s_t*:  func_s)(x) \


int main()
{
  char str[] = "I'm a string";
  s_t s = { .n = 123 };

  func(str);
  func(&s); 
}

จำไว้ว่าให้constทุกประเภทที่มีคุณสมบัติ ( ) ที่คุณต้องการสนับสนุน


หากคุณต้องการข้อผิดพลาดคอมไพเลอร์ที่ดีขึ้นเมื่อผู้เรียกผ่านประเภทผิดคุณสามารถเพิ่มการยืนยันแบบคงที่:

#define type_check(x) _Static_assert(_Generic((x), \
  char*:   1,  const char*: 1,  \
  s_t*:    1,  const s_t*:  1,  \
  default: 0), #x": incorrect type.")

#define func(x) do{ type_check(x); _Generic((x),     \
  char*: func_str, const char*: func_str,            \
  s_t*:  func_s,   const s_t*:  func_s)(x); }while(0) 

ถ้าคุณพยายามบางอย่างเช่นคุณจะได้รับข้อความคอมไพเลอร์int x; func(x);"x: incorrect type"

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