Memcheck: a memory error detector
int i, j;
int a[10], b[10];
for ( i = 0; i < 10; i++ ) {
j = a[i];
b[i] = j;
}
Memcheck emits no complaints about this, since it merely copies uninitialised values from
a[]
into
b[]
, and doesn’t
use them in a way which could affect the behaviour of the program. However, if the loop is changed to:
for ( i = 0; i < 10; i++ ) {
j += a[i];
}
if ( j == 77 )
printf("hello there\n");
then Memcheck will complain, at the
if
, that the condition depends on uninitialised values.
Note that it
doesn’t
complain at the
j += a[i];
, since at that point the undefinedness is not "observable". It’s only when a decision
has to be made as to whether or not to do the
printf
-- an observable action of your program -- that Memcheck
complains.
Most low level operations, such as adds, cause Memcheck to use the V bits for the operands to calculate the V bits for
the result. Even if the result is partially or wholly undefined, it does not complain.
Checks on definedness only occur in three places: when a value is used to generate a memory address, when control
flow decision needs to be made, and when a system call is detected, Memcheck checks definedness of parameters as
required.
If a check should detect undefinedness, an error message is issued. The resulting value is subsequently regarded as
well-defined. To do otherwise would give long chains of error messages. In other words, once Memcheck reports an
undefined value error, it tries to avoid reporting further errors derived from that same undefined value.
This sounds overcomplicated.
Why not just check all reads from memory, and complain if an undefined value is
loaded into a CPU register?
Well, that doesn’t work well, because perfectly legitimate C programs routinely copy
uninitialised values around in memory, and we don’t want endless complaints about that.
Here’s the canonical
example. Consider a struct like this:
struct S { int x; char c; };
struct S s1, s2;
s1.x = 42;
s1.c = ’z’;
s2 = s1;
The question to ask is: how large is
struct S
, in bytes?
An
int
is 4 bytes and a
char
one byte, so perhaps a
struct S
occupies 5 bytes? Wrong. All non-toy compilers we know of will round the size of
struct S
up to
a whole number of words, in this case 8 bytes. Not doing this forces compilers to generate truly appalling code for
accessing arrays of
struct S
’s on some architectures.
61