研发面试准备

本文详细介绍了C++从预处理到执行的完整过程,包括预处理、编译、优化、汇编和链接等步骤。同时,探讨了多线程的概念,包括线程池与内核数的关系、多线程实现方法、同步与互斥的实现及应用场景。此外,还讨论了友元函数和友元类在C++中的作用以及浏览器解析域名的整个流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. c++从编译到执行的过程

c++从编译到执行要经历以下过程:

源代码–>预处理–>编译–>优化–>汇编–>链接–>目标程序

1.1 预处理过程

要进行第一步,预处理过程,要干的事情有哪些呢, 要处理预编处理宏定义,条件编译指令,条件包含指令,特殊符号.
对于宏定义指令,会将宏定义变量替换成对应的部分.对条件编译指令,预处理过程按照程序定义过滤掉不必要的部分. 对条件包含指令,预编译程序会把它通通加入到目标文件当中. 对于一些特殊符号,预编译程序会识别出来并用合适的值替换.
综合来说实际上就是一个替换的动作,处理哪些带#符号的命令和一些特殊符号.
我们如何得到这个文件呢,linux可以敲下面的命令:
gcc -E hello.cpp hw.i
得到预处理之后的文件.

1.2 编译过程

词法分析,语法分析,在确定所有的指令都符合语法规则后,将其翻译成目标代码,最终目标文件是目标代码.
在编译的过程中,所有的变量都是虚拟地址.在汇编文件中有一个表,存储了虚拟地址对应的变量名称.

1.3 优化过程

优化处理是编译系统中一项比较艰深的技术。它涉及到的问题不仅同编译技术本身有关,而且同机器的硬件环境也有很大的关系。优化一部分是对中间代码的优化。这种优化不依赖于具体的计算机。另一种优化则主要针对目标代码的生成而进行的。上图中,我们将优化阶段放在编译程序的后面,这是一种比较笼统的表示。

对于前一种优化,主要的工作是删除公共表达式、循环优化(代码外提、强度削弱、变换循环控制条件、已知量的合并等)、复写传播,以及无用赋值的删除,等等。

后一种类型的优化同机器的硬件结构密切相关,最主要的是考虑是如何充分利用机器的各个硬件寄存器存放的有关变量的值,以减少对于内存的访问次数。另外,如何根据机器硬件执行指令的特点(如流水线、RISC、CISC、VLIW等)而对指令进行一些调整使目标代码比较短,执行的效率比较高,也是一个重要的研究课题。

经过优化得到的汇编代码必须经过汇编程序的汇编转换成相应的机器指令,方可能被机器执行。

1.4汇编过程

经历汇编过程编译转换成机器码,最终形成与源程序等效的机器码.目标文件由段组成. 通常一个目标文件至少有两个段
代码段和数据段.代码段包含了可读可执行的程序的指令. 数据段保存各种全局变量和静态的数据.一般数据段可读可写可执行.

1.5 链接程序

由汇编程序生成的目标文件并不能立即就被执行,其中可能还有许多没有解决的问题。例如,某个源文件中的函数可能引用了另一个源文件中定义的某个符号(如变量或者函数调用等);在程序中可能调用了某个库文件中的函数,等等。所有的这些问题,都需要经链接程序的处理方能得以解决。

链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够诶操作系统装入执行的统一整体。

根据开发人员指定的同库函数的链接方式的不同,链接处理可分为两种:

(1)静态链接 在这种链接方式下,函数的代码将从其所在地静态链接库中被拷贝到最终的可执行程序中。这样该程序在被执行时这些代码将被装入到该进程的虚拟地址空间中。静态链接库实际上是一个目标文件的集合,其中的每个文件含有库中的一个或者一组相关函数的代码。(个人备注:静态链接将链接库的代码复制到可执行程序中,使得可执行程序体积变大)

(2)动态链接  在此种方式下,函数的代码被放到称作是动态链接库或共享对象的某个目标文件中。链接程序此时所作的只是在最终的可执行程序中记录下共享对象的名字以及其它少量的登记信息。在此可执行文件被执行时,动态链接库的全部内容将被映射到运行时相应进程的虚地址空间。动态链接程序将根据可执行程序中记录的信息找到相应的函数代码。(个人备注:动态链接指的是需要链接的代码放到一个共享对象中,共享对象映射到进程虚地址空间,链接程序记录可执行程序将来需要用的代码信息,根据这些信息迅速定位相应的代码片段。)

