Linux | 编译原理、gcc的命令参数、自动化构建工具 make/Makefile

文章目录

  • 编译原理
    • 预处理
    • 编译
    • 汇编
    • 链接
    • gcc的常用命令参数
  • make 和 Makefile 的概念
    • make的运行
    • 通配符
    • 自动化变量
    • 伪目标.PHONE:【命令】


编译原理

在解释 makefile 前,首先解释一下 .c 文件变成 .exe 文件要经过的四个步骤——预处理、编译、汇编和链接(参考来源):
在这里插入图片描述
windows 系统下最后生成的可执行文件为 .exe ,但 Linux 系统下为 .out 。此处的可执行文件仅针对一般 .c/.cpp 代码而言。


预处理

预处理分为四步:

  • 展开所有的宏定义 #define
  • 处理含有 # 部分的代码。如:
    1. 条件编译 “#if”、“#ifdef”、“#elif”、“#else”、“#endif”
    2. 预编译指令 #include ,将被包含的头文件插入到该编译指令的位置。(这个过程是递归进行的,因为被包含的文件可能还包含了其他文件)
  • 删除所有的注释 “//”“/* */”
  • 添加行号和文件名标识,方便后续编译时 编译器产生调试用的行号 以及 在产生编译错误或警告时能够显示行号。
  • 保留所有的 #pragma 编译指令,因为编译器需要使用它们。

编译

编译过程是整个程序构建的核心部分,编译成功,会将源代码由 文本形式转换成机器语言 ,编译过程就是把预处理完的文件进行一系列 词法分析、语法分析、语义分析以及优化后生成相应的汇编代码文件(.s)。

  • 词法分析: 使用一种叫做 lex 的程序实现词法扫描,它会按照用户之前描述好的词法规则将输入的字符串分割成一个个记号。产生的记号一般分为:关键字、标识符、字面量(包含数字、字符串等)和特殊符号(运算符、等号等),然后他们放到对应的表中。

  • 语法分析: 语法分析器根据用户给定的语法规则,将词法分析产生的记号序列进行解析,然后将它们构成一棵语法树。对于不同的语言,只是其语法规则不一样。用于语法分析也有一个现成的工具,叫做:yacc。

  • 语义分析: 语法分析完成了对表达式语法层面的分析,但是它不了解这个语句是否真正有意义。有的语句在语法上是合法的,但是却是没有实际的意义,比如说两个指针的做乘法运算,这个时候就需要进行语义分析,但是编译器能分析的语义也只有静态语义。

    1. 静态语义:在编译期就可以确定的语义。 通常包括声明与类型的匹配、类型的转换。比如当一个浮点型的表达式赋值给一个整型的表达式时,其中隐含一个从浮点型到整型的转换,而语义分析就需要完成这个转换,而将一个浮点型的表达式赋值给一个指针,这肯定是不行的,语义分析的时候就会发现两者类型不匹配,编译器就会报错。
    2. 动态语义:只有在运行期才能确定的语义。 比如说两个整数做除法,语法上没问题,类型也匹配,听着好像没毛病,但是,如果除数是0的话,这就有问题了,而这个问题事先是不知道的,只有在运行的时候才能发现他是有问题的,这就是动态语义。
  • 中间代码生成: 初始代码是可以进行优化的,对于一些在编译期间就能确定的值,可以直接直接进行处理,比如说 2+6,在编译期间就可以确定他的值为8了,但是直接在语法上进行优化的话比较困难,这时优化器会先将语法树转成中间代码。中间代码一般与目标机器和运行环境无关。(不包含数据的尺寸、变量地址和寄存器的名字等)。中间代码在不同的编译器中有着不同的形式,比较常见的有三地址码和P-代码。
    中间代码使得编译器可以分为前端和后端。编译器前端负责产生于机器无关的中间代码,编译器后端将中间代码换成机器代码。

  • 目标代码生成与优化: 代码生成器将中间代码转成机器代码,这个过程是依赖于目标机器的,因为不同的机器有着不同的字长、寄存器、数据类型等。
    最后目标代码优化器对目标代码进行优化,比如选择合适的寻址方式、使用唯一来代替乘除法、删除出多余的指令等。


汇编

汇编过程调用 汇编器 as 来完成,将汇编代码转换成机器可以执行的指令,每一个汇编语句几乎都对应一条机器指令。

