CC Run-Time Library Considerations

[Previous] [Next]

Six C/C++ run-time libraries ship with Visual C++. The following table describes them.

Library Name Description
LibC.lib Statically linked library for single-threaded applications. (This is the default library when you create a new project.)
LibCD.lib Statically linked debug version of the library for single-threaded applications.
LibCMt.lib Statically linked release version of the library for multithreaded applications.
LibCMtD.lib Statically linked debug version of the library for multithreaded applications.
MSVCRt.lib Import library for dynamically linking the release version of the MSVCRt.dll library. This library supports both single-threaded and multithreaded applications.
MSVCRtD.lib Import library for dynamically linking the debug version of the MSVCRtD.dll library. The library supports both single-threaded and multithreaded applications.

When you implement any type of project, you must know which library you're linking your project with. You select a library using the Project Settings dialog box, shown below. On the C/C++ tab, in the Code Generation category, select one of the six options from the Use Run-Time Library combo box.

click to view at full size.

The first thing you're probably wondering is, "Why do I need one library for single-threaded applications and another library for multithreaded applications?" The reason is that the standard C run-time library was invented around 1970, long before threads were available on any operating system. The inventors of the library didn't consider the problems of using the C run-time library with multithreaded applications.

Consider the standard C run-time global variable errno. Some functions set this variable when an error occurs. Let's say you have the following code fragment:

 BOOL fFailure = (system("NOTEPAD.EXE README.TXT") == -1); if (fFailure) { switch (errno) { case E2BIG: // Argument list or environment too big break; case ENOENT: // Command interpreter cannot be found break; case ENOEXEC: // Command interpreter has bad format break; case ENOMEM: // Insufficient memory to run command break; } } 

Now let's say that the thread executing the code above is interrupted after the call to the system function and before the if statement. And imagine that the thread is being interrupted to allow a second thread in the same process to execute and that this new thread will execute another C run-time function that sets the global variable errno. When the CPU is later assigned back to the first thread, the value of errno no longer reflects the proper error code for the call to system in the code above. To solve this problem, each thread requires its own errno variable. In addition, there must be some mechanism that allows a thread to reference its own errno variable but not touch another thread's errno variable.

This is only one example of how the standard C/C++ run-time library was not originally designed for multithreaded applications. The C/C++ run-time library variables and functions that have problems in multithreaded environments include errno, _doserrno, strtok, _wcstok, strerror, _strerror, tmpnam, tmpfile, asctime, _wasctime, gmtime, _ecvt, and _fcvt—to name just a few.

For multithreaded C and C++ programs to work properly, a data structure must be created and associated with each thread that uses C/C++ run-time library functions. Then, when you make C/C++ run-time library calls, those functions must know to look in the calling thread's data block so that no other thread is adversely affected.

So how does the system know to allocate this data block when a new thread is created? The answer is that it doesn't. The system has no idea that your application is written in C/C++ and that you are calling functions that are not natively thread-safe. The onus is on you to do everything correctly. To create a new thread, you must not call the operating system's CreateThread function—you must call the C/C++ run-time library function _beginthreadex:

 unsigned long _beginthreadex( void *security, unsigned stack_size, unsigned (*start_address)(void *), void *arglist, unsigned initflag, unsigned *thrdaddr); 

The _beginthreadex function has the same parameter list as the CreateThread function, but the parameter names and types are not exactly the same. This is because Microsoft's C/C++ run-time library group believes that C/C++ run-time library functions should not have any dependencies on Windows data types. The _beginthreadex function also returns the handle of the newly created thread, just like CreateThread does. So, if you've been calling CreateThread in your source code, it is fairly easy to globally replace all these calls with calls to _beginthreadex. However, since the data types are not quite the same, you might have to perform some casting to make the compiler happy. To make things easier, I've created a macro, chBEGINTHREADEX, to use in my source code:

 typedef unsigned (_ _stdcall *PTHREAD_START) (void *); #define chBEGINTHREADEX(psa, cbStack, pfnStartAddr, \ pvParam, fdwCreate, pdwThreadID) \ ((HANDLE) _beginthreadex( \ (void *) (psa), \ (unsigned) (cbStack), \ (PTHREAD_START) (pfnStartAddr), \ (void *) (pvParam), \ (unsigned) (fdwCreate), \ (unsigned *) (pdwThreadID))) 

Note that the _beginthreadex function exists only in the multithreaded versions of the C/C++ run-time library. If you are linking to a single-thread run-time library, you get an "unresolved external symbol" error reported from the linker. This is by design, of course, because the single-threaded library does not work properly in a multithreaded application. Also note that Visual Studio defaults to selecting the single-threaded library when you create a new project. This is not the safest default, and for multithreaded applications you must explicitly change to a multithreaded C/C++ run-time library.

Since Microsoft ships the source code to the C/C++ run-time library, it's easy to determine exactly what _beginthreadex does that CreateThread doesn't do. In fact, I searched the Visual Studio CD-ROM and found the source code for _beginthreadex in Threadex.c. Rather than reprint the source code for it here, I'll give you a pseudocode version of it and highlight the interesting points.

 unsigned long _ _cdecl _beginthreadex ( void *psa, unsigned cbStack, unsigned (_ _stdcall * pfnStartAddr) (void *), void * pvParam, unsigned fdwCreate, unsigned *pdwThreadID) { _ptiddata ptd; // Pointer to thread's data block unsigned long thdl; // Thread's handle // Allocate data block for the new thread. if ((ptd = _calloc_crt(1, sizeof(struct tiddata))) == NULL) goto error_return; // Initialize the data block. initptd(ptd); // Save the desired thread function and the parameter // we want it to get in the data block. ptd->_initaddr = (void *) pfnStartAddr; ptd->_initarg = pvParam; // Create the new thread. thdl = (unsigned long) CreateThread(psa, cbStack, _threadstartex, (PVOID) ptd, fdwCreate, pdwThreadID); if (thdl == NULL) { // Thread couldn't be created, cleanup and return failure. goto error_return; } // Create created OK, return the handle. return(thdl); error_return: // Error: data block or thread couldn't be created. _free_crt(ptd); return((unsigned long)0L); } 

Here are the important things to note about _beginthreadex:

  • Each thread gets its very own tiddata memory block allocated from the C/C++ run-time library's heap. (The tiddata structure is in the Visual C++ source code in the Mtdll.h file). Just for fun, I'll reproduce the structure in Figure 6-2.
  • The address of the thread function passed to _beginthreadex is saved in the tiddata memory block. The parameter to be passed to this function is also saved in this data block.
  • _beginthreadex does call CreateThread internally since this is the only way that the operating system knows how to create a new thread.
  • When CreateThread is called, it is told to start executing the new thread with a function called _threadstartex, not pfnStartAddr. Also, note that the parameter passed to the thread function is the address of the tiddata structure, not pvParam.
  • If all goes well, the thread handle is returned just like CreateThread. If any operation fails, NULL is returned.

Figure 6-2. The C/C++ run-time library's thread local tiddata structure

 struct _tiddata { unsigned long _tid; /* thread ID */ unsigned long _thandle; /* thread handle */ int _terrno; /* errno value */ unsigned long _tdoserrno; /* _doserrno value */ unsigned int _fpds; /* Floating Point data segment */ unsigned long _holdrand; /* rand() seed value */ char * _token; /* ptr to strtok() token */ #ifdef _WIN32 wchar_t * _wtoken; /* ptr to wcstok() token */ #endif /* _WIN32 */ unsigned char * _mtoken; /* ptr to _mbstok() token */ /* following pointers get malloc'd at runtime */ char * _errmsg; /* ptr to strerror()/_strerror() buff */ char * _namebuf0; /* ptr to tmpnam() buffer */ #ifdef _WIN32 wchar_t * _wnamebuf0; /* ptr to _wtmpnam() buffer */ #endif /* _WIN32 */ char * _namebuf1; /* ptr to tmpfile() buffer */ #ifdef _WIN32 wchar_t * _wnamebuf1; /* ptr to _wtmpfile() buffer */ #endif /* _WIN32 */ char * _asctimebuf; /* ptr to asctime() buffer */ #ifdef _WIN32 wchar_t * _wasctimebuf; /* ptr to _wasctime() buffer */ #endif /* _WIN32 */ void * _gmtimebuf; /* ptr to gmtime() structure */ char * _cvtbuf; /* ptr to ecvt()/fcvt buffer */ /* following fields are needed by _beginthread code */ void * _initaddr; /* initial user thread address */ void * _initarg; /* initial user thread argument */ /* following three fields are needed to support signal handling and * runtime errors */ void * _pxcptacttab; /* ptr to exception-action table */ void * _tpxcptinfoptrs; /* ptr to exception info pointers */ int _tfpecode; /* float point exception code */ /* following field is needed by NLG routines */ unsigned long _NLG_dwCode; /* * Per-Thread data needed by C++ Exception Handling */ void * _terminate; /* terminate() routine */ void * _unexpected; /* unexpected() routine */ void * _translator; /* S.E. translator */ void * _curexception; /* current exception */ void * _curcontext; /* current exception context */ #if defined (_M_MRX000) void * _pFrameInfoChain; void * _pUnwindContext; void * _pExitContext; int _MipsPtdDelta; int _MipsPtdEpsilon; #elif defined (_M_PPC) void * _pExitContext; void * _pUnwindContext; void * _pFrameInfoChain; int _FrameInfo[6]; #endif /* defined (_M_PPC) */ }; typedef struct _tiddata * _ptiddata; 

So now that a tiddata structure has been allocated and initialized for the new thread, we need to see how this structure is associated with the thread. Let's take a look at the _threadstartex function (which is also in the C/C++ run-time library's Threadex.c file). Here is my pseudocode version of this function:

 static unsigned long WINAPI threadstartex (void* ptd) { // Note: ptd is the address of this thread's tiddata block. // Associate the tiddata block with this thread. TlsSetValue(_ _tlsindex, ptd); // Save this thread ID in the tiddata block. ((_ptiddata) ptd)->_tid = GetCurrentThreadId(); // Initialize floating-point support (code not shown). // Wrap desired thread function in SEH frame to // handle run-time errors and signal support. _ _try { // Call desired thread function, passing it the desired parameter. // Pass thread's exit code value to _endthreadex. _endthreadex( ( (unsigned (WINAPI *)(void *))(((_ptiddata)ptd)->_initaddr) ) ( ((_ptiddata)ptd)->_initarg ) ) ; } _ _except(_XcptFilter(GetExceptionCode(), GetExceptionInformation())){ // The C run-time's exception handler deals with run-time errors // and signal support; we should never get it here. _exit(GetExceptionCode()); } // We never get here; the thread dies in this function. return(0L); } 

Here are the important things to note about _threadstartex:

  • The new thread begins executing with BaseThreadStart (in Kernel32.dll) and then jumps to _threadstartex.
  • _threadstartex is passed the address to this new thread's tiddata block as its only parameter.
  • TlsSetValue is an operating system function that associates a value with the calling thread. This is called Thread Local Storage (TLS) and is discussed in Chapter 21. The _threadstartex function associates the tiddata block with the new thread.
  • An SEH frame is placed around the desired thread function. This frame handles many things related to the run-time library—for example, run-time errors (such as throwing C++ exceptions that are not caught) and the C/C++ run-time library's signal function. This is critically important. If you were to create a thread using CreateThread and then call the C/C++ run-time library's signal function, the function would not work correctly.
  • The desired thread function is called and passed the desired parameter. Recall that the address of the function and the parameter were saved in the tiddata block by _beginthreadex.
  • The return value from the desired thread function is supposed to be the thread's exit code. Note that _threadstartex does not simply return back to BaseThreadStart. If it were to do that, the thread would die and its exit code would be set correctly but the thread's tiddata memory block would not be destroyed. This would cause a leak in your application. To prevent this leak, another C/C++ run-time library function, _endthreadex, is called and passed the exit code.

The last function that we need to look at is _endthreadex (which is also in the C run-time library's Threadex.c file). Here is my pseudocode version of this function:

 void _ _cdecl _endthreadex (unsigned retcode) { _ptiddata ptd; // Pointer to thread's data block // Clean up floating-point support (code not shown). // Get the address of this thread's tiddata block. ptd = _getptd(); // Free the tiddata block. _freeptd(ptd); // Terminate the thread. ExitThread(retcode); } 

Here are the important things to note about _endthreadex:

  • The C run-time library's _getptd function internally calls the operating system's TlsGetValue function, which retrieves the address of the calling thread's tiddata memory block.
  • This data block is then freed and the operating system's ExitThread function is called to truly destroy the thread. Of course, the exit code is passed and set correctly.

Earlier in this chapter, I said that you should always try to avoid using the ExitThread function. This is true and I'm not going to go back on my word. I said that it kills the calling thread and doesn't allow it to return from the currently executing function. Because the function doesn't return, any C++ objects you can construct will not be destructed. Here's another reason not to call ExitThread: it prevents the thread's tiddata memory block from being freed, so your application will leak memory (until the whole process is terminated).

Microsoft's Visual C++ team realizes that developers like to call ExitThread anyway, so they have made this possible without forcing your application to leak memory. If you really want to forcibly kill your thread, you can have it call _endthreadex (instead of ExitThread) to free the thread's tiddata block and then exit. Still, I discourage you from calling _endthreadex.

By now you should understand why the C/C++ run-time library's functions need a separate data block for each thread created, and you should also see how calling _beginthreadex allocates, initializes, and associates this data block with the newly created thread. You should also understand how the _endthreadex function frees the data block when the thread terminates.

Once this data block is initialized and associated with the thread, any C/C++ run-time library functions the thread calls that require per-thread instance data can easily retrieve the address to the calling thread's data block (via TlsGetValue) and manipulate the thread's data. This is fine for functions, but you might wonder how this works for a global variable such as errno. Well, errno is defined in the standard C headers, like this:

 #if defined(_MT) || defined(_DLL) extern int * _ _cdecl _errno(void); #define errno (*_errno()) #else /* ndef _MT && ndef _DLL */ extern int errno; #endif /* _MT || _DLL */ 

If you're creating a multithreaded application, you must specify the /MT (multithreaded application) or /MD (multithreaded DLL) switch on the compiler's command line. This causes the compiler to define the _MT identifier. Then, whenever you reference errno, you actually make a call to the internal C/C++ run-time library function _errno. This function returns the address to the errno data member in the calling thread's associated data block. You'll notice that the errno macro is defined as taking the contents of this address. This definition is necessary because it's possible to write code like this:

 int *p = &errno; if (*p == ENOMEM) {  } 

If the internal _errno function simply returned the value of errno, the above code wouldn't compile.

The multithreaded version of the C/C++ run-time library also places synchronization primitives around certain functions. For example, if two threads simultaneously call malloc, the heap can become corrupted. The multithreaded version of the C/C++ run-time library prevents two threads from allocating memory from the heap at the same time. It does this by making the second thread wait until the first has returned from malloc. Then the second thread is allowed to enter. (Thread synchronization is discussed in more detail in Chapters 8, 9, and 10.)

Obviously, all this additional work affects the performance of the multithreaded version of the C/C++ run-time library. This is why Microsoft supplies the single-threaded version of the statically linked C/C++ run-time library in addition to the multithreaded version.

The dynamically linked version of the C/C++ run-time library was written to be generic so that it can be shared by any and all running applications and DLLs using the C/C++ run-time library functions. For this reason, the library exists only in a multithreaded version. Because the C/C++ run-time library is supplied in a DLL, applications (.exe files) and DLLs don't need to include the code for the C/C++ run-time library function and are smaller as a result. Also, if Microsoft fixes a bug in the C/C++ run-time library DLL, applications automatically gain the fix as well.

As you might expect, the C/C++ run-time library's startup code allocates and initializes a data block for your application's primary thread. This allows the primary thread to safely call any of the C/C++ run-time functions. When your primary thread returns from its entry-point function, the C/C++ run-time library frees the associated data block. In addition, the startup code sets up the proper structured exception handling code so that the primary thread can successfully call the C/C++ run-time library's signal function.

Oops—I Called CreateThread Instead of _beginthreadex by Mistake

You might wonder what happens if you create your new threads by calling CreateThread instead of the C/C++ run-time library's _beginthreadex function. When a thread calls a C/C++ run-time library function that requires the tiddata structure, here is what happens. (Most C/C++ run-time library functions are thread-safe and do not require this structure.) First, the C/C++ run-time function attempts to get the address of the thread's data block (by calling TlsGetValue). If NULL is returned as the address of the tiddata block, the calling thread doesn't have a tiddata block associated with it. At this point, the C/C++ run-time function allocates and initializes a tiddata block for the calling thread right on the spot. The block is then associated with the thread (via TlsSetValue) and this block stays with the thread for as long as the thread continues to run. The C/C++ run-time function can now use the thread's tiddata block, and so can any C/C++ run-time functions that are called in the future.

This, of course, is fantastic because your thread runs without a hitch (almost). Well, actually there are a few problems. First, if the thread uses the C/C++ run-time library's signal function, the entire process terminates because the structured exception handling frame has not been prepared. Second, if the thread terminates without calling _endthreadex, the data block cannot be destroyed and a memory leak occurs. (And who would call _endthreadex for a thread created with CreateThread?)

NOTE
If your module links to the multithreaded DLL version of the C/C++ run-time library, the library receives a DLL_THREAD_DETACH notification when the thread terminates and frees the tiddata block (if allocated). Even though this prevents the leaking of the tiddata block, I strongly recommend that you create your threads using _beginthreadex instead of CreateThread.

C/C++ Run-Time Library Functions That You Should Never Call

The C/C++ run-time library also contains two other functions:

 unsigned long _beginthread( void (_ _cdecl *start_address)(void *), unsigned stack_size, void *arglist); 

and

 void _endthread(void); 

These two functions were originally created to do the work of the new _beginthreadex and _endthreadex functions, respectively. However, as you can see, the _beginthread function has fewer parameters and is therefore more limited than the full-featured _beginthreadex function. For example, if you use _beginthread, you cannot create the new thread with security attributes, you cannot create the thread suspended, and you cannot obtain the thread's ID value. The _endthread function has a similar story: it takes no parameters, which means that the thread's exit code is hard-coded to 0.

The _endthread function has another significant problem that you can't see. Just before _endthread calls ExitThread, it calls CloseHandle, passing the handle of the new thread. To see why this is a problem, examine the following code:

 DWORD dwExitCode; HANDLE hThread = _beginthread(...); GetExitCodeThread(hThread, &dwExitCode); CloseHandle(hThread); 

The newly created thread might execute, return, and terminate before the first thread can call GetExitCodeThread. If this happens, the value in hThread will be invalid because _endthread has closed the new thread's handle. Needless to say, the call to CloseHandle will also fail for the same reason.

The new _endthreadex function does not close the thread's handle, so the code fragment above will work correctly if we replace the call to _beginthread with a call to _beginthreadex. Remember that when your thread function returns, _beginthreadex calls _endthreadex, while _beginthread calls _endthread.



Programming Applications for Microsoft Windows
Programming Applications for Microsoft Windows (Microsoft Programming Series)
ISBN: 1572319968
EAN: 2147483647
Year: 1999
Pages: 193

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net