Bagaimana cara memverifikasi apakah pointer batal (void *) adalah salah satu dari dua tipe data?

10

Saya menulis fungsi di mana saya ingin menerima 2 types parameter.

  • A string(char *)
  • Di structuremana akan ada n jumlah elemen.

Dan untuk mencapai hal ini saya berpikir untuk menggunakan void *tipe parameter yang sederhana . Tetapi saya tidak tahu bagaimana memverifikasi apakah parameternya adalah satu jenis atau yang lain, aman.

localhost
sumber
10
Kamu tidak bisa! Paling tidak, Anda perlu menambahkan parameter kedua ke fungsi, yang menunjukkan apa yang menjadi void*poin.
Adrian Mole
4
... dan jika Anda harus menambahkan parameter kedua, Anda bisa menulis dua fungsi terpisah func_strdan func_structdan mendapatkan pemeriksaan jenis pada waktu kompilasi.
M Oehm
Ya itu sebabnya saya berpikir jika itu mungkin dalam satu fungsi saja
localhost
1
Anda tidak dapat dengan cara yang aman dan portabel. Jika Anda cukup berani, Anda dapat mencoba menggunakan heuristik untuk mencoba dan menebak apakah byte pertama dari memori terlihat seperti apa yang dapat Anda harapkan untuk karakter, tetapi saya tidak akan menyebut itu aman .
Serge Ballesta
Jika Anda hanya ingin nama umum untuk fungsi string dan struct, Anda bisa menggunakan _Genericmakro. Anda juga dapat membuat jenis pengenal diri, misalnya dengan serikat yang ditandai , yang berarti bahwa Anda tidak dapat melewatkan char *string mentah . Semua itu mungkin lebih banyak masalah daripada nilainya.
M Oehm

Jawaban:

12

Terjemahan void*adalah
"terhormat compiler, ini adalah pointer, seorang tidak ada informasi tambahan untuk Anda dalam hal ini.".

Biasanya kompiler tahu lebih baik daripada Anda (programmer), karena informasi yang ia dapatkan sebelumnya dan masih ingat dan Anda mungkin sudah lupa.
Tetapi dalam kasus khusus ini, Anda lebih tahu atau perlu tahu lebih baik. Dalam semua kasus void*informasi tersedia sebaliknya, tetapi hanya untuk programmer, yang "kebetulan tahu". Pemrogram karenanya harus memberikan informasi kepada kompiler - atau lebih baik untuk menjalankan program, karena satu kelebihan yang void*dimiliki adalah bahwa informasi dapat berubah selama runtime.
Biasanya itu dilakukan dengan memberikan informasi melalui parameter tambahan ke fungsi, kadang-kadang melalui konteks, yaitu program "kebetulan tahu" (misalnya untuk setiap jenis yang mungkin ada fungsi yang terpisah, mana fungsi yang dipanggil menyiratkan jenis).

Jadi pada akhirnya void*tidak berisi info jenis.
Banyak programmer salah memahami hal ini sebagai "Saya tidak perlu tahu tipe info".
Tetapi yang sebaliknya adalah benar, penggunaan void* meningkatkan tanggung jawab programmer untuk melacak tipe info dan memberikannya secara tepat ke program / kompiler.

Yunnosch
sumber
Selain itu, kompiler sebenarnya tahu apa tipe data yang diarahkan. Jadi jika Anda melompat ke beberapa void*fungsi, pilih tipe yang salah, lalu hapus referensi datanya ... maka semua jenis perilaku tidak terdefinisi dipanggil.
Lundin
5

void*agak usang untuk pemrograman generik, tidak ada banyak situasi di mana Anda harus menggunakannya saat ini. Mereka berbahaya karena menyebabkan keamanan tipe yang tidak ada. Dan seperti yang Anda catat, Anda juga kehilangan informasi jenis, yang berarti Anda harus menyeret beberapa rumit enumbersama dengan void*.

Sebagai gantinya Anda harus menggunakan C11 _Genericyang dapat memeriksa jenis pada waktu kompilasi dan menambahkan keamanan jenis. Contoh:

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

Ingatlah untuk menyediakan constversi yang memenuhi syarat ( ) dari semua jenis yang ingin Anda dukung.


Jika Anda ingin kesalahan kompiler yang lebih baik ketika pemanggil melewati tipe yang salah, Anda bisa menambahkan pernyataan statis:

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

Jika Anda mencoba sesuatu seperti int x; func(x);Anda akan mendapatkan pesan kompiler "x: incorrect type".

Lundin
sumber