• 【C++】template方法undefined reference to(二):C++代码的编译过程


    接着上一篇文章讲,为什么单文件会编译失败。

    上一篇文章:【C++】template方法undefined reference to

    一个c/c++文件要经过预处理、编译、汇编和链接才能变成可执行文件。接下来,我们用 gcc(g++) 完成这个过程。

    预处理

    预处理就是将要包含(include)的文件插入原文件中、将宏定义展开、根据条件编译命令选择要使用的代码,最后将这些东西输出到一个“.i”文件中等待进一步处理。这个过程是不检查语法错误的,相当于只进行了替换。

    gcc 的预处理命令为

    g++ -E 源文件
    
    • 1

    我们将这次的两个源文件分别预处理,目标文件拥有相同的文件名,但扩展名改为.i

    g++ -E -o test.i test.cpp
    g++ -E -o templateFunc.i templateFunc.cpp
    
    • 1
    • 2

    templateFunc.h

    template <typename T>
    T addTwo(T &a, T &b);
    
    • 1
    • 2

    templateFunc.cpp

    #include "templateFunc.h"
    
    template <typename T>
    T addTwo(T &a, T &b)
    {
        return a + b;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    templateFunc.i

    # 0 "templateFunc.cpp"
    # 0 ""
    # 0 ""
    # 1 "templateFunc.cpp"
    # 1 "templateFunc.h" 1
    template <typename T>
    T addTwo(T &a, T &b);
    # 2 "templateFunc.cpp" 2
    
    template <typename T>
    T addTwo(T &a, T &b)
    {
        return a + b;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    显然只是把头文件在源文件里展开了。

    另一个test.cpp由于includeiostream这个大库,总文件行数达到了 34000 多行… …
    太多了就不展开了

    编译

    编译就是把C/C++代码(比如上述的“.i”文件)“翻译”成汇编代码。这一步如果有语法错误会检查出来的。

    gcc的编译命令为:

    g++ -S
    
    • 1

    此次将输出的文件,扩展名为.s

    g++ -S -o test.s test.i
    g++ -S -o templateFunc.s templateFunc.i
    
    • 1
    • 2

    这一步输出的文件使用汇编语言,我看不懂,就不解释了
    s

    汇编

    汇编就是将第二步输出的汇编代码翻译成符合一定格式的机器代码,输出的文件为.o文件

    gcc的汇编命令为:

    g++ -c
    
    • 1

    分别将两个编译完的.s文件汇编为.o文件

    g++ -c -o test.o test.s
    g++ -c -o templateFunc.o templateFunc.s
    
    • 1
    • 2

    机器代码就没法用编辑器看了,我们直接进行下一步吧。

    链接

    链接就是将上步生成的OBJ文件和系统库的OBJ文件、库文件链接起来,最终生成了可以在特定平台运行的可执行文件。

    gcc的链接命令为:

    g++ -o
    
    • 1

    我们将两个.o文件链接为一个可执行文件

    g++ -o test test.o templateFunc.o
    
    • 1

    然后又报了一个熟悉的错误:

    C:/tools/mingw-w64-gcc-13.2-stable-r40/bin/../lib/gcc/i686-w64-mingw32/13.2.0/../../../../i686-w64-mingw32/bin/ld.exe: test.o:test.cpp:(.text+0x5b): undefined reference to `int addTwo<int>(int&, int&)'
    C:/tools/mingw-w64-gcc-13.2-stable-r40/bin/../lib/gcc/i686-w64-mingw32/13.2.0/../../../../i686-w64-mingw32/bin/ld.exe: test.o:test.cpp:(.text+0xa1): undefined reference to `double addTwo<double>(double&, double&)'
    collect2.exe: error: ld returned 1 exit status
    
    • 1
    • 2
    • 3

    回溯

    我们将模板实例化的代码加入源文件:

    template int addTwo<int>(int &a, int &b);
    template double addTwo<double>(double &a, double &b);
    
    • 1
    • 2

    同样执行上面的流程,可以发现,两次生成的汇编代码完全不一样:

    未实例化

    	.file	"templateFunc.cpp"
    	.text
    	.ident	"GCC: (GNU) 13.2.0"
    
    • 1
    • 2
    • 3

    实例化

    	.file	"templateFunc.cpp"
    	.text
    	.section	.text$_Z6addTwoIiET_RS0_S1_,"x"
    	.linkonce discard
    	.globl	__Z6addTwoIiET_RS0_S1_
    	.def	__Z6addTwoIiET_RS0_S1_;	.scl	2;	.type	32;	.endef
    __Z6addTwoIiET_RS0_S1_:
    	pushl	%ebp
    	movl	%esp, %ebp
    	movl	8(%ebp), %eax
    	movl	(%eax), %edx
    	movl	12(%ebp), %eax
    	movl	(%eax), %eax
    	addl	%edx, %eax
    	popl	%ebp
    	ret
    	.section	.text$_Z6addTwoIdET_RS0_S1_,"x"
    	.linkonce discard
    	.globl	__Z6addTwoIdET_RS0_S1_
    	.def	__Z6addTwoIdET_RS0_S1_;	.scl	2;	.type	32;	.endef
    __Z6addTwoIdET_RS0_S1_:
    	pushl	%ebp
    	movl	%esp, %ebp
    	movl	8(%ebp), %eax
    	fldl	(%eax)
    	movl	12(%ebp), %eax
    	fldl	(%eax)
    	faddp	%st, %st(1)
    	popl	%ebp
    	ret
    	.ident	"GCC: (GNU) 13.2.0"
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    而实例化的代码,最后成功链接成了可执行文件。
    成功
    所以可以确认两件事:

    1. VS Code 的提示并不准确。
    2. 之前编译失败,是因为只编译了单个文件。
  • 相关阅读:
    如何入门Python——学习Python的指南针
    docker-java 用Java操作docker创建容器并运行运行容器
    C++ 在函数中定义函数
    nginx----(1)nginx的单机安装
    SpringBoot通过@Cacheable注解实现缓存功能
    Oracle 数据库用户创建、重启、导入导出
    houdini 使用HDA Processor 实现处理HDA输入输出
    功能基础篇1——壹文搞清编码、解码、字符集、编码方式、字符编码、编码方式
    Mac系统下Gauge初体验
    B. Find The Array
  • 原文地址:https://blog.csdn.net/qq_37387199/article/details/136789383