对于可执行文件中的函数调用,可分别采用动态链接或静态链接的方法。使用动态链接能够使最终的可执行文件比较短小,并且当共享对象被多个进程使用时能节约一些内存,因为在内存中只需要保存一份此共享对象的代码。但并不是使用动态链接就一定比使用静态链接要优越。在某些情况下动态链接可能带来一些性能上损害。

经过上述五个过程,C源程序就最终被转换成可执行文件了。缺省情况下这个可执行文件的名字被命名为a.out。

2. 多线程

不管是自己的项目还是涉及到基础知识,都避免不了多线程的问题,项目中的实际应用、问题整理

2.1 线程池中线程与内核数目的关系

2.2 多线程有几种实现方法,都是什么?

  1. 继承 Thread 类
  2. 实现 Runnable 接口再 new Thread(YourRunnableOjbect)

2.3 多线程同步和互斥有几种实现方法,都是什么?

线程间的同步方法大体可分为两类:用户模式和内核模式。顾名思义,内核模式就是指利用系统内核对象的单一性来进行同步,使用时需要切换内核态与用户态,而用户模式就是不需要切换到内核态,只在用户态完成操作。
用户模式下的方法有:原子操作(例如一个单一的全局变量),临界区。
内核模式下的方法有:事件,信号量,互斥量

2.4 多线程同步和互斥有何异同,在什么情况下分别使用他们?举例说明。

线程同步是指线程之间所具有的一种制约关系,一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程的消息时应等待,直到消息到达时才被唤醒。
线程互斥是指对于共享的进程系统资源,在各单个线程访问时的排它性。当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。线程互斥可以看成是一种特殊的线程同步(下文统称为同步)。

3. 友元函数和友元类

在C++中,我们使用类对数据进行了隐藏和封装,类的数据成员一般都定义为私有成员,成员函数一般都定义为公有的,以此提供类与外界的通讯接口。但是,有时需要定义一些函数,这些函数不是类的一部分,但又需要频繁地访问类的数据成员,这时可以将这些函数定义为该函数的友元函数。除了友元函数外,还有友元类,两者统称为友元。友元的作用是提高了程序的运行效率(即减少了类型检查和安全性检查等都需要时间开销),但它破坏了类的封装性和隐藏性,使得非成员函数可以访问类的私有成员。

友元函数 :
友元函数是可以直接访问类的私有成员的非成员函数。它是定义在类外的普通函数,它不属于任何类,但需要在类的定义中加以声明,声明时只需在友元的名称前加上关键字friend,其格式如下:
friend 类型 函数名(形式参数);
友元函数的声明可以放在类的私有部分,也可以放在公有部分,它们是没有区别的,都说明是该类的一个友元函数。
一个函数可以是多个类的友元函数,只需要在各个类中分别声明。
友元函数的调用与一般函数的调用方式和原理一致。
友元类 :
友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类中的隐藏信息(包括私有成员和保护成员)。
当希望一个类可以存取另一个类的私有成员时,可以将该类声明为另一类的友元类。定义友元类的语句格式如下:
friend class 类名;
其中:friend和class是关键字,类名必须是程序中的一个已定义过的类。

4. 在浏览器中输入一个域名之后都发生了什么

就TCP/IP五层模型的架构来分析
物理层(网关)<->链路层(MAC)<->网络层(IP)<->传输层(TCP)<->应用层(HTTP)

4.1 DNS解析

DNS解析的过程就是寻找哪台机器上有你需要资源的过程,寻找的过程遵循就近原则。
输入一个网址并按回车的时候浏览器会根据输入的URL去查找对应的IP,具体过程如下:
1)首先是查找浏览器缓存,浏览器会保存一段时间内访问过的一些网址的DNS信息,不同浏览器保存的时常不等。

(2)如果没有找到对应的记录,这个时候浏览器会尝试调用操作系统缓存来继续查找这个网址的对应DNS信息。

(3)如果还是没找到对应的IP,那么接着会发送一个请求到路由器上,然后路由器在自己的路由器缓存上查找记录,路由器一般也存有DNS信息。

(4)如果还是没有,这个请求就会被发送到ISP(注:Internet Service Provider,互联网服务提供商,就是网络运营商,中国电信中国移动等),ISP也会有相应的ISP DNS服务器,就是本地DNS服务器,请求的域名基本上都能在这里找得到。

(5)如果还是没有的话, ISP的DNS服务器会将请求发向根域名服务器进行搜索。根域名服务器就是面向全球的顶级DNS服务器,共有13台逻辑上的服务器,从A到M命名,真正的实体服务器则有几百台,分布于全球各大洲。

