首页 > 其他分享 >C语言编译和链接

C语言编译和链接

时间:2024-08-05 22:54:54浏览次数:18  
标签:__ 定义 int C语言 编译 2.1 链接 define

前言

我们已经写了这么多的代码,那我们是不是应该了解一下代码是运行的呢?

1. 翻译环境和运行环境

翻译环境将源代码转换为二进制指令。

运行环境用于执行实际代码

2. 翻译环境

翻译环境主要由编译和链接两个大过程组成,而编译又可以分解成:预处理/预编译,编译,汇编三个过程。

2.1 预编译

预编译阶段,源文件和头文件被处理成为以.i为后缀的文件。

该阶段主要处理文件中以#开始的预编译指令,例如#include,#define,处理规则:

1. 将所有的#define删除,展开所以的宏定义。

2. 处理所有的条件编译指令,如#if、#endif、#ifdef 、#elif、#else。

3. 删除所有注释

4. 处理#include指令,将所包含的头文件的内容插入到预编指令位置,这个过程递归,即头文件可能包含其他文件。

5. 添加行号和文件号标识,方便后续编译器生成调试信息等。

6. 保留所有的#pragma的编译指令,编译器后续会使用。

2.1.1  宏定义

2.1.1.1 #define定义常量

基本语法:

#define name stuff

举例:

#define M 100
#define A 50
#define CASE break;case
#define DEBUG_PRINT printf("file:%S\tline:%d\t\
                            date:%s\ttime:%s\n",\
                            __FILE__,__LINE__,\
                            __DATE__,__TIME__)

当定义的stuff过长,可以分成几行写,除了最后一行外,每行后面加一个反斜杠(续行符)。

通过第三行的代码可以以抽象的方式实现switch-case语句

int main()
{
   int n =0; 
   scanf("%d ",&n);
   switch(n)
{
   case :1;
   CASE :2;
   CASE :3;
   //.......
}

}

思考:在define定义标识符的时候,要不要在最后加上;?

有些情况不能加;,有些情况可以加,建议是不加。例如下面的情况不能加

比如:

#define MAX 10000
#define MAX 10000;
if (condition)
   max  =MAX;
else
   max = 0;

当替换之后,会出现两个分号,当没有大括号时,if后面只允许有一个语句,就会报错。

2.1.1.2  #define定义宏

申明方式:

#define name(parament-list) stuff

其中parament-list是由逗哈隔开的符号表,他们可能出现在stuff中。 

注意:左括号必须和name紧密相连,否则参数列表会被解释为stuff中的一部分。

宏定义举例:

#define  SQUARE(X) X*X

当使用SQUARE(5) 时,预处理器将用 5*5替换SQUARE(5)。

警告:

上面的其实是有问题的,例如当遇到下面情况时:

int a = 5;
printf("%d ",SQUARE(a+1));

输出的结果不是36,而是11。

计算过程:5+1*5+1 = 11.

原因:宏定义代换时不会进行运算操作,只会进行简单代换。

如何修改正确呢?

#define  SQUARE(x) ((x)*(x))

总结:当宏定义的时候尽量将每个参数都进行加括号操作,避免造成不可预估的结果。

2.1.1.3 带有副作用的宏参数

当宏参数在宏定义中出现超过一次的时候,如果参数带有副作用,那么使用的时候可能出现危险,导致不可预测的结果。

例如:

x+1;//不带有副作用
x++;//带有副作用

证明:

#define MAX(a,b) ((a)>(b)?(a):(b))
x = 5;
y = 8;
Z = MAX(x++,y++);

预处理后:

#define MAX(a,b) ((a)>(b)?(a):(b))
Z = ((x++)>(y++)?(x++):(y++));

进行运算后:x = 6, y = 10,z = 9。

2.1.1.4  宏替换规则

1. 在调用宏时,首先对参数进⾏检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。

2. 替换文本随后被插⼊到程序中原来文本的位置。对于宏,参数名被他们的值所替换。

3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。

注意:对于宏不能出现递归。

2.1.1.5 宏与函数的对比

宏通常被用于执行简单的运算。

如在找较大数时,写成宏更有优势。

原因:

1. 对于小型计算,宏不需要要调用函数栈帧等操作,节约时间。所以宏比函数在程序的规模和速度方面更胜一筹。

2. 宏的参数是类型无关的,什么类型都可以。

对比与宏的劣势:

1. 每次使用宏时,都需要插入代码,除非宏比较短,否则可能大幅度增加程序长度。

2. 宏没有办法调试。

3. 宏与类型无关,不够严谨。

4. 宏会带来运算符优先级的问题,导致容易出错。

宏的参数可以出现类型,但是函数不可以。

例如:

#define MALLOC(num,type) (type*) malloc (num * sizeof(type))

运用:

MALLOC(10,int);

预处理后:

(int *)malloc(10*sizeof(int));

宏和函数的对比

2.1.1.6 #和##
1. #运算符

当我们想写函数作为打印下列代码时,普通函数可能不是很好实现。因为这里面的n是在字符串里面的,带入参数时不好修改,而且如果打印时是其他参数呢?如%f等。那我们这里就可以运用宏来实现。

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

这里的format是数据的类型,如果传的是%f,就会输出浮点数类型。%d就是整型类型。

这里的#n用来输出参数n的名称,而不是参数n的数据。

#执行的操作就是字符串化。

2. ##运算符

##运算符的作用是将两边的符号合成一个符号。##被称为记号粘合。

当求两个数较大数时,一个函数不能应用于多种类型,所以我们需要写不同的函数,那我们这里就可以用宏来实现。

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

运用:

