目录
编译(-S选项,全称Stop after the stage of compilation proper)
1.知识回顾
预处理、编译、汇编和链接过程的复习:
总结
1.预处理功能主要包括宏定义,文件包含,条件编译,去注释等
2.编译功能主要是生成汇编代码
3.汇编功能主要是从汇编代码生成机器码
4.连接功能主要是生成可执行文件或库文件
2.gcc和g++的简单区别
gcc只能编译C语言,g++能编译C/C++
3.知识点
gcc或g++默认搜索头文件的路径
在/usr/include的路径下搜索
命令
这里只讲gcc的命令,g++也是类似的
预处理(-E选项,全称prEprocess only)
-E只执行预处理
例如有一个test.c文件,
#include <stdio.h>
int main()
{
printf("Hello World!\n");
return 0;
}
要求生成预处理后的文件,使用以下命令
gcc -E test.c -o test.i
解释参数: -E的作用:只进行预处理操作,注意:-E选项紧跟着要预处理的文件,-o选项紧跟着要生成的文件
有了上面的解释,反过来写也是可以的:
gcc -o test.i -E test.c
运行结果:
编译(-S选项,全称Stop after the stage of compilation proper)
要求对test.i文件生成编译后的文件,使用以下命令(其实-S选项严格来说是执行预处理、编译这两个过程)
gcc -S test.i -o test.s
注意:-S选项紧跟着要编译的文件,-o选项紧跟着要生成的文件
运行结果:
汇编
如果只是从test.s生成test.o目标文件,这个过程叫"汇编",可以使用-c选项执行(其实-c选项严格来说是执行预处理、编译、汇编这三个过程,-c的全称complie only,只编译,不链接)
(注:英文的complie指的是预处理、编译和链接三个过程,不单单指字面意义上的编译)
(来自https://embeddedprep.com/gcc-command-line-options/网站)
gcc -c test.s -o test.o
生成的test.o被称为可重定位的目标二进制文件,简称目标文件(在Windows平台下是*.obj文件)
注意:目标文件不可单独执行,需要经过链接才可以
运行结果:
因为生成的是二进制代码,所以vim打开是乱码
链接
将可重定位的目标二进制文件和库链接在一起形成可执行文件
如果只是从test.o生成test.out可执行文件,不用带其他选项,直接-o即可
gcc test.o -o test.out #生成的可执行文件是test.out
注:如果想从源文件直接得到可执行文件,可以直接使用此命令:gcc test.c -o test.out
(不建议写成gcc -o test.out test.c,-o后面紧跟的是生成的文件)
运行结果:
-std
例如-std=c++11
有些情况下,需要加入C/C++标准的选项,例如以下代码:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v={1,2,3};
for (auto a:v)
{
cout<<a<<endl;
}
return 0;
}
如果不用-std=c++11会报错:
因为范围for是C++11标准引入的(在CC7.【C++ Cont】范围for的使用和auto关键字文章讲过),所以要加-std=c++11,运行结果如下:
优化选项(-O0~-O3)
合理使用优化选项能提高程序的性能,例如以下代码,放到https://godbolt.org/网站上测试,编译器旋转x86_64 gcc 11.4
int func()
{
for (int i=0;i<=5;)//这个循环是没有用的
i++;
printf("teststring");
return 0;
}
O0优化结果:
O1优化结果:
开O1优化把没有用的循环优化掉了,如下反汇编代码:
-Wall和-Werror
-Wall允许大多数编译器的警告(Enable most compiler warnings)
-Werror让编译器把警告当成错误处理
例如以下代码使用了未定义的变量,如果不加这两个选项,gcc不会报警告
#include <stdio.h>
int main()
{
int a;
printf("%d",2);
return 0;
}
运行结果:
如果加了 -Wall和-Werror:
如果只加了 -Wall:
-g
作用:包含调试信息到目标文件或可执行文件中,一般调试时这两个选项经常在一起使用: -g -O0
gcc test.c -g -O0 -o test-debug.out
readelf命令
显示ELF(Executable and Linkable Format,可执行和可链接格式)文件的头信息、程序头表、节头表、符号表、重定位表等详细信息
对上述生成的test-debug.out使用readlyelf命令
readelf -S test-debug.out | grep "debug"
可以截取到带"debug"字样的信息
-D
作用:在命令中添加宏定义,这个在调试中非常有用
例如以下代码:
#include <stdio.h>
int main()
{
#ifdef __DEBUG__
printf("Debug mode\n");
#else
printf("Release mode\n");
#endif
printf("Hello World!\n");
return 0;
}
(有关#ifdef和#endif的介绍参见96.【C语言】解析预处理(4)文章)
如果执行gcc test.c命令:
如果执行gcc test.c -D__DEBUG__命令:
库的概念
1.编译型语言安装开发包时,必定是下载安装对应的头文件和库文件
2.链接过程和库有关,库文件提供了方法的实现,常见的库有C语言标准库
定义
库其实就是把经过一定的翻译后的源文件进行打包,最终只提供一个文件,不用提供太多的源文件,简单来说可执行文件=代码+库文件(方法实现)+头文件(方法声明),也可以达到隐藏源文件的目的
分类
后缀名
动态库
Linux下的*.so(全称shared object),Windows下的*.dll(全称dynamic link library)
静态库
Linux下的*.a(全称static library),WIndows下的*.lib(全称static library)
Linux下动态链接库文件的存放位置:/lib64/libc*
可以看出文件名有一定的规律,以lib开头,中间是名称,结尾是so和版本号,例如:
*注:一般云服务器上的安装好的Linux系统默认只有动态库,不带静态库,如果需要那就要手动安装
动态链接
动态链接:程序运行时加载和链接库文件,换而言之,动态库是依附于可执行文件创建的进程来执行的,可执行文件自己不带动态链接库,需要到系统中调用动态链接库,完成后返回代码的调用处
可以得出以下结论:
动态库不能缺失! 一旦对应的动态库缺失,影响的不止一个程序,可能导致很多程序都无法进行正常运行
比如Linux下的命令依赖很多动态库
为什么动态链接库可以隐藏源代码?
由上述所说: 调用者只需要根据自己要实现的功能调用动态链接库中对应的接口,而不需要知道每个接口函数具体的实现,则对于动态链接库的设计者而言,隐藏了接口的代码实现
ldd命令
作用:查看程序依赖的动态链接库
例如对于一个简单的Hello World程序,查看其动态链接库:
静态链接
和动态链接不同,编译器使用静态库进行静态链接时,会将自己方法拷贝到目标程序中,程序在调用静态链接库的方法时不会转到系统中的动态链接库中执行
安装静态链接库的命令:
sudo yum install -y glibc-static libstdc++-static #C++静态库和C语言静态库
Linux下动态链接库文件的存放位置:/lib64/lib*.a
显然静态链接只能使用静态库,动态链接只能使用动态库
gcc的-static选项
-static作用:编译时所有的库强制使用静态链接库
对于一个简单的Hello World程序,编译时分别使用动态和静态链接库:
gcc test.c -o test-static.out -static
gcc test.c -o test-dynamic.out
会发现用静态链接库生成的可执行文件体积比动态链接库的要大很多,因为进行静态链接编译器会将自己方法拷贝到目标程序中,因此开发中静态库很少使用
常见问题
1.无静态库能否使用-static选项?
显然不能
2.无动态库只有静态库,而且gcc命令没有-static选项,使用什么库?
显然是静态库,得出结论:gcc默认优先动态链接,-static的本质:改变链接的优先级
3.动静态链接可以是混合的,不一定只有一种,但-static选项是强制所有链接变成静态链接
file命令
作用:分析文件的内容来判断文件类型,而不是仅仅依靠文件扩展名
当然也可以看是动态链接还是静态链接
两类库的优缺点
1.动态库
动态库是共享库,因此节省资源(磁盘空间、内存空间和网络空间等),但是系统的动态库如果丢失,会导致很多程序无法运行
2.静态库
程序不依赖系统提供的动态库,可以独立运行,但体积大,会消耗较多资源
节省内存空间的原因:程序运行时要加载到内存,节省内存资源
节省网络空间的原因:省下载的流量