C编译和链接过程
# C 编译、链接过程
# extern
和static
的作用
# static
# 1. 修饰函数
static
修饰函数时表示,该函数只在本文件有效,也就是只能被本文件的其他函数调,其他文件不能调用(引用)。
a.c
int func1(void)
{
func(100); // 不能引用
}
2
3
4
b.c
static void func(int stu_num)
{
// .....
}
2
3
4
static
就像是一把锁,把函数func
的有效范围锁在了b.c
其他.c
看不见这个函数 ,自然无法调用。
提示
因为static
将函数名这个符号变成了“本地符号”,“本地符号”只在本文件可见,其他文件是看不见的。
# 2. 修饰变量
static
修饰局部变量改变的是局部变量的存储位置,从栈变为静态存储区,与“本地符号”什么的没有关系
int func(int va) { int a; int b = 10; // ... }
1
2
3
4
5
6
7a、b
为“自动局部变量”,空间开辟于栈中。加了
static
修饰int func(int va) { static int a; static int b = 10; // ... }
1
2
3
4
5
6
7a、b
变为了“静态局部变量”,a、b
的空间开辟于静态存储区,而不再是栈。- 如果没有初始化的话,比如
a
就没有初始化,a
的空间在静态区中的.bss
区 - 如果有初始化的话,比如
b
就有初始化,b
的空间在静态区中的.data
区
- 如果没有初始化的话,比如
static
修饰全局变量与修饰函数一样,让全局变量只在本文件有效,其它文件无法引用。
static
修饰全局变量的时候,与全局变量的存储位置无关,全局变量的空间本来就是在静态存储区,加不加static
并不能改变存储位置,static
只是改变了符号的属性,变成了本地符号。
# extern
# 1. 修饰函数
extern
修饰的函数,表示除了能被本文件引用外,还可以被其他的文件引用,在其他文件中是可见的,只不过在其他文件中引用时,需要做声明。刚好与static
相反。
a.c
extern void func(int stu_num);
extern int a_fun(void)
{
fun(100);
}
2
3
4
5
6
b.c
extern void fun(int stu_num)
{
// ...
}
2
3
4
事实上,extern
不需要明确写出,因为默认是extern
的,也就是说定义和声明可以写出如下格式:
void func(int stu_num);
int a_fun(void)
{
fun(100);
}
2
3
4
5
6
void fun(int stu_num)
{
// ...
}
2
3
4
# 编译过程
编译原理可以去看一本书:《现代编译原理》,有词法分析、语法分析、优化、生成中间代码
#include "stdio.h"
int main()
{
printf("hello world\n");
}
2
3
4
5
6
源码文件 --> gcc 处理 --> elf
可执行文件
有这样的一个过程:预处理、编译、汇编、链接
使用gcc
命令文档https://man7.org/linux/man-pages/man1/gcc.1.html (opens new window)
# 预处理:gcc -E
操作,使用预处理器
-E Stop after the preprocessing stage; do not run the compiler proper. The output is in the form of preprocessed source code, which is sent to the standard output.
预处理后的文件也是一个源码文件
gcc -E demo1.c -o demo1.i
# 查看文件内容
cat demo1.i
2
3
4
会发现有很多头文件和一些代码插入进来。
# 编译:gcc -S
,使用编译器
-S Stop after the stage of compilation proper; do not assemble. The output is in the form of an assembler code file for each non-assembler input file specified.
By default, the assembler file name for a source file is made by replacing the suffix .c, .i, etc., with .s.
Input files that don't require compilation are ignored.
gcc -S demo1.i -o demo1.s
# 生成汇编代码文件
2
3
➜ file demo1.s
demo1.s: assembler source text, ASCII text
2
# 汇编:gcc -c
,使用汇编器,将汇编源码转换为机器指令
-c Compile or assemble the source files, but do not link. The linking stage simply is not done. The ultimate output is in the form of an object file for each source file.
By default, the object file name for a source file is made by replacing the suffix .c, .i, .s, etc., with .o.
Unrecognized input files, not requiring compilation or
assembly, are ignored.
翻译:
编译或组装源文件,但不要链接的 连系阶段还没有完成。最终的输出是 in 每个源文件的目标文件的形式。默认情况下,源文件的对象文件名由 将后缀.c, .i, .s 等替换为.o。
无法识别的输入文件,不需要编译或
大会,将被忽略。
gcc -c demo1.s -o demo1.o
➜ file demo1.o
demo1.o: Mach-O 64-bit object arm64
2
3
4
5
我这边用的是mac unix
编译的,如果是 linux 则是elf
可重定向文件
file demo1.o
demo1.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
2
经过上述 3 个操作之后生成的文件是ELF
可重定向文件,顺带介绍一下elf
文件的类型
- REL(Relocatable file)
- EXEC(Executable file)
# 链接:使用链接器
主要功能:把各个模块链接起来组织成为可执行文件
如果是直接
gcc demo1.c
,会直接把你上述几个过程都做了直接生成可执行文件,包括现在的编辑器点击的Run
运行也是这样的一个过程。
例如:
a.c b.c 源码文件
a.o b.o 目标文件
a.o b.o 一些库文件[运行库]
最后一起链接得到可执行文件
2
3
4
为什么要链接:因为我们某些文件里的,使用的比如:printf
就是标准库提供的,链接的时候会去链接它所在的库文件。
gcc demo1.o -o demo1
➜ file demo1
demo1: Mach-O 64-bit executable arm64
2
3
4
5
现在这个文件就是一个可执行文件。
直接执行
./demo1
hello world
2
符号:Symbol
在链接中,把函数的名字,变量【全局变量】的名字都称为符号
在开发大型项目时,会把系统拆分成各个模块,去开发编写,在编译时,是单独编译成各个目标文件得到.o
文件,这些模块会涉及到函数以及变量的调用【访问】,如果a.c 调用foo函数
没有声明也没有定义,在b.c重定义好的foo函数
,链接器会帮它去找到对应的函数地址,将各个模块链接起来。
实际上我们可以直接使用gcc demo1.c
生成demo1
可执行文件,加上-o
可以指定对应的文件名称。