C标准库源码解读(VC9.0版本)——assert.h

      先列一下我手头的资料:

      1.The C Programming Language

      2.The C Standar Library

      3. Programming Language C  1988 http://flash-gordon.me.uk/ansi.c.txt 

      4. Rationale for International Standard—Programming Languages— C http://www.open-std.org/jtc1/sc22/wg14/www/docs/C99RationaleV5.10.pdf

      这些都是原始的权威资料

 

      目的:熟悉C语言库在windows下的实现原理,复习windows的API,期望达到精通C语言。

      原则:从C的标准库的实现跟进到WindowsAPI函数,API底层进内核的部分就不属于讨论范围了。有疑问的C语言的语法和规定需要追踪到C标准的权威文档里。

      先列举assert.h内容:

/***
*assert.h - define the assert macro
*
*       Copyright (c) Microsoft Corporation. All rights reserved.
*
*Purpose:
*       Defines the assert(exp) macro.
*       [ANSI/System V]
*
*       [Public]
*
****/

#include <crtdefs.h>

#undef  assert

#ifdef  NDEBUG

#define assert(_Expression)     ((void)0)

#else

#ifdef  __cplusplus
extern "C" {
#endif

_CRTIMP void __cdecl _wassert(_In_z_ const wchar_t * _Message, _In_z_ const wchar_t *_File, _In_ unsigned _Line);

#ifdef  __cplusplus
}
#endif

#define assert(_Expression) (void)( (!!(_Expression)) || (_wassert(_CRT_WIDE(#_Expression), _CRT_WIDE(__FILE__), __LINE__), 0) )

#endif  /* NDEBUG */

      ctrdefs.h包含了一些与系统和版本相关,以及部分结构体定义,在我们这里不关注。

      NDEBUG宏是VC下Release与Debug版本的开关,常用VC环境的应该很清楚。

      可以很清楚的看 到assert(_Expression)是一个宏,在Release版本下解析为((void)0)。这种写法,把0强制转换成空类型?平时编码的时候也很少看见。经过编译后看汇编执行,这一行没有在代码段里面,应该是被编译器直接过滤掉了。

      由此,想到了一个问题:只有数字的表达式(如 1; 2;),编译能通过吗?如果能过汇编后是什么样子的?实践出真知,试试就知道了。

