• 【C语言】程序环境和预处理


    程序环境:

    1、C语言的任何一种实现,存在两个不同的环境;

    2、翻译环境:将源代码转换成可执行的二进制指令(机器指令);.c文件(源文件——文本信息的代码)->(翻译环境)->.exe文件(可执行文件);

    2、运行环境:实现可执行文件,执行到我们想要的结果;


    翻译环境:

    1、编译:

    定义:

    依赖于编译器(vs中的编译器叫做:cl.exe),编译器只有一个;源文件通过编译器编译生成目标文件(目标文件已经是二进制的了);

    1、预编译(预处理):

    进行文本操作的处理(增、删、改),还是我们所写的C语言代码,生成.i文件;

    • 将代码中的注释替换成了空格;
    • 头文件的包含;
    • #开头的都是预处理指令,所有的预处理指令都是在预处理阶段处理的;
    2、编译:

    把C语言代码翻译成了汇编代码,生成.s文件;

    • 词法分析;语法分析;语义分析;符号汇总(专业术语);
    3、汇编:

    将汇编代码翻译成二进制指令,生成目标文件;

    • 生成符号表;

    2、链接:

    定义:

    依赖于连接器(vs中的链接器叫做:link.exe),链接库和目标文件通过连接器形成可执行文件/程序;多个目标文件可以一起链接;

    目标文件:windows环境下的目标文件后缀是:.obj;Linux环境下生成的目标文件后缀是:.o;

    链接:

    链接目标文件和链接库生成可执行程序(二进制的程序);

    • 合并段表;
    • 符号表的合并和重定位;

     运行环境:

    1、程序必须载入到内存中,一般在有操作系统的环境下完成;

    2、找到main函数开始运行;

    3、开始执行程序代码,使用一个运行时堆栈(函数栈帧空间),存储函数的局部变量和返回地址。程序还可以使用静态内存、存储在静态内存中的变量,在程序执行过程中一直保留这些静态变量的值;

    4、终止程序,结束main函数的运行;


     图示编译链接:


     书本推荐:

    程序员的自我修养


    预处理

    预定义符号:

    __FILE__ // 当前编译的文件

    __LINE__// 当前编译的行号

    __DATE__// 当前编译的日期

    __TIME__// 当前编译的时间

    __FUNCTION__// 当前编译的函数


    #define定义标识符

    1、定义的标识符只会替换,不会进行计算;

    2、#define定义的标识符在预处理阶段,这些标识符将会被替换;

    3、#dedine定义的内容可以是多种多样的;

    4、#define定义符号的最后不加分号,因为在替换的时候,如果加了分号后会把分号也替换进去;


     #define定义宏

    1、允许把参数替换到文本中;

    2、看代码会更容易理解;

    1. // #define定义宏
    2. #define a(n) (4 + 2)*(n)
    3. // 参数括号必须紧挨a,如果加了空格隔开,将会当做替换部分;
    4. // 需要将括号加上;
    5. int main()
    6. {
    7. int z = 0;
    8. z = 2 * (2 + a(4 + 1)); // a(4 + 1) -> a(5)
    9. // 2 * (2 + 6 * 5= 64
    10. return 0;
    11. }

     #define替换规则

    1、在调用宏时,先对参数进行检查,包含任何由#define定义的符号则首先被替换。

    2、将宏定义的替换文本插入到原来文本的位置;

    3、宏参数和#define定义中可以出现其他#define定义的变量;

    4、宏不能出现递归;


    # 和 ## 的作用

    # 和 ## 都是在宏内实现的;

    1、#把一个宏参数转化为对应的字符串;

    2、##可以将位于##两端的符号合成一个符号;

    #代码:

    1. #include <stdio.h>
    2. #define PRINT_1(value, format) printf("相加的值为:"format"\n", value)
    3. #define PRINT_2(value, format) printf(""#value"相加的值为:"format"\n", value)
    4. // #可以将宏的参数转化为对应的字符串;
    5. int main()
    6. {
    7. int a = 10;
    8. int b = 20;
    9. PRINT_1(a + b, "%d");
    10. PRINT_2(a + b, "%d");
    11. return 0;
    12. }

     带副作用的宏参数

    • 比如就是将a++当做参数,因为a++是先使用后加加,这就是带有副作用的宏参数;

    宏和函数的对比

    宏和函数的对比:

    函数
    用于执行简单运算宏的效率高(仅是完成替换)用于执行简单运算,函数相对宏的效率低(涉及函数调用、参数传递、栈帧的创建、计算、函数的返回)
    宏的参数类型可以是任意类型函数有固定的参数类型
    宏定义很长,插入程序中就会增加程序长度函数很长并不会增加程序长度,只需要调用函数即可
    不方便调试不可递归函数便于调试可以递归
    宏的参数是替换,会有运算符优先级问题,所以要多加括号函数参数是调用的时候计算的

     总结:逻辑比较简单的使用宏,计算逻辑比较复杂的使用函数;

    命令行定义:

    • 允许在命令行编译的时候指定大小;

    条件编译:

    1、满足条件则编译,否则就不编译(等同于if、else if、else)

    2、#if、#endif

    #if 常量表达式 // 如果满足常量表达式,则执行下面编译

    // 编译内容

    #endif // 结束条件编译

    3、#if、#elif、#else、#endif

    #if 常量表达式 // 满足常量表达式,执行以下内容

    #elif 常量表达式 // 满足常量表达式,执行以下内容

    #else // 上面#elif常量表达式不满足,就执行这句以下的

    #endif // 结束条件编译

    4、判断是否被定义过

    1、#if defined、#endif

    #if defined (宏名)

    // 内容 // 如果执行则定义了此宏

    #endif // 结束判断

    2、#ifdef、#endif

    #ifdef 宏名

    // 内容 // 执行则定义了此宏

    #endif // 结束判断

    3、#if !defined、#endif

    #if !defined (宏名)

    // 内容  // 如果执行,则表明未定义此宏

    #endif // 结束判断

    4、#ifndef、#endif

    #ifndef 宏名

    // 内容 // 如果执行,则表明未定义此宏

    #endif // 结束判断


    头文件的包含方式

    头文件的包含方式有两种,一种是本地文件的包含,另一种是库文件的包含;

    1、本地文件就是自己创建的.h文件,使用的是双引号引起来;

    2、库文件就是标准库中的、第三方的,使用尖括号进行括起来;

    3、使用双引号,编译器找头文件,是先找源文件目录,未找到的则去库函数的头文件里找,再没找到就报错;

    4、使用尖括号,编译器找头文件,查找头文件去标准路径下查找,未找到就报错;


    嵌套文件包含 

    • #pragam once // 防止头文件重复引入
    • ifndef/define/endif // 也是防止头文件的重复引入
  • 相关阅读:
    git -1
    网络安全是否有需求
    将json数据导入到ES集群——解决方案对比&填坑日记
    [CC2642R1][VSCODE+Embedded IDE+IAR Build+Cortex-Debug] TI CC2642R1基于VsCode的开发环境
    linux 命令curl`详解
    21天打卡挑战学习MySQL—Day
    CSS属性:定位属性+案例讲解:博雅互动 前端开发入门笔记(五)
    从InterruptedException深入理解AtomicReference的方方面面
    浅谈LockBit勒索病毒
    【QT】capture.obj:-1: error: LNK2019: 无法解析的外部符号 __imp_htons(解决方法)
  • 原文地址:https://blog.csdn.net/2201_75406088/article/details/133821804