GENERATE_MAX(int)
GENERATE_MAX(float)
int main()
{
  int m = int_max(2,3);
  printf("%d\n",m);
return 0;
}
2.1.1.7 命名约定

我们通常将宏全部大写,函数名不全部大写。

2.1.1.8 #undef

#undef用于移除宏定义

格式:

#undef name
2.1.1.9 条件编译

当编译时,我们可以通过条件编译指令进行编译或放弃编译。

常见条件编译指令:

1.

#if
//。。。。
#endif
如:
#define __DEBUG__ 1
#if __DEBUG__
//......
#endif

2. 多分支条件编译

#if 

#elif

#else

#endif

3. 判断是否被定义

#if defined(symbol//写法1
#ifdef symbol//写法2

#if !defined(symbol)
#ifndef symbol
2.1.1.10 头文件包含
1. 本地文件包含
#include"filename"

先在源文件目录查找,再到标准位置查找。

2. 库文件包含
#include<filename.h>

到标准位置查找。

总结:查找文件都可以用“”方式查找,但这样效率更低,且不容易区分是库文件还是本地文件。

3. 嵌套文件包含

当包含了多次同一文件时,为了避免造成资源的浪费,我们通过条件编译解决该问题。

方法1:

#ifndef __TEST_H__
#define __TEST_H__
//....
//endif 

分析:先判断是否已经被定义,没被定义再定义,定义了则跳过。

方法2:

#pragma once

分析:这个表示只能被引入一次。

2.2 编译

将预处理后的文件进行一系列的:词法分析,语法分析,语义分析及优化,生成汇编代码。

2.3 汇编

将汇编代码转换为二进制代码。

2.4 链接

链接是一个复杂的过程,链接的时候需要将一堆文件链接在一起才生成可执行程序。

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

标签:__,定义,int,C语言,编译,2.1,链接,define
From: https://blog.csdn.net/Ajiang2824735304/article/details/140934234

相关文章

  • Vs code写C语言代码配置(超级详细基础,小白也能看得懂)
    前言本文旨在为那些希望在VSCode中配置C语言开发环境的开发者提供一份详尽的指南。无论你是C语言的新手,还是希望提升开发效率的老手,本文都将引导你通过一系列简单的步骤,完成VSCode的C语言开发配置。我们将涵盖从安装VSCode开始,到配置编译器、调试器,以及安装必要的扩展,确保......
  • 模拟实现 memcpy --浅谈C语言
    内存拷贝-memcpy描述C库函数void*memcpy(void*str1,constvoid*str2,size_tn)从存储区str2复制n个字节到存储区str1。memcpy是最快的内存到内存复制子程序。它通常比必须扫描其所复制数据的strcpy,或必须预防以处理重叠输入的memmove更高效。memcpy,memcpy......
  • 嵌入式开发C语言学习day28-华清作业8.5
    思维导图作业1:pipe.c//使用有名管道实现一个进程用于给另一个进程发消息//另一个进程收到消息后展示到终端上并且将消息保存到文件上一份#include<myhead.h>intmain(intargc,charconst*argv[]){//创建一个有名管道if(mkfifo("./linux",0664)......
  • C语言入门 --- sizeof 与 strlen 的区别
    ......
  • C语言 随机函数
    随机函数随机函数是一个使用内部链接的静态变量的函数;ANSIC库提供了rand()函数生成随机数。在C语言中rand()生成的随机数,并不是真正意义上的随机数,而是一个伪随机数;实际上,rand()是“伪随机数生成器”,意思是可预测生成数字的实际序列,且数字在其取值范围内均匀分布......
  • 【Python】Python中的输入与输出——内附Leetcode【151.反转字符串中的单词】的C语言
    输入与输出导读一、Python中的输出1.1基本用法1.2格式化输出1.3通过`:`格式化值的输出1.4其它格式化输出二、Python中的输入2.1基本用法2.2`split()`方法2.3split()习题演练结语导读大家好,很高兴又和大家见面啦!!!在上一篇内容中我们介绍了Python中的数据类......
  • emsdk安装和编译2个C++基础示例
    参考地址:Downloadandinstall—Emscripten3.1.65-git(dev)documentation 环境:ubuntu24.04LTSgcc(Ubuntu13.2.0-23ubuntu4)13.2.0g++(Ubuntu13.2.0-23ubuntu4)13.2.0cmakeversion3.28.3 Firstcheckthe Platform-specificnotes belowandinstallan......
  • 短链接生成-短网址-短链接服务-短连接生成接口-短链接转换接口-短网址URL生成-短链接
    短链接是一种将原本较长的网址转换为更简短形式的链接。它具有以下几个优点:节省空间:在社交媒体、短信等场景中,能更方便地展示和传播,不占用过多字符。便于记忆:简短的形式更容易被用户记住。比如,在微博中分享一篇很长的文章网址,如果使用短链接,就不会显得那么冗长和复杂。......
  • 无法从谷歌浏览器中抓取链接
    我的代码正在打开选项卡,搜索主题并关闭,但它没有向我发送它应该收集的链接。fromseleniumimportwebdriverpesquisa=input("oquevocêquerpesquisar:")defget_results(search_term):url="https://www.startpage.com"driver=webdriver.Chrome()......
  • Java编译和运行的命令
    在Java中,编译和运行Java程序主要使用两个命令:javac和java。这两个命令是JDK(JavaDevelopmentKit)的一部分,分别用于编译Java源代码(.java文件)和运行编译后的Java字节码(.class文件)。编译Java程序编译Java程序时,你使用javac命令。这个命令会读取你的Java源代码文件(.java文件),并编译......