The following crashes with a seg-V:
// my code
int* ipt;
int bool set = false;
void Set(int* i) {
ASSERT(i);
ipt = i;
set = true;
}
int Get() {
return set ? *ipt : 0;
}
// code that I don't control.
struct S { int I, int J; }
int main() {
S* ip = NULL;
// code that, as a bug, forgets to set ip...
Set(&ip->J);
// gobs of code
return Get();
}
This is because while i
is not NULL
it still isn't valid. The same problem can happen if the calling code takes the address of an array index operation from a NULL
pointer.
One solution to this is to trim the low order bits:
void Set(int* i) {
ASSERT((reinterpret_cast<size_t>(i))>>10);
ipt = i;
set = true;
}
But how many bits should/can I get rid of?
Edit, I'm not worried about undefined behavior as I'll be aborting (but more cleanly than a seg-v) on that case anyway.
FWIW: this is a semi-hypothetical situation. The bug that caused me to think of this was fixed before I posted, but I've run into it before and am thinking of how to work with it in the future.
Things that can be assumed for the sake of argument:
- If Set is called with something that will seg-v, that's a bug
- Set may be called by code that isn't my job to fix. (E.g. I file a bug)
- Set may be called by code I'm trying to fix. (E.g. I'm adding sanity checks as part of my debuggin work.)
- Get my be called in a way that provide no information about where Set was called. (I.e. allowing Get to seg-v isn't an effective way to debug anything.)
- The code needn't be portable or catch 100% of bad pointers. It need only work on my current system often enough to let me find where things are going wrong.