Skip to content

Commit f4d4088

Browse files
author
Craig Ringer
committed
c11 _Generic __typeinfo to const char * str test
An experiment showing that C11's _Generic can be used, albeit awkwardly, to get type info as strings, then generate an argument-type static array to pass to a function along with the macro varargs. The called function can then consume the arguments with va_ functions or pass them to vsprintf etc.
1 parent 7516769 commit f4d4088

File tree

4 files changed

+364
-0
lines changed

4 files changed

+364
-0
lines changed

c/c11_generic_typename/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
test_*

c/c11_generic_typename/Makefile

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
CVERSIONS := gnu99 gnu11
2+
3+
test_%_preprocessed.c: c11_generic_typename.c
4+
gcc -E -Wall -Wextra -DNO_WARN_MISSING_GENERIC=1 -std=$* > $@ $<
5+
6+
test_%: test_%_preprocessed.c
7+
gcc -Wall -Wextra -DNO_WARN_MISSING_GENERIC=1 -std=$* -o $@ $<
8+
9+
define runtest_ver
10+
runtest_$(CVERSION): test_$(CVERSION)
11+
./$$< || echo "Exited with $$$$?"
12+
.PHONY: runtest_$(CVERSION)
13+
.PRECIOUS: test_$(CVERSION) test_$(CVERSION)_preprocessed.c
14+
endef
15+
16+
$(foreach CVERSION,$(CVERSIONS),$(eval $(runtest_ver)))
17+
18+
clean:
19+
rm -f $(foreach CVERSION,$(CVERSIONS),test_$(CVERSION) test_$(CVERSION)_preprocessed.c)
20+
21+
runall: $(foreach CVERSION,$(CVERSIONS),runtest_$(CVERSION))
22+
.DEFAULT_GOAL := runall
Lines changed: 326 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,326 @@
1+
/*
2+
* A fun experiment in using C11 _Generic to convert typenames to strings,
3+
* then use that to auto-generate arrays of type-names to pass to a function
4+
* along with a variadic argument list.
5+
*
6+
* That way the function can be called with many different argument lists
7+
* and type lists, e.g. for tracing/debug/logging purposes.
8+
*
9+
* I'm thinking of adapting the approach for use with systemtap SDTs, to
10+
* auto-generate data-type markers for the SDT tracepoints and insert them into
11+
* a suitable .stapsdt ELF section as const-data. Combined with stringifying
12+
* the argument names, that'd allow SDTs to be largely self-describing.
13+
*
14+
* It's not perfect since the _Generic can only understand a concrete list of
15+
* types. You can't pass any struct of your choice to it. It's also only going
16+
* to supply typenames, it doesn't appear possible to easily obtain any sort of
17+
* DWARF Die entry pointer or anything.
18+
*
19+
* An alternative might be to post-process the executable after compilation.
20+
* Generate the executable with -g3 so macro debuginfo is included. Read the
21+
* stap probes ELF section. Find the defining macros. Enumerate their
22+
* arguments, argument names (where simple variables) and argument types if
23+
* possible. Create a new section with the info and insert it into the
24+
* executable.
25+
*/
26+
27+
#include <stdio.h>
28+
#include <string.h>
29+
#include <stdlib.h>
30+
#include <stdarg.h>
31+
#include <assert.h>
32+
33+
#if __STDC_VERSION__ >= 201112L
34+
#define HAVE_TYPENAME
35+
#else
36+
#ifndef NO_WARN_MISSING_GENERIC
37+
#warning no c11 generic support available with STDC_VERSION = __STDC_VERSION__
38+
#endif
39+
#endif
40+
41+
struct typeformat {
42+
const char * const name;
43+
const char * const fmt;
44+
};
45+
46+
static const struct typeformat typeformats[] = {
47+
{"int", "%d"},
48+
{"unsigned int", "%u"},
49+
{"long", "%ld"},
50+
{"unsigned long", "%lu"},
51+
{"char", "%c"},
52+
{"signed char", "%c"},
53+
{"unsigned char", "%c"},
54+
{"char *", "%s"},
55+
{"const signed char", "%c"},
56+
{"const unsigned char", "%c"},
57+
{"const char *", "%s"},
58+
{"signed const char *", "%s"},
59+
{"unsigned const char *", "%s"},
60+
{"const void *", "%p"}
61+
};
62+
63+
#ifdef HAVE_TYPENAME
64+
/*
65+
* Note on _Generic:
66+
*
67+
* C11 only
68+
*
69+
* A unique type may only appear once.
70+
*
71+
* const qualifiers are ignored for the variable type itself, but not for the
72+
* target type of pointer types. So these are the same:
73+
*
74+
* int
75+
* const int
76+
*
77+
* and these are the same:
78+
*
79+
* char *
80+
* char * const
81+
*
82+
* but these are distinct:
83+
*
84+
* char *
85+
* const char *
86+
*
87+
* Qualifier order is not significant, so these are the same:
88+
*
89+
* signed const char *
90+
* const signed char *
91+
*
92+
*/
93+
94+
/* Map an expression to a type name */
95+
#define TYPENAME(x) _Generic((x), \
96+
int: "int", \
97+
unsigned int: "unsigned int", \
98+
long: "long", \
99+
unsigned long: "unsigned long", \
100+
char: "char", \
101+
signed char: "signed char", \
102+
unsigned char: "unsigned char", \
103+
char *: "const char *", \
104+
signed char *: "const signed char *", \
105+
unsigned char *: "const unsigned char *", \
106+
const char *: "const char *", \
107+
const signed char *: "const signed char *", \
108+
const unsigned char *: "const unsigned char *", \
109+
void *: "void *", \
110+
const void *: "void *", \
111+
default: ("TYPENAME(" #x "): ERROR unhandled type") \
112+
)
113+
#else
114+
#define TYPENAME(x) ""
115+
#endif
116+
117+
#define Expand1(x) x
118+
#define Expand(x) Expand1(x)
119+
120+
/* Create the __auto_type declaration list */
121+
#define _UNTYPED_ARG(x,n) const __auto_type arg ## n __attribute__((unused)) = x;
122+
#define UNTYPED_ARG1(x) _UNTYPED_ARG((x), 1)
123+
#define UNTYPED_ARG2(x,y) UNTYPED_ARG1(y); _UNTYPED_ARG((x),2)
124+
#define UNTYPED_ARG3(x,...) UNTYPED_ARG2(__VA_ARGS__); _UNTYPED_ARG((x),3)
125+
#define UNTYPED_ARG4(x,...) UNTYPED_ARG3(__VA_ARGS__); _UNTYPED_ARG((x),4)
126+
#define UNTYPED_ARG5(x,...) UNTYPED_ARG4(__VA_ARGS__); _UNTYPED_ARG((x),5)
127+
#define UNTYPED_ARG6(x,...) UNTYPED_ARG5(__VA_ARGS__); _UNTYPED_ARG((x),6)
128+
#define UNTYPED_ARG7(x,...) UNTYPED_ARG6(__VA_ARGS__); _UNTYPED_ARG((x),7)
129+
#define UNTYPED_ARG8(x,...) UNTYPED_ARG7(__VA_ARGS__); _UNTYPED_ARG((x),8)
130+
#define UNTYPED_ARG9(x,...) UNTYPED_ARG8(__VA_ARGS__); _UNTYPED_ARG((x),9)
131+
#define UNTYPED_ARG10(x,...) UNTYPED_ARG9(__VA_ARGS__); _UNTYPED_ARG((x),10)
132+
133+
#define ARGTYPEVAR(n) argtype ## n
134+
135+
/*
136+
* Create __auto_type declaration list with argtypeN constant definitions
137+
* containing string data type for each argN variable.
138+
*/
139+
#define _TYPED_ARG(x,n) \
140+
_UNTYPED_ARG(x,n); \
141+
const char * const Expand1(ARGTYPEVAR(n)) __attribute__((unused)) = TYPENAME(arg ## n)
142+
#define TYPED_ARG1(x) _TYPED_ARG((x), 1)
143+
#define TYPED_ARG2(x,y) TYPED_ARG1(y); _TYPED_ARG((x),2)
144+
#define TYPED_ARG3(x,...) TYPED_ARG2(__VA_ARGS__); _TYPED_ARG((x),3)
145+
#define TYPED_ARG4(x,...) TYPED_ARG3(__VA_ARGS__); _TYPED_ARG((x),4)
146+
#define TYPED_ARG5(x,...) TYPED_ARG4(__VA_ARGS__); _TYPED_ARG((x),5)
147+
#define TYPED_ARG6(x,...) TYPED_ARG5(__VA_ARGS__); _TYPED_ARG((x),6)
148+
#define TYPED_ARG7(x,...) TYPED_ARG6(__VA_ARGS__); _TYPED_ARG((x),7)
149+
#define TYPED_ARG8(x,...) TYPED_ARG7(__VA_ARGS__); _TYPED_ARG((x),8)
150+
#define TYPED_ARG9(x,...) TYPED_ARG8(__VA_ARGS__); _TYPED_ARG((x),9)
151+
#define TYPED_ARG10(x,...) TYPED_ARG9(__VA_ARGS__); _TYPED_ARG((x),10)
152+
153+
/*
154+
* List of generated argument names from TYPED_ARGn.
155+
*
156+
* These are reversed on purpose.
157+
*/
158+
#define ARGNAMES1 arg1
159+
#define ARGNAMES2 arg2, ARGNAMES1
160+
#define ARGNAMES3 arg3, ARGNAMES2
161+
#define ARGNAMES4 arg4, ARGNAMES3
162+
#define ARGNAMES5 arg5, ARGNAMES4
163+
#define ARGNAMES6 arg6, ARGNAMES5
164+
#define ARGNAMES7 arg7, ARGNAMES6
165+
#define ARGNAMES8 arg8, ARGNAMES7
166+
#define ARGNAMES9 arg9, ARGNAMES8
167+
#define ARGNAMES10 arg10, ARGNAMES9
168+
169+
/*
170+
* List of generated argument type-variable names from TYPED_ARGn.
171+
*
172+
* These are reversed on purpose.
173+
*/
174+
#define ARGTYPEVARS1 ARGTYPEVAR(1)
175+
#define ARGTYPEVARS2 ARGTYPEVAR(2), ARGTYPEVARS1
176+
#define ARGTYPEVARS3 ARGTYPEVAR(3), ARGTYPEVARS2
177+
#define ARGTYPEVARS4 ARGTYPEVAR(4), ARGTYPEVARS3
178+
#define ARGTYPEVARS5 ARGTYPEVAR(5), ARGTYPEVARS4
179+
#define ARGTYPEVARS6 ARGTYPEVAR(6), ARGTYPEVARS5
180+
#define ARGTYPEVARS7 ARGTYPEVAR(7), ARGTYPEVARS6
181+
#define ARGTYPEVARS8 ARGTYPEVAR(8), ARGTYPEVARS7
182+
#define ARGTYPEVARS9 ARGTYPEVAR(9), ARGTYPEVARS8
183+
#define ARGTYPEVARS10 ARGTYPEVAR(10), ARGTYPEVARS9
184+
185+
/* Create the __auto_type declaration list, and a static array of type names called _types */
186+
#define _WRAPCALL_TYPES(n) const char * const _types[n+1] __attribute__((unused)) = { Expand1(ARGTYPEVARS ## n), 0 }
187+
#define WRAPCALL_TYPES1(...) TYPED_ARG1(__VA_ARGS__); _WRAPCALL_TYPES(1)
188+
#define WRAPCALL_TYPES2(...) TYPED_ARG2(__VA_ARGS__); _WRAPCALL_TYPES(2)
189+
#define WRAPCALL_TYPES3(...) TYPED_ARG3(__VA_ARGS__); _WRAPCALL_TYPES(3)
190+
#define WRAPCALL_TYPES4(...) TYPED_ARG4(__VA_ARGS__); _WRAPCALL_TYPES(4)
191+
#define WRAPCALL_TYPES5(...) TYPED_ARG5(__VA_ARGS__); _WRAPCALL_TYPES(5)
192+
#define WRAPCALL_TYPES6(...) TYPED_ARG6(__VA_ARGS__); _WRAPCALL_TYPES(6)
193+
#define WRAPCALL_TYPES7(...) TYPED_ARG7(__VA_ARGS__); _WRAPCALL_TYPES(7)
194+
#define WRAPCALL_TYPES8(...) TYPED_ARG8(__VA_ARGS__); _WRAPCALL_TYPES(8)
195+
#define WRAPCALL_TYPES9(...) TYPED_ARG9(__VA_ARGS__); _WRAPCALL_TYPES(9)
196+
#define WRAPCALL_TYPES10(...) TYPED_ARG10(__VA_ARGS__); _WRAPCALL_TYPES(10)
197+
198+
#define WRAPCALL4(callfunc,...) do { WRAPCALL_TYPES4(__VA_ARGS__); callfunc(_types, 4, ARGNAMES4); } while (0)
199+
#define WRAPCALL4_TYPES(callfunc,_types,...) do { UNTYPED_ARG4(__VA_ARGS__); callfunc(_types, 4, ARGNAMES4); } while (0)
200+
201+
#ifdef HAVE_TYPENAME
202+
#define CHECK_ARGTYPES4(expected_argtypes,...) do { WRAPCALL_TYPES4(__VA_ARGS__); compare_argtypes(expected_argtypes, _types, 4); } while (0)
203+
#else
204+
#define CHECK_ARGTYPES4(expected_argtypes,...)
205+
#endif
206+
207+
208+
/*
209+
* Call with
210+
*
211+
* int foo;
212+
* char * bar;
213+
* const char * const argtypes = { "int", "char *" };
214+
* CHECK_ARGTYPES2(argtypes, foo, bar);
215+
*
216+
* The argtypes array is not counted.
217+
*/
218+
void
219+
compare_argtypes(const char * const * const explicit_types, const char * const * const detected_types, int nargs)
220+
{
221+
int i = 0;
222+
const char * et;
223+
const char * dt;
224+
int error = 0;
225+
226+
fputs("detected typenames:\n", stderr);
227+
while ((et = explicit_types[i]) && (dt = detected_types[i]))
228+
{
229+
if (i >= nargs)
230+
fprintf(stderr, "ERROR: wrong arg count %d\n", nargs);
231+
232+
if (!dt != !et)
233+
{
234+
fprintf(stderr, " ERROR: detected_types[%d] and explicit_types[%d] end-marker mismatch: %p vs %p\n",
235+
i, i, dt, et);
236+
error++;
237+
}
238+
else if (strcmp(dt, et) != 0)
239+
{
240+
fprintf(stderr, " ERROR: detected_types[%d] and explicit_types[%d] differ: \"%s\" vs \"%s\"\n",
241+
i, i, dt, et);
242+
error++;
243+
}
244+
else
245+
{
246+
fprintf(stderr, " %s\n", dt);
247+
}
248+
249+
i++;
250+
} while (et && dt);
251+
252+
if (error)
253+
exit(1);
254+
}
255+
256+
void
257+
format_args(const char * const * const types, int nargs, ...)
258+
{
259+
va_list vl;
260+
int argno;
261+
const char *typename;
262+
char formatstr[200];
263+
char *format_next = &formatstr[0];
264+
265+
memset(&formatstr[0], '\0', 200);
266+
for (argno = 0, typename = types[argno]; typename != 0; typename = types[++argno])
267+
{
268+
if (argno > 0) {
269+
strcpy(format_next, ", ");
270+
format_next += 2;
271+
}
272+
if (typename[0] == '\0') {
273+
/* No type provided */
274+
strcpy(format_next, "?");
275+
format_next += 1;
276+
}
277+
else if (strcmp(typename, "int") == 0) {
278+
strcpy(format_next, "%d");
279+
format_next += 2;
280+
} else if (strcmp(typename, "char *") == 0 || strcmp(typename, "const char *") == 0) {
281+
strcpy(format_next, "%s");
282+
format_next += 2;
283+
} else if (strcmp(typename, "void *") == 0) {
284+
strcpy(format_next, "%p");
285+
format_next += 2;
286+
} else {
287+
fprintf(stderr, "ERROR: format_char(): unhandled argtype \"%s\" at types[%d]\n", typename, argno);
288+
exit(1);
289+
}
290+
assert(argno <= nargs);
291+
}
292+
assert(argno == nargs);
293+
strcpy(format_next, "\n");
294+
295+
fprintf(stderr, "format string is %s", formatstr);
296+
297+
va_start(vl, types);
298+
vprintf(&formatstr[0], vl);
299+
va_end(vl);
300+
}
301+
302+
int main(void) {
303+
int a = 10;
304+
const char *b = "foo";
305+
const int c = 9;
306+
void *d = (void*)0xDEADBEEFL;
307+
/* expected_argtypes should ignore const qualifiers */
308+
const char * expected_argtypes[5] = {"int", "const char *", "int", "void *", 0};
309+
#ifdef HAVE_TYPENAME
310+
static const int using_typeof = 1;
311+
#else
312+
static const int using_typeof = 0;
313+
#endif
314+
315+
fprintf(stderr, "------- C standard: %ld, using typeof: %d ---------\n",
316+
(long int)(__STDC_VERSION__), using_typeof);
317+
318+
/* Compare autodetected types to manually listed types */
319+
CHECK_ARGTYPES4(expected_argtypes, a, b, c, d);
320+
/* Use autodetected types if supported */
321+
WRAPCALL4(format_args, a, b, c, d);
322+
/* Pass with manually supplied type strings */
323+
WRAPCALL4_TYPES(format_args, expected_argtypes, a, b, c, d);
324+
325+
return 0;
326+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
------- C standard: 199901, using typeof: 0 ---------
2+
format string is ?, ?, ?, ?
3+
format string is %d, %s, %d, %p
4+
?, ?, ?, ?
5+
10, foo, 9, 0xdeadbeef
6+
------- C standard: 201112, using typeof: 1 ---------
7+
detected typenames:
8+
int
9+
const char *
10+
int
11+
void *
12+
format string is %d, %s, %d, %p
13+
format string is %d, %s, %d, %p
14+
10, foo, 9, 0xdeadbeef
15+
10, foo, 9, 0xdeadbeef

0 commit comments

Comments
 (0)