【Linux系统】—— 编译器 gcc/g++ 的使用

news2025/1/22 13:31:06

【Linux系统】—— 编译器 gcc/g++ 的使用

  • 1 用 gcc 直接编译
  • 2 翻译环境
    • 2.1 预处理(进行宏替换)
    • 2.2 编译(生成汇编)
    • 2.3 汇编(生成机器可识别代码)
    • 2.4 链接
    • 2.5 记忆小技巧
    • 2.6 编译方式
    • 2.7 几个问题
      • 2.7.1 如何理解条件编译
      • 2.7.2 为什么编译器要先将代码翻译成汇编语言

1 用 gcc 直接编译

  我们平时学的 C/C++ 代码,都是文本文件,但是我们知道计算机只认识二进制,因此我们需要将C/C++代码翻译成二进制文件

  在 Windows 系统中,编辑代码和翻译过程我们都是用 VS 进行的,因为 VS 是集成的IDE环境,那么在 Linux 中我们又该如何完成代码的翻译工作呢?

  在 Linux 中,我们用到的编译器是 gcc/g++,其中gcc是对C语言进行编译,g++是对C++进行编译。因为 gcc 和 g++ 的指令操作等完全一样,本文主要是用 gcc 进行演示。
  
  我们创建一个 test.c 文件,写上代码:

#include<stdio.h>
 
int main()
{
     printf("hello world\n");
     printf("hello Linux\n");
     return 0;
}

  
  如何用 gcc 对代码进行编译呢?

指令如下:

  • 「gcc」 「要编译的文件」

  
  gcc 会默认生成一个叫 a.out 的可执行文件

在这里插入图片描述

  
  那如果我想指定生成文件的名字呢?

有两种方法

  1. 「gcc 」「要编译的文件」 「-o」 「目标文件」
  2. 「gcc 」 「-o」 「目标文件」「要编译的文件」
      
    在这里插入图片描述

  但是,仅仅学会指令是远远不够的,我们学习 gcc/g++ 更重要的是学习翻译过程背后的过程,我们知道,我们写的 C语言 代码最终要形成可执行程序,要经过预处理编译汇编链接这几个过程,下面我们通过 gcc,进一步认识这四个过程。
  
  

2 翻译环境

2.1 预处理(进行宏替换)

  预处理阶段主要处理那些源文件中 # 开始的编译指令。比如:# i n c l u d e include include,# d e f i n e define define,处理的规则如下:

  • 将所有的 # d e f i n e define define 删除展开所有的宏定义
  • 处理所有的条件编译指令,如:#if#ifdef#elif#else#endif
  • 处理 # i n c l u d e include include 预编译指令,将包含的头文件的内容插入到该预编译指令的位置。这个过程是递归进行的,也就是说被包含的头文件也可能包含其他文件。
  • 删除所有注释
  • 添加行号文件名标识,方便后续编译器生成调试信息等

  
  我们创建一个code1.c文件、写一段代码

在这里插入图片描述

  这一小段代码,头文件、宏定义、注释以及条件编译都有了,正好可以看看预处理的效果。
  

我们如何看到预处理后的结果呢?我们来学一个选项 「-E 」

  • 「-E 」:进行程序翻译,在预处理做完时停下来
      

指令如下:

  • 「gcc 」「-E 」「要编译的文件」 「-o」 「目标文件」

  :预处理后的文件后缀为.i

在这里插入图片描述

  
  我们用 vim 打开 code.i 来看看

在这里插入图片描述

  

  1. 我们发现注释不见了
  2. 我们定义的宏M和N,预处理后也消失不见了,M直接别替换成100,这叫做宏替换
  3. 并且printf("hello N\n")printf("no N\n")只剩下了printf("hello N\n") 。这是因为条件编译,我们定义了N(定义了就行,可以不写值),所以预处理后保留了printf("hello N\n")
  4. 为什么我们的文件变大了呢?根本原因就是头文件展开。 在编译的时候,只要预处理完了,头文件就可以不需要了。头文件展开的意思就是把你要包含的头文件全部拷贝至你的目标文件里,形成 .i 文件。这不过这个 .i 文件我们现在将其打印出来并且写到文件里了,如果不写的话它就是内存级的,在编译器内部。同时 <stdio.h> 头文件中也包含其他的头文件,因此它会类似递归式的拷贝

  因此一个你可能只写了几百行的代码,预处理后可能有上千行。

  我们 C语言 用到的众多头文件,在系统中默认都是安装了的,一般是存在 /usr/include 路径下

在这里插入图片描述

  
  如:我们包含的头文件 <stdio.h> 一般是存在/usr/include/stdio.h 路径下

在这里插入图片描述

  
  可以用 vim 打开来看看

在这里插入图片描述

  里面的代码近 900 行,但是它有条件编译,同时 <stdio.h> 中本身也包含了其他的头文件。


  这里,我问大家一个问题,预处理后的 code1.i 还是 C语言 吗?
  答案:还是C语言
  不过他是一个已经预处理过,是一个干净的C语言了。
  

2.2 编译(生成汇编)

  编辑将C语音翻译成汇编语言
  编译过后,生成的汇编文件后缀为 .s

需要用到命令行选项 「-S 」

  • 「-S 」:从现在开始进行程序翻译,在编译步骤做完时停下来 指令如下:
  • 「gcc 」「-S 」「要编译的文件」 「-o」 「目标文件」

  我们可以从 .c 到 .s 也可以从 .i 到 .s,因为之前已经做过 .c 到 .i 了,就不再重复做预处理步骤, 直接 .i.s

在这里插入图片描述

  
  我们用 vim 打开 code1.s

在这里插入图片描述

  
  

2.3 汇编(生成机器可识别代码)

  汇编是指通过汇编器将汇编代码转变成机器可执行的指令,每一个汇编语句几乎都对应一条机器指令。就是按照汇编指令和机器指令的对照表一一的进行翻译,也不做指令优化。

先直接上指令

  • 「gcc 」「-c 」「要编译的文件」 「-o」 「目标文件」
      

如:

  • gcc -c code1.s -o code1.o
      
  • 「-c 」:从现在开始进行程序翻译,在完成汇编后停下来

  以 .o 为后缀的文件全称叫:可重定位目标文件,也就是我们所说的目标文件。目标文件在 Windows 系统下是以 .obj 结尾的

  目标文件是二进制文件,因此我们打开它是啥都看不懂的

在这里插入图片描述

  虽说 code1.o 已经是二进制文件,但是它还是无法被执行的。因为目标文件仅仅是将我们自己写的代码编成二进制了,可我们的程序中还包含着许多库方法,如printf、scanf、STL容器,此时我们的程序还没有和库方法关联起来,比如我们用了 printf 方法,可我们根本没有 printf 方法的实现,所以我们的目标文件是跑不动的。

  所以我们的程序还要经过最后一步:链接,才能形成可执行文件

  
  

2.4 链接

  链接过程没有命令行选项

指令如下:

  • gcc code1.o -o code1

  这里我们并没有指定去链接哪个库,因为我们现在的代码里没有使用任何的第三方库,我们用的都是C语言标准库的方法,gcc会帮我们去系统里找我这个程序用了 C语言 的哪个标准库。但如果我们要依赖某个第三方库,就需要指定去链接了,这点我们以后再介绍。
  生成可执行序后,程序就可以运行了

在这里插入图片描述

  

2.5 记忆小技巧

  好像预处理、编译、汇编这三步的命令行选项很难记?有什么记忆方法吗?

  他们分别是 「-E 」「-S 」「-c 」
  合起来就是键盘左上角的「esc」键,我只需要记住前两个是大写的就行了。

  而预处理、编译、汇编这三步生成的文件后缀又怎么记呢?
  他们分别是『.i』『.s』『.o』
  连起来就是iso,我们可以记ios,再将后面两个反过来
  
  

2.6 编译方式

  一般我们在编译文件时,不会像上面一样 .i.s.o全部生成一遍,上述这样做只是为了然我们了解整个翻译的过程。

  我们编译文件的习惯是将所有的文件生成 .o 文件,再将所有相关的 .o 文件一起打个包生成可执行文件

在这里插入图片描述

  
为什么喜欢这么做呢?
主要原因是:

  1. 编译器在编译时,不仅仅要形成可执行程序,还可能要形成库(所谓的库其实就是把 .o 文件了个包),如果要形成库的话就不需要编译性成可执行程序
  2. 我们目前使用的 VS 最终就形成一个可执行程序,但往往实践中可能形成 10 个、100 个可执行程序,可能你有 1000 个源文件,其中 100 个形成程序A、50 个形成程序B、60 个形成程序C……我们需要将所有的 .o 做自由组合,形成多个可执行。在编译角度,我们可先将你们全部变成 .o,最后如何形成可执行,再自己做组合
      

为什么要有链接步骤呢?

  这是因为我们要站在巨人的肩膀上
  例如我们要用到的输入输出函数,要是自己来写的话那太费劲了,每做一个项目都要自己先敲一个函数出来,而且写出来也不够好,容易出问题。因此C语言将最基本的功能给我们全部开发好,再打成包,这个包就是库
  解下来我们写代码时,我们只需要将自己的代码编译好,和C语言标准库链接形成可执行就行
  


有小伙伴可能会问:预处理时不是已经展开头文件了吗?为什么还要链接呢?

  预处理展开的仅仅只是声明,因为头文件时公开的。
  其实我们包的头文件源代码都是公开的,只有声明没有实现,实现在对应的同名 .c 文件里。.c 文件 C语言 没有给你暴露出来,直接编成库了。
  要最终形成可执行,重要的是方法,而链接就是将方法找到


  当然,上述讲的只是一般情况,你要是不喜欢也可以一次就形成可执行文件
  
  

2.7 几个问题

2.7.1 如何理解条件编译

  我们创建 code.c 文件,写下如下代码

在这里插入图片描述

  
  根据我们前面的知识,我们知道此时我们并没有定义M,执行的应是printf("社区版/免费版 version1\n")语句
  我们执行一下看看

在这里插入图片描述

  


  gcc 编译时支持我们用 「-D 」 来进行命令行级别对指定源代码进行动态添加宏

如:
定义加写值

  • gccgcc code.c -o code.exe -DM=1
      

只定义不写值

  • gccgcc code.c -o code.exe -DM

  

在这里插入图片描述

  gcc不用「-D 」选项定义宏它又会变成免费版

在这里插入图片描述

  
  gcc这合理吗?其实是合理的
  gcc编译器进行编译时第一步就是预处理,预处理的本质其实就是 让编译器编辑(修改)我们的代码! 既然预处理时,编译器能去注释,能进行宏替换,那么编译器将命令行中的 -DM 解释成 #define M ,并将其当做字符串插入到我的代码当中不过分吧。
   「-D 」相当于在命令行给代码定义宏
  


  相信对条件编译大家都能理解,大家不理解的是条件编译的用途。下面我们简单来了解一下条件编译的应用场景

  • 对一款软件通过专业读、收费标准等进行区分,使用条件编译,进行代码的动态裁剪:
    我们平时看到的某些软件,像VS、Xshell等,往往都分为专业版和社区版(收费版和免费版)。他们两者的区别主要是在功能方面,比如收费版支持100个功能,而免费版只支持50个功能。
    这些软件也都是程序员开发的,那么程序员在维护这款软件需要维护几份源代码呢?毕竟这款软件有两个版本。
    事实上,如果将同一款软件的免费版和收费版当成两个项目来看,那么公司就需要有两套班子,但其实他们功能上无非就是收费版上做一下功能的裁剪就是免费版。
    所以在公司内部我们只需要维护一份源代码即可,最终在发布的时候只需要告诉别人编译这个代码时,编译成免费的还是收费的。怎么才能做到这点呢?我们可以将软件中的功能拆分一下:公共都有的放在一个模块里,需要收费的放在一个模块里,最后用条件编译将其维护起来。
    这样一份代码通过条件编译就能对其进行裁剪,从而实现对内只需维护一份源代码,对外实现多份版本的目的。

  • Linux 内核源代码也是采用条件编译进行点裁剪
    我们的 Linux 内核,编译好了其实体积还是很大的,但有些功能在很多的小型设备上:嵌入式设备、智能家电等,上面根本就不需要Linux支持那么多功能,这时就可以用条件编译实现代码的动态裁剪

  当然,条件编译的功能远远不仅于此,但大多应用场景离我们现在的水平太远,感兴趣的小伙伴可以自行深入了解。

  
  

