开发者

Initialize global array of function pointers at either compile-time, or run-time before main()

开发者 https://www.devze.com 2023-01-24 18:10 出处:网络
I\'m trying to initialize a global array of function pointers at compile-time, in either C or C++.Something like this:

I'm trying to initialize a global array of function pointers at compile-time, in either C or C++. Something like this:

module.h

typedef int16_t (*myfunc_t)(void);
extern myfunc_array[];

module.cpp

#include "module.h"
int16_t myfunc_1();
int16_t myfunc_2();
...
int16_t myfunc_N();

// the ordering of functions is not that important
myfunc_array[] = { myfunc_1, myfunc_2, ... , myfunc_N };

func1.cpp, func2.cpp, ... funcN.cpp (symbolic links to a single func.cpp file, so that different object files are created: func1.o, func2.o, func3.o, ... , funcN.o. NUMBER is defined using g++ -DNUMBER=N)

#include "module.h"
#define CONCAT2(x, y) x ## y
#define CONCAT(x, y) CONCAT2(x, y)

int16_t CONCAT(myfunc_, NUMBER)() { ... }

When compiled using g++ -DNUMBER=N, after preprocessing becomes:

func1.cpp

...
int16_t myfunc_1() { ... }

func2.cpp

...
int16_t myfunc_2() { ... }

and so on.

The declarations of myfunc_N() and the initialization of myfunc_array[] are not cool, since N changes often and could be 开发者_高级运维between 10 to 200. I prefer not to use a script or Makefile to generate them either. The ordering of functions is not that important, i can work around that. Is there a neater/smarter way to do this?


How To Make a Low-Level Function Registry

First you create a macro to place pointers to your functions in a special section:

/* original typedef from question: */
typedef int16_t (*myfunc)(void);

#define myfunc_register(N) \
    static myfunc registered_##myfunc_##N \
      __attribute__((__section__(".myfunc_registry"))) = myfunc_##N

The static variable name is arbitrary (it will never be used) but it's nice to choose an expressive name. You use it by placing the registration just below your function:

myfunc_register(NUMBER);

Now when you compile your file (each time) it will have a pointer to your function in the section .myfunc_registry. This will all compile as-is but it won't do you any good without a linker script. Thanks to caf for pointing out the relatively new INSERT AFTER feature:

SECTIONS
{
    .rel.rodata.myfunc_registry : {
        PROVIDE(myfunc_registry_start = .);
        *(.myfunc_registry)
        PROVIDE(myfunc_registry_end = .);
    }
}
INSERT AFTER .text;

The hardest part of this scheme is creating the entire linker script: You need to embed that snippet in the actual linker script for your host which is probably only available by building binutils by hand and examining the compile tree or via strings ld. It's a shame because I quite like linker script tricks.

Link with gcc -Wl,-Tlinkerscript.ld ... The -T option will enhance (rather than replace) the existing linker script.

Now the linker will gather all of your pointers with the section attribute together and helpfully provide a symbol pointing before and after your list:

extern myfunc myfunc_registry_start[], myfunc_registry_end[];

Now you can access your array:

/* this cannot be static because it is not know at compile time */
size_t myfunc_registry_size = (myfunc_registry_end - myfunc_registry_start);
int i;

for (i = 0; i < myfunc_registry_size); ++i)
    (*myfunc_registry_start[i])();

They will not be in any particular order. You could number them by putting them in __section__(".myfunc_registry." #N) and then in the linker gathering *(.myfunc_registry.*), but the sorting would be lexographic instead of numeric.

I have tested this out with gcc 4.3.0 (although the gcc parts have been available for a long time) and ld 2.18.50 (you need a fairly recent ld for the INSERT AFTER magic).

This is very similar to the way the compiler and linker conspire to execute your global ctors, so it would be a whole lot easier to use a static C++ class constructor to register your functions and vastly more portable.

You can find examples of this in the Linux kernel, for example __initcall is very similar to this.


I was going to suggest this question is more about C, but on second thoughts, what you want is a global container of function pointers, and to register available functions into it. I believe this is called a Singleton (shudder).

You could make myfunc_array a vector, or wrap up a C equivalent, and provide a function to push myfuncs into it. Now finally, you can create a class (again you can do this in C), that takes a myfunc and pushes it into the global array. This will all occur immediately prior to main being called. Here are some code snippets to get you thinking:

// a header

extern vector<myfunc> myfunc_array;

struct _register_myfunc { 
    _register_myfunc(myfunc lolz0rs) {
        myfunc_array.push_back(lolz0rs);
    }
}

#define register_myfunc(lolz0rs) static _register_myfunc _unique_name(lolz0rs);

// a source

vector<myfunc> myfunc_array;

// another source

int16_t myfunc_1() { ... }
register_myfunc(myfunc_1);

// another source

int16_t myfunc_2() { ... }
register_myfunc(myfunc_2);

Keep in mind the following:

  • You can control the order the functions are registered by manipulating your link step.
  • The initialization of your translation unit-scoped variables occurs before main is called, i.e. the registering will be completed.
  • You can generate unique names using some macro magic and __COUNTER__. There may be other sneaky ways that I don't know about. See these useful questions:
    • Unnamed parameters in C
    • Unexpected predefined macro behaviour when pasting tokens
    • How to generate random variable names in C++ using macros?


Your solution sounds much too complicated and error prone to me.

You go over your project with a script (or probably make) to place the -D options to the compiler, anyhow. So I suppose you are keeping a list of all your functions (resp. the files defining them).

I'd use proper names for all the functions, nothing of your numbering scheme and then I would produce the file "module.cpp" with that script and initialize the table with the names.

For this you just have to keep a list of all your functions (and perhaps filenames) in one place. This could be easier be kept consistent than your actual scheme, I think.

Edit: Thinking of it even this might also be overengineering. If you have to maintain a list of your functions somewhere in any case, why not just inside the file "module.cpp"? Just include all the header files of all your functions, there, and list them in the initializer of the table.


Since you allow C++, the answer is obviously yes, with templates:

template<int N> int16_t myfunc() { /* N is a const int here */ }
myfunc_array[] = { myfunc<0>, myfunc<1>, myfunc<2> }

Now, you might wonder if you can create that variable-length initializer list with some macro. The answer is yes, but the macro's needed are ugly. So I'n not going to write them here, but point you to Boost::Preprocessor

However, do you really need such an array? Do you really need the name myfunc_array[0] for myfunc<0> ? Even if you need a runtime argument (myfunc_array[i]) there are other tricks:

inline template <int Nmax> int16_t myfunc_wrapper(int i) {
assert (i<Nmax);
return (i==Nmax) ? myfunc<Nmax> : myfunc_wrapper(i-1);
}
inline int16_t myfunc_wrapper(int i) { 
  return myfunc_wrapper<NUMBER>(i); // NUMBER is defined on with g++ -DNUMBER=N
}


Ok I worked out a solution based on Matt Joiner's tip:

module.h

typedef int16_t (*myfunc_t)(void);
extern myfunc_array[];

class FunctionRegistrar {
public:
    FunctionRegistrar(myfunc_t fn, int fn_number) {
        myfunc_array[fn_number - 1] = fn; // ensures correct ordering of functions (not that important though)
    }
}

module.cpp

#include "module.h"

myfunc_array[100]; // The size needs to be #defined by the compiler, probably

func1.cpp, func2.cpp, ... funcN.cpp

#include "module.h"

static int16_t myfunc(void) { ... }

static FunctionRegistrar functionRegistrar(myfunc, NUMBER);

Thanks everyone!

0

精彩评论

暂无评论...
验证码 换一张
取 消