简单了解一下c编译过程
大一的时候,学习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吧
#include <stdio.h>
#define WOELD "World"
static int a=;
int main()
{
int j,jj;
int i=;
char ch='a';
printf("Hello ");
printf(WOELD);
printf("\n%d %d %c\n",i,a,ch);
return ;
}
先预处理一下,可以使用cpp命令,或者是使用gcc 的-E选项
gcc -E Hello.c > Hello.i
一个简单的Hello World都要800多行。
可以看到这些都是函数的声明。而函数的定义是在链接库中。我们可以认为是写了好多函数了。然后在main中调用。这个跟一般的函数是一个道理的。只是这些函数是又编译器帮你写了。这个就不得不提到,C语言只是定义了标准库函数的输入和输出,至于实现的过程,是没有规定的。这个由编译器厂商自己设计,这就是为什么有人说Linux下gcc编译后的程序会比Windows下VS编译后的程序运行效率上有些区别的一个原因吧。
像上面的printf函数的原型,如果有兴趣可以去下载源代码查看。
下面这个是VC6.0对printf函数的定义
int __cdecl printf (
const char *format,
...
)
/*
* stdout 'PRINT', 'F'ormatted
*/
{
va_list arglist;
int buffing;
int retval;
va_start(arglist, format);
_ASSERTE(format != NULL); _lock_str2(, stdout);
buffing = _stbuf(stdout);
retval = _output(stdout,format,arglist);
_ftbuf(buffing, stdout);
_unlock_str2(, stdout);
return(retval);
}
Gnu也有个对printf函数的定义,下面这个是glibc-2.2.5对printf的定义
int
printf (const char *format, ...)
{
va_list arg;
int done;
va_start (arg, format);
done = vfprintf (stdout, format, arg);
va_end (arg);
return done;
}
/* The function itself. */
int
vfprintf (FILE *s, const CHAR_T *format, va_list ap)
{
/* The character used as thousands separator. */
#ifdef COMPILE_WPRINTF
wchar_t thousands_sep = L'\0';
#else
const char *thousands_sep = NULL;
#endif /* The string describing the size of groups of digits. */
const char *grouping;
......
从这里可以看出刚才我们定义的宏 WORLD已经转换为字符串了。所有我们知道所有的预处理或者宏定义都会在这一步完成。
好了,我们现在开始第二步了。生成汇编代码,使用gcc的-S选项。
gcc -S Hello.c -o Hello.s
.file "Hello.c"
.lcomm _a,,
.def ___main; .scl ; .type ; .endef
.section .rdata,"dr"
LC0:
.ascii "Hello \0"
LC1:
.ascii "World\0"
LC2:
.ascii "\12%d %d %c\12\0"
.text
.globl _main
.def _main; .scl ; .type ; .endef
_main:
LFB13:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset
.cfi_offset , -
movl %esp, %ebp
.cfi_def_cfa_register
andl $-, %esp
subl $, %esp
call ___main
movl $, (%esp)
movb $, (%esp)
movl $LC0, (%esp)
call _printf
movl $LC1, (%esp)
call _printf
movsbl (%esp), %edx
movl _a, %eax
movl %edx, (%esp)
movl %eax, (%esp)
movl (%esp), %eax
movl %eax, (%esp)
movl $LC2, (%esp)
call _printf
movl $, %eax
leave
.cfi_restore
.cfi_def_cfa ,
ret
.cfi_endproc
LFE13:
.ident "GCC: (rev5, Built by MinGW-W64 project) 4.8.1"
.def _printf; .scl ; .type ; .endef
总共47行。如果我们使用-O优化
gcc -S -O3 Hello.c -o Hello.s
.file "Hello.c"
.def ___main; .scl ; .type ; .endef
.section .rdata,"dr"
LC0:
.ascii "Hello \0"
LC1:
.ascii "World\0"
LC2:
.ascii "\12%d %d %c\12\0"
.section .text.startup,"x"
.p2align ,,
.globl _main
.def _main; .scl ; .type ; .endef
_main:
LFB13:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset
.cfi_offset , -
movl %esp, %ebp
.cfi_def_cfa_register
andl $-, %esp
subl $, %esp
call ___main
movl $LC0, (%esp)
call _printf
movl $LC1, (%esp)
call _printf
movl $, (%esp)
movl $, (%esp)
movl $, (%esp)
movl $LC2, (%esp)
call _printf
xorl %eax, %eax
leave
.cfi_restore
.cfi_def_cfa ,
ret
.cfi_endproc
LFE13:
.ident "GCC: (rev5, Built by MinGW-W64 project) 4.8.1"
.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
#ifndef _MYLIB_H_
#define _MYLIB_H_
void weclome(void);
void outString(const char *str);
#endif
mylib.c
#include "mylib.h"
#include <stdio.h>
void welcome(void)
{
printf("welcome to libmylib\n");
}
void outString(const char *str)
{
if(str != NULL)
printf("%s\n", str);
}
test.c
#include "mylib.h"
#include <stdio.h>
int main(void)
{
printf("create and use library:\n");
welcome();
outString("it's successful\n");
return ;
}
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编译过程的更多相关文章
- Linux系统GCC常用命令和GCC编译过程描述
前言: GCC 原名为 GNU C 语言编译器(GNU C Compiler),因为它原本只能处理 C语言.GCC 很快地扩展,变得可处理 C++.后来又 扩展能够支持更多编程语言,如Fortran. ...
- 大型项目使用Automake/Autoconf完成编译配置(标准的编译过程已经变成了简单的三部曲:configure/make/make install,)
使用过开源C/C++项目的同学们都知道,标准的编译过程已经变成了简单的三部曲:configure/make/make install, 使用起来很方便,不像平时自己写代码,要手写一堆复杂的Makefi ...
- 鸿蒙内核源码分析(编译过程篇) | 简单案例窥视GCC编译全过程 | 百篇博客分析OpenHarmony源码| v57.01
百篇博客系列篇.本篇为: v57.xx 鸿蒙内核源码分析(编译过程篇) | 简单案例窥视编译全过程 | 51.c.h.o 编译构建相关篇为: v50.xx 鸿蒙内核源码分析(编译环境篇) | 编译鸿蒙 ...
- gcc编译过程简述
在linux系统上,从源文件到目标文件的转化是由编译器完成的.以hello.c程序的编译为例,如下: dfcao@linux: gcc -o hello hello.c 在这里,gcc编译器读取源文件 ...
- [译]C++, Java和C#的编译过程解析
1.1.1 摘要 我们知道计算机不能直接理解高级语言,它只能理解机器语言,所以我们必须要把高级语言翻译成机器语言,这样计算机才能执行高级语言编写的程序,在接下来的博文中,我们将介绍非托管和托管语音的编 ...
- C语言编译过程
GCC编译C源码有四个步骤: 预处理-----> 编译 ----> 汇编 ----> 链接 一. 编译和链接的流程 C语言的编译链接过程要把我们编写的一个c程序(源代码)转换成可以在 ...
- C程序编译过程浅析
前几天看了<程序员的自我修养——链接.装载与库>中的第二章“编译和链接”,主要根据其中的内容简单总结一下C程序编译的过程吧. 我现在一般都是用gcc,所以自然以GCC编译hellworld ...
- C程序编译过程
1.1程序被其他程序翻译成不同的格式 1.hello.c #include <stdio.h> int main() { printf("hello world\n") ...
- C语言编译过程详解
前言 C语言程序从源代码到二进制行程序都经历了那些过程?本文以Linux下C语言的编译过程为例,讲解C语言程序的编译过程. 编写hello world C程序: // hello.c #include ...
随机推荐
- Java连接Oracle数据库示例
1.数据库复用模块 package cn.jzy.database; import java.sql.Connection; import java.sql.DriverManager; import ...
- PHP高级教程-邮件
PHP 发送电子邮件 PHP 允许您从脚本直接发送电子邮件. PHP mail() 函数 PHP mail() 函数用于从脚本中发送电子邮件. 语法 mail(to,subject,message,h ...
- webstorm激活+汉化教程
1.安装教程+激活 输入的激活网址: http://idea.imsxm.com/ 2.汉化教程 软件适用于:webstorm2017.2以及以上,如有需要可直接加本人QQ 1940694428.
- BIOS和Bootloader的对比
桌面电脑刚加电时,一个叫做BIOS的软件程序立刻获得了处理器的控制权.(历史上,BIOS是Basic Input/Output Software的缩写,但现在这个单词已经有了自身的含义,因为其完成的功 ...
- 转:Python yield 使用浅析
初学 Python 的开发者经常会发现很多 Python 函数中用到了 yield 关键字,然而,带有 yield 的函数执行流程却和普通函数不一样,yield 到底用来做什么,为什么要设计 yiel ...
- mixin
mixin.scss //-----------------------------------浏览器前缀----------------------------------------- //例子: ...
- python 读帧和绘图的区别
capture = cv2.VideoCapture(0) while True: #img = cv.QueryFrame(capture) ret, frame = capture.read() ...
- JavaScript toString、String和stringify方法区别
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title&g ...
- java String字符串
五.java数据类型之String(字符串) CreateTime--2017年7月21日16:17:45 Author:Marydon (一)数据格式 (二)初始化 // 方式一 String ...
- TRIZ系列-创新原理-25-自服务原理
自服务原理的详细表述例如以下:1)物体在实施辅助和维修操作时.必须能自我服务:2)利用废弃的材料和能量: 自服务原理的第1)个比較好理解,假设一个系统在执行过程中须要进行辅助和维护操作时,最好不要借助 ...