Automatically generate table of function pointers in C
I'm looking for a way to automatically (as part of the compilation/build process) generate a "table" of function pointers in C.
Specifically, I want to generate an array of structures something like:
typedef struct {
void (*p_func)(void);
char * funcName;
} funcRecord;
/* Automatically generate the lines below: */
extern void func1(void);
extern void func2(void);
/* ... */
funcRecord funcTable[] =
{
{ .p_func = &func1, .funcName = "func1" },
{ .p_func = &func2, .funcName = "func2" }
/* ... */
};
/* End automatically-generated code. */
...where func1 and func2 are defined in other source files.
So, given a set of source files, each of which which contain a single function that takes no arguments and returns void, how would one automatically (as part of the build process) generate an array like the one above that contains each of the functions from the files? I'd like to be able to add new files and have them automatically inserted into the table when I re-compile.
I realize that this probably isn't achievable using the C language or preprocessor alone, so consider any common *nix-style tools fair game (e.g. make, perl, shell scripts (if you have to)).
But Why?
You're probably wondering why anyone would want to do this. I'm creating a small test framework for a library of common mathematical routines. Under this framework, there will be many small "test cases," each of which has only a few lines of code that will exercise each math function. I'd like each test case to live in its own source file as a short function. All of the test cases will get built into a single executable, and the test case(s) to be run can be specified on the command line when invoking the executable. The main() function will search through the table and, if it finds a match, jump to the test case function.
Automating the process of building up the "catalog" of test cases ensures that test cases don't get left out (for instance, because someone forgets to add it to the table) and makes it very simple for maintainers to add new test cases in the future (just create a new source file in the correct d开发者_JAVA技巧irectory, for instance).
Hopefully someone out there has done something like this before. Thanks, StackOverflow community!
Using macros
How about making a macro list as
#define FUNC_LIST \
FUNC( func1 ) \
FUNC( func2 ) \
FUNC( func3 ) \
FUNC( func4 ) \
FUNC( func5 )
and then expand the extern
definitions as
#define FUNC( _name ) extern void _name(void);
FUNC_LIST
#undef FUNC
and then expand the table as
#define FUNC( _name ) { .p_func = &_name, .funcName = #_name },
funcRecord funcTable[] = {
FUNC_LIST
};
#undef FUNC
Using dlsym(..)
If you have a strict naming convention for you test functions another suggestion is to look into using the function dlsym
with the handle set to RTLD_DEFAULT
and write a function that tries to look upp all functions at startup.
Example
#include <stdio.h>
#include <dlfcn.h>
void test2() {
printf("Second place is the first loser!\n");
}
void test42() {
printf("Read The Hitchhikers Guide To The Galaxy!\n");
}
int main() {
int i;
for (i=1; i<100; i++) {
char fname[32];
void (*func)();
sprintf(fname, "test%d", i);
func = dlsym(RTLD_DEFAULT, fname);
if (func)
func();
}
return 0;
}
If you are using MSVC, you can use
dumpbin /symbols foo.obj > foo_symbols.txt
To get all of the symbol names (not just functions) into a text file. Then parse the resulting file to extract the function names. Functions will be External
symbols in a section that is not UNDEF
Alternatively, you could link your objects into a temporary exe or dll and then look at the .MAP file produced by the linker to get the function names.
Or you could write your own code to parse the .obj files, they are in a modified COFF format and it's not that difficult to locate the symbol table and decode it. I did a partial decode of the COFF format once to get at the symbol table and it took a couple of days to write the code. http://en.wikipedia.org/wiki/COFF
Sounds like a job for code generation. Pick a scripting language of you choice, and figure out how to extract the name of the function from the appropriate header and write out
{ .p_func = &functionname, .funcName = "functionname" },
Then tell you build system to generate the header header file. In make it might look like
UTILITY_FUNCTION_HEADER:= func1.h func2.h func3.h
func_table.h: ${UTILITY_FUNCTION_HEADERS}
write_header.sh > $@
for file in @^; do extract_function_name.sh >> $@; done
write_footer.sh >>$@
About the extraction problem, I think I'd flag the functions I want to export, somehow, then extract that during the build process.
You could use a "semantic macro" à la gettext (i.e. a macro that does nothing except provide semantic information to external tools):
#define TEST_CASE(f) f
T TEST_CASE(f)(D x, ...)
{
/* ... */
}
Then you can easily extract that using sed or awk, or whatever you prefer, and make a list in the correct format based on that. Here's some simple code in awk since that's what I know best, but you might want to use something else:
match($0, /TEST_CASE\([a-zA-Z_][a-zA-Z_0-9]*\)/) {
name = substr($0, RSTART, RLENGTH)
sub(/^TEST_CASE\(/, "", name)
sub(/\)$/, "", name)
funcs[name]
}
END {
for (f in funcs)
printf "func_type %s;\n", f
print "funcRecord funcTable[] = {"
for (f in funcs)
printf "\t{ .p_func = %s, .funcName = \"%s\" },\n", f, f
print "};"
}
If you're going to sort the names (useful for bsearch()-ing), I'd recommend using three filters: an extraction filter (sed one-liner is appropriate here), sort(1), then a generation filter (I'd use awk here). You'd have to generate a header/footer separately, though, and make two passes or store the result of the extraction in a temporary file in order to generate both the extern declarations and the array entries.
I don't think it's a good idea to try to extract functions with a given prototype, e.g. a void (void). Better use a typedef (func_type in my example) and an explicit semantic macro, IMHO it's more robust (to changes, and also to different coding styles, e.g. putting the return type on a line by itself vs. not).
All that's left to do then is to add that generation pass to your makefile, as in dmckee's reply (though you'll actually want to put all that generated code in a .c rather than a .h, I think). For completeness, here's my version:
TEST_SRCS= test1.c test2.c test3.c
test_funcs.c: ${TEST_SRCS}
echo '#include "test.h"' >$@
awk -f extract_test_funcs.awk ${TEST_SRCS} >>$@
I tend to go about this a different way. When I develop something I usually think: right OK C file with code in it for such a function, say in /project-devel/project/new_function.c
. When I write that, I tend to also create /project-devel/tests/test_new_function.c
and /project-devel/tests/make_new_function_test
which you've guessed it builds a test binary including everything necessary to test that new function.
The question I guess is could you automate this? I'm not about to write code for it (because I haven't thought it through) but you could create a perl/python script that creates the makefile, new function.c/.h and test bootstrap all in one go. You can grap the includes you need by regexing something like r"#include \\\"(\s+?).h\\\""
and create a test-funct "update script" that re-creates the test case/makefile based on new information. It still relies on you to write the actual test.c but that's trivial with the idea set up.
Just an idea.
The C compiler does that. Under a Linux system, try calling nm foo.o
on a compiled C file. It will print out the "symbol table" which includes all functions, including static functions.
Edit: The difficulty lies in extracting the information ("there is a function named 'func1()'") from the source code. It requires parsing the C source file. The C compiler already does that (that's its job) so it makes sense to use the output of the C compiler. The output is an object file with a symbol table which contains that information. So the idea is to parse the output of nm
and generate the C source file which defines the "table of functions". This can be automated from the makefile, so that the table is always generated.
精彩评论