如何检查一个变量在 C 语言中是否属于某种类型(比较两种类型) ?

在 C 语言(不是 C + +/C #)中,如何检查变量是否属于某种类型?

例如,这样的东西:

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

或者更一般的: 我如何比较两种类型,使 compare(double1,double2)的计算结果为 true,而 compare(int,double)的计算结果为 false。我也想比较不同组成的结构。

基本上,我有一个对类型为“ structa”和“ structb”的变量进行操作的函数。我想用“ struct a”变量做一件事,用“ struct b”变量做另一件事。因为 C 不支持重载,而且 void指针丢失了它的类型信息,所以我需要检查类型。顺便说一句,如果不能比较类型,那么使用 typeof操作符有什么意义呢?


Sizeof 方法对我来说似乎是一个实用的解决方案。谢谢你的帮助。我仍然觉得有点奇怪,因为类型在编译时就已经知道了,但是如果我想象一下我能看到的机器中的进程,为什么信息不是按类型存储的,而是按字节大小存储的。除了地址,大小是唯一真正相关的东西。

296032 次浏览

C does not support this form of type introspection. What you are asking is not possible in C (at least without compiler-specific extensions; it would be possible in C++, however).

In general, with C you're expected to know the types of your variable. Since every function has concrete types for its parameters (except for varargs, I suppose), you don't need to check in the function body. The only remaining case I can see is in a macro body, and, well, C macros aren't really all that powerful.

Further, note that C does not retain any type information into runtime. This means that, even if, hypothetically, there was a type comparison extension, it would only work properly when the types are known at compile time (ie, it wouldn't work to test whether two void * point to the same type of data).

As for typeof: First, typeof is a GCC extension. It is not a standard part of C. It's typically used to write macros that only evaluate their arguments once, eg (from the GCC manual):

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

The typeof keyword lets the macro define a local temporary to save the values of its arguments, allowing them to be evaluated only once.

In short, C does not support overloading; you'll just have to make a func_a(struct a *) and func_b(struct b *), and call the correct one. Alternately, you could make your own introspection system:

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

You must, of course, remember to initialize the header properly when creating these objects.

As others have mentioned, you can't extract the type of a variable at runtime. However, you could construct your own "object" and store the type along with it. Then you would be able to check it at runtime:

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

Then set the type as needed in the code:

CheesyObject o;
o.type = 1;  // or better as some define, enum value...
o.u.d = 3.14159;

As other people have already said this isn't supported in the C language. You could however check the size of a variable using the sizeof() function. This may help you determine if two variables can store the same type of data.

Before you do that, read the comments below.

C is statically typed language. You can't declare a function which operate on type A or type B, and you can't declare variable which hold type A or type B. Every variable has an explicitly declared and unchangeable type, and you supposed to use this knowledge.

And when you want to know if void * points to memory representation of float or integer - you have to store this information somewhere else. The language is specifically designed not to care if char * points to something stored as int or char.

Getting the type of a variable is, as of now, possible in C11 with the _Generic generic selection. It works at compile-time.

The syntax is a bit like that for switch. Here's a sample (from this answer):

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

To actually use it for compile-time manual type checking, you can define an enum with all of the types you expect, something like this:

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

And then use _Generic to match types to this 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)

This is crazily stupid, but if you use the code:

fprintf("%x", variable)

and you use the -Wall flag while compiling, then gcc will kick out a warning of that it expects an argument of 'unsigned int' while the argument is of type '____'. (If this warning doesn't appear, then your variable is of type 'unsigned int'.)

Best of luck!

Edit: As was brought up below, this only applies to compile time. Very helpful when trying to figure out why your pointers aren't behaving, but not very useful if needed during run time.

Gnu GCC has a builtin function for comparing types __builtin_types_compatible_p.

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

This built-in function returns 1 if the unqualified versions of the types type1 and type2 (which are types, not expressions) are compatible, 0 otherwise. The result of this built-in function can be used in integer constant expressions.

This built-in function ignores top level qualifiers (e.g., const, volatile). For example, int is equivalent to const int.

Used in your example:

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

From 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; \
})

