首页 > 其他分享 >[C语言] 14--编译器、宏定义和头文件

[C语言] 14--编译器、宏定义和头文件

时间:2024-09-13 11:22:24浏览次数:3  
标签:__ __% 头文件 文件 -- 编译 编译器 printf define

1.编译器

概念:编译器是一个用来帮助我们把原码.c翻译成计算机能够直接识别的二进制编码。使用不同的编译器可以翻译出来不同机器的二进制编码。
gcc编译器:
gcc hello.c -o hello
gcc -->C语言编译器
hello.c -->需要编译的原码
-o -->指定输出文件名
hello --> 可执行文件的名字

编译过程:
在这里插入图片描述

1.1 预处理

gcc hello -o hello.i -E
加上一个编译选项 -E就可以使得gcc在进行完第一阶段的预处理之后停下来生成一个默认后缀名为.i的文本文件

预处理是指编译代码之前先进行预先的处理工作,这些工作包含哪些内容:
头文件被包含进来(复制): #include
宏定义会被替换:#define
取消宏定义: #undef
条件编译: #if #ifdef #ifndef #else #elif #endif
修改行号以及文件名: #line 998 “Hello.c”

清除注释
预处理大部分的工作是在处理以#开头的一些语句,从严格意义来讲这些语句不属于C语言的范畴,它们在编译的第一阶段被所谓的预处理器来处理。

1.2 编译

gcc hello -o hello.s -S
加上一个编译选项-S就可以使得gcc在进行完第一和第二阶段之后停下来,生成一个默认后缀名为.s的文本文件。打开此文件看一看,你就会发现这是一个符合x86汇编语言的源程序文件。
经过预处理之后生成的.i文件依然是一个文本文件,不能被处理器直接解释,我们需要进一步的翻译。接下来的编译阶段是四个阶段中最为复杂的阶段,它包括词法和语法的分析,最终生成对应硬件平台的汇编语言(不同的处理器有不同的汇编格式)。汇编文件取决于所采用的编译器,如果用的是gcc,那么将会生成x86格式的汇编文件,如果用的是针对ARM平台的交叉编译器,那么将会生成ARM格式的汇编文件。

1.3 汇编

gcc hello.s -o hell.o -C
-c则是让编译器在对汇编文件进行编译后停下来,这里会生成一个待链接的可执行文件
则会生成一个扩展名为.o的文件,这个文件是一个ELF格式的可重定位(relocatable)文件,所谓的可重定位,指的是该文件虽然已经包含可以让处理器直接运行的指令流,但是程序中的所有全局符号尚未定位,所谓的全局符号,就是指函数和全局变量,函数和全局变量默认情况下是可以被外部文件引用的,由于定义和调用可以出现在不同的文件当中,因此他们在编译的过程中需要确定其入口地址,比如a.c文件里面定义了一个函数func(), b.c文件里面调用了该函数,那么在完成第三阶段汇编之后,b.o文件里面的函数func()的地址将是0,显然这是不能运行的,必须找到a.c文件里面函数func()的确切的入口地址,然后将b.c中的“全局符号”func重新定位为这个地址,程序才能正确的运行。因此,接下来需要进行第四个阶段:链接。
可以尝试使用命令readelf来查看可重定位文件
$readelf demo.o -a
在这里插入图片描述

1.4 链接

gcc hello.o -o hello -lc -lgcc
-lc —> -l链接 c标准C库
-lgcc —> -l链接 gcc链接GCC的库
有两个很重要的工作没有完成,首先是重定位,其次是合并相同权限的段
一个可执行镜像文件可以由多个可重定位文件连接而成,比如a.o, b.o, c.o这三个可重定位文件链接生成一个叫x的可执行文件,这些文件不管是可重定位的,还是可执行的,它们都是ELF格式的,ELF格式是符合一定规范的文件格式,里面包含很多段(section),比如我们上面所述的hello.c变异生成的hello.c有如下的格式
在这里插入图片描述

2.宏的概念

概念:宏(macro)其实就是一个特定的字符串,用来直接替换
比如:#define PI 3.14
上面定义了一个宏名为PI,在下面代码的引用过程中PI将会被直接替换为实际的值

int main(int argc,char const *argv[])
{
    printf("%f\n",PI);
    printf("%f\n",3.14);
    return 0;
}

宏的作用:
使得程序的可读取有所提高,使用一个又有意义的单词来表示一个无意义数字(某个值);
方便对代码进行迭代更新, 如果代码中有多处使用到该值, 则只需要修改一处即可(定义);
提高程序的执行效率,可以使用宏来实现一个比较简单的操作。用来替代函数的调用。

2.1 无参宏

意味着我们在使用该宏的时候不需要携带额外的参数
// 定义一个宏用来表示 人数
#define PEOPLE 10
系统中有预定义一些宏:
比如一下宏, 如果需要使用则需要包含它所在的头文件
/usr/include/linux/input-event-codes.h

#define ABS_X 0x00
#define ABS_Y 0x01
#define ABS_Z 0x02
#define ABS_RX 0x03
#define ABS_RY 0x04
#define ABS_RZ 0x05

