图片来源:
【Linux】 gcc(linux下的编译器)程序的编译和链接详解_汇编语言编译器-优快云博客
.out文件
.out文件是编译生成的可执行文件
.out文件是如何生成得到的呢?
从中可以看到,当使用GCC编译器编译main.c文件完成以后会生成一个 a.out文件,这个 a.out就是编译生成的可执行文件,执行此文件看看是否和我们代码的功能一样,执行的方法很简单使用命令:“ :“./+可执行文件”
a.out这个文件的命名是 GCC编译器自动命名的,那我们能不能决定编译完生成的可执行文件名字呢?肯定可以的,在使用 gcc命令的时候加上 -o来指定生成的可执行文件名字,比如编译 main.c以后生成名为“ main”的可执行文件,操作如图
GCC编译时的格式
gcc main.c -o main 、 gcc main -o main.c、gcc -o main.c main、gcc -o main main.c区别
在使用 gcc
(GNU Compiler Collection)编译C程序时,不同的命令行参数和顺序会导致不同的行为。下面详细解释 gcc main.c -o main
、gcc main -o main.c
、gcc -o main.c main
和 gcc -o main main.c
这四个命令的区别:
1. gcc main.c -o main
作用
将 main.c
源文件编译成可执行文件 main
。
解释
main.c
:这是要编译的源文件。-o main
:指定输出文件名为main
,即生成的可执行文件名为main
。
示例
gcc main.c -o main
成功执行后,会生成一个名为 main
的可执行文件。
2. gcc main -o main.c
作用
尝试将 main
文件(假设它是一个源文件或目标文件)编译成可执行文件 main.c
。
解释
main
:这是要编译的文件,假设它是一个源文件或目标文件。-o main.c
:指定输出文件名为main.c
。
示例
gcc main -o main.c
这个命令通常是错误的,因为 main
通常不是一个源文件,且 main.c
应该是源文件而不是输出的可执行文件名。
3. gcc -o main.c main
作用
尝试将 main
文件(假设它是一个源文件或目标文件)编译成可执行文件 main.c
。
解释
-o main.c
:指定输出文件名为main.c
。main
:这是要编译的文件,假设它是一个源文件或目标文件。
示例
gcc -o main.c main
这个命令通常是错误的,因为 main
通常不是一个源文件,且 main.c
应该是源文件而不是输出的可执行文件名。
4. gcc -o main main.c
作用
将 main.c
源文件编译成可执行文件 main
。
解释
-o main
:指定输出文件名为main
,即生成的可执行文件名为main
。main.c
示例
gcc -o main main.c
区别小结:
gcc的用法:
gcc main.c -o main // 用法一
gcc 被编译文件 -o 目标文件
gcc -o main main.c // 用法二
gcc -o 目标文件 被编译文件
也就是,用法一和用法二的作用效果是一样的,这俩是等价的,随便采用哪个都行,正点原子用的是用法一,网上更多的使用用法二。
通过这种方式进行编译成功的目标文件是没有后缀名的,因为自己没有加,比如下图中就通过这种方式对main.c文件进行了编译,生成了一个没有后缀名的main可执行文件,虽然没有后缀名,但依然是可执行文件。如果是直接gcc main.c的方式进行编译,会生成一个.out的可执行文件。
再看一个例子,使用交叉编译工具编译linux上的文件放到基于arm架构的海思开发板上运行,采用方式二进行的编译,生成了一个没有后缀的可执行文件,说明看来还是方式二更常用,毕竟能自己定义名字:
这时候又有个新疑问,linux的文件没有后缀名,我怎么知道这个文件是可执行文件还是别的文件?通过ls命令查看每个文件权限部分,具体讲解:
*****************关于无后缀的疑问在此解决*****************************
Linux下的文件及文件后缀名 - the_tops - 博客园 (cnblogs.com)
补充小结一下:在Linux.中,文件的后缀名并不像 Windows那样决定文件的类型。虽然Linux不依赖后缀名来调用相应的程序,但为了跨平台的考虑,规范命名时还是有必要学习些常用的后缀。带有扩展名的文件在Linux中只代表程序的关联,并不能明确说明文件是否可执行。因此,Linux的文件扩展名并没有太太的意义。如果你想判断一个文件是否可执行,可以查看文件的属性,而不是仅仅依赖于后缀名。
.c文件多编译
也可以同时对多个文件进行编译
gcc main.c calcu.c input.c -o main
上面命令的意思就是使用 gcc编译器对 main.c、 calcu.c和 input.c这三个文件进行编译,编
译生成的可执行文件叫做 main,main.c里面调用了calcu.c和input.c里面的函数。
看来只需要编译.c文件,不用编译.h文件,详细讲解:
GCC编译为什么不用包含头文件_为什么.c文件不需要头文件-优快云博客
.o文件
.o文件是gcc使用-c参数进行编译时生成的目标文件
gcc命令格式:
编译程序的过程分为编译和链接(Linking)两个阶段,编译又分为:预处理(Preprocessing)、编译(Compilation)、汇编(Assembly)三个阶段
1.预处理阶段:进行一系列的预处理命令(如展开所有头文件里面的内容),将.c文件变成了.i文件
2.编译阶段:将预处理后的C代码转换为汇编代码,将.i文件变成了.s文件
3.汇编阶段:将汇编代码转换为机器代码(生成目标文件),将.s文件转为.o文件(二进制文件,包含机器指令)。
4.链接阶段:将多个目标文件和库文件合并,生成最终的可执行文件,也就是将.o文件变成了可执行文件(gcc模式是生成.out后缀,自己命名生成的可执行文件时一般不加后缀,上面第一个链接里面说了,linux文件重点不是文件后缀名,而是看它的属性(ls))
至此,.o文件的由来已经很清晰了。
详细过程参考如下文章,每个文件里面的内容该作者已经截图说的很清晰了,并进行了举例:
Linux - gcc 如何完成编译、链接_gcc 编译链接数据-优快云博客
接着进行延申补充,分隔符------------------------------------------------------------------------------------------------------------
使用-c时,只编译不链接,生成.o文件
gcc -c test.c // 生成test.o文件
使用-o,被编译文件是.c文件,则进行编译+链接
gcc -o test test.c // 生成test可执行文件
使用-o时,被编译文件是.o文件,只进行链接
gcc -o test test.o // 生成test可执行文件
test.c是C语言源文件,test是可执行文件,test.o是只编译未链接的文件,.o文件是配合Make使用的,Make里面使用的都是.o文件,没有.c文件。
.o文件多编译
这部分是为Makefile做准备的
上述命令前三行分别是将 main.c、 input.c和 calcu.c编译成对应的 .o文件,所以使用了“ “-c”选项 ,“-c”选项我们上面说了,是只编译不链接。最后一行命令是将编译出来的所有 .o文件链接成可执行文件 main。假如我们现在修改了 calcu.c这个文件,只需要将 caclu.c这一个文件重新编译成 .o文件,然后在将所有的 .o文件链接成可执行文件即,只需要下面两条命令即可:
.a文件
静态库是一种将目标文件(.o 文件)打包在一起的归档文件。静态库在链接时被直接包含到可执行文件中。
.a文件就是静态库文件,通俗来讲,使用静态库文件就是一种加密的c文件,你可以使用里面函数的功能,但是你不知道这个函数具体是怎么实现的,商业公司通过发布.a库文件和.h文件来给客户使用。
静态库其实就是商业公司将自己的函数库源代码经过只编译不链接形成.o的目标文件,然后用ar工具将.o文件归档成.a的归档文件(.a的归档文件又叫静态链接库文件)。商业公司通过发布.a库文件和.h头文件来提供静态库给客户使用。动态链接库比静态链接库出现的晚一些,效率更高一些,是改进型的。现在我们一般都是使用动态库。
静态库的创建和使用
情况1:单c文件生成静态库
下面创建一个静态库文件来直观的感受一下什么是静态库文件:
foo.c:
#include "foo.h"
void foo() {
printf("Hello from foo!\n");
}
foo.h:
#ifndef FOO_H
#define FOO_H
void foo();
#endif // FOO_H
main.c:
#include "foo.h"
int main() {
foo();
return 0;
}
步骤1:编译源文件
首先,将源文件foo.c编译成目标文件(.o 文件)foo.o。
gcc -c foo.c -o foo.o
步骤2:创建静态库
使用 ar
命令将目标文件打包成静态库。
ar rcs libfoo.a foo.o
这里,libfoo.a
是生成的静态库文件,rcs
是 ar
命令的选项:
r
:插入或替换库中的文件。c
:创建库。s
:创建库索引。
步骤3:使用静态库
在编译和链接时,将静态库链接到可执行文件中。
gcc main.c -L. -lfoo -o myapp
-L.
:指定库的搜索路径为当前目录。-lmylib
:链接库libmylib.a
。- main.c:被编译的文件
- myapp:生成的可执行文件名称
从步骤3开始,就和正常的gcc编译一样了,假如没有静态库,使用gcc编译:
gcc main.c -o myapp
对main.c文件编译为一个名叫myapp的可执行文件,与上面加了静态库的gcc进行对比,不难发现,加了静态库,起始就是在中间加了两个参数,一个是静态库所处的路径,一个是静态库的名称。作用:这样在执行可执行文件的时候,因为静态库已经通过gcc编译到myapp可执行文件当中了,所以可执行文件在运行的时候能直接调用静态库里面的函数,你把源静态库文件删了都没事,可执行文件在执行的时候也不会报错。
步骤4:运行可执行文件
./myapp
情况2:多c文件生成静态库
同情况1基本类似,就是在创建静态库的时候由单个文件变成了多个文件,函数代码就不放了
步骤1:编译源文件
首先,将源文件foo.c和bar.c编译成目标文件foo.o和bar.o(.o 文件)。【PS:目标文件一般都是指.o文件】
gcc -c foo.c -o foo.o
gcc -c bar.c -o bar.o
步骤2:创建静态库
使用 ar
命令将目标文件打包成静态库。
ar rcs libmylib.a foo.o bar.o
这里,libmylib.a
是生成的静态库文件,rcs
是 ar
命令的选项:
r
:插入或替换库中的文件。c
:创建库。s
:创建库索引。
步骤3:使用静态库
在编译和链接时,将静态库链接到可执行文件中。
gcc main.c -L. -lmylib -o myapp
-L.
:指定库的搜索路径为当前目录。-lmylib
:链接库libmylib.a
。
运行时行为 静态库在链接时被复制到可执行文件中,所以运行时不需要库文件的存在。
简单来说,就是在创建静态库的时候,由1个文件变成了多个文件
情况3:使用已经有的静态库
假设我们有两个 C 源文件:main.c
和 helper.c
,以及一个静态库 libmath.a
,包含一些数学函数。
1.编译源文件
编译 main.c
、helper.c
生成目标文件:
gcc -c main.c -o main.o
gcc -c helper.c -o helper.o
2.链接生成可执行文件
将目标文件和静态库链接成最终的可执行文件 app
:
gcc main.o helper.o -L. -lmath -o app
-L.
指定库搜索路径为当前目录。-lmath
指定链接库libmath.a
。- main.o、helper.o:需要被编译的文件
- app:生成的可执行文件
3.运行可执行文件
运行生成的可执行文件 app
:
./app
静态库优点
- 独立性:可执行文件包含所有依赖的代码,运行时不需要额外的库文件。
- 性能:省去了动态链接的开销。
静态库缺点
- 文件大小:可执行文件包含所有库代码,体积较大。
- 更新不便:更新库函数需要重新编译并链接可执行文件。
.so文件
动态库的创建和使用
动态库(也称为共享库)是在运行时动态加载的库。可执行文件在运行时依赖于动态库的存在。与静态库不同的是,静态库编译完之后,原库文件可以删除,可执行文件照样正常使用,动态库文件不能删除,否则可执行文件会报错。
生成动态链接库是直接使用 gcc 命令并且需要添加 -fPIC(-fpic) 以及 -shared 参数。
-fPIC 或 -fpic 参数的作用是使得 gcc 生成的代码是与位置无关的,也就是使用相对位置。
-shared参数的作用是告诉编译器生成一个动态链接库。
bar.c:
#include "bar.h"
void bar() {
printf("Hello from bar!\n");
}
bar.h:
#ifndef BAR_H
#define BAR_H
void bar();
#endif // BAR_H
main.c:
#include "bar.h"
int main() {
bar();
return 0;
}
步骤1:编译源文件
gcc -fPIC -c bar.c -o bar.o
-fPIC
:使得生成的代码可以在内存中的任何位置加载和运行。
将bar.c文件编译成bar.o
步骤2:创建动态库
使用 gcc
命令将目标文件打包成动态库。
gcc -shared -o libbar.so bar.o
-shared
:指定生成动态库。
将bar.o编译成动态库文件libbar.so
步骤3:使用动态库
在编译和链接时,指定动态库路径和库名称。
gcc main.c -L. -lbar -o myapp
-L.
:指定库的搜索路径为当前目录。-lbar
:链接库libbar.so
。
将main.c编译成可执行文件myapp,并且加载了动态库libbar.so
步骤4:运行可执行文件(添加环境变量)
运行时需要动态库的存在。可以使用 LD_LIBRARY_PATH
环境变量指定动态库的搜索路径,设置库路径并运行可执行文件,这里设置库的搜索路径为.,也就是当前目录:
export LD_LIBRARY_PATH=.
./myapp
优点
- 文件大小:可执行文件不包含库代码,体积较小。
- 更新方便:更新库文件不需要重新编译可执行文件。
缺点
- 依赖性:运行时需要动态库的存在,可能导致库缺失问题。
- 性能:有动态链接的开销。
还可以参考:
Linux下动态库(共享库)的制作与使用_linux动态库资源共享-优快云博客
C++静态库与动态库 | 菜鸟教程 (runoob.com)
值得一提的是,Linux下的.a文件就等价于Windows下的.lib,Linux下.so文件等价于Windows下的.dll。
.ko文件
.ko
文件是内核模块文件的扩展名,特指 Linux 内核模块文件。内核模块是 Linux 内核的可加载组件,能够在运行时动态加载或卸载,提供额外的功能而无需重新编译或重新启动内核。
以下是关于 .ko
文件的详细解释,包括如何创建、编译和使用它们的示例。
内核模块的基本结构
内核模块通常包含两个核心函数:
- 初始化函数:模块加载时执行的函数。
- 清理函数:模块卸载时执行的函数。
示例代码
hello.c(一个简单的内核模块示例):
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple Hello World kernel module");
MODULE_VERSION("1.0");
static int __init hello_init(void) {
printk(KERN_INFO "Hello, World!\n");
return 0; // 返回 0 表示成功
}
static void __exit hello_exit(void) {
printk(KERN_INFO "Goodbye, World!\n");
}
module_init(hello_init);
module_exit(hello_exit);
编译内核模块
要编译内核模块,需要使用内核构建系统(kbuild)。首先,确保在系统中安装了内核头文件。
编译 Makefile
创建一个 Makefile 文件来编译模块。
Makefile:
obj-m += hello.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
在 Makefile 中,obj-m
用于指定要编译的模块对象。-C /lib/modules/$(shell uname -r)/build
表示切换到当前运行的内核的构建目录。M=$(PWD)
表示将当前目录作为模块的构建目录。
编译模块
执行以下命令编译模块:
make
这将在当前目录中生成一个名为 hello.ko
的内核模块文件。
加载和卸载内核模块
使用 insmod
和 rmmod
命令来加载和卸载内核模块。
加载模块
sudo insmod hello.ko
检查模块
使用 lsmod
查看当前加载的模块:
lsmod | grep hello
使用 dmesg
查看模块加载时的输出信息:
dmesg | tail
卸载模块
sudo rmmod hello
再次使用 dmesg
查看模块卸载时的输出信息:
dmesg | tail
示例总结
- hello.c 是一个简单的内核模块,包含初始化和清理函数。
- Makefile 用于编译内核模块,生成
hello.ko
文件。 - 使用
insmod
加载模块,使用lsmod
检查加载的模块,使用rmmod
卸载模块。
关键点总结
.ko
文件是可加载的内核模块,提供可扩展的内核功能。- 内核模块包含初始化和清理函数。
- 使用
make
工具和内核头文件编译模块。 - 使用
insmod
加载模块,使用rmmod
卸载模块。 - 使用
dmesg
查看内核日志,调试模块加载和卸载过程。
注意事项
- 编写内核模块需要小心,任何错误可能导致系统崩溃或不可预见的行为。
- 确保遵循内核编程规范,尤其是内存管理和并发控制方面的规范。
- 模块加载和卸载需要超级用户权限。
补充:
Linux模块文件如何编译到内核和独立编译成模块?_linux make modules能指定defconfig吗-优快云博客
补充:
预处理、编译、汇编、链接,这四个阶段.c文件分别变为的文件后缀名?
1. 预处理
- 阶段:在这个阶段,源代码中的预处理指令(如
#include
、#define
等)被处理。 - 输出文件后缀名:通常是
.i
(预处理后的文件)。
2. 编译
- 阶段:将预处理后的源代码转换为汇编语言。
- 输出文件后缀名:通常是
.s
(汇编语言文件)。
3. 汇编
- 阶段:将汇编语言代码转换为机器代码,生成目标文件。
- 输出文件后缀名:通常是
.o
(目标文件,Object File)。
4. 链接
- 阶段:将目标文件与所需的库文件链接,生成可执行文件。
- 输出文件后缀名:通常是没有特定后缀名的可执行文件(在 Unix/Linux 系统中,通常是没有后缀;在 Windows 系统中,通常是
.exe
)。
- 预处理:
.i
- 编译:
.s
- 汇编:
.o
- 链接:可执行文件(通常无后缀或
.exe
)