Usually, stuff like this requires instrumentation of the code (either done automatically by a tool, or inserted manually in the source).
As noted in my comment, if you can work with having to insert the use(x)
statements yourself, you could do something like this:
static FILE* dev_null = 0;
static void use_var(char* var_addr, size_t var_size)
{
if (dev_null == 0) /* make sure we only open FILE* dev_null once */
{
dev_null = fopen("/dev/null", "wb");
assert(dev_null != 0); /* opening /dev/null CAN actually fail */
}
size_t i;
for (i = 0; i < var_size; ++i)
{
fputc(var_addr[i], dev_null); /* read every byte in the variable, write to dev_null */
}
}
#define use(x) use_var((char*)&x, sizeof(x))
/* Example of usage */
int main()
{
long x = 80;
struct { double d; char c[123]; } y;
memset(&y, 0, sizeof(y) - 1); /* initialize all bytes in y, except the last */
double z[2] = {3.14, 42.0};
use(x);
use(y);
use(z);
return 0;
}
However there is a problem with using struct
s that contain alignment-padding.
The padding is never used for anything, so this can be a legal reason for passing around uninitialized data. Valgrind can cause spurious errors regarding uninitialized reads in this case exactly.
These two posts discuss this issue specifically:
Is there anyway a valgrind message "Conditional jump or move depends on uninitialized value" can be a so called 'false positive'
Should I worry about "Conditional jump or move depends on uninitialised value(s)"?