This post is a follow-up for two previous posts. The first is my own called Verifying C++ Buffers which was replied with a second post by Sasha titled IsBadXxxPtr Is Really Harmful – Please Don’t Use These Functions. The first post mentioned the advantages of using IsBadXxxPtr API over verifying pointers by using ASSERT (or comparing to zero). The second post mentioned the problems with this set of API in a multi-threaded application. Just to clarify my comment here is not personal and Sasha and I are having an academic discussion which will benefit us all.
Let me start by saying that the information provided in the first post is valid and works well with a single threaded application. As Sasha explained the API does not consider parallel operations. This is noted on the MSDN page for the API and at the end of my first post. The reason I did not elaborate more on this is because it is not such a clear cut. Sasha has explained reasons not to use the API in a parallel environment and I will try to argue the reasons for using this API in a parallel environment.
Here are the scenarios I will be examining:
Scenario A: The pointer value is zero / NULL. This is usually because we initialized the pointer correctly or because a structure was never initialized in debug compilation
Scenario B: The pointer value is above zero but is very close to zero, for example address 1, 23, or 32K. This is usually because we are accessing a member of a struct which address is NULL. The address of the struct is zero so the address of a member is its offset from zero. Another possible reason for this scenario is an item in an array when the array is NULL.
Scenario C: A random number is used for the pointer. This is because we are using an uninitialized memory (in the stack for example), or because of buffer over-run / buffer under-run which caused memory corruption in the storage of the pointer.
Scenario D: The pointer is initialized correctly but the buffer was already deleted and therefore the pointer is pointing at an invalid buffer.
ASSERT or IsBadXxxPtr?
When we use ASSERT or test the buffer whether it is NULL or not we are actually testing to see whether or not we are using a buffer that was initialized to zero. This only works if the pointer was correctly initialized. Some functions would not expect NULL, such as printf( ) or puts( ), while other functions may consider this as a valid value for example the UserContext parameter when creating a thread – if it is NULL then there is no context.
Going over the scenarios comparing the pointer to zero or using ASSERT: Scenario A works fine. Scenario B will not find the bug and your function will try to access the buffer. Scenario C: same as B, and it is also the same for Scenario D.
Testing the same scenarios with IsBadXxxPtr: Scenario A works fine. Same for Scenario B. These two scenarios happen even if pointers are correctly initialized! Scenario C will work if the random value happens to fall on a Heap page out of the 4GB virtual space (pages are usually 4K for 32 bit systems). Scenario D will work if the buffer was the last on that page. When the last page on the buffer is deleted the page is marked as inaccessible.
When do we see problems with multi-threading?
Unmapping a page in a second thread: This means that one thread deleted an object while another thread was working on it. This is because you shared the resource (buffer) but forgot to lock or used the locks incorrectly. ASSERT will never detect that because the pointer has an address that it more than zero and ASSERT has no way of telling if the page is valid.
IsBadWritePtr corruption of memory: is because a resource was shared but the thread used the lock only after calling IsBadWritePtr because the programmer did not know that IsBadWritePtr is accessing the memory. This is a problem and you should know that calling IsBadWritePtr will modify memory even though the name suggests that it is only testing a pointer.
Guard Pages are used in an internal implementation which does not expect IsBadXxxPtr API to access these pages. This may happen for example when a thread is sharing a buffer which is located on its own stack, which is a terrible bug. Such a bug will randomly damage Guard Pages, or corrupt the other thread’s stack, or cause the other thread to access bad memory (by overwriting pointers located in the other thread’s stack). I can’t tell you which is better, but if your thread found an invalid pointer of an internal buffer then something is very wrong and you can’t find this using ASSERT.
There is a long and painful argument whether exceptions are good or bad. In a multithreading environment I consider exceptions to be always bad because an exception is a bursting Event that will be initiated even when you are in some execution flow. Multi-threading environments are all about synchronizing execution and flow control. The possibility of a flow breaking because of an event is really bad. It is a system wide problem if a thread is using a Semaphore object and then stopping in the middle of work because of an exception because some other thread did something wrong 5 minutes ago. It is simpler to verify pointers than to check all possible exceptions. An exception is something special, not a return value. When exceptions became return values functions started swallowing them and hide the real problems.
For conclusion, with all problems that come with using the IsBadXxxPtr Win32 API, I would still prefer using this API set than using ASSERT which allows bugs to happen. If the code has no bugs then IsBadXxxPtr causes no damage. If your code has bugs then using IsBadXxxPtr will solve more problems than cause them, unless you are going to use this to hide the problems.
If my application supports plug-ins then I would prefer to verify interface buffers than to let buffers from an external module damage my application.