使用命令 as hello.s -o hello.o 或者使用 gcc -c hello.s -o hello.o 来执行汇编,对应生成的文件是 .o 文件。


链接

链接的主要内容就是将各个模块之间相互引用的部分正确的衔接起来。它的工作就是把一些指令对其他符号地址的引用加以修正。

链接过程主要包括了地址和空间分配、符号决议和重定向:

  • 符号决议: 有时候也被叫做符号绑定、名称绑定、名称决议、或者地址绑定,其实就是指用符号来去标识一个地址。

              比如说 int a = 6;这样一句代码,用a来标识一个块4个字节大小的空间,空间里边存放的内容就是4.
    
  • 重定位: 重新计算各个目标的地址过程叫做重定位。

链接有两种模式:

  • 静态链接: 程序运行前,将每个模块的源代码文件编译成目标文件(Linux:.o Windows:.obj),然后将 目标文件 和 库 一起链接形成最后的可执行文件。

    库其实就是一组目标文件的包,就是一些最常用的代码变异成目标文件后打包存放。最常见的库就是运行时库,它是支持程序运行的基本函数的集合。

  • 动态链接: 程序运行期间,系统调用动态链接器(ld-linux.so)自动链接的过程。

举例描述:

  • 静态链接: 如果链接到可执行文件中的是 静态连接库 libmyprintf.a ,那么 虚拟内存代码段中的 .rodata 节区 在链接后需要被重定位到一个绝对的虚拟内存地址,以便程序运行时能够正确访问该节区中的字符串信息。
  • 动态链接: 而对于puts,因为它是动态连接库 libc.so 中定义的函数,所以会在程序运行时通过 动态符号链接 找出 puts 函数内存 中的地址,以便程序调用该函数。

gcc的常用命令参数

上面提到的四个步骤可以由 编程语言译器 gcc 来完成,gcc软件 通过 gcc这条命令 来实现各种功能,下面来看一下 gcc命令 的常用选项:

  1. 无选项: 编译链接
gcc test.c // 会默认生成a.out可执行程序
  1. -o :对生成的目标进行重命名,gcc 编译出来的默认文件名是 a.out
gcc test.c -o test  // 会生成名字是test可执行文件而不是默认的a.out
  1. -E :进行预处理,不生成文件, 需要通过 -o 把它重定向到一个输出文件里面。
gcc -E test.c -o test.i //会生成test.i文件
  1. -C :在预处理的时候不删除注释信息,一般和 -E 使用。
  2. -S :进行预处理、编译,生成 .s 文件
gcc -S test.c //会生成test.s文件
  1. -c :进行预处理、编译、和汇编,生成二进制(机器指令).o 文件。
gcc -c test.c //会生成test.o文件
  1. -O :使用编译优化级别1编译程序。级别为0~3(0即无优化),级别越大优化效果越好,但编译时间越长。
gcc -O1 test.c -o test
  1. -g :在编译的时候加入 debug 调试信息,用于 gdb 调试
  2. -pipe :使用管道代替编译中的临时文件。
gcc -pipe -o test test.c
  1. -include file :包含某个代码。相当于在文件中加入 #include<file>
gcc test.c -include /root/file.h
  1. -Idir :当你使用 #include”file” 的时候:

    如果使用 -I 指定了目录,gcc/g++ 会先在指定的目录查找;否则,在当前目录查找指定的头文件。

    如果没有找到,回到默认的头文件目录查找。

  2. -idirafter dir :在 -I 的目录里面查找失败,则到这个目录里面查找。

  3. -llibrary :定制编译的时候使用的库。

gcc -lpthread test.c // 在编译的时候要依赖pthread这个库
  1. -Ldir :指定编译的时候搜索库的路径。如果有自己的库,可以用它来定制搜索目录,否则编译器只在标准库目录里面找。dir 是目录的名字。

  2. -M :生成文件关联信息。包含目标文件所依赖的所有源代码。

gcc -M hello.c
  1. -MM :和 -M 一样,只不过忽略由 #include 所造成的依赖关系。
  2. -MD :和 -M 相同,只不过将输出导入到 .d 文件里面。
  3. -MMD :和 -MM 相同,将输出导入到 .d 文件里面。
  4. -static :链接时使用静态链接,但是要保证系统中有静态库。编译出来的东西,一般都很大。
  5. -share :此选项尽量的使用动态库,所以生成文件比较小,但是必须是系统有动态库。
  6. -shared :生成共享目标文件,通常用在建立共享库。