Here you can find explanation which statements from standard and which GNU extensions above code uses.

(Maybe a bit not in scope of the question, since question is not about failure on type mismatch, but anyway, leaving it here).

As another answer mentioned, you can now do this in C11 with _Generic.

For example, here's a macro that will check if some input is compatible with another type:

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

You can use the macro like so:

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
}

This can also be used on other types, including structs. E.g.

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

And on typedefs.

typedef char* string;


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

However, it doesn't always give you the answer you expect. For instance, it can't distinguish between an array and a pointer.

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

Answer borrowed from here: http://www.robertgamble.net/2012/01/c11-generic-selections.html

One possible way is to have your variables names prepend your variable definitions with the type information.

All integers will have i_ All floats will have f_ etc..

The variable name can be got out by the #<variable_name>, This

There is a built-in function in GCC.

Built-in Function: int __builtin_types_compatible_p (type1, type2) You can use the built-in function __builtin_types_compatible_p to determine whether two types are the same.

i've searched a solution to solve the issue of controlling data type for while , and i thought that maybe my founding could add up well with the initial demand @con-f-use, even if it's no exactly the same issue.

An other way around to control the datatype could be done using an union with predefined type. In my case, i had a defined structure in which i was originally using a void* to allow divers data type to be passed : originally:

//[main]:


uint32_t vtest3= 100000;
int32_t vtest2= 100000;
struct entity list[] = {
{ TYPE_INT32,     s_int32_t,  .label="tension", &vtest3},
{ TYPE_INT32,     s_int32_t,  .label="tension", &vtest3}
};






//[file.h]:




struct entity {
enum entity_type type;
uint32_t dimension;
char* label;
void* ptr_data;
uint32_t offset;
};
enum  entity_type {
TYPE_NONE     = 0,
TYPE_INT8     = 1,
TYPE_INT16    = 2,
TYPE_INT32    = 3,
TYPE_INT64    = 4,
TYPE_UINT8    = 5,
TYPE_UINT16   = 6,
TYPE_UINT32   = 7,
TYPE_UINT64   = 8,
TYPE_FLOAT32  = 9


};

The issue with this method is that it accept all type of variable in an uncontrolled way. There is no easy method to control the data type referenced by the void* pointer, Excepted maybe thought the use of a macro and _Generic as described before in this thread.

If the programmer decided to pass a type different from the list of type accepted ,there while be no error thrown at compile time. . They other way around is by replacing the void* by an union , this way the structure while only accept specific data type defined inside the union list . If the programmer decide to pass a pointer with an type which is not already defined inside the ptr_data union{...} , it will throw an error.

//[file.h]:
enum  entity_type {
TYPE_NONE     = 0,
TYPE_INT8     = 1,
TYPE_INT16    = 2,
TYPE_INT32    = 3,
TYPE_INT64    = 4,
TYPE_UINT8    = 5,
TYPE_UINT16   = 6,
TYPE_UINT32   = 7,
TYPE_UINT64   = 8,
TYPE_FLOAT32  = 9


};
struct entity {
enum entity_type type;
uint32_t dimension;
char* label;
union {
uint8_t *uint8;
uint16_t *uint16;
uint32_t *uint32;
uint32_t *uint;
int16_t *int16;
int32_t *int32;
int64_t *int64;


float *f;
} ptr_data;


uint32_t offset;
};


[main:]


uint32_t vtest3= 100000;
int32_t vtest2= 100000;


struct entity list[] = {
{ TYPE_INT32,   s_int32_t,  .label="a", .ptr_data = {.uint16=&vtest1}
},
{ TYPE_INT32,   s_int32_t,  .label="b", .ptr_data = {.int32=&vtest2}
};

This method make use of the union to control implicitly the data type of the variable inserted by the programmer in the structure. If not correct the compiler while throw an error at compile time.

Obviously this code example is far from perfect and cannot be used directly but i tried to explain in a way as clear as possible the logic and the the idea that i proposed ;)