从零开始,学习windows编程(3)—hello.c的疑惑

来源:互联网 发布:开淘宝网店怎么上货 编辑:程序博客网 时间:2024/04/30 17:55

上一篇,我们问了几个问题,这一篇就从hello.c的各个方面来研究研究,等到这一篇的结束来看这些疑惑有哪些是可以解答了的,当然还有一些可能要放到后面解决了……。

既然要抽丝剥茧,就要从手头已有的线索出发,同时加上搜索到的知识,以及自己的思考,任何问题的解决都是通过这个路径(当然,老板除外,老板这种生物只需要发出指令,然后得到结果就OK了……)。

我们现在有的,是一个hello.c文件,通过cl命令就可以生成目标hello.exe文件。

那我们就可以研究一下cl这个东西。

CL解释

CL.EXE(或cl.exe,似乎windows的文件系统对于大小写是不敏感的),是何许人也呢?直接引用微软的说辞吧(http://msdn.microsoft.com/en-us/library/9s7c9wdw(v=vs.71).aspx)。

CL.exe is a 32-bit tool that controls the Microsoft C and C++ compilers and linker. The compilers produce Common Object File Format (COFF) object (.obj) files. The linker produces executable (.exe) files or dynamic-link libraries (DLLs).

Note that all compiler options are case sensitive.

To compile without linking, use /c.

因为VC6的版本说明没有找到,这里的说明文字是Visual studio 2003的CL.EXE说明,不过都是一样的。从上面的说明可以看到CL是用来控制微软C和C++编译器(compiler)和链接器(linker),一共做了两件事情。编译器会生成COFF文件格式的目标文件(.obj);链接器会生成可执行文件(.exe)或者动态链接库文件(DLLs)。

对比我们之前的做法,的确是执行cl命令之后,生成了一个hello.obj文件和一个hello.exe文件。

另外在最后,还说明了一下,使用/c选项,则可以不进行链接过程。

CL选项

那cl还有哪些选项呢?使用cl /?来看看。

复制代码
1 d:\test>cl /?
2 Microsoft (R)32-bit C/C++ Optimizing Compiler Version 12.00.8168for 80x86
3 Copyright (C) Microsoft Corp 1984-1998. All rights reserved.
4 
5 C/C++ COMPILER OPTIONS
6 
7 -OPTIMIZATION-
8 
9  /O1 minimize space /Op[-] improve floating-pt consistency
10  /O2 maximize speed /Os favor code space
11  /Oa assume no aliasing /Ot favor code speed
12  /Ob<n> inline expansion (default n=0)/Ow assume cross-function aliasing
13  /Od disable optimizations (default)/Ox maximum opts.(/Ogityb1 /Gs)
14  /Og enable global optimization /Oy[-] enable frame pointer omission
15  /Oi enable intrinsic functions
16 
17 -CODE GENERATION-
18 
19  /G3 optimize for80386/Gy separate functions for linker
20  /G4 optimize for80486/Ge force stack checking for all funcs
21  /G5 optimize for Pentium /Gs[num] disable stack checking calls
22 /G6 optimize for Pentium Pro /Gh enable hook function call
23 /GB optimize for blended model (default)/GR[-] enable C++ RTTI
24 /Gd __cdecl calling convention /GX[-] enable C++ EH (same as /EHsc)
25 /Gr __fastcall calling convention /Gi[-] enable incremental compilation
26 /Gz __stdcall calling convention /Gm[-] enable minimal rebuild
27 /GA optimize for Windows Application /EHs enable synchronous C++ EH
28 /GD optimize for Windows DLL /EHa enable asynchronous C++ EH
29 
30 /Gf enable string pooling /EHc extern "C" defaults to nothrow
31 /GF enable read-only string pooling /QIfdiv[-] enable Pentium FDIV fix
32 /GZ enable runtime debug checks /QI0f[-] enable Pentium 0x0f fix
33 
34 -OUTPUT FILES-
35 
36 /Fa[file] name assembly listing file /Fo<file> name object file
37 /FA[sc] configure assembly listing /Fp<file> name precompiled header file
38 /Fd[file] name .PDB file /Fr[file] name source browser file
39 /Fe<file> name executable file /FR[file] name extended .SBR file
40 /Fm[file] name map file
41 
42 -PREPROCESSOR-
43 
44 /C don't strip comments /FI<file> name forced include file
45 /D<name>{=|#}<text> define macro /U<name> remove predefined macro
46 /E preprocess to stdout /u remove all predefined macros
47 /EP preprocess to stdout, no #line /I<dir> add to include search path
48 /P preprocess to file /X ignore "standard places"
49 
50 -LANGUAGE-
51 
52 /Zi enable debugging information /Zl omit default library name in .OBJ
53 /ZI enable Edit and Continue debug info /Zg generate function prototypes
54 
55 /Z7 enable old-style debug info /Zs syntax check only
56 /Zd line number debugging info only /vd{0|1} disable/enable vtordisp
57 /Zp[n] pack structs on n-byte boundary /vm<x>type of pointers to members
58 /Za disable extensions (implies /Op)/noBool disable "bool" keyword
59 /Ze enable extensions (default)
60 
61 -MISCELLANEOUS-
62 
63 /?,/help print this help message /V<string>set version string
64 /c compile only, no link /w disable all warnings
65 /H<num> max external name length /W<n>set warning level (default n=1)
66 /J default char type is unsigned /WX treat warnings as errors
67 /nologo suppress copyright message /Yc[file] create .PCH file
68 /Tc<source file> compile file as ./Yd put debug info in every .OBJ
69 /Tp<source file> compile file as .cpp /Yu[file] use.PCH file
70 /TC compile all files as ./YX[file] automatic .PCH
71 /TP compile all files as .cpp /Zm<n> max memory alloc (% of default)
72 
73 -LINKING-
74 
75 /MD link with MSVCRT.LIB /MDd link with MSVCRTD.LIB debug lib
76 /ML link with LIBC.LIB /MLd link with LIBCD.LIB debug lib
77 /MT link with LIBCMT.LIB /MTd link with LIBCMTD.LIB debug lib
78 /LD Create .DLL /F<num>set stack size
79 
80 /LDd Create .DLL debug libary /link [linker options and libraries]
复制代码

具体可以看(http://msdn.microsoft.com/en-us/library/19z1t1wy(v=VS.71).aspx)来了解。

选项的具体使用

原来有这么多的选项。数了一下,有99个选项之多。每个选项都有自己的作用,就需要使用的时候去熟悉了,不过我们现在不需要了解这么多,我们就来看下/c选项好了。

This option prevents the automatic call to LINK. Compiling with /c creates .obj files only. You must call LINK explicitly with the proper files and options to perform the linking phase of the build.

Any internal project created in the development environment uses the /c option by default.

To set this compiler option in the Visual Studio development environment

This option is not available from within the development environment.

To set this compiler option programmatically

This compiler option cannot be changed programmatically.

要彻底理解它,就必须知道C语言到目标代码的过程。首先,C语言中有两种文件,一个.c文件(我们一般称之为源文件),一个.h文件(我们一般称之为头文件)。头文件一般会被源文件包含,而源文件会被编译成一个二进制中间文件,之后所有的二进制中间文件会通过链接器进行链接,形成一个可以在某个特定系统上执行的二进制可执行文件。

于是,C语言-->可执行代码可以明显的分为编译过程和链接过程,而由于C语言中一种特殊语法--“宏”的存在,所以还有一个预编译过程,在预编译过程中,C语言中的宏会被展开;同时,包含的头文件中的代码,会根据条件编译等,囊括到源文件中,这些工作做好后,给编译器的就是一份新的源码文件,供编译阶段使用。

另外,相信大家都知道,C语言是从汇编语言发展过来的,汇编是从机器码发展过来的,以前的编译器都是先将C语言编译成汇编语言,再将其解释成机器码就可以了。现代编译器除了做这个,还可以对代码进行优化。不过基本思想是一直没有变的。

而我们做C开发的时候,有时稍微看一下汇编之后的代码,对于计算机是如何执行程序的过程,会有更清晰的认识。

那么,用cl可以生成汇编代码吗?答案当然是肯定的。

在命令行输入cl /FA hello.c,看看生成了哪些东西?当然,如果你不要生成.exe文件,可以加上/c选项。

可以看到生成了.obj文件和.asm文件。

查看asm文件内容

复制代码
1 TITLE hello.c
2 .386P
3 include listing.inc
4 if @Version gt 510
5 .model FLAT
6 else
7 _TEXT SEGMENT PARA USE32 PUBLIC 'CODE'
8 _TEXT ENDS
9 _DATA SEGMENT DWORD USE32 PUBLIC 'DATA'
10 _DATA ENDS
11 CONST SEGMENT DWORD USE32 PUBLIC 'CONST'
12 CONST ENDS
13 _BSS SEGMENT DWORD USE32 PUBLIC 'BSS'
14 _BSS ENDS
15 _TLS SEGMENT DWORD USE32 PUBLIC 'TLS'
16 _TLS ENDS
17 FLAT GROUP _DATA, CONST, _BSS
18 ASSUME CS: FLAT, DS: FLAT, SS: FLAT
19 endif
20 PUBLIC _main
21 EXTRN _printf:NEAR
22 _DATA SEGMENT
23 $SG336 DB 'abcdefg.', 0aH, 00H
24 _DATA ENDS
25 _TEXT SEGMENT
26 _main PROC NEAR
27 ; File hello.c
28 ; Line 4
29 push ebp
30 mov ebp, esp
31 ; Line 5
32 push OFFSET FLAT:$SG336
33 call _printf
34 add esp, 4
35 ; Line 7
36 xor eax, eax
37 ; Line 8
38 pop ebp
39 ret0
40 _main ENDP
41 _TEXT ENDS
42 END
复制代码

这个汇编代码,仅仅用cl再进行编译,我还没有找到方法,所以这里就要引入一个新工具--ml,ml是MASM中编译汇编代码使用的编译器,同样,它也会调用到link,并且让它不调用link,使用的编译选项也是/c。

从cl和hello.c,我们引出了hello.obj,hello.asm,以及ml.exe,包括C语言到可执行文件的过程。下面先跳过ml,来看看链接过程。

链接过程

链接过程也就是将.obj变成.exe的过程,使用的是link.exe。

直接使用cl /c hello.c产生的hello.obj文件,输入命令link hello.obj,直接就生成了.exe文件。

真简单~不过,且慢,我们有main函数,但是至少printf函数这个东西,是在哪里呢?

秘密就藏在生成的那个.obj文件中啦!如果用另外一个.obj文件(稍候我们会用另外一个hello.obj),那就会得到错误哦。

那我们打开obj文件来看看吧。怎么?还要看二进制文件?不用担心,看的还是英文字符,多说无用,还是看下就知道了。

这里我使用的是winhex,其实使用其他任何一个文本编辑器都是可以的,不过看二进制文件我还是习惯使用专门的二进制编辑器。截图用的picpick。顺便说明下。

helloobj

左边是文件内容,主要看右边,红线划过的地方。看不清的同学可以看这里:

-defaultlib:LIBC –defaultlib:OLDNAMES

其实,链接过程会将所有的静态库和目标文件进行链接,而在这份hello.obj文件中,指定了两个default lib,一个为libc.lib,一个为oldnames.lib。而这两个lib文件又是何方神圣呢?

libc.lib为单线程静态C标准库(在cl中可使用/ML选项定义对其的链接),而oldnames.lib则是为了兼容微软以前的C/C++开发系统,基本不使用了,至少在我们这个hello.c编译链接成exe的过程中,可以忽略掉它。

其实,我们用一个很简单的方法可以知道我们的程序链接了哪些库。

复制代码
1 d:\test>link /verbose:lib hello.obj
2 Microsoft (R) Incremental Linker Version 6.00.8168
3 Copyright (C) Microsoft Corp 1992-1998. All rights reserved.
4 
5 Searching Libraries
6 Searching D:\Program Files\Microsoft Visual Studio\VC98\lib\LIBC.lib:
7 Searching D:\Program Files\Microsoft Visual Studio\VC98\lib\OLDNAMES.lib:
8 Searching D:\Program Files\Microsoft Platform SDK\Lib\.\kernel32.lib:
9 
10 Done Searching Libraries
复制代码

这里还可以使用一个小工具,叫做dumpbin的,可以利用它的选项来分析hello.obj文件,看到defaultlib,而不用打开文件自己查看。

复制代码
1 d:\test>dumpbin /DIRECTIVES hello.obj
2 Microsoft (R) COFF Binary File Dumper Version 6.00.8168
3 Copyright (C) Microsoft Corp 1992-1998. All rights reserved.
4 
5 Dump of file hello.obj
6 
7 File Type: COFF OBJECT
8 
9 Linker Directives
10 -----------------
11 -defaultlib:LIBC
12 -defaultlib:OLDNAMES
13 
14 Summary
15 
16 A .data
17 26 .drectve
18 14 .text
复制代码

oldnames.lib我们可以略过,但是libc.lib我们还是需要了解的,今后要做VC相关项目,使用到第三方库的会经常用到。为了解释libc.lib,我们需要引入一个新的概念--“C语言运行库”,这里我们只讨论windows平台,VC中使用的被称为MSVC CRT。

另外还要牵涉到的一个概念就是线程。

C语言运行时库(C Run-time Library)---CRT

要了解这个东西,就需要对C语言的发展史有一定的了解。C语言是在上世纪70年代在B语言的基础上被发明出来的,之后Dennis Ritchie 和 Brian Kernighan 就用C本身重写了90%以上的 UNIX 系统函数。并且把其中最常用的部分独立出来,形成头文件和对应的 LIBRARY,C run-time library 就是这样形成的。在类UNIX系统中,C运行库被认为是系统的一部分。

我们看看维基百科上面的定义吧

In computer programming, a runtime library is a special program library used by a compiler, to implement functions built into a programming language, during the execution (runtime) of a computer program. This often includes functions for input and output, or for memory management.

而随着C语言的流行,出现了很多C编译器,也就有很多C运行时库的实现。主要有下面的几种

  • BSD libc, implementations distributed under BSD operating systems.
  • GNU C Library, used in GNU/Linux and GNU/HURD.
  • Dinkum C99 Library from Dinkumware, most common commercially licensed one
  • Microsoft C Run-time Library, part of Microsoft Visual C++
  • dietlibc, an alternative small implementation of the C standard library (MMU-less)
  • uClibc, a C standard library for embedded Linux systems (MMU-less)
  • Newlib, a C standard library for embedded systems (MMU-less)
  • klibc, primarily for booting Linux systems.
  • EGLIBC, variant of glibc for embedded systems.
  • musl, another lightweight C standard library implementation for Linux systems

可以看到,我们现在讨论的Microsoft C Run-time Library也是其中的一种。在那个年代,线程还没有被应用到操作系统上,应用程序都是单线程的。所以最初的C Run-time Library都是单线程的。另外动态库的概念也是后期出现的,所以一开始C Run-time Library也只是静态链接。

随着时间的推移,计算机技术也在不断进步。C语言运行时库也根据单线程、多线程、静态链接、动态链接,是否需要debug信息的不同,分为不同的版本。在微软是MSVC CRT实现中,具体是按照表格中的实现来分类的(从VS2003 MSDN中摘录)。其中,静态链接有单线程和多线程版本,动态链接仅有多线程版本。

C run-time library (without iostream or standard C++ library)CharacteristicsOption 
(编译选项)Preprocessor directives(预编译宏)LIBC.LIBSingle-threaded, static link/ML不带任何宏LIBCMT.LIBMultithreaded, static link/MT_MTMSVCRT.LIBMultithreaded, dynamic link (import library for MSVCR71.DLL). Be aware that if you use the Standard C++ Library, your program will need MSVCP71.DLL to run./MD

_MT, _DLL

LIBCD.LIBSingle-threaded, static link (debug)

/MLd

_DEBUGLIBCMTD.LIBMultithreaded, static link (debug)

/MTd

_DEBUG, _MTMSVCRTD.LIB

Multithreaded, dynamic link (import library for MSVCR71D.DLL) (debug)

/MDd

_DEBUG, _MT, _DLL

这里要提及一下的是VS2003之后,也就是VS2005版本中,LIBC.LIB是不会出现了。下面这段说明是从VS2005的CRT说明页面中摘下来的。

NoteNote

The single-threaded CRT (libc.lib, libcd.lib) (formerly the /ML or /MLd options) is no longer available. Instead, use the multithreaded CRT. See Multithreaded Libraries Performance.

所以,VC6中很多代码,直接在VS2005中编译是会有找不到libc.lib这个错误报出来的,而VS2003则没有这个问题。这个问题的原因大家应该很清楚了。至于解决方法,在网上找到的文章一般都会让你忽略掉这个库编译,然后结果有的是编译通过了,有的还是不行,其中涉及到的知识就是这些了,也可以通过这些了解到为什么可以或为什么不行。(具体例子暂时没有,如果有童鞋有,可以在这里加上具体的例子,能更好的说明问题)

同时,细心的童鞋可以发现,VS2005中增加了对CLR的支持,按照我的理解,应该是对Managed C++的支持而使用的(具体没有研究过,如果有错误,请指正)

CRT的作用,因为篇幅和时间的原因,就要再找时间研究和另发一篇了。

使用link链接CRT

前面我们的obj文件中就包含有default lib,那如果我们的obj文件中不包含该信息呢?

首先,我们需要生成一个不包含default lib信息的obj文件。

这里有两种方法,一种是生成ASM文件,然后利用ml来生成一个obj文件,其中不包含该信息(当然ml也会主动调用link,这里也需要使用选项来控制,具体有兴趣的童鞋可以自行研究,因为这里主要还是C/C++编程为主,汇编会涉及,但是不会深入);还有一种方法是利用cl的/Zl选项,这里要注意的是l是小写的L而不是I,我一开始就是看错了,结果编译出来还是带有default lib信息的。

cl //Zl hello.c

之后利用dumpbin来查看一下是否生成正确。然后再调用link看看。

结果如下:

复制代码
1 d:\test>dumpbin /DIRECTIVES hello.obj
2 Microsoft (R) COFF Binary File Dumper Version 6.00.8168
3 Copyright (C) Microsoft Corp 1992-1998. All rights reserved.
4 
5 Dump of file hello.obj
6 
7 File Type: COFF OBJECT
8 
9 Summary
10 
11 A .data
12 14 .text
13 
14 d:\test>link hello.obj
15 Microsoft (R) Incremental Linker Version 6.00.8168
16 Copyright (C) Microsoft Corp 1992-1998. All rights reserved.
17 
18 hello.obj : error LNK2001: unresolved external symbol _printf
19 LINK : error LNK2001: unresolved external symbol _mainCRTStartup
20 hello.exe : fatal error LNK1120: 2 unresolved externals
复制代码

现在就找不到_printf和_mainCRTStartup这两个符号了。

之后使用link hello.obj libc.lib看看。

成功生成hello.exe!

复制代码
1 d:\test>link hello.obj libc.lib
2 Microsoft (R) Incremental Linker Version 6.00.8168
3 Copyright (C) Microsoft Corp 1992-1998. All rights reserved.
4 
5 
6 d:\test>hello.exe
7 abcdefg.
复制代码

到这里,回过头看看,上一篇的问题有哪些是可以解决了,哪些还没有,但是有思路了的。还有没有新的问题?

总结

使用到的工具

  • CL.EXE
  • LINK.EXE
  • ML.EXE
  • DUMPBIN.EXE
  • WINHEX

整个C程序编译过程, MSVC和GCC对比

过程描述具体行为MSVCGCC预编译过程将宏进行展开,形成预编译文件cl /E hello.c >hello.igcc –E hello.c –o hello.i (调用预编译编译程序cc1)编译过程生成汇编代码文件只找到生成汇编代码文件和COD文件的方法,但不清楚如何通过.i文件来生成.asm文件gcc –S hello.i –o hello.s 
(同样也是cc1完成该过程)汇编过程从汇编代码生成目标文件不清楚如何通过.asm文件生成.obj文件(不使用MASM)gcc –c hello.s –o hello.o 
或as hello.s –o hello.o 
(汇编器as完成该过程)链接过程从目标文件生成可执行文件cl hello.cld –static crt1.o crti.o crtbeginT.o hello.o –start –group –lgcc –lgcc_en –lc-end-group crtend.o crtn.o 
(链接器ld完成该过程)

VC文件后缀名

后缀名解释如何得到ASM汇编文件cl /FAs /c hello.cCODAssembly With Machine Code生成机器代码和汇编代码文件cl /FAsc /c hello.cI预编译处理后的文件cl /P hello.cOBJ目标文件cl /c hello.cEXE可执行文件cl hello.c

参考:

  1. http://msdn.microsoft.com/en-us/library/abx4dbyh(v=VS.71).aspx
  2. http://msdn.microsoft.com/en-us/library/abx4dbyh(v=VS.80).aspx
  3. http://en.wikipedia.org/wiki/C_Run-Time_Library#Common_support_libraries
  4. http://en.wikipedia.org/wiki/Runtime_library
  5. http://msdn.microsoft.com/en-us/library/f1tbxcxh(v=VS.71).aspx
  6. http://www.woyouxian.net/c/c_overview_index.html
  7. http://www.cnblogs.com/taoxu0903/archive/2008/06/30/1232712.html
  8. http://msdn.microsoft.com/zh-tw/library/y0zzbyt4.aspx
  9. http://www.systemshell.org/viewthread.php?tid=256&sid=9wJZAqso
  10. http://hi.baidu.com/li_nemo/blog/item/4ad4b2f26562d8df0b46e0fd.html