Как я могу проверить, является ли переменная определенного типа (сравнить два типа) в C? - PullRequest
49 голосов
/ 08 июня 2011

В C (не C ++ / C #) как проверить, относится ли переменная к определенному типу?

Например, что-то вроде этого:

double doubleVar;
if( typeof(doubleVar) == double ) {
    printf("doubleVar is of type double!");
}

Или более общий:Как сравнить два типа, так что compare(double1,double2) будет иметь значение true, а compare(int,double) - false.Также я хотел бы сравнить структуры различного состава.

По сути, у меня есть функция, которая работает с переменными типа "struct a" и "struct b".Я хочу сделать одну вещь с переменными "struct a", а другую - с переменными "struct b".Поскольку C не поддерживает перегрузку и указатель void теряет информацию о типе, мне нужно проверить тип.Кстати, какой смысл иметь оператор typeof, если вы не можете сравнивать типы?

Метод sizeof представляется мне практичным решением проблемы.Спасибо за вашу помощь.Я все еще нахожу это немного странным, так как типы известны во время компиляции, но если я представляю процессы в машине, я могу понять, почему информация хранится не в виде типов, а в виде размера байта.Размер - единственное, что действительно важно, кроме адресов.

Ответы [ 10 ]

36 голосов
/ 06 марта 2015

Получение типа переменной на данный момент возможно в C11 с общим выбором _Generic.Он работает во время компиляции.

Синтаксис немного похож на switch.Вот пример (из этого ответа ):

#define typename(x) _Generic((x),                                                 \
        _Bool: "_Bool",                  unsigned char: "unsigned char",          \
         char: "char",                     signed char: "signed char",            \
    short int: "short int",         unsigned short int: "unsigned short int",     \
          int: "int",                     unsigned int: "unsigned int",           \
     long int: "long int",           unsigned long int: "unsigned long int",      \
long long int: "long long int", unsigned long long int: "unsigned long long int", \
        float: "float",                         double: "double",                 \
  long double: "long double",                   char *: "pointer to char",        \
       void *: "pointer to void",                int *: "pointer to int",         \
      default: "other")

Чтобы фактически использовать его для ручной проверки типов во время компиляции, вы можете определить enum со всеми ожидаемыми типами, что-то вроде этого:

enum t_typename {
    TYPENAME_BOOL,
    TYPENAME_UNSIGNED_CHAR,
    TYPENAME_CHAR,
    TYPENAME_SIGNED_CHAR,
    TYPENAME_SHORT_INT,
    TYPENAME_UNSIGNED_CHORT_INT,
    TYPENAME_INT,
    /* ... */
    TYPENAME_POINTER_TO_INT,
    TYPENAME_OTHER
};

А затем используйте _Generic, чтобы сопоставить типы с этим enum:

#define typename(x) _Generic((x),                                                       \
        _Bool: TYPENAME_BOOL,           unsigned char: TYPENAME_UNSIGNED_CHAR,          \
         char: TYPENAME_CHAR,             signed char: TYPENAME_SIGNED_CHAR,            \
    short int: TYPENAME_SHORT_INT, unsigned short int: TYPENAME_UNSIGNED_SHORT_INT,     \
          int: TYPENAME_INT,                     \
    /* ... */                                    \
        int *: TYPENAME_POINTER_TO_INT,          \
      default: TYPENAME_OTHER)
16 голосов
/ 08 июня 2011

C не поддерживает эту форму самоанализа типа.То, что вы спрашиваете, невозможно в C (по крайней мере, без специфичных для компилятора расширений; это возможно в C ++, однако).

В общем, с C вы должны знать типы вашей переменной.,Поскольку каждая функция имеет конкретные типы для своих параметров (за исключением, вероятно, varargs), вам не нужно проверять тело функции.Единственный оставшийся случай, который я вижу, - это тело макроса, и, ну, макросы C на самом деле не настолько мощны.

Кроме того, обратите внимание, что C не сохраняет никакой информации о типе во время выполнения.Это означает, что, даже если бы гипотетически существовало расширение сравнения типов, оно работало бы корректно только тогда, когда типы известны во время компиляции (т. Е. Не работало бы для проверки, указывают ли два void * на один и тот же типdata).

Что касается typeof: Во-первых, typeof является расширением GCC.Это не стандартная часть C. Обычно она используется для написания макросов, которые оценивают свои аргументы только один раз, например (из руководства GCC ):

 #define max(a,b) \
   ({ typeof (a) _a = (a); \
      typeof (b) _b = (b); \
     _a > _b ? _a : _b; })

Ключевое слово typeofпозволяет макросу определять локальный временный объект для сохранения значений его аргументов, что позволяет оценивать их только один раз.

Короче говоря, C не поддерживает перегрузку;вам просто нужно набрать func_a(struct a *) и func_b(struct b *) и позвонить на правильный.Кроме того, вы можете создать свою собственную систему самоанализа:

struct my_header {
  int type;
};

#define TYPE_A 0
#define TYPE_B 1

struct a {
  struct my_header header;
  /* ... */
};

struct b {
  struct my_header header;
  /* ... */
};

void func_a(struct a *p);
void func_b(struct b *p);

void func_switch(struct my_header *head);
#define func(p) func_switch( &(p)->header )

void func_switch(struct my_header *head) {
  switch (head->type) {
    case TYPE_A: func_a((struct a *)head); break;
    case TYPE_B: func_b((struct b *)head); break;
    default: assert( ("UNREACHABLE", 0) );
  }
}

При создании этих объектов вы, конечно, должны не забывать правильно инициализировать заголовок.

9 голосов
/ 08 июня 2011

