Passing variable type as function parameter - c

Is it possible to pass variable type as part of a function parameter, e.g.:
void foo(varType type)
{
// Cast to global static
unsigned char bar;
bar = ((type *)(&static_array))->member;
}
I remember it has something to do with GCC's typeof and using macros?

You could make an enum for all different types possible, and use a switch to make the dereferencing:
typedef enum {
CHAR,
INT,
FLOAT,
DOUBLE
} TYPE;
void foo(TYPE t, void* x){
switch(t){
case CHAR:
(char*)x;
break;
case INT:
(int*)x;
break;
...
}
}

You can't do that for a function, because then it needs to know the types of the arguments (and any other symbols the function uses) to generate working machine code. You could try a macro like:
#define foo(type_t) ({ \
unsigned char bar; \
bar = ((type_t*)(&static_array))->member; \
... \
})

Eh, of course you can. Just use a macro like so:
#include <stdio.h>
#define swap(type, foo, bar) ({type tmp; tmp=foo; foo=bar; bar=tmp;})
int main() {
int a=3, b=0;
swap(int, a, b); // 👈 check it out!
printf("a=%d, b=%d \n", a, b); // a=0, b=3
return 0;
}

I don't see how you could do this in the general case, given that C is a statically typed language.
The compiler needs to know at compile time what the type of type * is in order to be able to generate the reference to ->member.

Related

Function overloading in C with _Generic when __VA_ARG__ can be empty

