C语言:编译和链接

发布时间:2024年01月19日

前言:

我们写的代码实际上是一个文本文件,对文本文件运行不会生成可执行程序。但是在VS或者gcc等开发环境中,内置了编译器和链接器,使它生成一个计算机可以理解可以执行的二进制程序,这个可执行程序用.exe为后缀。

编译和链接主要就是解释代码是如何从文本文件变成可执行文件的。

编译这个过程就是把一个工程中的各源文件,通过编译器,编译成.obj为后缀的目标文件。

链接就是把这一个个的.obj为后缀的目标文件整合处理成一个.exe为后缀的可执行程序。

需要注意,.obj为后缀的文件也是计算机可以理解的二进制文件。

一、编译

编译的内容大致可以分为三部分:预处理、编译、汇编

1.预处理

预处理阶段主要处理以下内容。

1.头文件的包含:如#include? <stdio.h>? , 如#include? "add.h"。

2.宏定义:如 #define MAX? 5

3.删除注释? 注释的内容不参与后面的编译和任务,在预处理阶段就会被删除,它只是给程序员看的。

预处理过后,.c的程序文件会生成一个.i为后缀的文件,这个文件可以看到在预处理后,我们的代码是怎样的。

但是在VS的集成开发环境中,这种.i为后缀的文件在生成之后就被删掉了,不会被保留辖来。我们难以在当前目录底下看到程序在这个阶段的细节。所以可以通过Linux在gcc环境下,通过一些指令来看到.i文件的代码。

头文件包含

头文件的包含是这样的。如果是<>的包含,那么会先从库文件中查找,在预处理后,就会把所包含的库文件内容,替换掉#include <stdio.h>这句代码(以它为例)。如果是" "的包含,那么会先从当前目录下查找所包含的文件名字,如果没找到,再从库目录下面找,找到了就把该文件的内容替换掉#include "add.h" 的代码。相当于把它们的内容展开了。

有时候包含的文件比较大,重复包含会占用很多空间。因此可以选择条件编译(见下文编译部分),也可以选择#pragma once 来避免头文件的重复引用。

宏定义

常见的有:

1)表达式

例:

#define FLOWER(n)  ((n)+(n))

int main()
{
    int a = 3;
    a = FLOWER(a);
    printf("%d\n",a);
    return 0;
}

这个例子是把一个数变成它的两倍。其他的还可以有乘法除法等等。

需要注意的是:

宏定义是没有类型检验的,所以无论传什么类型都可以,只要它能够执行。

宏是直接替换的,它可能会与外面的式子发生一些优先级运算的问题。所以要保证宏定义的运算结果是一个整体,最好把它们都加上括号。

宏一般是每个字母都大写,与函数区分。命名约定。

2)定义函数
#define MALLOC(type,num) ((type*)malloc(sizeof(type)*num))

int main()
{
   int *p = NULL;
   p = MALLOC(int,10);
   //...
   free(p);
   return 0;
}

它和函数的功能很相似,但是它没有类型检查,而且宏也不能递归。

在执行简单的程序时,宏由于是直接把代码替换的,所以它的运行速度比函数要快。但是在复杂的程序中,宏的多次使用会使代码量大大提升。而函数始终只有一份,用的时候调用一次。此时用函数更好。

宏的使用不参与调试,它在预处理就直接替换了,所以没办法在调试中显示出来。

宏没有类型检验,相对来说,没有函数严谨。

宏的参数在字符串里是,不会被替换。比如“type = 3”,就算参数是type,预处理阶段也只会把它当作字符串常量来处理。

3)#和##

#是把宏的参数字符串常量化。

#define PRINT(n) printf("the value of "#n" is %d\n",n)

假设传的是num,预处理之后,PRINT(m)的地方就会被下面的语句所替代。

printf (“the value of num is %d\n",num)

##

#define GENERIC_MAX(type)\
type type##_max(type x, type y) \
{\
     return ((x) > (y))?(x):(y);\
}

##就是把前面的type和后面的_max连在一起当作一个整体。这里表示的是一个函数名,当我们要使用这个定义的函数时,如果不用##,这个type不会被当作参数转换,那么每次调用的函数名就是重复的,在编译时会报错。声明不够清晰。

2.编译

编译主要处理词法分析、语法分析和语义分析。

词法分析主要是把一个语句中包含的每一个关键字、标识符、字面量、特殊字符等进行分析。

语法分析主要是产生语句中的各种表达式为节点的数。

语义分析主要是包含类型的声明和匹配,类型的转换等,这个阶段会报告错误的语法信息。

条件编译

有些代码删除了可惜,保留了有时有点多余,所以用条件编译指令。

#if? 常量表达式

#endif

//如果常量表达式为真,则编译以下代码。然后用#endif结束。下面的类似。不再解释。

#ifdef? //#if defined(? ? )

#endif

#ifndef//#if !defined(? ? )

#endif

#if? 常量表达式

#elif? 常量表达式

#elif? ?常量表达式

#else

以上配对使用。

3.汇编

汇编就是把汇编代码转换成机器可执行的指令,这个过程也不做指令优化。会生成一个符号表。符号表里是变量和它对应的地址,函数名和它对应的地址等等。

汇编之后会生成一个.o为后缀的目标文件。

二、链接

链接主要是把这些符号表和段表合并和重新定位。地址和空间的分配。把各个文件链接在一起生成一个可执行程序。这个过程因为会重定义,所以变量对应的无效的地址,没有声明的函数等都会被发现,然后报错。

链接解决的是一个项目中多文件多模块互相调用的问题。

三、运行环境

1.程序必须载入内存中,在有操作系统的环境中,这个由操作系统完成,独立的环境中,由手动安排。

2.程序执行开始,调用main函数。

3.开始执行代码,调用堆栈,或者静态内存(全局变量,static修饰的变量)。

4.终止程序。

文章来源:https://blog.csdn.net/2301_79490289/article/details/135701450
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。