c语言学习第三十五课——程序环境与预处理

简介: c语言学习第三十五课——程序环境与预处理

程序的翻译环境与执行环境

在ANSIC的任何一种实现中,存在不同的两个环境:

第一种环境是翻译环境:在这个环境中源代码被转换为可执行的机器指令。

第二中是执行环境:用于执行实际的代码。

1f0f8f2554474055b707f8f8e0fc7dab.png

我们用图来认识到一个项目是如何编译运行的:

源文件单独通过编译器生成一个目标文件(.obj文件),在利用链接器将众多的可执行程序链接起来


542af4cdafdd4e26a0d6ed6a09db5030.png每一个源文件单独经过编译器编译生成对应的目标文件,我们可在源文件下找到这些目标文件。

在vs编译器上:


9eab1a269cab4ecc96bbc0956e57a5b5.png

编译器是cl.exe文件,链接器是link.exe文件 。

现在详解一下编译环境下的操作:

f90706aae0c4479ea77c82ba9166a6de.png

在进入编译时的

编译分三个阶段

预编译   编译  汇编

gcc test.c   预处理中1.头文件的包含 2.注释的删除及define定义的符号的替换/gcc.test.i -S编译过程中会生成一个.s文件,把c语言代码翻译成汇编代码(通过语法分析,词法分析,语义分析,符号汇总等)参考《编译原理》

汇编  gcc.test -C 该过程会生成一个test.o的文件,在gcc中就是目标文件-一个二进制文件,所以会变就是把汇编代码转换为二进制指令,形成符号表。

预处理阶段会做三个事:                  编译做的事:                    汇编做的事:

这里的编译,感兴趣可以去看《程序员的自我修养》,里面会详细讲到如何翻译成汇编代码。

这里的汇编,会形成符号表。

之后在进行链接:                  

链接  链接所有的.o的可执行文件,包括库形成可执行程序  1.合并段表 2.符号表的合并与重定位

符号(一般是全局变量的与函数的名称)。将这些符号汇总到一起,形成符号表。

因为文本文件的格式是elf格式

合并段表,把对应的段的数据合并到一起

在形成符号表时分模块,static修饰外部链接属性为内部连接属性,即他们符号表合并到一起。

与链接库链接生成可执行文件。

整个过程可理解为就是将高级语言转化位低级语言给计算机识别:

bafaa2e2bbe9481f8026145219cf2061.png

在程序执行的过程中:

1.程序必须载入内存,再有操作系统的环境中:一般由这个操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。

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

3开始执行程序代码时,这个时候程序将使用一个运行堆栈,存储函数的局部变量和返回值地址,程序同时也可以使用静态内存,存储在静态内存中的变量在整个程序中保存着他们的值。

4.终止程序,正常种植main函数,亦有可能是意外终止。

预处理详解

1.预定义符号

__LINE__  //文件当前行号

__FUNCTION__//

__FILE__          //进行的源文件

__DATA__        //文件被编译的日期

 __FUNCDNAME__//

__TIME__             //文件被编译的时间

等都是可以展示该文件的属性的指令

#include<stdio.h>
int main()
{
    int i;
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    for (i = 0; i < 10; i++)
    {
        printf("%d------%s,%s %s line=%d\n", arr[i], __FILE__, __DATE__, __TIME__, __LINE__);
    }
    return 0;
}

59e401bbad75464698985330b810ae4f.png

#define

定义一个标识符 完成替换.

这里   \为续行符  在代码后 \之后可以换行,表示还是在一行上。

//在有些计算机语言没有break语句,但在c语言里是必须要有break语句来结束case语句。
//有人就会定义宏来实现不去写break语句
#define CASE  break;case
int main()
{
   int n=0;
   scanf("%d",&n);
   switch(n)
 {
   case 1:
        //....
   CASE 2:
        //.....
   CASE 3:
        //.....
     break;
 }
   return 0;
}

#define 定义宏 替换参数

实现一个数的平方

