目录
使用指令gcc 文件名进行编译
也可以在底行模式中直接编译
生成一个a.out的可执行程序
./a.out运行这个程序
这是gcc的一个快速认识
关于gcc其他的指令
-E
只激活预处理
,
这个不生成文件
,
你需要把它重定向到一个输出文件里面
-S
编译到汇编语言不进行汇编和链接
-c
编译到目标代码
-o
文件输出到 文件
可以控制生成的可执行程序名字不叫a.out,
gcc test.c -o mybin,此时目录下的mubin就是一个可执行文件。可以./运行
也可以: gcc -o mybin test.c
-static
此选项对生成的文件采用静态链接
-g
生成调试信息。
GNU
调试器可利用该信息。
-shared
此选项将尽量使用动态库,所以生成文件比较小,但是需要系统由动态库
.
-O3
编译器的优化选项的
4
个级别,
-O0
表示没有优化
,-O1
为缺省值,
-O3
优化级别最高
-w
不生成任何警告信息。
-Wall
生成所有警告信息。
—D 后面跟宏, -D VER=1,
给编译器传递不同的宏值示实现对代码的裁剪,也叫作条件编译
编译器专业版本和社区版本:收费版本和免费版本。
凡是免费版本有的,专业版也有,专业版提供的技术更多。维护这个产品的时候是维护一份,使用预编译对代码进行裁剪就可以生成不同版本(指社区版本和专业版本)的软件
有时在编写程序的时候:我们往往会写下这种代码:
#ifdef __CDDE_H
#define_CODE_H_
XXXXX
#endif
这段代码的作用是为了防止头文件被重复包含
那么原因是:头文件展开就是头文件被拷贝到源文件中,第一次拷贝没有被定义,都二次拷贝就是被定义了,就不会再编译剩下的代码。
g++ 也是一款编译器,与gcc不同
我们编写一段c++的代码
我们使用gcc编译一下这个文件
c++的代码使用g++编译器来编译,C语言也可以用g++来编译。
1. 程序的翻译环境和执行环境
在ANSI C(c标准)的任何一种实现中,存在两个不同的环境。
- 第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。
- 计算机是能够执行二进制指令的,但是我们写的C语言代码是文本信息,计算机不能直接理解,翻译环境将C语言代码翻译为二进制指令,放在了可执行程序中。
- 第2种是执行环境,它用于实际执行代码 执行的是二进制代码
2.编译和链接
2.1翻译环境
组成一个程序的每个源文件通过编译过程(都单独经过编译器生成目标文件 。obj)分别转换成目标代码(object code)。 这个过程叫做编译。
每个目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序。
链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人 的程序库,将其需要的函数也链接到程序中,这个过程叫做链接。
链接库说明:库函数编译放在了静态库中
我们的vs中集成了编译器(cl.exe)和链接器(link.exe)+调试器,所以程序执行起来自动链接库
上述过程在具体的项目中:
两个.c 文件
生成两个目标文件
经过连接形成一个可执行程序
这是笼统的,接下来就看看详细过程
从C语言代码到可执行程序经过两个大的过程:
编译和链接
vs 集成开发环境不方便看逐个过程 ,使用linux下的gcc环境来观察
2.2编译环境
1. 预处理 gcc -E指令 test.c(源文件) -o test.i(生成在一个文件中,可以自己指定) 预处理完成之后就停下来,预处理之后产生的结果都放在test.i文件中。
在vim中编写源文件
在test.i文件中我们可以发现,不仅仅有我们自己写的代码,还多出了800行左右的代码
这些代码就是来自我们包含的头文件《stdio.h》
然而对于为什么右边有900多行代码,左边只有八百多行:
注释和#define的语句没有了
所以我们预编译阶段处理的事:
①#include 头文件的包含,将头文件和我们自己写的代码整合在一起
②注释的删除
③#define 符号的替换
这些都是一些文本操作
编译 选项 gcc -S test.c 编译完成之后就停下来,结果保存在test.s中。
这里面存储的是汇编指令,这个阶段是将C语言代码翻译成为了汇编语言,并且做了词法的分析、语义分析和符号的汇总。
3. 汇编 gcc -c test.c 汇编完成之后就停下来,结果保存在test.o中。
这个阶段将汇编语言翻译称为了二进制文件,同时形成了符号表
目前这个二进制文件不可以被执行:因为我们的头文件中包含了很多函数的接口,比如我们的输入输出函数,printf、scanf等,都需要找到对应的函数才可以,所以目前只是将函数名等做了一个汇编整理。
我们修改一个更为复杂一点的代码来看一下更为详细的过程:
对这段代码进行编译,生成目标文件和可执行程序
虽然还是看不懂的二进制指令,但是可以看出有规定的ELF格式:
linux下,gcc编译产生的目标文件和可执行程序,都是按照ELF这种文件的格式来存储的
使用readelf 工具可以识别elf格式的文件
将代码分成了一段一段的进行处理:头部等等.......
我们可以发现,在编译阶段,函数名和全局变量这些符号就做了汇总:
具体图解如下:
2.3 运行环境
程序执行的过程:
- 1. 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序 的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
- 2. 程序的执行便开始。接着便调用main函数
- 3. 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回 地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程 一直保留他们的值。
- 4. 终止程序。正常终止main函数;也有可能是意外终止
3.动静态库
3.1铺垫
ldd命令可以查看一个可执行程序依赖的库文件
这里就可以看到我们的当前可执行文件依赖的是/lib64/lib.so.6这个路径下的库
这就是我们C语言的库
那么我们所说的库一般分为两类:动态库和静态库
在linux中动态库以.so为后缀,静态库以.a为后缀
在windows下:动态库以.dll为后缀,静态库以.lib为后缀。
有时我们运行软件时可能由于操作不当或者错误杀毒,运行是报警告动态库缺失
原因是:
某些杀毒软件会扫描系统的库文件,有些病毒会将.dll这个库删掉,然后自己伪装成对应的库,就是在原有的库中新增加了一些代码,当我们编译运行的时候就会进入到库中就会执行到这些病毒代码,就会导致信息泄露,这种就叫做对库进行恶意替换,所以很多杀毒软件都要扫码库文件,可能杀毒文件也有bug就会删除掉用户的正常的库,导致后续使用出问题。
如何知道我们的程序使用的是什么样的库呢
所有的库都是用lib开头,将前缀lib去掉,然后将.so包括.so后续内容去掉,就可以看到,我们使用的是c库。
为什么我们的linux或者vs支持我们进行C语言的开发?
原因就是,linux已经某认给我们提供了C语言的头文件
所以一个开发平台必须要提前在系统中安装至少语言的标准头文件和库文件。windows中安装vs就是因为没有某认的C语言环境。
3.2 动静态库的链接
那么我们所说的库一般分为两类:动态库和静态库
在linux中动态库以.so为后缀,静态库以.a为后缀
在windows下:动态库以.dll为后缀,静态库以.lib为后缀。
与动态库的链接叫做动态链接,与静态库的链接叫做静态链接。
库是个文件
linux下的动态库:
l
C语言源文件变成库就是将源文件编译成.o文件,将.o文件打包变成库文件,所以链接的时候就是将我们的.o文件和库的.o文件合并。
3.2.1动态库理解
动态库就是在可执行程序运行代码时,如果要运行比如库函数,就需要找到库文件,这个叫进入库中进行库调用,而后,找到我们需要的那段代码并执行,这个过程叫做库方法,执行完后返回继续我们的程序。动态库可以被多个程序公用,一但缺失,大家都会崩溃。
优点:每个人的程序中只有调用函数的地址,调用链接才会实现实例化,大家都用一个库,比较节省资源,不会出现太多重复代码。 加载到内存中就会节省内存资源,别人网络下载的时候,下砸周期就会变短,网络资源也会节省。
所以动态库特点:被多个程序共同使用,一但缺失,所使用这个库的所有程序都不可在运行。
这也是一个缺点:程序对库的依赖性比较强。
误操作:对库升级覆盖等等导致库丢失出问题,就会导致程序大面积崩溃。
其次,库调用的时候会涉及到地址的保存,和跳转,调用周期相比于静态库会稍微长一点,效率低一些。
我们的linux中指令常用的ls pwd等等都是依赖于我们的C语言库
3.2.2 静态库理解
静态库就是将程序所需要的调用的库函数具体内容拷贝一份到自己的可执行程序中。此时就叫做静态链接。从此我们的程序不再依赖三方库。同类型平台都可以直接运行使用。
缺点:代码拷贝,可执行程序体积比较大,资源会浪费(磁盘、内存、网络)
所以允许拷贝的库就是静态库,运行关联的库就叫做动态库。两个库是两个文件,两个库在实现的时候,编址的方式也不一样,动态库的方法不能直接拷贝到可执行文件。所以是两个文件
我们的可执行程序都是自己的代码+库的代码构成。那么库将常用的函数实现打包,大大提高了我们开发的效率
3.3 gcc某认链接动态库
我们编译运行一个程序
此时生成的可执行文件是链接到动态库的
当我们需要静态链接,就需要执行以下指令:
文件正常运行
当我们ldd时:
显示我们没有动态连接
file查看这个可执行文件,staticlink 静态链接
所以gcc默认采用动态链接。
这里我们程序中只有一个printf,二者的大小区别已经很明显
我们的centos默认是没有装我们的静态库的
当我们运行指令gcc -static mytest.c -o mybinsta
报错
/usr/bin/ld: cannot find -lc
就是因为没有C语言静态库,因为某认gcc链接动态库
解决方式搜索关键字:cetenos 7 c/c++静态库下载
执行指令
yum install -y glibc-static
yum install -y glibc++-static
这是在root用户下,普通用户注意提权。
安装好后就可以使用了
4.语言的自举过程
在早期的计算机中,或者到现在为止,计算机能识别的都是二进制的语言,而早期的计算机是通过打孔纸袋来识别信息,有孔的地方就是1,没有孔的地方就是0.
而后诞生了汇编语言,由于汇编语言不能直接在计算机上执行,所以诞生了编译器,(而后才诞生了操作系统,linux就是通过汇编语言就行编写的,第一版),由于对汇编语言的使用不流畅,所以在汇编语言的基础上诞生了C语言。C语言也需要对应的编译器,后续由于对于面向对象这种思维方式编程常用,所以c++等面向对象的语言诞生。而像python/php/shell/java等都是解释型或者半解释型语言,都要有自己的解释器,不够这些解释器都是由C语言写的。那么:
先有语言还是现有编译器?
比如现在有汇编语言但是没有编译器(是特指汇编语言编写的编译器),那么第一个将汇编语言编译成二进制的编译器肯定不可能是汇编语言写的,同理第一个将C语言翻译成汇编语言的编译器也不可能是C语言写的。一定是先有语言才有这个语言编写的编译器,比如现在有了汇编语言,肯定是没有将汇编语言编译的编译器的,但是我们可以用二进制的语言写一个汇编编译器。(怎么理解:假设汇编语言是我们当前最新的语言,但是编译器也是一款软件,也是需要语言编译形成,那么汇编语言刚诞生,我们可以使用语言来写软件,但是也需要编译才可以形成这个汇编语言形成的编译器呀,所以此时不可能有汇编语言写的编译器,但是可以用二进制的语言写一款汇编编译器。来编译汇编语言代码,形成软件。那么就有了一款用汇编语言编写的编译器,不过这款编译器从汇编代码变成软件是由二进制编译器来生成的。)然后汇编语言编译器自我迭代变得越来越好,这个过程就叫做语言的自举过程。汇编语言到C语言之间也是这样。
C语言出现--》用低级语言(汇编语言)写一个c编译器---》C语言代码变成软件
用C语言写一个编译器--》经过低级语言写的编译器翻译执行---》形成C语言写的编译器
那么自然的,从C语言直接翻译到二进制语言是没有C语言翻译到汇编语言,再从汇编语言翻译到二进制好,所以才有了预处理,编译、汇编、链接等过程。
所以人工智能最初没有,人为写一个人工智能程序,人为训练,然后未来就会演变成人工智能训练人工智能。也是软件自举的原理,所以有chat-gpt各种版本。
5.结尾
上述就是今天分享的内容,如果大家觉得有用,创作不易,希望收获三连。我是Nicn,欢迎一起交流学习。