C语言 — 编译和链接

发布于:2025-06-07 ⋅ 阅读:(18) ⋅ 点赞:(0)

1.程序从源文件到结果输出的执行过程

在这里插入图片描述

2.预处理

预处理阶段的执行操作:

预处理阶段会将#define定义的常量或宏进行替换,经过预处理后的文件#define的语句就不存在;

预处理阶段会将注释进行删除,并对行号进行标识;

预处理阶段会处理#include包含的头文件,将其内容进行插入,此过程是可以递归进行的,因为包含的头文件中可能包含其它头文件;

预处理阶段会处理#if #endif #elif #ifdef #else的条件编译指令,对表达式进行判断处理;

预处理阶段会保留所有#pragma指令。

以下为简单例子展示,创建三个文件:Add.h,Add.c,test.c

//Add.h文件
#pragma once//防止头文件重复包含

//类似效果
//#ifndef C //判断是否没有定义符号M
//
//#define C //定义符号C
//
//#include<stdio.h>//重复包含多次
//#include<stdio.h>
//#include<stdio.h>
//
//#endif//结束#ifenf条件判断指令

#include<stdio.h>

#ifndef M //判断是否没有定义符号M

#define M 100//定义符号M

#endif//结束#ifenf条件判断指令

#define N 200

int Add(int x, int y);//函数声明

//Add.c文件
#include"Add.h"//包含头文件
//函数定义
int Add(int x, int y)
{
	return (x + y);
}
//test.c文件
#include "Add.h"//包含头文件

int main()
{
	//输出两个数的和
	printf("%d", Add(M, N));

	return 0;
}

将以上代码在VS环境下输入后Ctrl + S保存,打开文件保存的位置,点击输入cmd后回车,打开cmd.

在这里插入图片描述
在这里插入图片描述
打开cmd后需要输入指令,将源文件转变为.i为后缀的中间文件

gcc -E test.c -o  test.i

在这里插入图片描述
gcc是一个编译器,需要下载相应的插件才可以使用,具体流程可以参考以下文章:

https://blog.csdn.net/qq_36318563/article/details/140336690

以上文章可能介绍的网站打不开,可以使用以下链接:MinGW下载

在这里插入图片描述

输入以上指令后会生成一个test.i的文件,可以在VS2022中打开观察

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
因为头文件的插入有400多行代码,以上就不做过多的展示,最后可以看到处理后的#define定义的常量和条件编译指令进行了处理和替换,并将注释删除。

3.编译

编译阶段会进行词法分析,语法分析和语义分析及优化,以下面例子为参考

  arr[n] = i + 1;

3.1 词法分析

词法分析阶段会将语句的标识符,操作符,数字进行标记,以上语句可以得到以下标记

在这里插入图片描述

3.2 语法分析

语法分析阶段会根据标记生成语法树,语法树是根据表达式为节点的数。
在这里插入图片描述

3.3 语义分析

语义分析阶段是对语法层面的意思进行转换,包括声明,类型的匹配和转换等,此时会报告错误信息,经过语义分析后的语法树。

在这里插入图片描述

3.4 生成test.s文件

对test.i的中间文件通过以下指令编译生成test.s的文件

gcc -S test.i -o test.s

在这里插入图片描述

在这里插入图片描述
将生成的test.s文件在VS2022中打开,文件内容是汇编代码。

	.file	"test.c"
	.text
	.globl	_Add
	.def	_Add;	.scl	2;	.type	32;	.endef
_Add:
LFB10:
	.cfi_startproc
	pushl	%ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	movl	%esp, %ebp
	.cfi_def_cfa_register 5
	movl	8(%ebp), %edx
	movl	12(%ebp), %eax
	addl	%edx, %eax
	popl	%ebp
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
LFE10:
	.def	___main;	.scl	2;	.type	32;	.endef
	.section .rdata,"dr"
LC0:
	.ascii "%d\0"
	.text
	.globl	_main
	.def	_main;	.scl	2;	.type	32;	.endef
_main:
LFB11:
	.cfi_startproc
	pushl	%ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	movl	%esp, %ebp
	.cfi_def_cfa_register 5
	andl	$-16, %esp
	subl	$16, %esp
	call	___main
	movl	$200, 4(%esp)
	movl	$100, (%esp)
	call	_Add
	movl	%eax, 4(%esp)
	movl	$LC0, (%esp)
	call	_printf
	movl	$0, %eax
	leave
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
LFE11:
	.ident	"GCC: (MinGW.org GCC-6.3.0-1) 6.3.0"
	.def	_printf;	.scl	2;	.type	32;	.endef

4.汇编

汇编过程是将汇编代码转换为二进制代码,每一条汇编语句都代表一条指令,将汇编语句与机器语句通过对照表转换,不做任何优化,通过以下指令将test.s的文件转换为test.o的目标文件。

gcc -c test.s -o test.o

在这里插入图片描述

在这里插入图片描述

打开test.o的文件,可以观察到都是二进制的编码

L?.textH? 0`.data@0?bss€0?rdataL@0@/4$P@0@/15Xt?@0@U夊婾婨衇肬夊冧饍?枨D$惹$d柩塂$?$韪擅悙%dGCC: (MinGW.org GCC-6.3.0-1) 6.3.0zR|?
A?B
I?<9A?B
u?6; @.file?gtest.c_Add _main
 .textF.data.bss.rdata#$X___main _printf ..rdata$zzz.eh_frame.rdata$zzz.eh_frame

5.链接

链接过程主要处理地址和空间分配,符号决议和符号重定位等操作,最后通过链接库链接生成可执行程序。

例子:

add.c文件

int n = 100;//全局变量

int Add(int x,int y)
{
  return x + y;
}
test.c文件

extern int n;//声明外部符号
extern int Add(int ,int)//声明外部符号

int main()
{
    int r = Add(2,3)printf("r = %d",r);
    printf("n = %d",n);
    return 0;
}

地址和空间分配在链接过程,每一个.o为后缀的文件生成后都会有对应的符号表,对于add.o文件和test.o文件如下:一般符号标记录的是函数名和全局变量或静态变量。

在这里插入图片描述

因为test.c文件中的n和Add是外部符号,在链接前并不确定这些外部符号的具体地址,系统会随机分配一个虚假的地址;有了各自文件的符号表就可以进行符号的决议和重定位。

在这里插入图片描述

通过以上过程确定最终的符号表,再通过链接库的链接就可以生成可执行程序,可以通过以下的指令

//因为需要test.o和Add.o文件进行链接,此时有了test.o文件,再生成一个Add.o的文件

gcc -c Add.c -o Add.o

在这里插入图片描述
在这里插入图片描述

有了test.o和Add.o文件就可以通过链接库链接生成可执行程序,使用以下指令

//生成一个test.exe的可执行程序
gcc test.o Add.o -o test

在这里插入图片描述
在这里插入图片描述

6.运行

运行过程需要在运行时环境下进行,可执行程序的运行必须先将程序植入内存,在操作系统中,一般由操作系统完成;调用函数时会开辟运行时堆栈(即函数栈帧的创建),一般函数是从main函数进入,在开辟过程会保存局部变量和函数的地址,全局或静态变量会存储于静态区,直到函数销毁而销毁,最后随main函数的结束而终止程序 ,并输出结果。

对应可执行程序在cmd指令中直接输入文件名后回车即可输出结果:

test.exe

在这里插入图片描述

以上内容只是编译和链接的大概介绍,如果对编译链接想要进一步的了解,可以参考以下书籍:

《程序员的自我修养》

其它推荐书籍:高质量c/c++编程


网站公告

今日签到

点亮在社区的每一天
去签到