I am looking to use the _Generic preprocessor directive to achieve function overloading. I learned to use it from this wonderfully detailed answer.
However, it doesn't seem to cover this case:
#include <stdio.h>
void foo_one(int);
void foo_two(int, float*);
#define FIRST_VARG(_A, ...) _A
#define foo(_X, ...) _Generic( (FIRST_VARG(__VA_ARGS__,)), \
float* : foo_two, \
default : foo_one) (_X, __VA_ARGS__)
void foo_one(int A)
{
printf("FOO ONE: %d\n", A);
}
void foo_two(int A, float* B)
{
printf("FOO TWO: %d, %f", A, *B);
}
void main()
{
float x = 3.14;
float* y = &x;
foo(1); // This statement pops an error
foo(2, y);
}
Here, you can see that the first argument to both functions is an integer. However, the second argument of the second function is a float*. Visual Studio complains about the calling foo(1), but not when calling foo(2, y). The error is
error C2059: syntax error: ')'
I know Visual Studio can support _Generic with a small trick. So, I feel like there is something I am doing wrong. There is a comment in the answer where I learned about _Generic that suggests using (SECOND(0, ##__VA_ARGS__, 0), etc. But I don't understand it.
Can someone walk me through how I could achieve my intended result?
There are two issues. First is selecting the second argument of foo for generic selection in the case when there is no second argument.
Other is #define foo(_X, ...) which will not work for foo(1) because the function macro expect two or more arguments. It often works but it a compiler specific extensions. Compiling in pedantic mode will raise a warning. See https://godbolt.org/z/z7czvGvbc
A related issue is expanding to (_X, __VA_ARGS__)which will not work for foo(1) where ... maps to nothing.
The both issues can be addressed with placing a dummy type (NoArg) at the end of the list prior to extracting the second argument. It will both extend the list and add a value that can be used by _Generic to correctly dispatch the function expression.
#include <stdio.h>
void foo_one(int);
void foo_two(int, float*);
typedef struct { int _; } NoArg;
// use compound literal to form a dummy value for _Generic, only its type matters
#define NO_ARG ((const NoArg){0})
#define foo_(args, a, b, ...) \
_Generic((b) \
,NoArg: foo_one \
,default: foo_two \
) args
// pass copy of args as the first argument
// add NO_ARG value, only its type matters
// add dummy `~` argument to ensure that `...` in `foo_` catches something
#define foo(...) foo_((__VA_ARGS__), __VA_ARGS__, NO_ARG, ~)
void foo_one(int A)
{
printf("FOO ONE: %d\n", A);
}
void foo_two(int A, float* B)
{
printf("FOO TWO: %d, %f\n", A, B ? *B : 42.0f);
}
#define TEST 123
int main(void)
{
float x = 3.14;
float* y = &x;
foo(1); // This statement pops an error
foo(2, y);
foo(TEST, NULL);
return 0;
}
The last issue is addressed by passing a tuple with original arguments as extra argument to foo_ macro, this argument is later passed to the call operator of expression selected by _Generic.
This solution works with all major C17 compilers (gcc, clang, icc, msvc).

Is it possible to find the datatype of a variable in C?

I know it is very clearly mentioned during input operation but want to know . Like in Python3.6 we have "type()" function. Just curious about it.
You can do something with _Generic() from C11 as long as you know the types you care about ahead of time:
#include <stdio.h>
#define Type(x) _Generic((x), \
int: "int", \
short: "short", \
long: "long", \
char: "char", \
float: "float", \
double: "double", \
default: "unknown" \
)
int main(void) {
int i;
short s;
long lng;
long long llng;
char c;
float f;
double d;
printf("%s\n", Type(i));
printf("%s\n", Type(s));
printf("%s\n", Type(lng));
printf("%s\n", Type(llng));
printf("%s\n", Type(c));
printf("%s\n", Type(f));
printf("%s\n", Type(d));
}
when compiled and run produces:
int
short
long
unknown
char
float
double
No, nothing of the sorts of type() is available in C. In Python the variables do not have types. They all are references - or pointers if you will - to the actual objects. This is unlike in C where variables too are objects themselves
The type of the objects is often the type of the lvalue used to access the memory. All tricks given in the answers here only find the type compile-time type i.e. the type of the lvalue or value of expression - all that is compile-type information - the compiler, while processing the file, can find out what those types are since they were declared there, i.e. they can find out the type of the variable.
But that is not what the Python type function does: type can find out the type identity of the pointed-to object:
def foo(i):
if type(i) is int:
...
In here it is not the type of i that is being compared against int but the type of that object that is currently bound to that name i.
In C if you cast a pointer to object to void *, there's nothing to recover that information from. For example:
void do_something(void *p) {
// ???
}
...
int *a = malloc(sizeof int);
*a = 42;
void *p = a;
do_something(p);
the p points to an object of (effective) type int with current value 42, but it is of type void *. There is absolutely no generic mechanism in C for do_something to find out, given p, that it points to an object of type int. It just needs to be known.
Another possibility is to create your own variables manager through the precompiler:
#include <stdio.h>
#define STR1(x) #x
#define STR(x) STR1(x)
#define variable(x,name) x var_##name; char* type_##name = STR(x)
int main() {
variable(float, testF);
variable(int, testI);
}
See here
Python variables don't have types. Python values have types. Therefore it is sometines necessary to use type(): the same variable can hold values of different types, and the programmer doesn't necessarily know which type is currently being held.
p = foo()
print (type(p)) # could be any type
p = bar() # the type can change
p = baz() # and again
C variables have types. They are statically known and explicitly declared by the programmer. The type of the value is the type of the variable. Therefore something like type() would be redundant. For a given variable, the tyoe is always the same. It is always possible to look at the declaration. With an IDE, this can be done in one click, no matter where the declaration is.
int p = foo(); // the type of p is int
p = bar(); // the type of p is int
p = baz(); // guess what, still int
There is more to this subject, e.g. macros, generics, void pointers, casts, dynamically allocated objects and more. These are all somewhat advanced topics though. One should unserstand the basics first.

typeof uses in C, besides macros

It's well known the use of typeof in Macros to make them type independent, such as container_of() and many other macros from the Linux kernel. It is unarguable that the typeof keyword unleashes a lot of power when used in these macros.
This question is about further use of the typeof keyword. What other contexts could the keyword bring lots of gain in C code, besides Macros?
One use of typeof is to const-cast a 2-dimensional array. In gcc, the construct:
extern void foo(const int a[2][2]); // or equivalently a[][2]
int a[2][2];
foo(a);
will generate:
"warning: passing argument 1 of 'foo' from incompatible pointer type".
(See http://c-faq.com/ansi/constmismatch.html for the reason why.) One way to fix this is to use a sledge-hammer-like cast, such as:
foo((void *)a);
Such a cast will happily take whatever you, perhaps mistakenly, give it.
But we can be much more delicate. By using the casting-macro CONST_CAST_2D given in the following code sample, the warning is eliminated. And more importantly, if you try to apply it to anything other than a 2-D array, you will get a compiler error/warning. CONST_CAST_PP works similarly, for a pointer-to-a-pointer.
#define CONST_CAST_2D(x) ((const typeof((x)[0][0])(*)[countof((x)[0])])(x))
#define CONST_CAST_PP(x) ((const typeof(**(x))**)(x))
#define countof(x) (sizeof(x) / sizeof 0[x]) // semi-standard define
static void foo(const int a[][2]) {} // takes const
static void bar(const int **b) {} // takes const
int main(void) {
int a[2][2]; // non-const
int **b; // non-const
foo(CONST_CAST_2D(a)); // ok
bar(CONST_CAST_PP(b)); // ok
return 0;
}
CONST_CAST_PP provides a clean and robust solution to a commonly-asked problem, e.g.:
Double pointer const-correctness warnings in C
c compiler warning when passing a char *arr[] to a function as const char **arr
What type is the reference to an array variable?
const cast and pointers to pointers
Why it's not safe to cast `char **` to `const char **`?
Why does implicit conversion from non-const to const not happen here?
Intel C++ Compiler warning 167 when non-const argument is passed as const parameter
And CONST_CAST_2D resolves:
How to eliminate warning for passing multidimensional array as const multidimensional array?
C function const multidimensional-array argument strange warning
A second usage of typeof is to generate pointers to constants, or pointers to function return values, as shown in the following example:
#include <stdio.h>
#include <time.h>
#include <sys/socket.h>
#define AMPERSAND(x) (&(typeof(x)){x})
int main(void) {
printf("%s\n", ctime(AMPERSAND(time(0)))); // pointer to time_t
setsockopt(0, SOL_SOCKET, SO_REUSEADDR, AMPERSAND(1), sizeof 1);
return 0;
}
This allows for straight-forward function composition, rather than having to save temporaries in named variables. (Unfortunately this doesn't extend to g++.)
Some people (myself included) dislike the syntax of the C++ const_cast<> operator, because;
It seems misnamed, because it removes const.
It seems to violate DRY, because it requires a redundant type arg.
But I am wrong: it is not misnamed, since it can also add const and/or volatile "cv" qualifiers, and it only partially violates DRY, since the compiler will catch any errors. So I dislike it slightly less and use it: it is safer than the C-style cast.
Using gcc's typeof, you can have almost the same type safety in C.
The following C code sample gives a CONST_CAST(T, x) macro, and illustrates its use:
#define REMOVE_QUALIFIER(cv, T, x) /* this macro evaluates its args only once */ \
__builtin_choose_expr(__builtin_types_compatible_p(typeof(x), cv T), ((T)(x)), \
(void)0)
#define ADD_QUALIFIER(cv, T, x) /* this macro evaluates its args only once */ \
__builtin_choose_expr(__builtin_types_compatible_p(typeof(x), T), ((cv T)(x)), \
(void)0)
#ifdef __GNUC__
#define CONST_CAST(T, x) REMOVE_QUALIFIER(const, T, x) // "misnamed"
#else
#define CONST_CAST(T, x) ((T)(x)) // fallback to standard C cast
#endif
void foo(void);
void foo(void) {
const int *a = 0;
const float *x = 0;
int *b = a; // warning
int *c = (int *)a; // no warning, unsafe standard cast
int *d = (int *)x; // no warning, and likely wrong
int *e = CONST_CAST(int *, a); // ok
int *f = CONST_CAST(int *, x); // error
unsigned *g = CONST_CAST(unsigned *, a); // error
const int **h = &b; // warning
const int **i = ADD_QUALIFIER(const, int **, &b); // ok
const int **j = ADD_QUALIFIER(const, int **, &x); // error
}
This technique can also be used to change the signedness of a type, reminiscent of C++'s std::make_signed and std::make_unsigned, or Boost traits. For example:
#define MAKE_UNSIGNED(T, x) ADD_QUALIFIER(unsigned, T, x) // T usually char*
This use of gcc's typeof is yet another reinterpret cast, using union-punning.
It can be applied to scalars and structures, as well as to pointers. It gives only an R-value.
#ifdef __GNUC__
#define PUN_CAST(T, x) (((union {typeof(x) src; T dst;})(x)).dst)
#else
#define PUN_CAST(T, x) (*(T*)&(x)) //<-- classic pun: breaks strict aliasing rules
#endif
Caveat: you can use this to cast a pointer into an array of 4 or 8 bytes, or vice versa. But you can't use it to cast a pointer into another pointer, in an attempt to avoid the strict aliasing rules.

How do I check if a variable is of a certain type (compare two types) in C?

In C (not C++/C#) how do I check if a variable is of a certain type?
For example, something like this:
double doubleVar;
if( typeof(doubleVar) == double ) {
printf("doubleVar is of type double!");
}
Or more general: How do I compare two types so that compare(double1,double2) will evaluate to true, and compare(int,double) will evaluate to false. Also I'd like to compare structs of different composition as well.
Basically, I have a function that operates on variables of type "struct a" and "struct b". I want to do one thing with the "struct a" variables and the other with the "struct b" variables. Since C doesn't support overloading and the void pointer losses its type information I need to check for type. BTW, what would be the sense in having a typeof operator, if you can't compare types?
The sizeof method seems to be a practical workaround solution for me. Thanks for your help. I still find it a bit strange since the types are known at compile time, but if I imagine the processes in the machine I can see, why the information is not stored in terms of types, but rather in terms of byte size. Size is the only thing really relevant besides addresses.
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)
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 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.
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!");
}
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 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
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).
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.
As of C2x, typeof is now a part of the language's standard. This allows the creation of a macro that compares the types of two values:
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#define sametypeof(A,B) _Generic(A, typeof(B): true, default: false)
int main() {
if (sametypeof(1, 2)) {
printf("1 and 2 have the same type.\n");
} else {
printf("1 and 2 don't have the same type.\n");
}
}
(This compiles with the latest experimental version of GCC 13, using the -std=c2x flag)
If you want to compare between two types, you can use the following workaround:
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#define sametype(A,B) _Generic(*((A*)0), B: true, default: false)
int main() {
if (sametype(void*, nullptr_t)) {
printf("void* and nullptr_t are the same type.\n");
} else {
printf("void* and nullptr_t are not the same type.\n");
}
}
Although *((A*)0) is not valid code at runtime, the compiler will still be able to deduce its type as A, so it will work in _Generic, as the code itself will not run and will be discarded. (as far as I remember, this trick has worked in every C11 compliant compiler I've used, including Clang and the Tiny C Compiler)
(you also cannot just do (A)0 because 0 cannot be cast to a struct)
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.
One possible way is to have your variables names prepend your variable definitions with the type information.
Ex:
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 ;)

Typechecking macro arguments in C

Is it possible to typecheck arguments to a #define macro? For example:
typedef enum
{
REG16_A,
REG16_B,
REG16_C
}REG16;
#define read_16(reg16) read_register_16u(reg16); \
assert(typeof(reg16)==typeof(REG16));
The above code doesn't seem to work. What am I doing wrong?
BTW, I am using gcc, and I can guarantee that I will always be using gcc in this project. The code does not need to be portable.
gcc supports typeof
e.g. a typesafe min macro taken from the linux kernel
#define min(x,y) ({ \
typeof(x) _x = (x); \
typeof(y) _y = (y); \
(void) (&_x == &_y); \
_x < _y ? _x : _y; })
but it doesn't allow you to compare two types. Note though the pointer comparison which Will generate a warning - you can do a typecheck like this (also from the linux kernel)
#define typecheck(type,x) \
({ type __dummy; \
typeof(x) __dummy2; \
(void)(&__dummy == &__dummy2); \
1; \
})
Presumably you could do something similar - i.e. compare pointers to the arguments.
The typechecking in C is a bit loose for integer-related types; but you can trick the compiler by using the fact that most pointer types are incompatible.
So
#define CHECK_TYPE(var,type) { __typeof(var) *__tmp; __tmp = (type *)NULL; }
This will give a warning, "assignment from incompatible pointer type" if the types aren't the same. For example
typedef enum { A1,B1,C1 } my_enum_t;
int main (int argc, char *argv) {
my_enum_t x;
int y;
CHECK_TYPE(x,my_enum_t); // passes silently
CHECK_TYPE(y,my_enum_t); // assignment from incompatible pointer type
}
I'm sure that there's some way to get a compiler error for this.
This is an old question, But I believe I have a general answer that according to Compiler Explorer apears to work on MSVC, gcc and clang.
#define CHECK_TYPE(type,var) { typedef void (*type_t)(type); type_t tmp = (type_t)0; if(0) tmp(var);}
In each case the compiler generates a useful error message if the type is incompatible. This is because it imposes the same type checking rules used for function parameters.
It can even be used multiple times within the same scope without issue. This part surprises me somewhat. (I thought I would have to utilize "__LINE__" to get this behavior)
Below is the complete test I ran, commented out lines all generate errors.
#include <stdio.h>
#define CHECK_TYPE(type,var) { typedef void (*type_t)(type); type_t tmp = (type_t)0; if(0) tmp(var);}
typedef struct test_struct
{
char data;
} test_t;
typedef struct test2_struct
{
char data;
} test2_t;
typedef enum states
{
STATE0,
STATE1
} states_t;
int main(int argc, char ** argv)
{
test_t * var = NULL;
int i;
states_t s;
float f;
CHECK_TYPE(void *, var); //will pass for any pointer type
CHECK_TYPE(test_t *, var);
//CHECK_TYPE(int, var);
//CHECK_TYPE(int *, var);
//CHECK_TYPE(test2_t, var);
//CHECK_TYPE(test2_t *, var);
//CHECK_TYPE(states_t, var);
CHECK_TYPE(int, i);
//CHECK_TYPE(void *, i);
CHECK_TYPE(int, s); //int can be implicitly used instead of enum
//CHECK_TYPE(void *, s);
CHECK_TYPE(float, s); //MSVC warning only, gcc and clang allow promotion
//CHECK_TYPE(float *, s);
CHECK_TYPE(float, f);
//CHECK_TYPE(states_t, f);
printf("hello world\r\n");
}
In each case the compiler with -O1 and above did remove all traces of the macro in the resulting code.
With -O0 MSVC left the call to the function at zero in place, but it was rapped in an unconditional jump which means this shouldn't be a concern. gcc and clang with -O0 both remove everything except for the stack initialization of the tmp variable to zero.
No, macros can't provide you any typechecking. But, after all, why macro? You can write a static inline function which (probably) will be inlined by the compiler - and here you will have type checking.
static inline void read_16(REG16 reg16) {
read_register_16u(reg16);
}
Building upon Zachary Vander Klippe's answer, we might even go a step further (in a portable way, even though that wasn't a requirement) and additionally make sure that the size of the passed-in type matches the size of the passed-in variable using the "negative array length" trick that was commonly used for implementing static assertions in C (prior to C11, of course, which does provide the new _Static_assert keyword).
As an added benefit, let's throw in some const compatibility.
#define CHECK_TYPE(type,var) \
do {\
typedef void (*type_t) (const type);\
type_t tmp = (type_t)(NULL);\
typedef char sizes[((sizeof (type) == sizeof (var)) * 2) - 1];\
if (0) {\
const sizes tmp2;\
(void) tmp2;\
tmp (var);\
}\
} while (0)
Referencing the new typedef as a variable named tmp2 (and, additionally, referencing this variable, too) is just a method to make sure that we don't generate more warnings than necessary, c.f., -Wunused-local-typedefs and the like. We could have used __attribute__ ((unused)) instead, but that is non-portable.
This will work around the integer promotion "issue" in the original example.
Example in the same spirit, failing statements are commented out:
#include <stdio.h>
#include <stdlib.h>
#define CHECK_TYPE(type,var) \
do {\
typedef void (*type_t) (const type);\
type_t tmp = (type_t)(NULL);\
typedef char sizes[((sizeof (type) == sizeof (var)) * 2) - 1];\
if (0) {\
const sizes tmp2;\
(void) tmp2;\
tmp (var);\
}\
} while (0)
int main (int argc, char **argv) {
long long int ll;
char c;
//CHECK_TYPE(char, ll);
//CHECK_TYPE(long long int, c);
printf("hello world\n");
return EXIT_SUCCESS);
}
Naturally, even that approach isn't able to catch all issues. For instance, checking signedness is difficult and often relies on tricks assuming that a specific complement variant (e.g., two's complement) is being used, so cannot be done generically. Even less so if the type can be a structure.
To continue the idea of ulidtko, take an inline function and have it return something:
inline
bool isREG16(REG16 x) {
return true;
}
With such as thing you can do compile time assertions:
typedef char testit[sizeof(isREG16(yourVariable))];
No. Macros in C are inherently type-unsafe and trying to check for types in C is fraught with problems.
First, macros are expanded by textual substitution in a phase of compilation where no type information is available. For that reason, it is utterly impossible for the compiler to check the type of the arguments when it does macro expansion.
Secondly, when you try to perform the check in the expanded code, like the assert in the question, your check is deferred to runtime and will also trigger on seemingly harmless constructs like
a = read_16(REG16_A);
because the enumerators (REG16_A, REG16_B and REG16_C) are of type int and not of type REG16.
If you want type safety, your best bet is to use a function. If your compiler supports it, you can declare the function inline, so the compiler knows you want to avoid the function-call overhead wherever possible.

Resources