2.2 带参宏

意味着我们在使用这些宏的时候,需要额外传递参数

#define   MAX(a,b)  a>b? a:b 
int main(int argc, char const *argv[])
{
     printf("%d\n" , MAX(198,288) );
     //printf("%d\n" , 198>288? 198:288 );  --> 预处理后
     return 0;
}

以上MAX 的宏在使用的时候 ,把198 以及 288 分别作为 a ,和b 的值, 然后进行替换。
注意:
宏只是直接文本替换,没有任何的判断以及语法检查的操作甚至运算;
宏在编译的第一个阶段, 被处理完成,运行的过程中不占用时间(宏已经不存在);
宏在预处理的时候直接展开。

2.3 带参宏的副作用

由于宏只是一个简单的文本替换,中间不涉及任何的计算以及语法检查(类型),因此在使用复杂宏的时候需要小心注意

#define   MAX(a,b)  a>b? a:b 
int x = 100 ;
int y = 200 ;
printf("%d\n" , MAX( x, y==200 ? 988:1024 ) );
// printf("%d\n" , x>y==200 ? 988:1024? x:y==200 ? 988:1024 );

从直观上来看不管 y==200 ? 988:1024 结果如何都应该比100 大 ,但是由于宏是直接的文本替换,导致替换之后三目运算符的运算结果出现了偏差。
由于带参宏的这个偏差出现的原因主要是优先级的问题,因此可添加括号来解决:

#define   MAX(a,b)  (a)>(b)? (a):(b)

printf("%d\n" , (x)>(y==200 ? 988:1024)? (x):(y==200 ? 988:1024) );

注意:
宏只能写在同一行中(逻辑行),如果迫不得已需要使用多行来写一个宏则可以使用转义字符\把换行符转义掉
在这里插入图片描述

如上 41 42 43 为3个物理行, 但是通过转义字符让这三个物理行被看作同一个逻辑行

3.条件编译

3.1 无值宏

在定义宏的时候不需要给定某一个值,对于无值来说只是用来做一个简单的判断(是否有定义)
#define DE_BUG

3.2 条件编译

根据某一个条件来决定某一代码块是否需要编译。
语法:
形式1:
通过无值的宏来判断 , 则只能判断是否有定义

#ifdef    // 判断某一个宏是否有定义

        // 代码块

#endif     // 判断语句的结束

#ifdef  DE_BUG  // 如果定义了DE_BUG 宏则一下代码块会被编译,反之则不会被编译
    printf("new:%s--%d--%s--%d\n" , 
    new->Name ,new->age ,new->skill , new->udel );
#endif
#ifdef  DE_BUG  // 判断是否定义了
    printf("__%s__%s__%d__\n", __FUNCTION__ , __FILE__ , __LINE__ );
#endif

#ifndef  DE_BUG  // 判断是否没定义 
      printf("__%s__%s__%d__\n", __FUNCTION__ , __FILE__ , __LINE__ );
#endif

形式2 :
通过有值的宏来进行判断, 则可以通过值来判断(非零则真)

#define MACRO  0  // 非零则真
#define MACRO  "Hello" // 错误的, 不允许出现字符串
#define MACRO  'A'  // 允许

#if MACRO  // 只要判断MACRO为非零值则表示条件为真
    printf("__%s__%s__%d__\n", __FUNCTION__ , __FILE__ , __LINE__ );
#endif
#if MACRO
    printf("__%s__%s__%d__\n", __FUNCTION__ , __FILE__ , __LINE__ );
#else
    printf("__%s__%s__%d__\n", __FUNCTION__ , __FILE__ , __LINE__ );
#endif


#if MACRO1
    printf("__%s__%s__%d__\n", __FUNCTION__ , __FILE__ , __LINE__ );
#elif MACRO2
    printf("__%s__%s__%d__\n", __FUNCTION__ , __FILE__ , __LINE__ );
#else
    printf("__%s__%s__%d__\n", __FUNCTION__ , __FILE__ , __LINE__ );
#endif

注意:
在使用有值宏进行条件编译的时候, 宏的值只允许出现整型/字符
多路分支可以根据自己的需求继续延续下去
使用条件编译必须有结束的语句 #endif 与开头进行对应

3.3 条件编译的实际应用场景

除了打开代码进行修改宏的值或者重新定义或删除宏的定义,还可以通过编译命令来定义宏

$ gcc ifdef.c  -DDE_BUG
-D     --> 定义宏  define
DE_BUG  --> 需要定义宏的名字为 DE_BUG

4.头文件

4.1 头文件的作用

一般来说我们C语言程序需要用到的很多的.c 文件,当某一些公共的资源需要在各个源文件中使用的时候,就可以把它写在头文件中,被其它的.c文件包含,可以避免编写同样的代码。
头文件内部放:

  1. 普通函数声明
  2. 宏定义
  3. 结构体、共用体模板定义 (声明)
  4. 枚举常量列表
  5. static 函数和 inline内函数定义
  6. 其他头文件
    在这里插入图片描述

4.2 头文件的格式

