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

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

详解编译+链接

翻译环境

第六十一天学习记录:C语言进阶:C语言预处理1-LMLPHP

![在这里插入图片描述](https://img-blog.csdnimg.cn/04bd03e2cb554aa298fb6a8349722f89.png
第六十一天学习记录:C语言进阶:C语言预处理1-LMLPHP

上图截取自比特科技免费课程

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

Linux环境方便观察编译过程:
1、预处理 选项gcc -E test.c -o test.i 预处理完成之后就停下来,预处理之后产生的结果都放在test.i文件中。
2、编译 选项gcc -S test.c编译完成之后就停下来,结果保存在test.s中。
3、汇编 gcc -c test.c汇编完成之后就停下来,结果保存在test.o中。

比特科技推荐网站:
VIM学习资料
简明VIM练级攻略:
https://coolshell.cn/articles/5426.html
给程序员的VIM速查卡
https://coolshell.cn/articles/5479.html

运行环境

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

问:在编译过程中实现的内存堆栈和通过exe运行程序时进行的内存堆栈是不是一样的?
ChatAI答:
在编译过程中实现的内存堆栈和通过exe运行程序时进行的内存堆栈是不一样的。

在编译过程中,内存堆栈是由编译器实现的,用于在程序执行时为函数、变量等分配内存空间和管理内存使用情况。编译器会根据程序的代码框架和变量的作用域等因素,对内存空间进行分配和释放,并且对程序和函数的调用栈进行跟踪和管理。

在执行程序时,内存堆栈也存在,但实现方式是由操作系统和运行时环境决定的。当程序运行时,操作系统会为程序分配一定的内存空间,这些内存空间被划分为不同的存储区域,包括进程虚拟地址空间、栈、堆、全局/静态数据区等。程序使用内存空间时,根据内存空间的访问类型和权限进行对应的操作。

因此,编译器实现的内存堆栈和运行时环境实现的内存堆栈,并不是完全相同的。它们的实现方式、管理机制、内存分配和释放方式等都可能存在差异。这也是为什么在一些情况下,程序在编译时可以通过编译,但在执行时却会出现运行时错误的原因之一。

《程序员的自我修养》

预处理详解

预定义符号

__FILE__				//进行编译的源文件
__LINE__				//文件当前的行号
__DATE__				//文件被编译的日期
__TIME__				//文件被编译的时间
__STDC__				//如果编译器遵循ANSI C,其值为1,否则未定义(PS:VS2015无效)
__FUNCTION__     //打印函数名

这些预定义符号都是语言内置的。

#define _CRT_SECURE_NO_WARNINGS 1

#include <stdio.h>

int main()
{
	printf("file:%s line:%d\n",__FILE__,__LINE__);
	printf("date:%s time:%s\n",__DATE__,__TIME__);
	return 0;
}

第六十一天学习记录:C语言进阶:C语言预处理1-LMLPHP
用于写日志文件:

#define _CRT_SECURE_NO_WARNINGS 1

#include <stdio.h>

int main()
{
	//写日志文件
	int i = 0;
	int arr[10] = { 0 };
	FILE* pf = fopen("log.txt", "w");
	for (i = 0; i < 10; i++)
	{
		arr[i] = i;
		fprintf(pf, "file:%s line:%d date:%s time:%s i=%d\n",
			__FILE__, __LINE__, __DATE__, __TIME__,i);
	}
	fclose(pf);
	pf = NULL;
	for (i = 0; i < 10; ++i)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

第六十一天学习记录:C语言进阶:C语言预处理1-LMLPHP

预处理指令:

#define
#include
#pragma
#if
#endif
#ifdef
#line

#define

#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定义宏

#defne 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)。
下面是宏的声明方式:

#define name( parament-list ) stuff其中的parament-list是一个由逗号隔开的符号表,它们可能出现在stuff中。

注意:参数列表的左括号必须与name紧邻。如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。
如:

#define SQUARE( x ) x*x

这个宏接收一个参数x,如果在上述声明之后,你把

SQUARE( 5 );

置于程序中,预处理器就会用下面这个表达式替换上面的表达式:

5*5
#include <stdio.h>
#define SQUARE(X) X*X

int main()
{
	int ret = SQUARE(5);
	printf("%d\n",ret);
	return 0;
}

输出:25

#include <stdio.h>
#define SQUARE(X) X*X

int main()
{
	int ret = SQUARE(5+1);
	printf("%d\n",ret);
	return 0;
}

输出:11
注意:宏是用来替换的而不是传参的。上面被替换成了:

5+1*5+1   =   11

建议优化写法:

#define SQUARE(X) ((X)*(X))

define替换规则

在程序中扩展#define定义符号和宏时,需要涉及几个步骤。
1、在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
2、替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值替换。
3、最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
注意:
1、宏参数和#define定义中可以出现其他#define定义的变量。但是对于宏,不能出现递归。
2、当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。

#和##

如何把参数插入到字符串中?

#include <stdio.h>
#define PRINT(X) printf("the value of "#X" is %d\n",X)

int main()
{
	int a = 10;
	int b = 20;
	PRINT(a);
	PRINT(b);
	return 0;
}

第六十一天学习记录:C语言进阶:C语言预处理1-LMLPHP
另一个技巧是:使用#,把一个宏参数变成对应的字符串。比如:

#include <stdio.h>
#define PRINT(FORMAT,VALUE)\
	printf("the value of "#VALUE" is "FORMAT"\n",VALUE)

int main()
{
	int a = 10;
	int b = 20;
	PRINT("%d",a+8);
	PRINT("%d",b+3);
	return 0;
}

第六十一天学习记录:C语言进阶:C语言预处理1-LMLPHP

##的作用

##可以把位于它两边的符号合成一个符号。它允许宏定义从分离的文本片段创建标识符。


#include <stdio.h>

#define CAT(X,Y) X##Y

int main()
{
	int chung1991 = 2023;
	printf("%d\n", CAT(chung, 1991));
	return 0;
}

输出:2023
注:这样的连接必须产生一个合法的标识符。否则其结果就是未定义的。

06-02 08:53