C语言-程序的编译与链接

C语言-程序的编译与链接,第1张

C语言-程序编译与链接

程序的编译与链接详解
  • 1. 程序环境和预处理
    • 1.1 程序的编译环境和执行环境
  • 2. 详解编译+链接
    • 2.1 编译环境
    • 2.3 运行环境
  • 3.预处理详解
    • 3.1 预定义符号
    • 3.2 #define
      • #define宏定义
      • #define的替换规则
      • 带副作用的宏参数
      • 宏和函数的对比
    • 3.3#undef
    • 3.5条件编译

1. 程序环境和预处理 1.1 程序的编译环境和执行环境

在ANSI C的任何一种实现中,存在两个不同的环境。
第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。
第2种是执行环境,它用于实际执行代码

2. 详解编译+链接

下面我们将分别对编译环境以及执行环境来分别进行讲解

2.1 编译环境

编译环境:
在编写c语言中的文件时,我们有时候可能会思考一个源文件是如何从一个xx.c文件变成一个可执行的exe程序的,接下来我和大家一起来解决一下:

从上面的图中我们可以知道,编译(Compile)会将源文件(.c文件)转换为目标文件。对于 VC/VS,目标文件后缀为.obj;对于GCC,目标文件后缀为.o。(组成一个程序的每个源文件通过编译过程分别转换成目标代码(object code)。每个目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序。链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人的程序库,将其需要的函数也链接到程序中)

2.3 运行环境

程序执行的过程:

  1. 程序必须载入内存中。在有 *** 作系统的环境中:一般这个由 *** 作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
  2. 程序的执行便开始。接着便调用main函数。
  3. 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
  4. 终止程序。正常终止main函数;也有可能是意外终止。
3.预处理详解 3.1 预定义符号
__FILE__      //进行编译的源文件
__LINE__     //文件当前的行号
__DATE__    //文件被编译的日期
__TIME__    //文件被编译的时间
__STDC__    //如果编译器遵循ANSI C,其值为1,否则未定义

这些预定义符号都是在C语言中内置的

3.2 #define #define宏定义

宏定义的一般形式为:

#define  宏名  字符串

#define 叫做宏定义命令,它也是C语言预处理命令的一种。所谓宏定义,就是用一个标识符来表示一个字符串,如果在后面的代码中出现了该标识符,那么就全部替换成指定的字符串。
举一个简单的例子:

#include 

#define N 100

int main(){
    int sum = 30 + N;
    printf("%dn", sum);
    return 0;
}

#define N 100就是宏定义,N为宏名,100是宏的内容(宏所表示的字符串)。在预处理阶段,对程序中所有出现的“宏名”,预处理器都会用宏定义中的字符串去代换,这称为“宏替换”或“宏展开”。

对 #define 用法的几点说明

  1. 宏定义是用宏名来表示一个字符串,在宏展开时又以该字符串取代宏名,这只是一种简单粗暴的替换。字符串中可以含任何字符,它可以是常数、表达式、if 语句、函数等,预处理程序对它不作任何检查,如有错误,只能在编译已被宏展开后的源程序时发现。

  2. 宏定义不是说明或语句,在行末不必加分号,如加上分号则连分号也一起替换。

  3. 宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结束。如要终止其作用域可使用#undef命令

#define的替换规则

在程序中扩展#define定义符号和宏时,需要涉及几个步骤。

  1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
  2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值替换。
  3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。

注意:

  1. 宏参数和#define 定义中可以出现其他#define定义的变量。但是对于宏,不能出现递归。
  2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
带副作用的宏参数

当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。
例如:

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

我们来看一下这么一个代码:

#define MAX(a, b) ( (a) > (b) ? (a) : (b) )
...
x = 5; y = 8; z = MAX(x++, y++);
printf("x=%d y=%d z=%dn", x, y, z);//输出的结果是什么?

这里我们得知道预处理器处理之后的结果是什么:

z = ( (x++) > (y++) ? (x++) : (y++));

所以输出的结果是:

x=6 y=10 z=9
宏和函数的对比

借用一下大佬做的图,和我想表达的一样哈哈哈- -

3.3#undef

这条指令用于移除一个宏定义。

#undef NAME
//如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除。
3.5条件编译

在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件编译指令。
比如说:调试性的代码,删除可惜,保留又碍事,所以我们可以选择性的编译。

#include 
#define __DEBUG__
int main()
{
 int i = 0;
 int arr[10] = {0};
 for(i=0; i<10; i++)
 {
 arr[i] = i;
 #ifdef __DEBUG__
 printf("%dn", arr[i]);//为了观察数组是否赋值成功。 
 #endif //__DEBUG__
 }
 return 0; }

常见的条件编译指令:

1.
#if 常量表达式
 //...
#endif
//常量表达式由预处理器求值。
如:
#define __DEBUG__ 1
#if __DEBUG__
 //..
#endif
2.多个分支的条件编译
#if 常量表达式
 //...
#elif 常量表达式
 //...
#else
 //...
#endif
3.判断是否被定义
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol
4.嵌套指令
#if defined(OS_UNIX)
 #ifdef OPTION1
 unix_version_option1();
 #endif
 #ifdef OPTION2
 unix_version_option2();
 #endif
#elif defined(OS_MSDOS)
 #ifdef OPTION2
 msdos_version_option2();
 #endif
#endif

今天就写到这儿了,还有头文件的包含和嵌套文件的包含以及命令行的定义没有谈及到,以后有空一定把后面的补上,(记一个小标记把),最近有点被Java虐了,可能是换了一个老师有点不适应吧,突然就觉得Java要比C语言难很多,我反而挺喜欢C的指针和C映射到内存的感觉,但是Java学起来很没有理清头绪,感觉很乱,让我有点手慢脚乱的感觉,但是不管怎样归根结底都是自己的原因,既然选择了就一定要下定决心沉下心来,奥里给!!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/4968510.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-11-13
下一篇 2022-11-13

发表评论

登录后才能评论

评论列表(0条)

保存