/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __LED_H
#define __LED_H
/* Includes ------------------------------------------------------------------*/
/* Exported types ------------------------------------------------------------*/
/* Exported constants --------------------------------------------------------*/
/* Exported macro ------------------------------------------------------------*/
/* Exported functions ------------------------------------------------------- */
#endif /* __LED_H */

由于项目中各个文件比较多,建议使用不同的文件夹来存放不同类型的文件:
bin : 二进制的可执行文件
inc : 用户自己写的头文件
src : 源文件
在这里插入图片描述

如何编译:

$ gcc src/*.c -o bin/Tiezhu  -I./inc

gcc          编译器
src/*.c      需要编译的源文件路径 + 文件名
-o           指定声明文件的名字  后边必须接 目标文件的路径+名字
bin/Tiezhu   目标文件的路径 + 名字
-I./inc       -I 指定头文件路径  后面必须接头文件所在的路径

标签:__,__%,头文件,文件,--,编译,编译器,printf,define
From: https://blog.csdn.net/qq_46422460/article/details/142057538

相关文章

  • AI绘画:24最新ComfyUI文生图食用指南,学不会你来找我!
    前言ComfyUI作为一款基于StableDiffusion的节点式操作界面,为用户提供了一个更加灵活和高效的文生图(文本生成图像)创作环境。本篇博客将详细介绍如何使用ComfyUI进行文生图操作,无论你是初学者还是有一定基础的用户,都能够通过本指南快速上手。书接上文,香型大家已经完成了Sta......
  • 适合初学者的[JAVA]:Redis(2:I/O多路复用模型与事件派发)
    目录说明前言I/O多路复用模型备注:用户空间和内核空间:备注:阻塞IO:(了解)非阻塞IO:(了解)IO多路复用:(重点)常见的方式有:差异:事件派发说明:Redis网络模型总结: 说明本文适合刚刚学习Java的初学者,也可以当成阿岩~的随手笔记.接下来就请道友们和我一起来......
  • MySQL数据库之存储引擎(附简历案例,客户案例)
     作者简介:我是团团儿,是一名专注于云计算领域的专业创作者,感谢大家的关注 座右铭:   云端筑梦,数据为翼,探索无限可能,引领云计算新纪元 个人主页:团儿.-CSDN博客前言:在当今的信息化时代,数据库作为信息存储与管理的核心基础设施,其性能、可靠性和可扩展性直接决定了应用的运......
  • Google 发布 DataGemma 技术减少 AI 生成错误信息 (即幻觉)
    Google推出了DataGemma,这是基于真实世界数据的两个新Gemma模型版本,旨在减少AI生成中的“幻觉”问题。DataGemma通过利用GoogleDataCommons中超过2400亿个来自可信来源的统计数据,显著提高了模型在处理数值和统计数据时的准确性。DataGemma采用了两种关键技术:检索......
  • OpenAI 推出专门用于解决复杂问题的模型 OpenAI o1
    2024年9月12日(当地时间),北京时间9月13日凌晨,OpenAI推出了OpenAIo1,这是一系列致力于解决复杂问题的新型AI模型。据说,这些模型在科学、编码和数学等领域的表现比以前的模型更好。本文将详细介绍OpenAIo1的功能、价格和使用方法。OpenAIo1是什么?OpenAIo......
  • 分享一些程序员常用的C++知识点
    以下是一些C++中的常用知识点:一、基础语法数据类型基本数据类型:整型(int):用于表示整数,通常占用4个字节(32位系统)。例如:intnum=10;浮点型(float、double):用于表示小数,float精度较低,double精度较高。如floatf=3.14f;(注意f后缀表示float类型),doubled=3.1415926......
  • CDH Hive集群的create/drop慢问题,在200s 多一点处理分析
    现象:CREATETABLEtest911(SNString,PN_CODEString);Totaltimespentinthismetastorefunctionwasgreaterthan1000ms:createTable_(Table,)=200091Hive集群的create/drop操作时间基本都稳定在200s多一点。分析:HMS会实时向Sentry同步Notifications请......
  • 【Java】Ruoyi(若依)——6.微服务版项目启动
    http://doc.ruoyi.vip/ruoyi-cloud/document/hjbs.html#%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C最早的时候,并没有打算写ruoyi框架的微服务版的安装和部署,原因如下:1.当时的项目中并没有用到微服务版。2.虽然微服务很有名,也是未来的发展趋势。但是我对微服务了解知之甚少,学起来......
  • 线性代数重难点&题目
    抽象性矩阵求特征值例题1例题2知识点1相似对角化的2个充要和充分条件知识点2&例题3抽象型二次型的正定问题知识点3实对称矩阵的相似对角化例题4相似对角化反求参数或矩阵知识点4相似对角化与相似之间的关系知识点5二次型的可能考点知识点6&例题5等价......
  • C语言学习--重难点易错点
    define易错;只是全局替换在输入数据时候,遇到以下情况时,认为该数据结束①遇空格,或按回车,或跳格键;②指定宽度结束,如%3d;③遇非法输入类型转换inti=5;floatf=i/2;df:floatf=(float)i/2;注意上面的区别C语言只有整型,实型(浮点精度值),字符型,无逻辑型——bool......