gcc -shared test.c -o libtest.so // 编译动态库
  1. -w :不生成任何警告信息。
  2. -Wall :生成所有警告信息。

make 和 Makefile 的概念

推荐一个非常全的关于 Makefile 的文章:跟我一起学写 Makefile

在我们日常写代码中,一个工程的源文件不计其数, 按照类型、功能、模块等分别放在若干个目录中,这时候我们就可以利用 Makefile 来指定哪些文件先编译,哪些后编译,以及更复杂的操作。

make 是一个命令工具,它解释 Makefile 中的指令。我们只需要在 Makefile 里指定所有的操作,再用 make 这个操作,即可让整个工程自动编译。


makefile 的格式如下:

target : prerequisitescommand
  • target: 目标文件 ,可以是多个文件,以空格分开,可以使用通配符。可以是 Object File执行文件 。甚至还可以是一个 标签(Label),如:clean
  • prerequisites: target依赖对象 。如果其中的 某个文件 要比 目标文件 要新,那么,目标文件 就被认为是 过时的 ,需要重新生成。
  • command: 命令行 ,如果其不与 target:prerequisites 在一行,那么,必须以 [Tab键] 开头,如果在一行,那么可以用分号做为分隔。

一般来说,make会以UNIX的标准Shell,也就是/bin/sh来执行命令。


写一个 makefile 文件为例:
在这里插入图片描述

目标程序:
在这里插入图片描述

执行 make 指令:
在这里插入图片描述
这样就生成了 .i,.s.o.out 文件。那么 make 是怎么运行的呢?


make的运行

  1. 在当前目录下依次找三个文件—— GNUmakefilemakefileMakefile 。其按顺序找这三个文件,一旦找到,就开始读取这个文件并执行。

也可以给 make 命令 指定一个 特殊名字Makefile 。这需要使用 make-f 或是 --file 参数( --makefile 参数也行)。例如,我们有个 Makefile 的名字是 hchen.mk ,则可以这样执行 make 命令:

make –f hchen.mk

如果在 make 的命令行中,不只一次地使用了 -f 参数,那么,所有指定的 Makefile 将会被连在一起传递给 make 执行。

  1. 接下来,它会找文件中的第一个 target (上面例子中的 test ),并把这个目标文件作为最终生成的文件。
  2. 如果 test 文件尚未生成;或是虽然 test 已经生成,但后面的依赖对象 test.o 文件的最后修改时间要比 test 这个文件新(可以用命令 touch 测试),那么,make 就会重新生成 test 这个文件。
  3. 如果 test 所依赖的 test.o 文件不存在,那么 make 会在当前文件中找目标文件为 test.o 的规则,如果找到则再根据那一个规则生成test.o文件。
  4. 如果没有目标文件为 test.o 的规则,则提前退出;否则,生成 test 文件并退出。

这就是整个 make 的运行过程,make 会一层又一层地去找文件的依赖关系,直到:

  • 最终编译出第一个目标文件(默认目标)并返回退出码;
  • 或者因为缺少必要规则而直接返回退出码。

make命令执行后有三个退出码:

  • 0 :表示成功执行。
  • 1 :如果 make 运行时出现任何错误,返回 1
  • 2 :如果你使用了 make-q 参数,导致一些目标不需要更新,那么返回 2

而对于所定义的命令的错误,或是编译不成功,make根本不理。


通配符

可以通过通配符来简化命令行:

  • ~ :Unix下, ~/test 表示当前用户的 $HOME 目录下的 test 目录。而 ~hchen/test 则表示用户 hchen 的宿主目录下的 test 目录。而在 Windows 或是 MS-DOS下用户没有宿主目录 ,那么波浪号所指的目录则根据环境变量 HOME 而定。(make支持UNIX下的通配符用法)
  • * :表示任意长度的字符串,*.c 表示所有后缀为c的文件。而当文件名中有通配符,如: ~ ,那么可以用转义字符 \ ,如 \~ 来表示真实的 ~ 字符。
  • ? :表示任意一个字符串。

自动化变量

