With the edit, I'll have a go at an answer. It requires your compiler to support __FUNCTION__
, which MSVC and GCC both do.
First, write a set of functions which maps strings to integers in memory, all stored in some global instance of a structure. This is left as an exercise for the reader, functionally it's a hashmap, but I'll call the resulting instance "global_x_map". The function get_int_ptr
is defined to return a pointer to the int
corresponding to the specified string, and if it doesn't already exist to create it and initialize it to 0. reset_int_ptr
just assigns 0 to the counter for now, you'll see later why I didn't just write *_inc_x_tmp = 0;
.
#define INC_X do {\
int *_inc_x_tmp = get_int_ptr(&global_x_map, __FILE__ "{}" __FUNCTION__); \
/* maybe some error-checking here, but not sure what you'd do about it */ \
++*_inc_x_tmp; \
} while(0)
#define PRINT_X do {\
int *_inc_x_tmp = get_int_ptr(&global_x_map, __FILE__ "{}" __FUNCTION__); \
printf("%d\n", *_inc_x_tmp); \
reset_int_ptr(&global_x_map, _inc_x_tmp); \
} while(0)
I've chose the separator "{}" on the basis that it won't occur in a mangled C function name - if your compiler for some reason might put that in a mangled function name then of course you'd have to change it. Using something which can't appear in a file name on your platform would also work.
Note that functions which use the macro are not re-entrant, so it is not quite the same as defining an automatic variable. I think it's possible to make it re-entrant, though. Pass __LINE__
as an extra parameter to get_int_ptr
. When the entry is created, store the value of __LINE__
.
Now, the map should store not just an int
for each function, but a stack of ints. When it's called with that first-seen line value, it should push a new int onto the stack, and return a pointer to that int thereafter whenever it's called for that function with any other line value. When reset_int_ptr
is called, instead of setting the counter to 0, it should pop the stack, so that future calls will return the previous int.
This only works of course if the "first" call to INC_X is always the same, is called only once per execution of the function, and that call doesn't appear on the same line as another call. If it's in a loop, if()
block, etc, it goes wrong. But if it's inside a block, then declaring an automatic variable would go wrong too. It also only works if PRINT_X is always called (check your early error exits), otherwise you don't restore the stack.
This may all sound like a crazy amount of engineering, but essentially it is how Perl implements dynamically scoped variables: it has a stack for each symbol name. The difference is that like C++ with RAII, Perl automatically pops that stack on scope exit.
If you need it to be thread-safe as well as re-entrant, then make global_x_map
thread-local instead of global.
Edit: That __FILE__ "{}" __FUNCTION__
identifier still isn't unique if you have static functions defined in header files - the different versions in different TUs will use the same counter in the non-re-entrant version. It's OK in the re-entrant version, though, I think. You'll also have problems if __FILE__
is a basename, not a full path, since you could get collisions for static
functions of the same name defined in files of the same name. That scuppers even the re-entrant version. Finally, none of this is tested.