#define POW(x) x*x
int main()
{
printf("%d", POW(8));
return 0;
}

预处理的操作符#

define print_format(num,format)    printf("the value of "#num" is "format,num)

#的作用是不是替换内容,而是变成对应的字符串

int main()
{
  int a = 10;
  printf("the value of a  is %d", a);
  int b = 20;
  printf("the value of b  is %d", b);
  //我们可以这样写
  print_format(a, "%d\n");
}

预处理的操作符##

可以把位于它两边的符号合成一个符号

int Class110 = 2023;
#define CAT(x,y)  x##y
int main()
{
  printf("%d\n", CAT(Class, 110));//2023  前提是合成的符号具有意义
  return 0;
}

带副作用的宏参数

#define MAX(x,y)    ((x)>(y)?(x):(y))
int main()
{
  //这里想求最大值
  int a = 3;
  int b = 5;
  int c = MAX(3, 5);
  //int c=((3++)>(5++)?(3++):(5++))
  //这里反而不能实现最大值的求解
  printf("%d\n", c);//6
  printf("%d\n", a);//4
  printf("%d\n", b);//7
  return 0;
}

我们从中可以发现。在定义宏参与运算时,我们最好加上括号,防止因为运算优先级而出现问题,因为宏的作用仅仅是替换。

宏和函数对比

1.调用函数的从函数返回的代码比实际执行这个计算需要的时间多。考虑函数栈帧,调用前到运行结束都是需要花费时间的而宏只有主要运算,时间更短,效率更高。

2.宏无类型,函数传参还需定义类型宏的替换可以是类型

如 #define MALLOC(num,type)  (type*)malloc(num,sizoef(type))

3.但是宏只能实现简单的效果,若一个程序实现复杂,宏需多次定义

4.宏无法调试,与类型无关,当然也不够严谨

5.宏会带来运算符优先级的问题

命名约定

宏名都是大写

函数一般部分字母大写

#undef  

取消符号的宏定义

#define MAX 100
#undef MAX;
int main()
{
  int a = 0;
  int b = a + MAX;
  printf("%d", b);
  return 0;
}

这里的MAX未被定义

条件编译

#if   #endif   #if define  #if !define

满足条件可以进入编译 不满足不进入编译
#if (条件)//条件一般为常量表达式
.....
#endif
//多分支
#if  (条件)
....
#elif(条件)
.........
#endif
//判断是否被定义
#if defined (定义的变量)
......
#endif
//如果定义了MAX,语句参与编译
//如果没定义,语句参与编译
#if !defined(定义的变量)
...
#endif
#ifndef MAX
....
#endif

头文件包含

头文件分两种:

库文件#include<stdio.h>  

当前文件#include"file name"

对于两者的区别:

" "先在文件路径查找之后才在库文件查找。

<>包含的头文件会直接去库文件里调用。

我们一般不去将库函数的头文件用“”来调用,这样反而效率会变低。

若头文件被重复包含,里面的内容也被重复的调用进来

可以定义#pragma once  #define __TEST.H__,(头文件)这样重复包含也只调用一次

其他还有许多预处理指令,详见高质量c++/c编程


相关文章
|
2月前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
78 5
|
2月前
|
C语言
C语言编程中,错误处理至关重要,能提升程序的健壮性和可靠性
C语言编程中,错误处理至关重要,能提升程序的健壮性和可靠性。本文探讨了C语言中的错误类型(如语法错误、运行时错误)、基本处理方法(如返回值、全局变量、自定义异常处理)、常见策略(如检查返回值、设置标志位、记录错误信息)及错误处理函数(如perror、strerror)。强调了不忽略错误、保持处理一致性及避免过度处理的重要性,并通过文件操作和网络编程实例展示了错误处理的应用。
79 4
|
2月前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
72 1
|
2月前
|
网络协议 物联网 数据处理
C语言在网络通信程序实现中的应用,介绍了网络通信的基本概念、C语言的特点及其在网络通信中的优势
本文探讨了C语言在网络通信程序实现中的应用,介绍了网络通信的基本概念、C语言的特点及其在网络通信中的优势。文章详细讲解了使用C语言实现网络通信程序的基本步骤,包括TCP和UDP通信程序的实现,并讨论了关键技术、优化方法及未来发展趋势,旨在帮助读者掌握C语言在网络通信中的应用技巧。
58 2
|
2月前
|
程序员 C语言
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门。本文深入探讨了指针的基本概念、声明方式、动态内存分配、函数参数传递、指针运算及与数组和函数的关系,强调了正确使用指针的重要性,并鼓励读者通过实践掌握这一关键技能。
47 1
|
2天前
|
算法 C语言
【C语言程序设计——函数】利用函数求解最大公约数和最小公倍数(头歌实践教学平台习题)【合集】
本文档介绍了如何编写两个子函数,分别求任意两个整数的最大公约数和最小公倍数。内容涵盖循环控制与跳转语句的使用、最大公约数的求法(包括辗转相除法和更相减损术),以及基于最大公约数求最小公倍数的方法。通过示例代码和测试说明,帮助读者理解和实现相关算法。最终提供了完整的通关代码及测试结果,确保编程任务的成功完成。
26 15
|
2天前
|
C语言
【C语言程序设计——函数】亲密数判定(头歌实践教学平台习题)【合集】
本文介绍了通过编程实现打印3000以内的全部亲密数的任务。主要内容包括: 1. **任务描述**:实现函数打印3000以内的全部亲密数。 2. **相关知识**: - 循环控制和跳转语句(for、while循环,break、continue语句)的使用。 - 亲密数的概念及历史背景。 - 判断亲密数的方法:计算数A的因子和存于B,再计算B的因子和存于sum,最后比较sum与A是否相等。 3. **编程要求**:根据提示在指定区域内补充代码。 4. **测试说明**:平台对代码进行测试,预期输出如220和284是一组亲密数。 5. **通关代码**:提供了完整的C语言代码实现
39 24
|
2天前
|
存储 算法 C语言
【C语言程序设计——函数】素数判定(头歌实践教学平台习题)【合集】
本内容介绍了编写一个判断素数的子函数的任务,涵盖循环控制与跳转语句、算术运算符(%)、以及素数的概念。任务要求在主函数中输入整数并输出是否为素数的信息。相关知识包括 `for` 和 `while` 循环、`break` 和 `continue` 语句、取余运算符 `%` 的使用及素数定义、分布规律和应用场景。编程要求根据提示补充代码,测试说明提供了输入输出示例,最后给出通关代码和测试结果。 任务核心:编写判断素数的子函数并在主函数中调用,涉及循环结构和条件判断。
39 23
|
2天前
|
存储 编译器 C语言
【C语言程序设计——函数】回文数判定(头歌实践教学平台习题)【合集】
算术运算于 C 语言仿若精密 “齿轮组”,驱动着数值处理流程。编写函数求区间[100,500]中所有的回文数,要求每行打印10个数。根据提示在右侧编辑器Begin--End之间的区域内补充必要的代码。如果操作数是浮点数,在 C 语言中是不允许直接进行。的结果是 -1,因为 -7 除以 3 商为 -2,余数为 -1;注意:每一个数据输出格式为 printf("%4d", i);的结果是 1,因为 7 除以 -3 商为 -2,余数为 1。取余运算要求两个操作数必须是整数类型,包括。开始你的任务吧,祝你成功!
15 1
|
1月前
|
存储 C语言 开发者
【C语言】字符串操作函数详解
这些字符串操作函数在C语言中提供了强大的功能,帮助开发者有效地处理字符串数据。通过对每个函数的详细讲解、示例代码和表格说明,可以更好地理解如何使用这些函数进行各种字符串操作。如果在实际编程中遇到特定的字符串处理需求,可以参考这些函数和示例,灵活运用。
75 10