C语言程序的编译上

作者:旧梦拾遗186

专题:C语言编程----小比特成长日记

 

每日励志:

人的潜能是无限的,努力一把,才知自己真正的实力。因为树的方向,风决定。人的方向,自己决定。

前言:

今天小编带大家一起来认识c语言的底层逻辑编译及链接

目录

1. 程序的翻译环境和执行环境

2. 详解编译+链接 

2.1 翻译环境

2.2 编译本身也分为几个阶段: 

2.3 运行环境  

3. 预处理详解 

3.1 预定义符号

3.2 #define

3.2.1 #define 定义标识符


1. 程序的翻译环境和执行环境

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

2. 详解编译+链接 

当我们写好代码,并进行变异和链接时,会弹出一个黑框框,在我们的项目目录下会生成 .exe 可执行文件,我们现在要了解的就是这个东西是怎么形成的。

 

2.1 翻译环境

我们大体上可以知道是,可执行文件是由编译、链接这两个部分组合产生。用文字阐述可以说:源文件(可以是多个)经过编译器编译产生目标文件,这些目标文件被送进链接库进行链接,从而产生可执行程序。

 

 

组成一个程序的每个源文件通过编译过程分别转换成目标代码( object code )。
每个目标文件由链接器( linker )捆绑在一起,形成一个单一而完整的可执行程序。
链接器同时也会引入标准 C 函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人 的程序库,将其需要的函数也链接到程序中

 

2.2 编译本身也分为几个阶段: 

 我们需要注意一个点,编译器的编译过程不止编译这一个环节。 也就是说,编译本身也可以分为几个阶段。

因为 Visual Studio 这些编译器是一个集成开发环境,所以不方便展示各个部分的详细内容。但是在以后学习 Linux 的过程中,我们回过头来是一定可以观察的。这里我就用文字阐述。

1.在预编译阶段,编译器会将头文件的内容、define 定义的文本替换都写入 .i 文件当中,并且删除注释。也就是说,预编译产生的 .i 文件里面写的 C 语言代码,是包括头文件的所有 C 语言代码。
2.编译阶段,从这个阶段开始,就要逐渐让计算机硬件能够识别我们的代码。所以这个阶段就会把 C 语言代码翻译成汇编语言代码。比如说我们有 int max = 10; ,那么编译阶段就会把 int 看成类型生成对应的汇编指令,max 是变量名,生成对应的汇编语句, = 是赋值符号,同样也会生成汇编语句,等等……这些汇编代码存放在 .s 文件中。
3.汇编,这里就是生成目标文件的步骤。这个阶段会对汇编代码进行更加细化的拆分,生成最原始的机器指令,即二进制指令。这些指令会被放在 .obj 文件当中(Linux 环境下为 .o 文件),在 Linux 环境下可观察这些二进制指令,但我们的大脑会认为这些是乱码。
 

2.3 运行环境  

程序执行的过程:
1. 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序
的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
2. 程序的执行便开始。接着便调用 main 函数。
3. 开始执行程序代码。这个时候程序将使用一个运行时堆栈(
stack ),存储函数的局部变量和返回 地址。程序同时也可以使用静态(static )内存,存储于静态内存中的变量在程序的整个执行过程 一直保留他们的值。
4. 终止程序。正常终止 main 函数;也有可能是意外终止。

3. 预处理详解 

3.1 预定义符号

__FILE__      //进行编译的源文件
__LINE__     //文件当前的行号
__DATE__    //文件被编译的日期
__TIME__    //文件被编译的时间
__STDC__    //如果编译器遵循ANSI C,其值为1,否则未定义
这些预定义符号都是语言内置的。

 举个栗子:

#include<stdio.h>
int main()
{
	FILE* p;
	p = fopen("test.txt", "w");
	if (p == NULL)
	{
		perror("open");
	}
	//格式化输出
	fprintf(p, "file:%s line:%d date:%s time:%s\n", __FILE__, __LINE__, __DATE__,__TIME__);
	fclose(p);
	p = NULL;

	return 0;
}

 

3.2 #define

3.2.1 #define 定义标识符



语法:
#define name stuff

 

#define MAX 1000
#define reg register          //为 register这个关键字,创建一个简短的名字
#define do_forever for(;;)     //用更形象的符号来替换一种实现
#define CASE break;case        //在写case语句的时候自动把 break写上。
// 如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
                          date:%s\ttime:%s\n" ,\
                          __FILE__,__LINE__ ,       \
                          __DATE__,__TIME__ )  

提问:

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

最好不要加;

比如:

#include<stdio.h>
#define MAX 100;
int main()
{
	int n = 0;
	scanf("%d", &n);
	if (n > MAX)
	{
		n = MAX;
	}
	return 0;
}

 需要注意的是,在我们定义标识符时,尽量不要在后面添加分号,因为在我们书写代码时会习惯自己加上分号,如果定义的标识符后有分号,可能会产生不易察觉的错误。

 

 当然我们也可以使用这个功能来实现一些令人难以捉摸的骚操作,假设有一个程序员每次使用 swich 语句内的 case ,后面都要写 break,他觉得很麻烦,那么他便写出了这么一段代码:

#define CASE break;case

#include <stdio.h>

int main()
{
	int n = 3;
	switch (n)
	{
	case 1:
		CASE 2 :
			CASE 3 :
			printf("hello world!\n");
		CASE 4 :
		break;
	}
	return 0;
}

 

 

  • 27
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 43
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 43
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

旧梦拾遗186

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值