Як перевірити, чи недійсний покажчик (void *) є одним із двох типів даних?


10

Я пишу функцію, де я хотів би прийняти 2 types параметрів.

  • A string(char *)
  • A, structureде буде n кількість елементів.

І для досягнення цього я думаю використовувати простий void *тип параметра. Але я не знаю, як перевірити, чи є параметр того чи іншого типу безпечно.


10
Ти не можеш! Як мінімум, вам потрібно буде додати другий параметр до функції, який вказує, на що вказують void*точки.
Адріан Моль

4
... і якщо у вас є , щоб додати другий параметр в будь-якому випадку, ви можете просто написати, а дві окремі функції func_strі func_structі отримати перевірки типів під час компіляції.
M

Так, саме тому я думав, чи можна лише в одній функції
localhost

1
Ви не можете безпечним і портативним способом. Якщо ви досить сміливі, ви можете спробувати скористатися евристикою, щоб спробувати відгадати, чи схожі перші байти пам'яті, як можна було очікувати для персонажів, але я б не назвав це безпечним .
Серж

Якщо ви просто хочете загальну назву для рядкових і структурних функцій, ви можете використовувати _Genericмакрос. Ви також можете створити типи, що ідентифікують себе, наприклад, з тегами об'єднань , що означатиме, що ви не можете передати необмежену char *рядок. Все, напевно, більше клопоту, ніж варто.
M

Відповіді:


12

Переклад void*є
«Дорогий компілятор, це покажчик, немає ніякої додаткової інформації для вас на цьому.».

Зазвичай компілятор знає краще, ніж ви (програміст), оскільки інформація, яку він отримав раніше, і досі пам’ятає, і ви, можливо, про неї забули.
Але в цьому окремому випадку ви знаєте краще або потрібно краще знати. У всіх випадках void*інформація доступна інакше, але лише програмісту, який "випадково знає". Програміст для цього повинен надати інформацію компілятору - або краще запущеній програмі, оскільки одна з переваг a void*- це те, що інформація може змінюватися під час виконання.
Зазвичай це робиться шляхом надання інформації за допомогою додаткових параметрів функціям, іноді через контекст, тобто програма "випадково знає" (наприклад, для кожного можливого типу існує окрема функція, залежно від того, яка функція називається, передбачає тип).

Отже, врешті-решт void*не міститься інформація про тип.
Багато програмістів неправильно розуміють це як "мені не потрібно знати інформацію про тип".
Але справедливо навпаки, використання void* збільшує відповідальність програміста за відстеження інформації про тип та надання його відповідним чином програмі / компілятору.


Крім того, компілятор насправді знає, на який тип даних вказано. Отже, якщо ви переходите до якоїсь void*функції, перекинутої на неправильний тип, потім де-посилаєте на дані ... тоді викликається будь-яка не визначена поведінка.
Лундін

5

void*є депресивним для загального програмування, існує не так багато ситуацій, коли ви повинні їх використовувати сьогодні. Вони небезпечні тим, що призводять до безпеки неіснуючого типу. І як ви зазначали, ви також втрачаєте інформацію про тип, тобто вам доведеться перетягувати деякі громіздкі enumразом із void*.

Натомість слід використовувати 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".

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.