shell 中的 自动化变量(又名:特殊变量) ,make 也是支持的,经常用到下面前三个自动化变量

  • $@目标对象 。在模式规则中,如果有多个目标,那么, $@ 就是匹配于目标中模式定义的集合。
  • $^ :所有 依赖对象 ,以空格分隔。如果在依赖目标中有多个重复的,那么这个变量会去除重复的依赖目标,只保留一份。
  • $< :所有 依赖对象第一个 。如果依赖目标是以 模式(即 % )定义的,那么 $< 将是符合模式的一系列的文件集。注意,其是一个一个取出来的。
  • $? :所有比 目标对象 依赖对象 的集合。以空格分隔。
  • $+ : 这个变量很像 $^ ,也是所有 依赖对象 的集合。只是它不去重。
  • $% :仅当 目标对象 是函数库文件中、表示规则中的目标成员名。例如,如果一个目标是 foo.a(bar.o) ,那么, $% 就是 bar.o$@ 就是 foo.a 。如果目标不是函数库文件(Unix下是 .a ,Windows下是 .lib ),那么,其值为空。
  • $* :这个变量表示目标模式中 % 及其之前的部分。(如果 目标对象dir/a.foo.b ,并且 目标对象模式a.%.b ,那么, $* 的值就是 dir/a.foo 。)
    • 这个变量对于构造有关联的文件名是比较有用的。(如果 目标对象 中没有 模式 的定义,那么 $* 也就不能被推导出,但是,如果 目标文件 的后缀是 make 所识别的,那么 $* 就是除了后缀的那一部分。)

例如:如果 目标对象foo.c ,因为 .cmake 所能识别的后缀名,所以, $* 的值就是 foo 。这个特性是 GNU make 的,很有可能不兼容于其它版本的 make ,所以,尽量避免使用 $* ,除非是在 隐含规则 或是 静态模式 中。如果 目标对象 中的后缀是 make 所不能识别的,那么 $* 就是空值。

我们可以利用 自动化变量 简化 makefile 文件:
在这里插入图片描述
执行 make 命令:
在这里插入图片描述

我们还能进一步再简化,可以利用通配符来表示,在多个 目标对象依赖对象命令行 都相似时,利用通配符 % 来减少工作量,这样就可以不用一个个写出每个文件的生成规则了。


伪目标.PHONE:【命令】

.PHONE: [命令]  // 声明伪目标,无论目标是否最新,每次都重新生成。

举个 伪目标 的例子:

clean:rm *.o temp

既然我们生成了许多编译文件,那么我们也应该提供一个清除它们的 目标 以备完整地重编译。 (以“make clean”来使用该目标)

之所以将 clean 称为 伪目标 , 是因为我们并不生成 clean 这个文件。伪目标 并不是一个 文件 ,只是一个 标签 ,由于 伪目标 不是 文件 ,所以 make 无法生成它的 依赖对象 ,无法决定它是否要执行 命令行 。我们只有显式地指明这个 目标 才能让其生效。当然,伪目标 的取名不能和 文件名 重名,不然其就失去了 伪目标 的意义了。

因此我们需要用 .PHONY 声明 伪目标 ,从而区分 伪目标目标文件

.PHONY : clean

而只要有 .PHONY:clean 这个声明,不管是否有 clean 文件,只要执行 make clean 命令,就会运行 clean 。因此,我们要在声明后面跟上 clean 的具体内容:

.PHONY : clean
clean :rm *.o temp

通常需要生成的程序不会设置伪对象,因为每个项目的构建需要很长的时间,所以尽可能判断不需要生成就不用重新生成。

伪目标一般没有依赖的文件。但是,我们也可以为伪目标指定所依赖的文件。

一个示例就是,如果你的 Makefile 需要一口气生成若干个可执行文件,但你只想简单地敲一个 make 完事,并且,所有的目标文件都写在一个 Makefile 中,那么你可以这样做:

all : prog1 prog2 prog3
.PHONY : allprog1 : prog1.o utils.occ -o prog1 prog1.o utils.oprog2 : prog2.occ -o prog2 prog2.oprog3 : prog3.o sort.o utils.occ -o prog3 prog3.o sort.o utils.o

Makefile 中的第一个目标会被作为其默认目标。 我们声明了一个 all 的伪目标,其依赖于其它三个目标。由于 默认目标总是被执行的 ,而上面的 Makefile 文件中的第一个目标(默认目标) all 又是一个伪目标。因此 all 是一定会被执行的,但又因为伪目标只是一个标签不会生成文件,所以不会有 all 文件产生。于是,其它三个目标的规则总是会被执行。也就达到了我们一口气生成多个目标的目的。 .PHONY : all 声明 all 这个目标为 伪目标 。(注:这里的显式 .PHONY : all 不写的话一般情况也可以正确的执行,这样 make 可通过隐式规则推导出, all 是一个伪目标,执行 make 不会生成 all 文件,而是执行后面的多个目标。建议:显式写出是一个好习惯。)

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

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

