亂想亂寫飛舞之空間
從零開始,學習windows編程(4)--從libc.lib開始
從上一篇文章中,大家已經了解到有C運行時庫這個概念,這個不算是新東西,但是一般都隱藏在幕後,C/C++語言教學的時候不講,windows/linux編程的時候似乎也不會專門講到。不過它一般是我們C/C++編程中默認會使用的一個重要部分。回想想,我們隨手打出的strcpy, memset, memcpy等等,不就是C運行時庫所提供出來的東西嗎?
既然這樣,就要好好研究一下這個東西了。
前面已經說過,針對單線程/多線程,靜態/動態鏈接,是否是debug版本,VC6的C運行時庫提供了6個版本。具體可以看下面的截圖。
而其中每一個選擇對應的LIB文件,在上一篇中已經有一個列表介紹了,這裡就不重復寫了。這裡也不全部一下子將所有的都研究一下,還是按照由淺入深的原則,從最簡單的部分開始,當然,也會在牽涉到其他部分的時候,進行一定的說明。
最簡單的當然是Single-Threaded,同時也是static link的了。其對應的文件為LIBC.LIB。對應CL的編譯選項為/ML。
既然要研究這個LIB文件,那當然是有源碼最好了,jjhou不是說過,“源碼面前,了無秘密”嗎。那我們在哪裡找到有源碼呢?
只要你安裝了VC6,它就帶有CRT的源碼,具體目錄和你安裝VC6的目錄有關,在我電腦上的路徑為“d:Program FilesMicrosoft Visual StudioVC98CRTSRC”,你進去之後會發現,裡面有不少熟悉的名字,如“MATH.H”、“STDIO.H”、“STDLIB.H”、“STRING.H”等,由於C運行時庫被C++運行時庫包含,所以這裡面還有C++標准庫的代碼,所以還能看到“IOSTREAM”、“CSTDIO”、“algorithm”等C++的std頭文件。
這裡就出現了一個問題,這裡的文件有成百上千個,我們一個個全部看是不可能的,那如何找出關鍵的部分來呢?
如果還對上一篇的分析有印象,並且帶有問題的同學,應該很容易聯想到,我們在link不帶有defaultlibs信息的hello.obj文件時,出現了兩個LINK2001錯誤,分別是找不到_printf和_mainCRTStartup這兩個symbol文件。
編譯器CL在編譯C程序的時候,內部將需要編譯的函數前面加上下劃線(_)用來標識,_printf具體指的就是printf函數,_mainCRTStartup則是mainCRTStartup,是在哪裡使用的呢?
我們已經知道,printf函數和mainCRTStartup函數的實現都是在LIBC.LIB中,printf,是我們main函數中用來打印信息的,而mainCRTStartup,則是hello.exe的入口(entry point)。
入口
學過C語言的人都知道,有一個main函數,是一個程序的入口。不管怎樣,main函數是特殊的。在TCPL (“The C programming Language” by K&R) 的1.1章節,介紹Hello World的時候說的一段話:
Now, for some explanations about the program itself. A C program, whatever its size, consists of functions and variables. A function contains statements that specify the computing operations to be done, and variables store values used during the computation. C functions are like the subroutines and functions in Fortran or the procedures and functions of Pascal. Our example is a function named main. Normally you are at liberty to give functions whatever names you like, but “main” is special - your program begins executing at the beginning of main. This means that every program must have a main somewhere.
因為這一段話,因為這本書的經典,很多人包括我思路都很難轉變。一直都認為main就是C程序的入口函數。不過,真的是這樣嗎?
使用匯編的童鞋都知道,匯編的入口函數只是一個符號,是可以隨意定義的,之後就從入口開始,PC一條條的開始執行匯編代碼。對於C程序來說,main也是一個符號而已,不過這個符號與匯編的_start有些區別,_start可以用其他符號直接代替,而在windows系統下,VC開發的環境中,我們的hello.exe的main函數之前還有一個mainCRTStartup(呼,好多限制條件,好繞口……)。
crt0.c
為什麼需要mainCRTStartup呢,我們還是要看一下源碼實現。先搜索到mainCRTStartup所在的文件,為crt0.c,其全部代碼如下:
show sourceview sourceprint?001 /***
002 *crt0.c - C runtime initialization routine
003 *
004 * Copyright (c) 1989-1997, Microsoft Corporation. All rights reserved.
005 *
006 *Purpose:
007 * This the actual startup routine for apps. It calls the users main
008 * routine [w]main() or [w]WinMain after performing C Run-Time Library
009 * initialization.
010 *
011 * (With ifdefs, this source file also provides the source code for
012 * wcrt0.c, the startup routine for console apps with wide characters,
013 * wincrt0.c, the startup routine for Windows apps, and wwincrt0.c,
014 * the startup routine for Windows apps with wide characters.)
015 *
016 *******************************************************************************/
017
018 #ifdef _WIN32
019
020 #ifndef CRTDLL
021
022 #include <cruntime.h>
023 #include <dos.h>
024 #include <internal.h>
025 #include <stdlib.h>
026 #include <string.h>
027 #include <rterr.h>
028 #include <windows.h>
029 #include <awint.h>
030 #include <tchar.h>
031 #include <dbgint.h>
032
033 /*
034 * wWinMain is not yet defined in winbase.h. When it is, this should be
035 * removed.
036 */
037
038 int
039 WINAPI
040 wWinMain(
041 HINSTANCE hInstance,
042 HINSTANCE hPrevInstance,
043 LPWSTR lpCmdLine,
044 int nShowCmd
045 );
046
047 #ifdef WPRFLAG
048 _TUCHAR * __cdecl _wwincmdln(void);
049 #else /* WPRFLAG */
050 _TUCHAR * __cdecl _wincmdln(void);
051 #endif /* WPRFLAG */
052
053 /*
054 * command line, environment, and a few other globals
055 */
056
057 #ifdef WPRFLAG
058 wchar_t *_wcmdln; /* points to wide command line */
059 #else /* WPRFLAG */
060 char *_acmdln; /* points to command line */
061 #endif /* WPRFLAG */
062
063 char *_aenvptr = NULL; /* points to environment block */
064 wchar_t *_wenvptr = NULL; /* points to wide environment block */
065
066
067 void (__cdecl * _aexit_rtn)(int) = _exit; /* RT message return procedure */
068
069 static void __cdecl fast_error_exit(int); /* Error exit via ExitProcess */
070
071 /*
072 * _error_mode and _apptype, together, determine how error messages are
073 * written out.
074 */
075 int __error_mode = _OUT_TO_DEFAULT;
076 #ifdef _WINMAIN_
077 int __app_type = _GUI_APP;&n