(6)如果到了这里还是找不到域名的对应信息,那只能说明一个问题:这个域名本来就不存在,它没有在网上正式注册过。或者域名过期了。

这也就是为什么有时候打开一个新页面会有点慢,因为如果本地没什么缓存,查找域名的过程要这样递归地查询下去,查找完还要一层层的向上返回。例如"mp3.baidu.com",域名先是解析出这是个.com的域名,然后跑到管理.com域的服务器上进行进一步查询,然后是.baidu,最后是mp3, 所以域名结构为:三级域名.二级域名.一级域名。

所以DNS根据域名查询IP地址的过程为:浏览器缓存 --> 操作系统缓存 --> 路由器缓存–>本地(ISP)域名服务器缓存 --> 根域名服务器。

4.2 进行TCP连接

浏览器终于得到了IP以后,向服务器发送TCP连接,TCP连接经过三次握手

4.3.浏览器发送HTTP请求

浏览器和服务器建立连接以后,浏览器接着给这个IP地址给服务器发送一个http请求,方式为get,例如访问www.baidu.com。其本质是在建立起的TCP连接中,按照HTTP协议标准发送一个索要网页的请求。

这个get请求包含了主机(Host)、用户代理(User-Agent),用户代理就是自己的浏览器,它是你的"代理人",Connection(连接属性)中的keep-alive表示浏览器告诉对方服务器在传输完现在请求的内容后不要断开连接,不断开的话下次继续连接速度就很快了。可能还会有Cookies,Cookies保存了用户的登陆信息,一般保存的是用户的JSESSIONID,在每次向服务器发送请求的时候会重复发送给服务器。

在建立连接发送请求时每个服务端需要和客户端保持通信,有很多客户端都会和服务器进行通信。服务器为了识别是哪个客户端与它通信,就必须用一个标识记录客户端的信息。客户端首次访问服务器,服务端返回响应时通过附带一个记录的客户端信息的标识来返回给客户端,这个标识就是JSESSIONID,JSESSIONID就放在了客户端的Cookies里。当客户端再次向服务器发送请求时上就使用上次记录的Cookies里面的JSESSIONID,这样服务器就知道是哪个浏览器了。这样他们之间就能保持通信了。

发送完请求接下来就是等待回应了。

4.4 服务器处理请求

服务器收到浏览器的请求以后),会解析这个请求(读请求头),然后生成一个响应头和具体响应内容。接着服务器会传回来一个响应头和一个响应,响应头告诉了浏览器一些必要的信息,例如重要的Status Code,2开头如200表示一切正常,3开头表示重定向,4开头是客户端错误,如404表示请求的资源不存在,5开头表示服务器端错误。响应就是具体的要请求的页面内容。

4.5 浏览器解析渲染页面

4.6 关闭TCP连接

当数据完成请求到返回的过程之后,根据Connection的Keep-Alive属性可以选择是否断开TCP连接,HTTP/1.1一般支持同一个TCP多个请求,而不是1.0版本下的完成一次请求就发生断开。TCP的断开与连接不一样,断开可以分为主动关闭和被动关闭,需要经过4次握手。

当浏览器需要的全部数据都已经加载完毕,一个页面就显示完了。

手撕代码合集

  1. 最大子序和 dp

  2. 生产者消费者、读者写者

  3. C++单例模式(饿汉式 懒汉式 区别,以及他们的重点 构造函数设置为私有)

  4. 链表有环,如何找到链表环点

  5. 手撕two linkedlist megre to one

  6. 翻转链表

  7. 给定一个数组返回打乱数组leetcode shuffer an array

  8. 有序数组的二分查找,返回第一次出现的位置


class BinarySearch {
public:
    int getPos(vector<int> A, int n, int val) {
        if (n == 0) return -1;
        if (n == 1) return 0;
        int high = n - 1;
        int low = 0;
        while (low < high) {
            int mid = low + (high - low)/2; //防止溢出
            if (val == A[mid]) {
                 high = mid;   //如果找到了val,则继续对low~mid段数据进行二分查找
            } else if (val > A[mid]) {
                low = mid + 1;
            } else {
                high = mid - 1;
            }
        }
        if (A[high] == val) { //如果存在val,则high对应的值就是第一次出现的位置
            return high;
        }
       return -1;
    }
};
 


  1. 层序遍历
    10.二叉树的最小深度
int run(TreeNode *root) {
        if(!root)
            return 0;
        int left = run(root->left);
        int right = run(root->right);
        if(left==0 || right==0)
        {
            return 1+left+right;  
        }
        return 1+min(left,right);
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值