相关文章

Linux | 进程概念、进程状态(僵尸进程、孤儿进程、守护进程)、进程地址空间

文章目录进程和程序操作系统如何控制和调度程序进程控制块–PCB子进程进程状态僵尸进程孤儿进程守护进程&#xff08;精灵进程&#xff09;进程地址空间引言页表进程和程序 程序&#xff1a; 一系列有序的指令集合&#xff08;就是我们写的代码&#xff09;。进程&#xff1a;…

Linux 进程控制 :进程创建,进程终止,进程等待,程序替换

文章目录进程创建进程等待程序替换进程终止进程创建 fork函数&#xff1a; 操作系统提供的创建新进程的方法&#xff0c;父进程通过调用 fork函数 创建一个子进程&#xff0c;父子进程代码共享&#xff0c;数据独有。 当调用 fork函数 时&#xff0c;通过 写时拷贝技术 来拷贝…

Linux 内存管理 | 连续分配方式 和 离散分配方式

文章目录前言连续分配单一连续分配分区式分配固定分区分配动态分区分配可重定位分区分配离散分配分段分页多级页表快表(TLB)段页式Linux前言 Linux 内存管理 | 虚拟内存管理&#xff1a;虚拟内存空间、虚拟内存分配 Linux 内存管理 | 物理内存、内存碎片、伙伴系统、SLAB分配器…

操作系统 | 用户态和内核态的切换(中断、系统调用与过程(库函数)调用)

文章目录中断过程调用系统调用过程调用和系统调用的区别中断 用户态、内核态之间的切换是怎么实现的? 用户态→内核态 是通过中断实现的。并且 中断是唯一途径 。核心态→用户态 的切换是通过执行一个特权指令&#xff0c;将程序状态字 (PSW) 的标志位设置为 用户态 。 中断…

管道实现父子进程的信息传递(二)【标准流和其文件描述符、fwrite函数、perror函数】

文章目录代码实现标准流 和 标准流文件描述符代码中用到的函数fwrite()perror()在复习进程间的通信方式时又写了一遍&#xff0c;和 管道实现父子进程的信息传递&#xff08;一&#xff09;【fork函数、pipe函数、write/read操作、wait函数】 的区别不是特别大&#xff0c;只是…

命名管道实现进程的信息传递【mkfifo函数、open函数】

文章目录代码实现mkfifo函数open函数代码实现 #include<fcntl.h> // open() #include<sys/wait.h> // wait() #include<sys/types.h> // mkfifo() #include<sys/stat.h> // mkfifo() #include<iostream> #include<unistd.h> // fork()usi…

Linux 进程 | 进程间的通信方式

文章目录管道匿名管道 pipe命名管道 FIFO共享内存共享内存的使用流程&#xff1a;消息队列信号量套接字在之前的博客中讲过&#xff0c;虚拟空间出现的其中一个目的就是解决 进程没有独立性&#xff0c;可能访问同一块物理内存 的问题。因为这种独立性&#xff0c;进程之间无法…

Linux网络编程 | socket介绍、网络字节序与主机字节序概念与两者的转换、TCP/UDP 连接中常用的 socket 接口

文章目录套接字socket 地址通用 socket 地址专用 socket 地址网络字节序与主机字节序地址转换TCP/UDP 连接中常用的 socket 接口套接字 什么是套接字&#xff1f; 所谓 套接字 (Socket) &#xff0c;就是对网络中 不同主机 上的应用进程之间进行双向通信的端点的抽象。 UNIX/L…

网络协议分析 | 传输层 :史上最全UDP、TCP协议详解,一篇通~

文章目录UDP概念格式UDP如何实现可靠传输基于UDP的应用层知名协议TCP概念格式保证TCP可靠性的八种机制确认应答、延时应答与捎带应答超时重传滑动窗口滑动窗口协议后退n协议选择重传协议流量控制拥塞控制发送窗口、接收窗口、拥塞窗口快速重传和快速恢复连接管理机制三次握手连…

JDom,jdom解析xml文件

1.要解析的文件模板如下&#xff1a; <?xml version"1.0" encoding"GBK"?> <crsc> <data><举报信息反馈><R index"1"><举报编号>1</举报编号><状态>1</状态><答复意见>填写…