2.7.2 为什么编译器要先将代码翻译成汇编语言

  C语言翻译成二进制指令相信大家都能理解,因为机器只认识二进制。但为什么编译器要先将C语言翻译成汇编语言,再将汇编语言翻译成二进制呢?


为什么计算机只认识二进制

  简单来说是因为 0 和 1 是最简单的硬件电路,简单就意味着可靠,计算机通过与非门各种各样的门电路组合成各种复杂电路。
  


这里讲一下计算机的发展史
  
  计算机都是要进行输入输出的:我们将数据喂给它,它处理完后将结果返回。我们编程的本质就在在控制计算机,我们编译代码其实就是在要求计算机帮我们做这做那
  早期的计算机都是非常大的,而且其运算力非常差。早期我们没有编程,控制计算机用的是计算机上的开关,早期的计算机科学家都是在计算机前掰来掰去的,其实就是在通过开关来给计算机输入 0 和 1

  后来人们觉得开关的方式不太好,到了五六十年代,人们开始用打孔编程。

在这里插入图片描述

打孔纸带

  打了孔的地方,光能透过去,我们认为是1,否则为0。《三体》中,叶文洁向外星人发送信号时,手上捏着一条纸带,就是这个东西
  
  但打孔编程本质依然是二进制编程,二进制编程可是很恶心的。而且打孔打错了,纸就报废了,要重新打孔,浪费纸张不说还效率低下。后来人们发明了一种编程语言:汇编语言
  用汇编语言控制计算机效率无疑比直接二进制编程高很多。
  从我们的汇编语言开始,就需要一个东西:编译器。因为汇编语言本质上也是文本,所以我们需要一种编译器将汇编语言编译成对应的二进制
  


这里有个问题:第一个汇编语言编译器是用什么写的呢?
  这个编译器要编译汇编语言,那编译器自己应该用什么语言来写呢?
  用汇编吗?你用还没法翻译成二进制的汇编,来将汇编翻译成二进制,这不鸡蛋和鸡吗?

  因此第一版编译汇编的编译器,是用二进制写的。先用二进制写一个二进制版的汇编编译器。有了第一个编译器,此时就可以编译汇编语言了,此时我们就可以用汇编语言写一份汇编版本的编译器,第一版的编译器就可以不要了,此后我们就可以用自己语言写的编译器编译自己语言。这个过程叫做编译器的自举过程
  不仅如此,语言也是可以自举的。比如C++推出C++11,但此时的编译器只支持C++98,这时就可用98写个能编11的编译器,再用C++11进行重写

  最早期的,比较好的操作系统叫 Unix,它第一版本就是由肯·汤普逊用汇编语言写出来的,后来丹尼斯里奇发明了 C语言,肯·汤普逊和丹尼斯里奇即一起用C语言把 Unix 进行重构,发布的 C语言 版本的 Unix。我什么肯·汤普逊最开始用汇编语言写,因为最开始只有汇编,后来 C语言 出来了,C语言 对应的编译器也诞生了,为了代码本身的可维护性,他就把 Unix 操作系统调整为 C语言 了。
  


  再后来,人们觉得汇编语言也太麻烦,所以基于汇编语言产生了许多分支,编译型语言在那个阶段就开始爆发了。最典型的就是 70 年代产生的 C语言,再到后来的 C++/java/go
  

在这里插入图片描述

  
  现在有了 C语言,C语言 最终肯定也要翻译成二进制。现在的问题是,我们是直接将C语言翻译成二进制还是先翻译成汇编语言再翻译成二进制。
  我们肯定会选择方案二。为什么呢?

  首先将C语言翻译成汇编语言,毕竟还是从文本到文本,它的翻译难度相对较低;其次,在C语言产生之前,汇编语言已经发展了很多年了,我们只需要将C翻译成汇编,而将汇编翻译成二进制这项工作已经发展的很成熟,可以不用做了,我们可以站在巨人的肩膀上
  如果直接将 C 翻译成二进制,那么翻译的成本会特别高,而且 C++ 等后来者是基于C语言发明出来的,你让我 C++ 怎么办,难道我 C++ 也要直接翻译成二进制吗?
  我们要学会站在巨人的肩膀上,计算机每一阶段的发展都经过了十几年,我们要将每一阶段的发展好好用上。

  而编译是逆历史的过程:C语言 -> 汇编 -> 二进制
  

