预处理(2)

刚才讲了text.c预编译成了text.i,然后编译成立text.o,然后汇编编译成立.o文件

链接有两个步骤

1.合并段表

2.符号表的合并和重定位

我们汇编生成add.o文件和test.o文件(都是二进制格式)

我们刚才用的viscode是Linux格式

一种文件都有自己的特殊格式

前方纺放啥,后面放啥都有自己的格式

上面汇编生成的文件,是Linux elf格式

他会把add,o文件的数据分成几种段

每一个段放他对应的属性的数据

我们接下来要把这几个文件

add.o

test.o

链接库

进行整体的链接,然后输出一个可执行的程序

把这些文件最终生成一个可执行的程序

这个可执行程序是Linux环境下的可执行程序

也是elf格式,最终要把elf格式合并按照不同的段落放相关类型的数据

最终合并成一个·文件,形成可执行程序,未来大家就可以用了

所谓合并段表,就是把他的相关格式的文件进行相关的合并

除了合并段表还有符号表的合并和重定位

符号表的合并和重定位其实就取决了下面这个东西

我们把add和test合并成一个符号表

add.o和test.o都有自己的符号表

在我们的符号表中只有一个符号表所以要把他们合并

这个地址是没有意义的,因为我只是申明,有没有的话我不知道,地址在哪我也不知道

合并的话先把相同的有意思的先合并

我们把两个合并了然后得到了下面右边的符号表

我们那两个相同名字的符号是进行筛选的最终选了Add

所以这就叫做符号表的合并和重定位

你问我上面的东西有什么用?

我们在链接期间能不能用这个Add的函数完全取决于add的地址是不是有效的

当我们的这个地址是有效的时候,我们的可执行程序就会用到这个符号表去查找这个函数

如果这个地址是正就能找到这个函数

假设没有这个函数就没有这个符号表

我们得到的地址就是错误的,最终的符号表也是错误的,当我们从错误的地址是找不到这个函数的

这个时候就会出现链接性的错误

这时候就会出现这样的报错,链接的时候会出现LINK的链接性的错误

因为我没有那个函数的地址,所以最后形成是一个错误的地址,所以就找不到那个函数,就会出现链接性的错误

如果你天真的问,有两个函数声明可不可以

那么百分百不可以的,他练链接都走不到,编译的时候就会报错了

接下来我们讲运行程序

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

2.程序的执行便开始,接着使用调用main函数

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

4.终止程序,正常终止main函数,也有可能是意外终止

上面讲了大概的流程

现在我们仔细讲一讲预处理

预定义符号

我们先用的是___FILE___这个可以找到你打印文件的地方,就是.exe的文件所在处

___LINE__这个可以打印你代码所在的行号

__DATE__可以打印你年和月份,__TIME__可以打印时间

实操

#include<stdio.h>
int main()
{
	int i = 0;
	for (int i = 0; i < 10; i++)
	{
		printf("file %s line %d date %s time %s  i %d\n", __FILE__, __LINE__, __DATE__, __TIME__, i);
	}
	return 0;
}

你问我上面哪些预定义符号有什么用,先听我讲讲文件

FILE

缓冲文件系统中,关键的概念是“文件指针”。每个被使用的文件都在内存中开辟一块空间,用来存放文件的有关信息,文件的名字信息什么的。这些是保存在一个结构体变量中的。该结构体类型是由系统定义的,取名为FILE。

每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,然后填充其中的信息。一般都是通过一个FILE的指针来维护这个FILE结构的变量,这样使用起来更加方便。

定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。通过文件指针变量j就能够找到与它关联的文件

文件的打开与关闭

在使用完一个文件后应该关闭它,以防止它再被误用。“关闭”就是使文件指针变量不指向该文件,以后不能再通过该指针对原来与其相联系的文件进行读写操作。除非再次打开,使该指针变量重新指向该文件。(反正记住要关闭文件就行了)

fopen(需要打开文件的文件名,打开文件的方式)

fopen(_In_z_ charconst* _FileName,_In_z_ charconst* _Mode);

fclose函数

fclose(_Inout_ FILE* _Stream);

perror() 是 C 语言标准库 <stdio.h> 中提供的函数,用于打印与最近的错误代码相关的错误消息。

voidperror(constchar*s);r

这个可以在未来用于写个日志,现在我演示写到文件

实操

#include<stdio.h>

#include <cstdlib>//EXIT_SUCCESS;的头文件
int main()
{
	int i = 0;
	
	FILE* pf = fopen("log.txt","w");//w表示我们的要对他进行写的操作
	if (pf == NULL)
	{
		perror("fopen");
		return  EXIT_FAILURE;//失败返回1
		//return  EXIT_SUCCESS;//成功返回0
	}
	for (int i = 0; i < 10; i++)
	{
		fprintf(pf,"file %s line %d date %s time %s  i %d\n", __FILE__, __LINE__, __DATE__, __TIME__, i);
	}
	fclose(pf);
	pf = NULL;
	return 0;
}

但是你在最终编译的时候你的fopen会报错,就是下面那个错误

错误原因:没有包含头文件所在目录

现在我给你讲一下怎么解决这个报错

右击project

点击属性

点到我这个步骤

再点击这个

加入这两行东西就可以啦

_CRT_SECURE_NO_DEPRECATE

_CRT_SECURE_NO_WARNINGS

这样就可以了,已经打印进去了

__STDC__这个预定义符

作用是检测你有没有遵循 ANSI C的标准就返回1,没有就返回0

在我们vs的编译器是没有遵循的,在viscode的linux系统是遵循ANSI C的标准的

如果当你在vs和gcc那里都写了一样的代码但是不同的结果,你就要以gcc那边的为标准

那么我接下来讲#define 定义标识符宏

之前我们学过,程序在预编译(预处理)的时候

宏定义的变量会被定义的值替换

宏定义也会不见

预处理时

预处理后

宏定义不仅能定义整数

还能定义字符串,形式多样

把这个该为是

然后打开test,i的文件,你就可以看到预编译后的代码了

头文件不建议重复多次包含,会导致预编译时代码的行数增多

然后看完之后记得把是改为否不然用不了

一个语句完了不是要加上分号吗

不要加上分号,再说一次不要加分号

加分号可能会导致问题

举例

给他强行加上一个分号

预编译时替换

1000;;是没问题啊

1000;

;只是这个多的分号变成了空语句

但是把MAX改成1000;那就有语法问题了

反正就是尽量不要在宏定义的时候加上我们的代码

加分号是超级坑的

#define他的后面不只是整数也可以是关键字,也可以代码。

写程序最好不要乱定义,因为你看的懂,我不一定能看懂

你看这行代码太长,我去我想把他分开怎么办

如果我直接换行会直接报错,因为编译器会把你前面的空格也算进去的

但是我们可以用到续行符,续行符的意思就是相当于下面的东西就直接续到他后面了

这个续行符下面我们刚才是不是敲了个回车,其实就相当于转义了个回车,让回车不是回车就直接到他后面去了

续行符后面只能是回车,不能有其他东西,否者就不行

  • 42
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值