Как уже говорили другие люди, это не поддерживается на языке Си.Однако вы можете проверить размер переменной, используя функцию sizeof().Это может помочь вам определить, могут ли две переменные хранить данные одного и того же типа.

Перед этим прочитайте комментарии ниже .

5 голосов
/ 25 ноября 2015

Gnu GCC имеет встроенную функцию для сравнения типов __builtin_types_compatible_p.

https://gcc.gnu.org/onlinedocs/gcc-3.4.5/gcc/Other-Builtins.html

Эта встроенная функция возвращает 1, если неквалифицированные версии типов type1 иtype2 (которые являются типами, а не выражениями) совместимы, 0 в противном случае.Результат этой встроенной функции можно использовать в выражениях целочисленных констант.

Эта встроенная функция игнорирует квалификаторы верхнего уровня (например, const, volatile).Например, int эквивалентно const int.

Используется в вашем примере:

double doubleVar;
if(__builtin_types_compatible_p(typeof(doubleVar), double)) {
    printf("doubleVar is of type double!");
}
5 голосов
/ 08 июня 2011

Как уже упоминали другие, вы не можете извлечь тип переменной во время выполнения. Однако вы можете создать свой собственный «объект» и сохранить тип вместе с ним. Тогда вы сможете проверить это во время выполнения:

typedef struct {
   int  type;     // or this could be an enumeration
   union {
      double d;
      int i;
   } u;
} CheesyObject;

Затем установите необходимый код в коде:

CheesyObject o;
o.type = 1;  // or better as some define, enum value...
o.u.d = 3.14159;
2 голосов
/ 08 июня 2015

Это безумно глупо, но если вы используете код:

fprintf("%x", variable)

и при компиляции используете флаг -Wall, то gcc выдаст предупреждение о том, что ожидает аргумент «unsigned»int ', в то время как аргумент имеет тип' ____ '.(Если это предупреждение не появляется, то ваша переменная имеет тип «unsigned int».)

Удачи!

Редактировать: Как было сказано ниже, это относится только к времени компиляции.Очень полезно при попытке выяснить, почему ваши указатели не ведут себя, но не очень полезно при необходимости во время выполнения.

1 голос
/ 13 апреля 2018

Из linux / typecheck.h :

/*
 * Check at compile time that something is of a particular type.
 * Always evaluates to 1 so you may use it easily in comparisons.
 */
#define typecheck(type,x) \
({  type __dummy; \
    typeof(x) __dummy2; \
    (void)(&__dummy == &__dummy2); \
    1; \
})

Здесь вы можете найти объяснение, какие операторы из стандарта и какие расширения GNU над кодом используются.

(Может быть, немного не в рамках вопроса, поскольку вопрос не в сбое при несовпадении типов, а в любом случае, оставив его здесь).

0 голосов
/ 16 апреля 2018

Как уже упоминалось в другом ответе, теперь вы можете сделать это в C11 с помощью _Generic.

Например, вот макрос, который проверит, совместим ли какой-либо ввод с другим типом:

#include <stdbool.h>
#define isCompatible(x, type) _Generic(x, type: true, default: false)

Вы можете использовать макрос следующим образом:

double doubleVar;
if (isCompatible(doubleVar, double)) {
    printf("doubleVar is of type double!\n");  // prints
}

int intVar;
if (isCompatible(intVar, double)) {
    printf("intVar is compatible with double too!\n");  // doesn't print
}

Это также может быть использовано для других типов, в том числе конструкций. Э.Г.

struct A {
    int x;
    int y;
};

struct B {
    double a;
    double b;
};

int main(void)
{    
    struct A AVar = {4, 2};
    struct B BVar = {4.2, 5.6};

    if (isCompatible(AVar, struct A)) {
        printf("Works on user-defined types!\n");  // prints
    }

    if (isCompatible(BVar, struct A)) {
        printf("And can differentiate between them too!\n");  // doesn't print
    }

    return 0;
}

И по typedefs.

typedef char* string;

string greeting = "Hello world!";
if (isCompatible(greeting, string)) {
    printf("Can check typedefs.\n");
}

Однако, это не всегда дает вам ожидаемый ответ. Например, он не может различить массив и указатель.

int intArray[] = {4, -9, 42, 3};

if (isCompatible(intArray, int*)) {
    printf("Treats arrays like pointers.\n");
}

// The code below doesn't print, even though you'd think it would
if (isCompatible(intArray, int[4])) {
    printf("But at least this works.\n");
}

Ответ заимствован здесь: http://www.robertgamble.net/2012/01/c11-generic-selections.html

0 голосов
/ 20 марта 2018

Для этой цели я написал простую C-программу для этого ... Он находится в github ... GitHub Link

Вот как это работает ... Сначала конвертируйте ваш двойникв строку символов с именем s ..

char s[50];
sprintf(s,"%.2f", yo);

Затем используйте функцию dtype для определения типа ... Моя функция вернет один символ ... Вы можете использовать его следующим образом ...

char type=dtype(s);
//Return types are :
//i for integer
//f for float or decimals
//c for character...

Тогда вы можете использовать сравнение для проверки ... Вот и все ...

0 голосов
/ 08 июня 2011

C - статически типизированный язык. Вы не можете объявить функцию, которая работает с типом A или типом B, и вы не можете объявить переменную, которая содержит тип A или тип B. Каждая переменная имеет явно объявленный и неизменяемый тип, и вы должны использовать эти знания.

И когда вы хотите знать, указывает ли void * на представление в памяти чисел с плавающей точкой или целых чисел - вы должны хранить эту информацию где-то еще. Язык специально разработан так, чтобы его не волновало, если char * указывает на что-то, хранящееся как int или char .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...