程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C >> 關於C >> C標准庫源碼解讀(VC9.0版本)——assert.h

C標准庫源碼解讀(VC9.0版本)——assert.h

編輯:關於C
          目的:熟悉C語言庫在windows下的實現原理,復習windows的API,期望達到精通C語言。         原則:從C的標准庫的實現跟進到WindowsAPI函數,API底層進內核的部分就不屬於討論范圍了。有疑問的C語言的語法和規定需要追蹤到C標准的權威文檔裡。         先列舉assert.h內容:   [cpp]   /***  *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;),編譯能通過嗎?如果能過匯編後是什麼樣子的?實踐出真知,試試就知道了。   [cpp]  #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有如下內容:   [plain]   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文件實現,由於代碼中套了太多微軟本身實現的宏和檢測處理,一些該忽略的就不要關注。   [cpp]   #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: [cpp]  #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分的答案:   [cpp]  char * strcpy( char *strDest, const char *strSrc )    {     assert( (strDest != NULL) && (strSrc != NULL) );     char *address = strDest;     while( (*strDest++ = * strSrc++) != ‘/0’ );      return address;    }           現在我覺得,如果assert是要調用strcpy?怎麼辦?給答案的考官有考慮這個麼?       處理好字符串然後調用:   [cpp]  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代碼:         [cpp]   #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;               }           }   }               
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved