大一的时候,学习c语言,用的是VC6.0。用了1年多,到后来了解了Linux,知道了gcc编译器,开始使用gcc Hello.c -o a.out 这样的命令进行编译。后来又学了gcc的一些其他的命令,不同的命令可以编译出不同的目标代码。现在想想类似于VC这种IDE虽然方便,但是对于具体是怎样的一个过程就不得而知了。作为一个优秀的程序员怎么可以不了解这个过程呢。

  Gcc/g++ 在执行编译工作的时候,总共4步

  1.预处理,生成.i的文件  (预处理器cpp)

  2.将预处理后的文件转换成汇编语言,生成文件.s文件  ()

  3.从汇编变为目标代码(机器代码)生成.o(.obj)的文件  (汇编器as)

  4.连接目标代码,生成可执行程序  (连接器ld)

  我们现在先写一个Hello World吧

  1. #include <stdio.h>
  2. #define WOELD "World"
  3. static int a=;
  4. int main()
  5. {
  6. int j,jj;
  7. int i=;
  8. char ch='a';
  9. printf("Hello ");
  10. printf(WOELD);
  11. printf("\n%d %d %c\n",i,a,ch);
  12. return ;
  13. }

  先预处理一下,可以使用cpp命令,或者是使用gcc 的-E选项

  gcc -E Hello.c > Hello.i

  一个简单的Hello World都要800多行。

  可以看到这些都是函数的声明。而函数的定义是在链接库中。我们可以认为是写了好多函数了。然后在main中调用。这个跟一般的函数是一个道理的。只是这些函数是又编译器帮你写了。这个就不得不提到,C语言只是定义了标准库函数的输入和输出,至于实现的过程,是没有规定的。这个由编译器厂商自己设计,这就是为什么有人说Linux下gcc编译后的程序会比Windows下VS编译后的程序运行效率上有些区别的一个原因吧。

  像上面的printf函数的原型,如果有兴趣可以去下载源代码查看。

  下面这个是VC6.0对printf函数的定义

  1. int __cdecl printf (
  2. const char *format,
  3. ...
  4. )
  5. /*
  6. * stdout 'PRINT', 'F'ormatted
  7. */
  8. {
  9. va_list arglist;
  10. int buffing;
  11. int retval;
  12. va_start(arglist, format);
  13. _ASSERTE(format != NULL);
  14.  
  15. _lock_str2(, stdout);
  16. buffing = _stbuf(stdout);
  17. retval = _output(stdout,format,arglist);
  18. _ftbuf(buffing, stdout);
  19. _unlock_str2(, stdout);
  20. return(retval);
  21. }

  Gnu也有个对printf函数的定义,下面这个是glibc-2.2.5对printf的定义

  1. int
  2. printf (const char *format, ...)
  3. {
  4. va_list arg;
  5. int done;
  6. va_start (arg, format);
  7. done = vfprintf (stdout, format, arg);
  8. va_end (arg);
  9. return done;
  10. }
  11. /* The function itself. */
  12. int
  13. vfprintf (FILE *s, const CHAR_T *format, va_list ap)
  14. {
  15. /* The character used as thousands separator. */
  16. #ifdef COMPILE_WPRINTF
  17. wchar_t thousands_sep = L'\0';
  18. #else
  19. const char *thousands_sep = NULL;
  20. #endif
  21.  
  22. /* The string describing the size of groups of digits. */
  23. const char *grouping;
  24. ......

  从这里可以看出刚才我们定义的宏 WORLD已经转换为字符串了。所有我们知道所有的预处理或者宏定义都会在这一步完成。

  好了,我们现在开始第二步了。生成汇编代码,使用gcc的-S选项。

  gcc -S Hello.c -o Hello.s

  1. .file "Hello.c"
  2. .lcomm _a,,
  3. .def ___main; .scl ; .type ; .endef
  4. .section .rdata,"dr"
  5. LC0:
  6. .ascii "Hello \0"
  7. LC1:
  8. .ascii "World\0"
  9. LC2:
  10. .ascii "\12%d %d %c\12\0"
  11. .text
  12. .globl _main
  13. .def _main; .scl ; .type ; .endef
  14. _main:
  15. LFB13:
  16. .cfi_startproc
  17. pushl %ebp
  18. .cfi_def_cfa_offset
  19. .cfi_offset , -
  20. movl %esp, %ebp
  21. .cfi_def_cfa_register
  22. andl $-, %esp
  23. subl $, %esp
  24. call ___main
  25. movl $, (%esp)
  26. movb $, (%esp)
  27. movl $LC0, (%esp)
  28. call _printf
  29. movl $LC1, (%esp)
  30. call _printf
  31. movsbl (%esp), %edx
  32. movl _a, %eax
  33. movl %edx, (%esp)
  34. movl %eax, (%esp)
  35. movl (%esp), %eax
  36. movl %eax, (%esp)
  37. movl $LC2, (%esp)
  38. call _printf
  39. movl $, %eax
  40. leave
  41. .cfi_restore
  42. .cfi_def_cfa ,
  43. ret
  44. .cfi_endproc
  45. LFE13:
  46. .ident "GCC: (rev5, Built by MinGW-W64 project) 4.8.1"
  47. .def _printf; .scl ; .type ; .endef

  总共47行。如果我们使用-O优化

  gcc -S -O3 Hello.c -o Hello.s

  1. .file "Hello.c"
  2. .def ___main; .scl ; .type ; .endef
  3. .section .rdata,"dr"
  4. LC0:
  5. .ascii "Hello \0"
  6. LC1:
  7. .ascii "World\0"
  8. LC2:
  9. .ascii "\12%d %d %c\12\0"
  10. .section .text.startup,"x"
  11. .p2align ,,
  12. .globl _main
  13. .def _main; .scl ; .type ; .endef
  14. _main:
  15. LFB13:
  16. .cfi_startproc
  17. pushl %ebp
  18. .cfi_def_cfa_offset
  19. .cfi_offset , -
  20. movl %esp, %ebp
  21. .cfi_def_cfa_register
  22. andl $-, %esp
  23. subl $, %esp
  24. call ___main
  25. movl $LC0, (%esp)
  26. call _printf
  27. movl $LC1, (%esp)
  28. call _printf
  29. movl $, (%esp)
  30. movl $, (%esp)
  31. movl $, (%esp)
  32. movl $LC2, (%esp)
  33. call _printf
  34. xorl %eax, %eax
  35. leave
  36. .cfi_restore
  37. .cfi_def_cfa ,
  38. ret
  39. .cfi_endproc
  40. LFE13:
  41. .ident "GCC: (rev5, Built by MinGW-W64 project) 4.8.1"
  42. .def _printf; .scl ; .type ; .endef

  总共有42行,从这里就可以看出优化参数的作用了。

  第三步了。生成目标文件,可以使用gcc的-c参数

  gcc -c Hello.c -o Hello.o

  生成后的文件有 962字节

  gcc -c -O3 Hello.c -o Hello.o

  生成后的文件有1022字节

  大概就是这样了。这个目标文件的使用,到后面的动态静态连接库的时候提到。

  第四步了。

  gcc Hello.c -o Hello.exe

  gcc Hello.c -o a.out

  然后就可以运行了。

  动态链接库/静态链接库(补充)

  在windows下一般可以看到后缀为dll和后缀为lib的文件而linux下一般是so和lib***.a,但这两种文件可以分为三种库,分别是动态链接库(Dynamic-Link Libraries),目标库(Object Libraries)和导入库(Import Libraries),下面一一解释这三种库。

  目标库(Object Libraries)

  目标库又叫静态链接库,是扩展名为.LIB(.a)的文件,包括了用户程序要用到的各种函数。它在用户程序进行链接时,“静态链接”到可执行程序文件当中。例如,在VC++中最常使用到的C运行时目标库文件就是LIBC.LIB。在链接应用程序时常使用所谓“静态链接”的方法,即将各个目标文件(.obj)、运行时函数库(.lib)以及已编译的资源文件(.res)链接到一起,形成一个可执行文件(.exe)。使用静态链接时,可执行文件需要使用的各种函数和资源都已包含到文件中。这样做的缺点是对于多个程序都使用的相同函数和资源要重复链接到exe文件中,使程序变大、占用内存增加。

  导入库(Import Libraries)

  导入库是一种特殊形式的目标库文件形式。和目标库文件一样,导入库文件的扩展名也是.LIB(.so),也是在用户程序被链接时,被“静态链接”到可执行文件当中。但是不同的是,导入库文件中并不包含有程序代码。相应的,它包含了相关的链接信息,帮助应用程序在可执行文件中建立起正确的对应于动态链接库的重定向表。比如KERNEL32.LIB、USER32.LIB和GDI32.LIB就是我们常用到的导入库,通过它们,我们就可以调用Windows提供的函数了。如果我们在程序中使用到了Rectangle这个函数,GDI32.LIB就可以告诉链接器,这个函数在GDI32.DLL动态链接库文件中。这样,当用户程序运行时,它就知道“动态链接”到GDI32.DLL模块中以使用这个函数。

  动态链接库(Dynamic-Link Libraries)

  “动态链接”是将一些公用的函数或资源组织成动态链接库文件(.dll),当某个需要使用dll中的函数或资源的程序启动时(准确的说是初始化时),系统将该 dll映射到调用进程的虚拟地址空间、增加该dll的引用计数值,然后当实际使用到该dll时操作系统就将该dll载入内存;如果使用该dll的所有程序都已结束,则系统将该库从内存中移除。使用同一dll的各个进程在运行时共享dll的代码,但是对于dll中的数据则各有一份拷贝(当然也有在dll中共享数据的方法)。动态链接库中可以定义两种函数:输出函数和内部函数。输出函数可以被其他模块调用,内部函数只能被动态链接库本身调用。动态链接库也可以输出数据,但这些数据通常只被它自己的函数所使用。

  库是一种软件组件技术,库里面封装了数据和函数。

  库的使用可以使程序模块化。

  Windows系统包括静态链接库(.lib文件)和动态链接库(.dll文件)。

  Linux通常把库文件存放在/usr/lib或/lib目录下。

  Linux库文件名由:前缀lib、库名和后缀3部分组成,其中动态链接库以.so最为后缀,静态链接库通常以.a作为后缀。

  在程序中使用使用静态库和动态库时,他们载入的顺序是不同的。

  静态库的代码在编译时就拷贝的应用程序中,这样的优点是节省编译时间。

  动态链接库时程序在开始运行后调用库函数时才被载入。

  创建静态库

  1.编写静态库用到的函数

  mylib.h

  1. #ifndef _MYLIB_H_
  2. #define _MYLIB_H_
  3. void weclome(void);
  4. void outString(const char *str);
  5. #endif

  mylib.c

  1. #include "mylib.h"
  2. #include <stdio.h>
  3.   
  4. void welcome(void)
  5. {
  6. printf("welcome to libmylib\n");
  7. }
  8. void outString(const char *str)
  9. {
  10. if(str != NULL)
  11. printf("%s\n", str);
  12. }

  test.c

  1. #include "mylib.h"
  2. #include <stdio.h>
  3. int main(void)
  4. {
  5. printf("create and use library:\n");
  6. welcome();
  7. outString("it's successful\n");
  8. return ;
  9. }

  2.编译mylib.c生成目标文件

    gcc -c mylib.c -o mylib.o

  3.将目标文件加入到静态库中

    ar rcs libmylib.a mylib.o

  4.将静态库复制到Linux的库目录(/usr/lib或/lib)下,而mingw是放在mingw32/lib/gcc/***/4.8.1/ 中。

    gcc test.c -o test.exe -lmylib   #这里的mylib是 libmylib.a后缀a和前缀lib都不用写

    如果不想放在库目录可以通过-L参数进行指定。

    gcc test.c -o test.exe -lmylib -L .  #(这里的点表示当前目录)

    连接生成后的test.exe 目录下就可以不用有libmylib.a这个文件了。

  创建动态库

  1.生成目标文件,然后生成动态库,要加编译器选项-fpic和链接器选项-shared

    gcc -fpic -c mylib.c -o mylib.o   #生成中间目标文件

    gcc -shared -o libmylib.so mylib.o   #生成动态库

    也可以使用一步完成

    gcc -fpic -shared mylib.c -o libmylib.so

  2.使用动态链接库

    在编译程序时,使用动态链接库和静态库是一致的,使用”-l库名”的方式,在生成可执行文件的时候会链接库文件。

    gcc -o test.exe test.c -L ./ -lmylib

    -L指定动态链接库的路劲,-lmylib链接库函数mylib。-lmylib是动态库的调用规则。Linux系统下的动态库命名方式是lib*.so,而在链接时表示位-l*,*是自己命名的库名。

    但是程序会提示错误。

    这是因为程序运行时没有找到动态链接库造成的。程序编译时链接动态库和运行时使用动态链接库的概念是不同的,在运行时,程序链接的动态链接库需要在系统目录下才行。

    使用以下方法可以解决此问题

    a. 在linux下最方便的解决方案是拷贝libmylib.so到绝对目录 /lib 下(但是,要是超级用户才可以)。就可以生成可执行程序了

    b.第二种方法是:将动态链接库的目录放到程序搜索路径中,可以将库的路径加到环境变量LD_LIBRARY_PATH中实现(一般放在当前目录)

  3.再次使用动态链接库

    动态库的分为隐式调用和显式调用(上面那种)两种调用方法:

    隐式调用的使用使用方法和静态库的调用差不多,具体方法如下:

    gcc -c -I . test.c

    gcc -o main.exe -L . Test.o libmylib.so

    此时把main.exe移动到其他目录就会出现这个情况。(可以把libmylib.so移动到系统的lib目录就不会出现丢失so文件的问题)

    而test.exe这个通过静态库的就没有这个问题。

    使用的环境是mingw32+gcc 4.8.1

  参考资料:

   http://www.cnblogs.com/lidan/archive/2011/05/25/2239517.html

   http://blog.sina.com.cn/s/blog_56d8ea900100xy1l.html

   http://www.oschina.net/question/54100_32476

  本文地址(转载注明出处):http://www.cnblogs.com/wunaozai/p/3707842.html

简单了解一下c编译过程的更多相关文章

  1. Linux系统GCC常用命令和GCC编译过程描述

    前言: GCC 原名为 GNU C 语言编译器(GNU C Compiler),因为它原本只能处理 C语言.GCC 很快地扩展,变得可处理 C++.后来又 扩展能够支持更多编程语言,如Fortran. ...

  2. 大型项目使用Automake/Autoconf完成编译配置(标准的编译过程已经变成了简单的三部曲:configure/make/make install,)

    使用过开源C/C++项目的同学们都知道,标准的编译过程已经变成了简单的三部曲:configure/make/make install, 使用起来很方便,不像平时自己写代码,要手写一堆复杂的Makefi ...

  3. 鸿蒙内核源码分析(编译过程篇) | 简单案例窥视GCC编译全过程 | 百篇博客分析OpenHarmony源码| v57.01

    百篇博客系列篇.本篇为: v57.xx 鸿蒙内核源码分析(编译过程篇) | 简单案例窥视编译全过程 | 51.c.h.o 编译构建相关篇为: v50.xx 鸿蒙内核源码分析(编译环境篇) | 编译鸿蒙 ...

  4. gcc编译过程简述

    在linux系统上,从源文件到目标文件的转化是由编译器完成的.以hello.c程序的编译为例,如下: dfcao@linux: gcc -o hello hello.c 在这里,gcc编译器读取源文件 ...

  5. [译]C++, Java和C#的编译过程解析

    1.1.1 摘要 我们知道计算机不能直接理解高级语言,它只能理解机器语言,所以我们必须要把高级语言翻译成机器语言,这样计算机才能执行高级语言编写的程序,在接下来的博文中,我们将介绍非托管和托管语音的编 ...

  6. C语言编译过程

    GCC编译C源码有四个步骤: 预处理-----> 编译 ----> 汇编 ----> 链接 一. 编译和链接的流程 C语言的编译链接过程要把我们编写的一个c程序(源代码)转换成可以在 ...

  7. C程序编译过程浅析

    前几天看了<程序员的自我修养——链接.装载与库>中的第二章“编译和链接”,主要根据其中的内容简单总结一下C程序编译的过程吧. 我现在一般都是用gcc,所以自然以GCC编译hellworld ...

  8. C程序编译过程

    1.1程序被其他程序翻译成不同的格式 1.hello.c #include <stdio.h> int main() { printf("hello world\n") ...

  9. C语言编译过程详解

    前言 C语言程序从源代码到二进制行程序都经历了那些过程?本文以Linux下C语言的编译过程为例,讲解C语言程序的编译过程. 编写hello world C程序: // hello.c #include ...

随机推荐

  1. 【CSWS2014 Summer School】深度问答技术及其在搜索中的应用-马艳军

    Title: 深度问答技术及其在搜索中的应用 马艳军博士, 百度 Abstract: 深度问答(DeepQA)是一种基于对自然语言深度理解的智能问答技术,其核心技术涉及知识图谱建设.语义表示和计算.语 ...

  2. 嵌入式Linux的web视频服务器的构建

    http://blog.sina.com.cn/s/blog_53d02d550102v8bu.html随着嵌入式处理器和开源Linux 的广泛应用,各种视频服务在嵌入式系统中逐渐发展起来. 1.引言 ...

  3. 亲自己主动手从源代码 构建 Groovy 2.3.8 公布包

    今天为了学习 怎样使用 Groovy 写 Groovy 的測试代码, 所以到 http://groovy.codehaus.org/Download 下载了 Groovy 2.3.8 的源码包. Gr ...

  4. java格式化百分比

    NumberFormat nf = NumberFormat.getPercentInstance(); System.out.println(nf.format(0.47)); 显示:47% Dec ...

  5. 用Java实现AES加密(坑!)

    大坑!使用SecureRandom默认的加密方式即SHA1PRNG生成的密码有误,即使使用相同的password来生成,不同runtime或时刻生成的随机密码也有可能不同,造成的错误为javax.cr ...

  6. [Unity-1] Unity简单介绍

    Unity是一套包含图形.声音.物理等功能的游戏引擎,提供了一个强大的关卡编辑器.支持大部分主流3D软件格式,使用C#或者JavaScript等高级语言实现脚本功能.使开发人员无需了解底层复杂技术,高 ...

  7. UsageLog4j

      迁移时间:2017年5月21日09:42:46CreateTime--2017年1月2日09:35:55Author:Marydon原文链接:http://www.360doc.com/conte ...

  8. java的过滤器对session进行检查

    private void sendErrorToBrowser(HttpServletRequest request,HttpServletResponse response) throws IOEx ...

  9. vsftp限制FTP用户只能访问自己的目录

    修改配置文件/etc/vsftpd/vsftpd.conf chroot_local_user=YESallow_writeable_chroot=YESchroot_list_enable=YESc ...

  10. poj 2187 Beauty Contest , 旋转卡壳求凸包的直径的平方

    旋转卡壳求凸包的直径的平方 板子题 #include<cstdio> #include<vector> #include<cmath> #include<al ...