• 【大厂面试重点(程序环境和预处理以及C语言不能函数重载但C++却可以的原理分析)】


    大家好啊✨
    先简单介绍一下自己💎
    本人目前大二在读,专业是计算机科学与技术。
    写博客的目的是督促自己记好每一章节的笔记,同时也希望结交更多同仁,大家互相监督,一起进步!☀️


    👀在这篇文章中,将对程序环境、代码文件转换为可执行程序的过程、C++的函数重载以及它们之间的关系进行讲解。这可是大厂面试重点哦✨
    👀我的宗旨就是将所有知识点一网打尽🔥🔥🔥
    👀内容有点多,大家一定要耐心看完。切记,学习如逆水行舟,不进则退.在学习的路上一定要坚持!坚持!再坚持!
    如果大家看完觉得有所收获,不妨关注点赞收藏方便以后回顾,也当做给博主的小小鼓励了~❤️


    一、🔭程序的翻译环境和执行环境

    ANSI C的任何一种实现中,存在两个不同的环境。

    第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。
    第2种是执行环境,它用于实际执行代码。

    本篇文章将重点对翻译环境中的各种细节进行讲解。

    二、🔭详解编译+链接

    2.1💻翻译环境

    我们知道,在一个工程里可能包含多个源文件,那计算机是怎么将这些文件整合起来最终形成一个可执行程序的呢❓
    其实每一个源文件都会单独经过编译器的处理形成一个目标文件,然后这些目标文件会和链接库一起经过链接器的处理,最终形成一个可执行程序。

    在这里插入图片描述
    那么在编译器和链接器里到底进行了什么操作呢❓
    下面来一一解释
    首先我们先写一个简单的源文件👇

    #define _CRT_SECURE_NO_WARNINGS
    #include 
    
    int main()
    {
    	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    	int i = 0;
    	for (i = 0; i < 10; i++)
    	{
    		printf("%d ", arr[i]);
    	}
    	printf("\n");
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    然后让这段代码执行起来,之后就会在代码路径下看到这么一个文件:
    在这里插入图片描述
    其实这个以obj为后缀名的文件就是上面所说的目标文件。
    同样的,如果工程中含有多个源文件,则编译器处理之后,就会生成多个对应的.obj文件。
    需要注意的是,在不同的编译器下,生成的目标文件的后缀名会不同,例如在Linux gcc编译器下会生成.o为后缀的目标文件(这篇文章中也会进行演示)

    而所谓的可执行程序也就是以.exe为后缀的文件。

    这里再补充一个链接库的作用:在我们写程序的时候,往往会包含一些头文件,而这些头文件所依赖的就是链接库,也就是说,有了链接库的支持,我们才能正常地使用头文件中的各种函数。

    再补充一点,所谓的编译器和链接器就是分别为cl.exe和link.exe的文件(可执行程序),如下:👇

    在这里插入图片描述

    2.2💻组成编译的几个阶段

    下面对源文件转换成可执行程序的过程进行分解:

    源文件经过编译和链接生成可执行程序,而编译又分为预处理、编译和汇编

    由于在VS环境下,一旦我们开始执行程序,就会直接生成最终的可执行程序,所有我们没有办法观察到各个阶段到底发生了什么,因此在这里我们采用Linux来进行演示。
    先给出一个在Linux环境下编写的一个C程序,代码如下:
    在这里插入图片描述

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

    先执行第一步,生成一个test.i文件:

    在这里插入图片描述
    然后打开test.i:
    在这里插入图片描述
    可以看到,test.i文件中有长达几百行的代码,而在代码的最后是test.c里的内容。唯一不同的是,test.i文件中并没有出现"#include ",所以我们不难得出结论:test.i文件前面几百行代码其实就是头文件中的内容。----故预处理过程的作用之一就是包含头(展开)文件

    下面我们对test.c中的内容稍加修改:
    在这里插入图片描述

    在test.c文件中添加了一个宏、一个注释,并且使用了这个宏。
    接下来,重新生成test.i文件:
    在这里插入图片描述
    根据上面出现的不同,可以得出,文件预处理阶段还会进行#define定义的值得替换,删掉定义的宏,并且删除注释
    接下来执行第二个命令,进入编译过程:
    在这里插入图片描述

    可以看到,执行-S选项后,在生成的test.s文件中出现了一堆汇编代码。
    而产生这些汇编代码的过程又包括:语法分析,词法分析,语义分析和符号汇总。这里我们着重讲解符号汇总❗️❗️❗️
    符号汇总主要就是将程序中出现的函数汇总在一起,最终生成一个.s文件,而这些.s文件又会在下一步的汇编过程中生成.o文件,如下:
    在这里插入图片描述
    而这里的test.o文件中存放的是一些我们看不懂的二进制符号,它其实是由汇编指令转化而来的。
    而与此同时,前面已经进行过的符号汇总又会形成一个符号表,举个例子:
    在这里插入图片描述
    上面的代码在编译阶段会分别经过符号汇总,然后再汇编阶段形成自己的符号表:
    在这里插入图片描述
    下面就要进入链接阶段了,在这一阶段,主要进行了合并段表以及符号表的合并和重定位。这里重点讲符号表的合并和重定位。

    顾名思义,符号表的合并就是将汇编阶段所产生的所有符号表合并为一个,而重定位就是把所有相同的符号合并为一个,并使用一个地址。例如上述add.o和test.o中的Add就将被合并为一个。而又因为test.c中的Add只是一个声明,并没有什么价值,所以,合并后的Add所使用的地址就是地址一。

    在这里插入图片描述
    总结一下上面四个过程:
    在这里插入图片描述
    经过上面的四个阶段,就生成了可执行程序。

    2.3💻运行环境

    程序执行的过程:

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

    三、🔭解释为什么C++可以实现函数重载,C语言却不可以

    3.1💻什么是函数重载

    自然语言中,一个词可以有多重含义,人们可以通过上下文来判断该词真实的含义,即该词被重载了。
    比如:以前有一个笑话,国有两个体育项目大家根本不用看,也不用担心。一个是乒乓球,一个是男足。前
    者是“谁也赢不了!”,后者是“谁也赢不了!”

    函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 顺序)必须不同,常用来处理实现功能类似数据类型不同的问题

    下面的几个函数构成重载

    int Add(int left, int right) 
    {
    	return left+right;
    }
    double Add(double left, double right)
    {
    	 return left+right; 
    }
    long Add(long left, long right)
    {
    	 return left+right; 
    }
    int main()
    {
    	 Add(10, 20);
    	 Add(10.0, 20.0);
    	 Add(10L, 20L);
    	 
     	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    但这两个并不构成重载:

    short Add(short left, short right) 
    {
     	return left+right; 
    }
    int Add(short left, short right) 
    {
     	return left+right;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    3.2💻C++和C语言的函数名修饰规则

    根据上文,我们可以知道编译过程中要进行符号汇总,链接过程中要进行符号表的合并,接下来分别看一下Linux下gcc和g++中的函数名修饰规则:
    C语言代码如下:
    在这里插入图片描述
    执行-c选项结果如下:
    在这里插入图片描述
    很显然,C语言并不能出现函数名相同的函数
    具体原因通过汇编代码了解一下:
    在这之前要重新写一个.c文件:
    在这里插入图片描述
    执行-S选项产生的汇编代码如下:
    在这里插入图片描述
    可以看到gcc对C语言处理得到的汇编中,函数名最后生成的(修饰过的)名字就是他们本身,所以当出现相同名字的函数时,编译器无法区分,故而报错。
    下面再来看一下C++中的情况:
    代码如下:
    在这里插入图片描述
    执行-S选项后结果如下:
    在这里插入图片描述
    可以看到,两个相同名字的函数经过修饰后得到的函数名并不相同,这里以第一个为例解释命名规则:

    _Z3Addii
    _Z是名字前面固定不变的前缀
    3代表函数名的字符长度(Add为3个字符)
    数字后面紧跟着的就是函数名字Add
    最后的两个i是两个参数类型的简写

    所以只要C++中的函数符合函数重载的要求,就不会报错。
    但因为函数名修饰规则不同,C语言中的修饰规则过于单一简单,故不能区别函数名相同的函数,也就不能构成函数重载。

    四、🔭总结

    👀本篇博客主要介绍了程序环境和预处理各个过程中进行的操作,以及C语言和C++中函数名修饰规则,还进行了C++可以实现函数重载的原理分析。这些都是大厂面试的重点!
    👀当然这些过程并没有进行最底层的分析,因为最底层原理内容远比想象的更多,又想进一步了解的小伙伴可以看一下《程序员的自我修养》这本书,读完一定受益匪浅。但如果面试室被问到,只要把这篇文章中的内容讲出来就已经能得到面试官的认可了!
    👀不得不说,写这么一篇博客还是挺累的,朋友们看完如果感觉有收获的话,就点点赞和收藏给博主一点小鼓励吧!💖
    👀 博主也在学习中,如有不严谨之处,希望各位不吝指出,我们一起加油,一起进步!

  • 相关阅读:
    uniapp上传头像并裁剪图片
    Vue/Vuex (actions) 核心概念 使用方法、辅助函数 mapActions使用方法说明
    Vue3学习(十七) - 点击二级分类实现自动筛选功能
    20年前,微软给金山那刀,现今一举将WPS推上领奖台,WPS,赢了
    沈阳航空航天大学计算机考研资料汇总
    Linux环境变量
    你好OPPO R11可以更改串号 蓝牙 等参数吗?
    SignalR 入门
    sqlite3自动插入创建时间和更新时间
    Python框架:Django和Flask介绍应用场景和优缺点
  • 原文地址:https://blog.csdn.net/m0_62618590/article/details/126682426