目录
1、gcc和g++(本文中所有用gcc作示范的例子用g++也是同样的操作)
2.3、汇编阶段(将汇编语言文本文件转换成可重定向目标二进制文件)
2.4、链接阶段(将多个可重定向二进制文件链接成一个可执行文件)
4、为什么我们看到的二进制文件不是0101这些整形数字,而是一堆乱码
1、gcc和g++(本文中所有用gcc作示范的例子用g++也是同样的操作)
1.1、gcc和g++是什么
gcc是一款专门用来编译链接C语言的编译器,所以不可以用gcc编译C++,而g++是用于编译C++的编译器,由于C++支持C语言的语法,所以g++也可以用来编译C语言。
1.2、关于gcc和g++形成的可执行程序无法调试的问题
1.gcc和g++默认形成的可执行程序是relase版本,无法被调试,只有debug版本可以调试。
2.形成debug版本的指令:在原有的基础上添加 -g 选项即可,如:gcc xx -o xxx-g,xx代表被编译文件,xxx代表可执行文件。
1.3、g++的下载
指令:yum install -y gcc-c++。
1.4、查看两种编译器的版本
1.指令:gcc -v
2.指令:g++ -v
1.5、指令使用方法
1.gcc file.c obj.exe,注意这里文本文件的后缀名必须以.c结尾,这是gcc这个文件自身的规定。
2. gcc file.c -o file.exe(或者gcc -o file.exe file.c),和上面一样,都是一步编译成可执行文件。注意这里的 -o不是生成目标文件的意思,而是给一个文件命名的意思。
3.编译c++文件时,需要注意文件后缀必须是 .cpp 或者 .cc 或者 .cxx ,不然g++编译器不认识。下图的-std = c++11就是按C++2011标准来编译,早期一些的是用C++1998编译的。
2、gcc或者g++可以在编译时分阶段编译
编译器编译链接过程分为四步,gcc和g++可以用指令执行指定的步骤。
2.1、预处理阶段(去注释,宏替换,头文件展开,条件编译)
对应选项:-E,代表开始翻译,预处理阶段完成就停止翻译,如gcc -E file.c,此时shell会将file.c预处理后的结果打印在显示器上,如果不想打印,可以gcc -E file.c -o temp.i生成一个临时文件 。.i文件代表进行预编译之后生成的文件。
2.2、编译阶段(将C语言文本转换成汇编语言文本)
什么是汇编语言?
汇编语言是二进制指令的文本形式,与指令是一一对应的关系。比如,加法指令00000011写成汇编语言就是 ADD。只要还原成二进制,汇编语言就可以被 CPU 直接执行,所以它是最底层的低级语言。
对应选项: -S,代表开始翻译,汇编阶段结束后就停止翻译,如gcc -S file.i -o temp.s,注意这里如果汇编对象是.c文件而不是.i文件也是可以的,因为-S也会经过预处理阶段,.s文件代表进行汇编后生成的文件。
2.3、汇编阶段(将汇编语言文本文件转换成可重定向目标二进制文件)
(注意这里的可重定向目标二进制文件中的可重定向和指令中的可重定向>没有任何关系。)
对应选项: -c ,代表开始翻译,汇编阶段完成就停止翻译,如gcc -c file.s -o temp.o,注意这里的.o文件代表经过汇编后生成的文件,对应Window下的.obj文件,.o文件此时已经是一个二进制文件了,但是不可以执行,因为还不是可执行文件。
2.4、链接阶段(将多个可重定向二进制文件链接成一个可执行文件)
指令:不需要选项,直接gcc file.o -o file.exe即可。
链接阶段有两种方式,动态链接(需要动态库)和静态链接(需要静态库)。具体内容看下文的动静态库。
2.4.1、对文件分离编写和编译链接的误解
1.我之前对链接有个误解,以为头文件即.h文件也会被编译链接,实际上头文件是不会参与编译的,更不用说链接了,源文件即 .c文件中#include了头文件,源文件在编译阶段前,即预处理阶段,源文件中包含的头文件就被展开了,即将头文件代码拷贝到源文件里,只有若干个源文件会参与编译,然后变成若干个不可执行二进制文件,最后若干个不可执行二进制文件和动态库链接在一起形成一个可执行二进制文件。
2.#include不止可以包含头文件即.h文件,还可以包含源文件即.c文件。因为#include是一个宏,作用是把包含文件的内容完全拷贝到当前文件里。但包含.c文件容易引发重定义错误,如下图。图中test.c文件里有一份show()函数的定义,由于main.c文件里包含了test.c,此时相当于main.c里也有一份show()函数的定义。由于编译阶段中源文件各自编译各自的,所以编译通过,并且由各自源文件生成了对应的.o文件,(如上两张图中即使因为链接失败没有生成mytest可执行文件,但还是由各自源文件生成了对应的.o文件)但在链接阶段,会因为有两个show()函数的定义,引发重定义的错误,即用show函数时不知道调用哪个,所以干脆链接失败,将问题抛给用户。所以一般会包含.h头文件,注意头文件里一般不能有函数的实现,因为如果此时其他.c文件也有函数的实现,也会造成重定义。
记忆技巧
每一步流程的指令选项记忆技巧,-E -S -c,即退出的热键ESc。
每一步流程生成的文件的后缀名记忆技巧,-i -s -o。打篮球的小伙伴可以用赵强iso记忆。
3、为什么需要编译器
首先,计算机只认识 “二进制” ,因为计算机组件,如内存,cpu,磁盘等的物理介质只能表示两态,如磁盘这种磁化的东西就有南极和北极(或者说正极和负极),由于二进制的特性,就可以用0,1这两个二进制数表示这两态,所以本质上计算机是不认识二进制数的。由于可以用二进制表示计算机信息,我们编写的文本文件也必须被翻译成二进制文件,所以才需要编译器,但这只是原因之一。
4、为什么我们看到的二进制文件不是0101这些整形数字,而是一堆乱码
因为我们查看二进制文件时一般使用的编辑器是普通的文本编辑器,文本编辑器按照他的编码规则将二进制整形数字(不是字符数字)翻译成字符,甚至有些是不可打印的控制字符,所以看到的是一些乱码,如果想看到0101这些整形数字,应该用二进制编辑器看。
5、使用Linux机器的二进制编辑器所需的指令
比如:od file.o
6、动静态库
6.1、标准库中的头文件里只有函数声明,而没有函数的实现
我们的C程序中,并没有定义“printf”的函数实现, 且在预编译中包含的“stdio.h”中也只有该函数的声明, 而没有定义函数的实现, 那么, 是在哪里实“printf”函数的呢 ?
最后的答案是 : 系统把这些函数实现都被做到名为 libc.so.6 的库文件中去了, 在没有特别指定时, gcc 会到系统默认的搜索路径“ / usr / lib”下进行查找, 也就是链接到 libc.so.6 库函数中去, 这样就能实现函数“printf”了, 而这也就是链接的作用。
6.2、链接阶段的两种方式
动态链接(需要动态库)和静态链接(需要静态库)。
动态链接即将动态库里函数的地址填入可执行文件中建立关联。即二进制文件被编译成可执行文件后,代码执行到调用函数的那一步时,由于函数名本身就是一个地址,所以使用这个函数时就去对应的地方找。优点是代码不会冗余,缺点是如果库文件丢失,则许多函数会失效。
静态链接是指编译链接时, 把库文件的代码全部加入到可执行文件中, 因此生成的文件比较大, 但在运行时也就不再需要库文件了。其后缀名一般为“.a”。优点是不依赖库,缺点是代码冗余。如下图中静态链接生成的可执行文件所占空间大小是动态链接生成的可执行的文件的10倍多。
Linux下,.so代表动态库, .a代表静态库。
Windows下, .dll代表动态库, .lib代表静态库。
不管在Linux还是Windows下,编译器默认都是动态链接的,如果想选择静态链接,则在动态链接的指令后添加 -static选项即可。
6.3、Linux下静态库的下载方式:
C静态库: yum install -y glibc-static
C++静态库:yum install -y libstdc++-static
6.4、file命令
用来探测给定文件的类型;file命令对文件的检查分为文件系统、魔法幻数检查和语言检查3个过程。
6.5、ldd命令
用来打印或者查看程序运行所需的共享库(访问共享对象依赖关系),常用来解决程序因缺少某个库文件而不能运行的一些问题。注意ldd不是一个可执行程序,而只是一个shell脚本。
7、makefile
7.1、makfile的用途以及如何编写makefile
makefile是一个文件,用于创建和删除目标文件,编写makefile需要:
7.1.1、依赖关系
如上图中mytest是目标文件(即需要生成的文件),而生成目标文件需要test.c文件,这就是依赖关系,上图中冒号(:)的右边叫做依赖文件列表。
7.1.2、依赖方法
如下图中的gcc test.c -omytest,依赖方法必须和依赖关系的那一行紧挨着,不可以空行,然后必须空一个table键的空间。
7.2、使用makefile时用到的相关指令
1.make xxx:利用依赖关系和依赖方法生成makefile文件中xxx目标文件。
2.make:自顶向下扫描,利用依赖关系和依赖方法生成makefile文件中第一个声明的目标文件。
7.3、伪目标
伪目标总是被执行,总是会按照依赖关系执行依赖方法,什么意思呢?
如上图,此时mytest没有声明成伪目标,make mytest一次之后,再次make mytest就会失败,如下图:
但如果声明成伪目标则不会执行失败,每一次make mytest都会按照依赖关系执行依赖方法。如下图:
那么makefile是怎么判断file.c是否需要重新被编译的呢?答案是:根据源文件的最近修改时间(即Modify时间或者Change时间)。比如说file.c已经是最新版本,即没有被修改过,那么可执行文件生成的时间一定比源文件晚,假如可执行文件生成的时间比file.c的修改时间要早,那说明file.c已经被修改过了,此时可以被重新编译。(Modify时间和Change时间的详情在下文)
7.3.1、stat指令
使用方式:stat xx,xx代表文件名,stat指令可以观察Access时间(最近访问时间),Modify时间(文件内容最近修改时间,如文件添加几行代码),Change时间(文件属性最近修改时间),如下图。(可以巧记为ACM时间)
7.3.2、Acess时间注意事项
修改和读取都算访问,但我们做实验的时候,读取如cat某个文件,发现并没有修改Acess的时间,这是因为后来Linux内核修改了Acess时间的规定,规定需要经过一段时间,再次访问才会被修改。因为读取文件这种操作比较高频,所以Acess时间肯定需要高频的修改,而文件是在磁盘上的,高频修改Acess时间需要大量的访问磁盘,会降低程序效率.
7.3.3、Change时间注意事项
比如修改权限,注意有时文件内容修改,如添加了10行代码,那文件大小肯定也要修改,这时文件属性也就修改了
7.3.4、可以利用伪目标文件删除目标文件
可以利用伪目标文件(如下图中的clean)删除目标文件(如清除下图中的mytest)注意这个clean也可以是其他名字,只是开源项目一般用clean命名。清除目标文件(mytest)也需要依赖关系生成目标文件(clean),因为目标文件(clean)是一个伪目标,所以第4行有个.PHONY:clean声明,由于当前场景下伪目标文件(clean)不需要依赖文件,所以冒号(:)右边是空的,如下图。依赖方法是rm -f mytest。
7.4、makefile文件里可以分步骤编译目标文件
如下图。前面也说过makefile会自顶向下扫描,下图中首先形成mytest可执行文件,需要test.o和main.o,然后发现没有这两个文件,生成mytest失败,所以向下扫描看有没有可以生成test.o和main.o文件的方法,发现有,所以生成了test.o和main.o,然后再次生成mytest,生成成功。
7.5、makefile里的特殊符号
7.5.1、如上图中的$@
表示目标文件。即myproc。
7.5.2、如上图中的$^
表示依赖关系,即可以表示生成目标文件所需要的若干个依赖文件。如在x1 :x2 x3 x4这样的情景中,依赖方法可以写成:gcc -o x1 $^,一个$^就可以表示x2,x3,x4。
7.6、如何使用makefile一次编译多个文件呢?
如上图中的all,all是一个伪目标,只有依赖关系,即生成all前需要exec文件和mycmd文件,而没有依赖方法,所以all是无法生成的,但注意我们的目的是通过一次make生成两个目标文件mycmd和exec。如上图情景,在shell中输入make,此时开始生成目标文件all,发现需要exec和mycmd两个文件,于是继续向下扫描,依次生成mycmd和exec,此时依赖关系已经就绪,然后再次尝试生成all,但发现目标文件all没有依赖方法,所以makefile至此结束,虽然all文件没有生成,但我们的目的达到了,生成了mycmd和exec。