#ifndef _CRT_WIDE
#define __CRT_WIDE(_String) L ## _String
#define _CRT_WIDE(_String) __CRT_WIDE(_String)
#endif

      再看Debug版本的展开。先判断(void)((!!(_Expression)),两个叹号,双重非语句,最终为true或false;就是说如果为true,assert语句在Debug下直接跳过。

      __FILE__和__LINE__是文件名和当前行号。因为我看这些C标准库的目的就是向着“精通C语言”这几个在招聘条件里面经常列出来的技能,当然要追一下根底:__FILE__,__LINE___究竟——是不是C语言规定的内容?引用这篇文章:http://www.cnblogs.com/lixiaohui-ambition/archive/2012/08/21/2649052.html 这两个是C编译器内置宏。我再追根到了ANSI C的发布文档:http://flash-gordon.me.uk/ansi.c.txt有如下内容:

3.8.8 Predefined macro names

   The following macro names shall be defined by the implementation:
The line number of the current source line (a decimal constant).  The
presumed name of the source file (a character string literal).  The
date of translation of the source file (a character string literal of
the form Mmm dd yyyy , where the names of the months are the same as
those generated by the asctime function, and the first character of dd
is a space character if the value is less than 10).  If the date of
translation is not available, an implementation-defined valid date
shall be supplied.  The time of translation of the source file (a
character string literal of the form hh:mm:ss as in the time generated
by the asctime function).  If the time of translation is not
available, an implementation-defined valid time shall be supplied.
the decimal constant 1./79/

   The values of the predefined macros (except for __LINE__ and
__FILE__ ) remain constant throughout the translation unit.

   None of these macro names, nor the identifier defined , shall be
the subject of a #define or a #undef preprocessing directive.  All
predefined macro names shall begin with a leading underscore followed
by an upper-case letter or a second underscore.

      上面内容列出了ANSI C要求编译器实现的预定义宏,包括行号、源文件名、指定格式的执行编译的日期、指定格式的执行编译的时间,并且__LINE__和__FILE__是在预编译时设置,日期时间在预编译期间是不转换展开的。

      ——那么,可以下结论:__FILE__和__LINE__是C语言标准的一部分,要求编译器实现处理,是在预编译期间展开的预定义宏。

      _CRT_WIDE(x)宏是把x展开为L##String形式,也就是成为一个宽字节的字符串,无论传进去的是什么内容,都统统转为宽字节字符串。对于C语言中井号的用法,简单来说,#x是转换成”x“,也就是展开为字符串 ;L##x是转换成Lx,也就是展开成连接串。我们在VC的debug环境下一般断言语句为假的时候,会弹出一个提示窗口,这也是设计断言用于调试时候的目的。从这个方向看来,_wassert()函数就是跳出窗口的函数咯。

      _wassert()在assert.c文件实现,由于代码中套了太多微软本身实现的宏和检测处理,一些该忽略的就不要关注。

#ifdef _UNICODE
void __cdecl _wassert (
        const wchar_t *expr,
        const wchar_t *filename,
        unsigned lineno
        )
#else  /* _UNICODE */
void __cdecl _assert (

        const char *expr,
        const char *filename,
        unsigned lineno
        )
#endif  /* _UNICODE */
{
        /*
         * Build the assertion message, then write it out. The exact form
         * depends on whether it is to be written out via stderr or the
         * MessageBox API.
         */
        if ( (_set_error_mode(_REPORT_ERRMODE)== _OUT_TO_STDERR) ||
             ((_set_error_mode(_REPORT_ERRMODE) == _OUT_TO_DEFAULT) &&
              (__app_type == _CONSOLE_APP)) )
        {
#ifdef _UNICODE
            {
                TCHAR assertbuf[ASSERTBUFSZ];
                HANDLE hErr ;
                DWORD written;

                hErr = GetStdHandle(STD_ERROR_HANDLE);

                if(hErr!=INVALID_HANDLE_VALUE && hErr!=NULL)
                {
                    if(swprintf(assertbuf, ASSERTBUFSZ,_assertstring,expr,filename,lineno) >= 0)
                    {
                        if(GetFileType(hErr) == FILE_TYPE_CHAR)
                        {
                            if(WriteConsoleW(hErr, assertbuf, (unsigned long)wcslen(assertbuf), &written, NULL))
                            {
                                abort();
                            }
                        }
                    }
                }
            }

#endif  /* _UNICODE */

            /*
             * Build message and write it out to stderr. It will be of the
             * form:
             *        Assertion failed: <expr>, file <filename>, line <lineno>
             */
            if ( !anybuf(stderr) )
            /*
             * stderr is unused, hence unbuffered, as yet. set it to
             * single character buffering (to avoid a malloc() of a
             * stream buffer).
             */
             (void) setvbuf(stderr, NULL, _IONBF, 0);


            _ftprintf(stderr, _assertstring, expr, filename, lineno);
            fflush(stderr);

        }

      我们只看UNICODE版本的函数,_set_error_mode我们先忽略,后面_app_type指明:如果是控制台程序,并且取得标准错误句柄,就向控制台输出一些信息然后退出。可以看到,这个if里调用了几个函数,大写开头的全是WindowsAPI函数,里面的实现就不追究了;但其他的如swprintf, abort,setvbuf,_ftprintf,fflush,我们可以追踪其实现。等等,我们在实现assert呢!如果swprintf里面调用了assert了怎么办?在The C programming language里面P20:

A naive, way to write the active form of the macro is:
#define assert (test) i f ( ! (test) ) \
fprintf (stderr, "Assertion failed: %s, f i l e %s, line %i\nW, \
#test, __FILE__, __LINE__) /* UNACCEPTABLE! */

This form is unacceptable for a variety of reasons:The macro must not directly call any of the library output functions, suchas fprintf. Nor may it refer to the macro stderr. These names areproperly declared or defined only in the header <stdio. h>. The programmight not have included that header, and the header <assert. h> mustnot. A program can define macros that rename any of the names fromanother header, provided it doesn't include that header. That mandatesthat the macro call a function with a secret name to do the actual output.The macro must expand to a void expression. The program can containan expression such as (assert (O < X) , x < y) . That rules out use of theif statement, for example. Any testing must make use of one of theconditional operators within an expression.The macro should expand to efficient and compact code. Otherwise,programmers will avoid writing assertions. This version always makesa function call that passes five arguments.

      为什么使用fprintf不被接受?这里给出的一个原因是fprintf的某些实现会有可能会使用到assert。但微软搞什么呢,他应该是觉得,我自己写的fprintf不会使用到assert,我不管你的标准,方便就好,我只能这么理解了。

      接下来,如果不是应用台程序,程序处理有不少对要输出的字符串处理的代码段,GetModuleFIleName, _tcscpy_s, _tcscat_s, _tcslen都有使用。

      在这里先吐下槽,经常看到有人拿个C语言题目:请写一个库函数strcpy。然后是3分、5分、7分、10分的答案:

char * strcpy( char *strDest, const char *strSrc ) 
{ 
 assert( (strDest != NULL) && (strSrc != NULL) ); 
 char *address = strDest; 
 while( (*strDest++ = * strSrc++) != ‘/0’ ); 
  return address; 
}  

      现在我觉得,如果assert是要调用strcpy?怎么办?给答案的考官有考虑这个么?

      处理好字符串然后调用:

            nCode = __crtMessageBox(assertbuf,
                _T("Microsoft Visual C++ Runtime Library"),
                MB_ABORTRETRYIGNORE|MB_ICONHAND|MB_SETFOREGROUND|MB_TASKMODAL);

      我们直觉肯定知道这是弹出一个窗口显示信息,然后用户选择点击哪个按钮返回对应值,但这可不是windowsAPI函数,需要再跟踪进去,到底干了什么。源代码在ctrmbox.c里。源代码也不需要一行行解析,有一个原则就是,这是底层函数,基本上都直接使用API了,肯定不会使用其他库函数——因为这个函数在实现其他库函数时要使用!看完差不多就明白了,从USER32.dll取得几个函数的地址,以函数指针形式调用弹出窗口。问题来了:为什么它不直接调用函数?而要LoadLibrary然后GetProcAddress成函数指针,再通过函数指针调用。按道理来说(我从一些windows系统理论的书看到的),Kernel32.dll和User32.dll的地址在加载系统的时候就定了,而且是按固定虚拟地址加载(按windows核心编程作者经验所说)。再想一想,我觉得可能的解释是:为了防止不同版本的User32.dll库加载有可能的变化,Jeffrey Richter也只是说那是他的经验,微软并没有表明要加载到固定地址。但是Kernel32.dll应该就不会有什么变化的了,只要能兼容的都需要从相同的地址加载,这样应该说得过去。附__crtMessageBox代码:


 

#ifdef _UNICODE
int __cdecl __crtMessageBoxW(
#else  /* _UNICODE */
int __cdecl __crtMessageBoxA(
#endif  /* _UNICODE */
        LPCTSTR lpText,
        LPCTSTR lpCaption,
        UINT uType
        )
{
        typedef int (APIENTRY *PFNMessageBox)(HWND, LPCTSTR, LPCTSTR, UINT);
        typedef HWND (APIENTRY *PFNGetActiveWindow)(void);
        typedef HWND (APIENTRY *PFNGetLastActivePopup)(HWND);
        typedef HWINSTA (APIENTRY *PFNGetProcessWindowStation)(void);
        typedef BOOL (APIENTRY *PFNGetUserObjectInformation)(HANDLE, int, PVOID, DWORD, LPDWORD);

        void *pfn = NULL;
        void *enull = _encoded_null();
        static PFNMessageBox pfnMessageBox = NULL;
        static PFNGetActiveWindow pfnGetActiveWindow = NULL;
        static PFNGetLastActivePopup pfnGetLastActivePopup = NULL;
        static PFNGetProcessWindowStation pfnGetProcessWindowStation = NULL;
        static PFNGetUserObjectInformation pfnGetUserObjectInformation = NULL;

        HWND hWndParent = NULL;
        BOOL fNonInteractive = FALSE;
        HWINSTA hwinsta=NULL;
        USEROBJECTFLAGS uof;
        DWORD nDummy;

        if (NULL == pfnMessageBox)
        {
            HMODULE hlib=LoadLibrary(_T("USER32.DLL"));
            if(hlib==NULL)
            {
                return 0;
            }

            if (NULL == (pfn =
#ifdef _UNICODE
                            GetProcAddress(hlib, "MessageBoxW")))
#else  /* _UNICODE */
                            GetProcAddress(hlib, "MessageBoxA")))
#endif  /* _UNICODE */
                return 0;

            pfnMessageBox = (PFNMessageBox) _encode_pointer(pfn);

            pfnGetActiveWindow = (PFNGetActiveWindow)
                _encode_pointer(GetProcAddress(hlib, "GetActiveWindow"));

            pfnGetLastActivePopup = (PFNGetLastActivePopup)
                _encode_pointer(GetProcAddress(hlib, "GetLastActivePopup"));

            pfn =
#ifdef _UNICODE
                GetProcAddress(hlib, "GetUserObjectInformationW");
#else  /* _UNICODE */
                GetProcAddress(hlib, "GetUserObjectInformationA");
#endif  /* _UNICODE */

            pfnGetUserObjectInformation = (PFNGetUserObjectInformation) _encode_pointer(pfn);

            if (pfnGetUserObjectInformation != NULL)
                pfnGetProcessWindowStation = (PFNGetProcessWindowStation)
                _encode_pointer(GetProcAddress(hlib, "GetProcessWindowStation"));
        }

        /*
         * If the current process isn't attached to a visible WindowStation,
         * (e.g. a non-interactive service), then we need to set the
         * MB_SERVICE_NOTIFICATION flag, else the message box will be
         * invisible, hanging the program.
         *
         * This check only applies to Windows NT-based systems (for which we
         * retrieved the address of GetProcessWindowStation above).
         */

        if (pfnGetProcessWindowStation != enull && pfnGetUserObjectInformation != enull)
        {
            /* check for NULL expliticly to pacify prefix */
            PFNGetProcessWindowStation dpfnGetProcessWindowStation=(PFNGetProcessWindowStation) _decode_pointer(pfnGetProcessWindowStation);
            PFNGetUserObjectInformation dpfnGetUserObjectInformation=(PFNGetUserObjectInformation) _decode_pointer(pfnGetUserObjectInformation);

            if(dpfnGetProcessWindowStation && dpfnGetUserObjectInformation)
            {
                if (NULL == (hwinsta = (dpfnGetProcessWindowStation)()) ||
                    !(dpfnGetUserObjectInformation)
                    (hwinsta, UOI_FLAGS, &uof, sizeof(uof), &nDummy) ||
                    (uof.dwFlags & WSF_VISIBLE) == 0)
                {
                    fNonInteractive = TRUE;
                }
            }
        }

        if (fNonInteractive)
        {
            uType |= MB_SERVICE_NOTIFICATION;
        }
        else
        {
            if (pfnGetActiveWindow != enull)
            {
                PFNGetActiveWindow dpfnGetActiveWindow=(PFNGetActiveWindow) _decode_pointer(pfnGetActiveWindow);
                if(dpfnGetActiveWindow)
                {
                    hWndParent = (dpfnGetActiveWindow)();
                }
            }

            if (hWndParent != NULL && pfnGetLastActivePopup != enull)
            {
                PFNGetLastActivePopup dpfnGetLastActivePopup=(PFNGetLastActivePopup) _decode_pointer(pfnGetLastActivePopup);
                if(dpfnGetLastActivePopup)
                {
                    hWndParent = (dpfnGetLastActivePopup)(hWndParent);
                }
            }
        }

        /* scope */
        {
            PFNMessageBox dpfnMessageBox=(PFNMessageBox) _decode_pointer(pfnMessageBox);
            if(dpfnMessageBox)
            {
                return (dpfnMessageBox)(hWndParent, lpText, lpCaption, uType);
            }
            else
            {
                /* should never happen */
                return 0;
            }
        }
}


 

     

 

 

展开阅读全文

没有更多推荐了,返回首页