网络协议分析 | 应用层:HTTP协议详解、HTTP代理服务器

文章目录概念URLHTTP协议的特点HTTP协议版本格式请求报文首行头部空行正文响应报文首行头部空行正文Cookie与SessionHTTP代理服务器正向代理服务器反向代理服务器透明代理服务器概念 先了解一下 因特网&#xff08;Internet&#xff09; 与 万维网&#xff08;World Wide Web&…

MySQL命令(一)| 数据类型、常用命令一览、库的操作、表的操作

文章目录数据类型数值类型字符串类型日期/时间类型常用命令一览库的操作显示当前数据库创建数据库使用数据库删除数据库表的操作创建表显示当前库中所有表查看表结构删除表数据类型 mysql 的数据类型主要分为 数值类型、日期/时间类型、字符串类型 三种。 数值类型 数值类型可…

C++ 继承 | 对象切割、菱形继承、虚继承、对象组合

文章目录继承继承的概念继承方式及权限using改变成员的访问权限基类与派生类的赋值转换回避虚函数机制派生类的默认成员函数友元与静态成员多继承菱形继承虚继承组合继承 继承的概念 继承可以使得子类具有父类的属性和方法或者重新定义、追加属性和方法等。 当创建一个类时&…

博弈论 | 博弈论简谈、常见的博弈定律、巴什博弈

文章目录博弈论什么是博弈论&#xff1f;博弈的前提博弈的要素博弈的分类非合作博弈——有限两人博弈囚徒困境合作博弈——无限多人博弈囚徒困境常见的博弈定律零和博弈重复博弈智猪博弈斗鸡博弈猎鹿博弈蜈蚣博弈酒吧博弈枪手博弈警匪博弈海盗分金巴什博弈博弈论 什么是博弈论…

MySQL命令(二)| 表的增删查改、聚合函数(复合函数)、联合查询

文章目录新增 (Create)全列插入指定列插入查询 (Retrieve)全列查询指定列查询条件查询关系元素运算符模糊查询分页查询去重&#xff1a;DISTINCT别名&#xff1a;AS升序 or 降序更新 (Update)删除 (Delete)分组&#xff08;GROUP BY&#xff09;联合查询内连接&#xff08;inne…

MySQL | 数据库的六种约束、表的关系、三大范式

文章目录数据库约束NOT NULL&#xff08;非空约束&#xff09;UNIQUE&#xff08;唯一约束&#xff09;DEFAULT&#xff08;缺省约束&#xff09;PRIMARY KEY&#xff08;主键约束&#xff09;AUTO_INCREMENT 自增FOREIGN KEY&#xff08;外键约束&#xff09;CHECK&#xff08…

哈希 :哈希冲突、负载因子、哈希函数、哈希表、哈希桶

文章目录哈希哈希&#xff08;散列&#xff09;函数常见的哈希函数字符串哈希函数哈希冲突闭散列&#xff08;开放地址法&#xff09;开散列&#xff08;链地址法/拉链法&#xff09;负载因子以及增容对于闭散列对于开散列结构具体实现哈希表&#xff08;闭散列&#xff09;创建…

C++ 泛型编程(一):模板基础:函数模板、类模板、模板推演成函数的机制、模板实例化、模板匹配规则

文章目录泛型编程函数模板函数模板实例化隐式实例化显式实例化函数模板的匹配规则类模板类模板的实例化泛型编程 泛型编程旨在削减重复工作&#xff0c;如&#xff1a; 将一个函数多次重载不如将他写成泛型。 void Swap(int& left, int& right) {int temp left;lef…

你真的了解静态变量、常量的存储位置吗?

文章目录引言C对内存的划分如何落实在Linux上自由存储区和堆之间的问题栈常量区静态存储区静态局部变量静态局部变量、静态全局变量、全局变量的异同macOS系统的测试结果总结引言 在动态内存的博客中&#xff0c;我提到&#xff1a; 在Linux 内存管理的博客中&#xff0c;我提…

C++ 泛型编程(二):非类型模板参数,模板特化,模板的分离编译

文章目录非类型模板参数函数模板的特化类模板的特化全特化偏特化部分参数特化参数修饰特化模板分离编译解决方法非类型模板参数 模板的参数分为两种&#xff1a; 类型参数&#xff1a; 则是我们通常使用的方式&#xff0c;就是在模板的参数列表中在 class 后面加上参数的类型…