在这里插入图片描述

  
  
  
  


  好啦,本期关于编译器 gcc/g++ 就介绍到这里啦,希望本期博客能对你有所帮助。同时,如果有错误的地方请多多指正,让我们在 Linux 的学习路上一起进步!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2280386.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Unity3D】3D物体摆放、场景优化案例Demo

目录 PlaceManager.cs(放置管理类) Ground.cs(地板类) 和 GroundData.cs(地板数据类) 额外知识点说明 1、MeshFilter和MeshRenderer的Bounds区别 2、Gizmos 绘制一个平行于斜面的立方体 通过网盘分享的文件&#xff1a;PlaceGameDemo2.unitypackage 链接: https://pan.baid…

智能系统的感知和决策

智能系统在感知和决策过程中具备的关键能力表现在智能感知/自主判定上&#xff0c;下面可以从感知的本质、自主判断的含义及其在智能系统中的作用进行深入分析。 1、智能感知&#xff1a;信息获取与理解 智能感知是指智能系统通过传感器或其他数据采集手段获取环境中的信息&…

Spring 中的事件驱动模型

事件驱动的基本了解 事件模式也就是观察者模式&#xff0c;当一个对象改变的时候&#xff0c;所有依赖的对象都会收到一个通知。 Subject&#xff1a;抽象主题 Observer&#xff1a;具体主题 Concrete Subject&#xff1a;抽象观察者&#xff0c;在得到更新通知之后去更新自…

linux-FTP服务配置与应用

也许你对FTP不陌生&#xff0c;但是你是否了解FTP到底是个什么玩意&#xff1f; FTP 是File Transfer Protocol&#xff08;文件传输协议&#xff09;的英文简称&#xff0c;而中文简称为 “文传协议” 用于Internet上的控制文件的双向传输。同时&#xff0c;它也是一个应用程序…

linux-NFS网络共享存储服务配置

1.NFS服务原理 NFS会经常用到&#xff0c;用于在网络上共享存储&#xff0c;这样讲&#xff0c;你对NFS可能不太了解&#xff0c;举一个例子&#xff0c; 加入有三台机器A,B,C&#xff0c;它们需要访问同一个目录&#xff0c;目录中都是图片&#xff0c;传统的做法是把这些 图…

Jenkins 启动

废话 这一阵子感觉空虚&#xff0c;心里空捞捞的&#xff0c;总想找点事情做&#xff0c;即使这是一件微小的事情&#xff0c;空余时间除了骑车、打球&#xff0c;偶尔朋友聚会 … 还能干什么呢&#xff1f; 当独自一人时&#xff0c;究竟可以做点什么&#xff0c;填补这空虚…

消息队列篇--原理篇--Pulsar(Namespace,BookKeeper,类似Kafka甚至更好的消息队列)

Apache Pulusar是一个分布式、多租户、高性能的发布/订阅&#xff08;Pub/Sub&#xff09;消息系统&#xff0c;最初由Yahoo开发并开源。它结合了Kafka和传统消息队列的优点&#xff0c;提供高吞吐量、低延迟、强一致性和可扩展的消息传递能力&#xff0c;适用于大规模分布式系…

Python配置MITMPROXY中间人监听配置

1、安装python 环境&#xff0c;此处可以使用conda安装:conda create --name my_new_env python3.12 2、pip安装mitmproxy&#xff1a;pip install mitmproxy&#xff0c;安装后如果使用mitmproxy --version 成功返回结果&#xff0c;说明已经在环境变量路径中&#xff0c;如果…

Java-数据结构-二叉树习题(2)

第一题、平衡二叉树 ① 暴力求解法 &#x1f4da; 思路提示&#xff1a; 该题要求我们判断给定的二叉树是否为"平衡二叉树"。 平衡二叉树指&#xff1a;该树所有节点的左右子树的高度相差不超过 1。 也就是说需要我们会求二叉树的高&#xff0c;并且要对节点内所…

【网络原理】万字详解 HTTP 协议

