This question implies the use of reflection on the local and global variables. Sadly, C doesn't have such concept.
I'm sure gdb can help you generate the list of local and global variables (maybe here?)
However, since you didn't explicitly say how to achieve this, I'll throw my 2 cents on this.
There are some projects implementing reflection on C, but my preference always goes into exploiting the preprocessor to achieve this. Although you can avoid the header-only library, I will use P99 to ease the macro programming.
a MWE can be the following one:
#include <P99/p99_for.h>
#include <P99/p99_args.h>
#include <stdio.h>
enum print_type {
PT_char,
PT_int,
PT_ptr,
PT_string,
};
#define FOR_PAIR(CONTEXT, OP, FUNC, ...) P99_PASTE2(_BASE_FOR_PAIR_, P99_NARG(__VA_ARGS__))(CONTEXT, OP, FUNC, ## __VA_ARGS__)
#define _BASE_FOR_PAIR_2(CONTEXT, OP, FUNC, value1, value2) FUNC(CONTEXT, 0, value1, value2)
#define _BASE_FOR_PAIR_4(CONTEXT, OP, FUNC, value1, value2, ...) OP(CONTEXT, 1, FUNC(CONTEXT, 1, value1, value2), _BASE_FOR_PAIR_2(CONTEXT, OP, FUNC, ## __VA_ARGS__))
#define _BASE_FOR_PAIR_6(CONTEXT, OP, FUNC, value1, value2, ...) OP(CONTEXT, 2, FUNC(CONTEXT, 2, value1, value2), _BASE_FOR_PAIR_4(CONTEXT, OP, FUNC, ## __VA_ARGS__))
#define _BASE_FOR_PAIR_8(CONTEXT, OP, FUNC, value1, value2, ...) OP(CONTEXT, 3, FUNC(CONTEXT, 3, value1, value2), _BASE_FOR_PAIR_6(CONTEXT, OP, FUNC, ## __VA_ARGS__))
#define _BASE_FOR_PAIR_10(CONTEXT, OP, FUNC, value1, value2, ...) OP(CONTEXT, 4, FUNC(CONTEXT, 4, value1, value2), _BASE_FOR_PAIR_8(CONTEXT, OP, FUNC, ## __VA_ARGS__))
#define _LENGTH_VAR _localsTrackingSystem_arrayLengths
#define _NAMES_VAR _localsTrackingSystem_names
#define _VARIABLES_VAR _localsTrackingSystem_variables
#define _PRINTMETHOD_VAR _localsTrackingSystem_printMethod
#define STR(x) #x
#define _NAMES_REDUCE(NAME, I, REC, RES) REC, RES
#define _NAMES_MAP(context, length, type, name) (STR(name))
#define _GENERATE_NAMES(...) FOR_PAIR(, _NAMES_REDUCE, _NAMES_MAP, ## __VA_ARGS__)
#define _POINTERS_REDUCE(NAME, I, REC, RES) REC; RES
#define _POINTERS_MAP(arrayLength, length, type, aname) _VARIABLES_VAR[arrayLength - length - 1] = ((void*)&aname)
#define _GENERATE_POINTERS(...) FOR_PAIR(P99_DIV(P99_NARG(__VA_ARGS__), 2), _POINTERS_REDUCE, _POINTERS_MAP, ## __VA_ARGS__)
#define _PRINT_REDUCE(NAME, I, REC, RES) REC, RES
#define _PRINT_MAP(context, length, type, name) (P99_PASTE2(PT_, type))
#define _GENERATE_PRINT(...) FOR_PAIR(, _PRINT_REDUCE, _PRINT_MAP, ## __VA_ARGS__)
//variadic needs to be always even
// _GENERATE_POINTERS needs to be initialized every time since pointers may change (although length doesn't)
#define TRACKED_FUNCTION(...) \
static const int _LENGTH_VAR = P99_DIV(P99_NARG(__VA_ARGS__), 2); \
static const char* _NAMES_VAR[] = {_GENERATE_NAMES(__VA_ARGS__)}; \
static const enum print_type _PRINTMETHOD_VAR[] = {_GENERATE_PRINT(__VA_ARGS__)}; \
static const void* _VARIABLES_VAR[P99_DIV(P99_NARG(__VA_ARGS__), 2)]; \
_GENERATE_POINTERS(__VA_ARGS__)
#define printState() _printState(_LENGTH_VAR, _NAMES_VAR, _VARIABLES_VAR, _PRINTMETHOD_VAR);
void _printState(int length, const char** nameArray, const void** pointerArray, const enum print_type* printMethodArray) {
for (int i=0; i<length; ++i) {
printf("at %p %s = ", pointerArray[i], nameArray[i]);
switch (printMethodArray[i]) {
case PT_char: {
printf("%c", *((char*)pointerArray[i]));
break;
}
case PT_int: {
printf("%d", *((int*)pointerArray[i]));
break;
}
case PT_ptr: {
printf("%p", *((void**)pointerArray[i]));
break;
}
case PT_string: {
printf("%s", *((char**)pointerArray[i]));
break;
}
default: {
exit(1);
}
}
printf("\n");
}
}
int func(int x, const char* name){
//LOCALS DEFINITIONS
int y = 0;
int* yPtr = &y;
x = y + x;
//declare which variables you want to track... like your "variables.txt" files
TRACKED_FUNCTION(int, x, int, y, ptr, yPtr, string, name);
//MAIN BODY
if(x > 0) {
x = x % 4;
printf("expected x=%d, y=%d, yPtr=%p name=%s\n", x, y, yPtr, name);
printState();
/* I want to know the value of x at this point.*/
} else {
x = x + 1;
printf("expected x=%d, y=%d, yPtr=%p name=%s\n", x, y, yPtr, name);
printState();
/* I want to know the value of x at this point.*/
}
return x;
}
int main() {
func(5, "Hello World!");
}
My output:
expected x=1, y=0, yPtr=0x7ffec1e5b5ec name=Hello World!
at 0x7ffec1e5b5dc x = 1
at 0x7ffec1e5b5ec y = 0
at 0x7ffec1e5b5f0 yPtr = 0x7ffec1e5b5ec
at 0x7ffec1e5b5d0 name = Hello World!
I've already treated a similar concept here, however let me briefly explain what this code does:
- Usage: very simple; after having declared all the variables you're interested in printing, you call
TRACKED_FUNCTION
and pass a sequences of pairs of variables: the first element of the pair is a string representing how you wish to print the content of the variable (e.g., you might have a char
, but maybe you want to print it as int
); the second is the name of the variable itself; so in the example TRACKED_FUNCTION(int, x, int, y, ptr, yPtr, string, name);
we want to track x
which needs to be printed as an int
, y
as a int
, yPtr
as a pointer and name
as a string; After calling such macro, you can call printState()
whenever you want to obtain a list of all the variables you explicitly declared;
- Internal details: my main idea is to create 4 "hidden" variables: an array containing all the variables names, another containing all the variables pointers, another one containing for each variable how to print it and an integer representing the length of all the arrays;
TRACKED_FUNCTION
generates those 3 arrays while printState()
just print them. In particular, the first element of each pair in TRACKED_FUNCTION
is actually concatenated into an identifier which belong to the enum print_type
: this is the main reason why we can use a "type" string
but we cannot use a type void*
: PT_void*
is not a vlaid enum identifier!
- Pros: This implementation is, aside a bunch of headers of
P99
, library-free. Some macro programming is required though; Furthermore it is C99 compliant;
- Cons: It's C99 compliant, but we can probably improve the method if we were on C11, however, OP didn't specify it in the tags, so whatever :). Another cons (I think you've guessed it) is the fact that this solution doesn't use the file at all. Even worse, it cannot work with the file because the lines fetched from the file cannot be handled in any way with macro programming. This is why I didn't use it. I don't know if you require to use the file or it suffices list in another way (with this solution in
TRACKED_FUNCTION
) the variables you're interested in.
Hope it helps