&#x1f970;&#x1f970;&#x1f970;来都来了&#xff0c;不妨点个关注叭&#xff01; &#x1f449;博客主页&#xff1a;欢迎各位大佬!&#x1f448; 文章目录 1. HTTP 前置知识1.1 HTTP 是什么1.2 HTPP 协议应用场景1.3 HTTP 协议工作过程 2. HTTP 协议格式2.1 fiddler…

基于STM32的智能寝室控制系统设计(论文+源码)

1 .系统整体设计 通过需求分析&#xff0c;本设计基于STM32的智能寝室控制系统整体架构如图2.1所示&#xff0c;整系统利用DHT11温湿度传感器获取室内环境数据&#xff0c;并通过OLED显示&#xff0c;提供用户实时信息&#xff0c;火焰传感器和烟雾传感器用于监测火灾情况&…

日历热力图,月度数据可视化图表(日活跃图、格子图)vue组件

日历热力图&#xff0c;月度数据可视化图表&#xff0c;vue组件 先看效果&#x1f447; 在线体验https://www.guetzjb.cn/calanderViewGraph/ 日历图简单划分为近一年时间&#xff0c;开始时间是 上一年的今天&#xff0c;例如2024/01/01 —— 2025/01/01&#xff0c;跨度刚…

铁电存储器FM25CL64B简介及其驱动编写(基于STM32 hal库)

铁电存储器FM25CL64B简介及其驱动编写&#xff08;基于STM32 hal库&#xff09; 文章目录 铁电存储器FM25CL64B简介及其驱动编写&#xff08;基于STM32 hal库&#xff09;前言一、FM25CL64B简介二、驱动代码1.头文件2.c文件 总结 前言 FM25CL64B是赛普拉斯cypress出品的一款铁…

基于微信小程序的科创微应用平台设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

HarmonyOS Next 最强AI智能辅助编程工具 CodeGenie介绍

随着大模型的兴起&#xff0c;在智能编码领域首先获得了应用。 市面上从Microsoft Copilot到国内阿里通义&#xff0c;字节marscode等&#xff0c;都提供了copilot方式的智能编码工具。HarmonyOS Next作为诞生一年的新事物&#xff0c;由于代码量和文档迭代原因&#xff0c;在智…

WPF2-1在xaml为对象的属性赋值.md

1. AttributeValue方式 1.1. 简单属性赋值1.2. 对象属性赋值 2. 属性标签的方式给属性赋值3. 标签扩展 (Markup Extensions) 3.1. StaticResource3.2. Binding 3.2.1. 普通 Binding3.2.2. ElementName Binding3.2.3. RelativeSource Binding3.2.4. StaticResource Binding (带参…

Appium(四)

一、app页面元素定位 1、通过id定位元素: resrouce-id2、通过ClassName定位&#xff1a;classname3、通过AccessibilityId定位&#xff1a;content-desc4、通过AndroidUiAutomator定位5、通过xpath定位xpath、id、class、accessibility id、android uiautomatorUI AutomatorUI自…

Windows图形界面(GUI)-QT-C/C++ - Qt List Widget详解与应用

公开视频 -> 链接点击跳转公开课程博客首页 -> ​​​链接点击跳转博客主页 目录 QListWidget概述 使用场景 常见样式 QListWidget属性设置 显示方式 (Display) 交互行为 (Interaction) 高级功能 (Advanced) QListWidget常见操作 内容处理 增加项目 删除项目…

Oracle 创建并使用外部表

目录 一. 什么是外部表二. 创建外部表所在的文件夹对象三. 授予访问外部表文件夹的权限3.1 DBA用户授予普通用户访问外部表文件夹的权限3.2 授予Win10上的Oracle用户访问桌面文件夹的权限 四. 普通用户创建外部表五. 查询六. 删除 一. 什么是外部表 在 Oracle 数据库中&#x…

靠右行驶数学建模分析(2014MCM美赛A题)

笔记 题目 要求分析&#xff1a; 比较规则的性能&#xff0c;分为light和heavy两种情况&#xff0c;性能指的是 a.流量与安全 b. 速度限制等分析左侧驾驶分析智能系统 论文 参考论文 两类规则分析 靠右行驶&#xff08;第一条&#xff09;2. 无限制&#xff08;去掉了第一条…