C++基础:clang的分步编译-了解build细节
文章目录
- Clang
- 预处理器(Preprocessor)
- 编译器(compiler)
- 汇编器(assembler)
- 链接器(Linker)
Clang
Clang(发音为/ˈklæŋ/类似英文单字clang) 是一个C、C++、Objective-C和Objective-C++编程语言的编译器前端。它采用了LLVM作为其后端,由LLVM2.6开始,一起发布新版本。它的目标是提供一个GNU编译器套装(GCC)的替代品,支持了GNU编译器大多数的编译设置以及非官方语言的扩展。作者是克里斯·拉特纳(Chris Lattner),在苹果公司的赞助支持下进行开发,而源代码许可是使用类BSD的伊利诺伊大学厄巴纳-香槟分校开源码许可。
Clang项目包括Clang前端和Clang静态分析器等。
之前我非常好奇,编程中从编写源代码到可执行文件的过程,很多集成开发环 (IDE)(比如Code::Blocks ,Microsoft Virtual Studio,Dev-Cpp等)都是先build一下,再run就完事。详细一点的涉及到编译器(Compiler) 和链接器(Linker),虽然又看了很多更为详细的文章,但始终还是缺乏实践操作,在捣鼓clang这玩意的时候,发现了竟然还有分步编译,这使得我能上手操作,真正直观感受一下这一过程:源代码(source code)→ 预处理器(preprocessor)→ 编译器(compiler)→ 汇编程序(assembler)→ 目标代码(object code)→ 链接器(linker)→ 可执行文件(executables),最后打包好的文件就可以给电脑去判读执行了。
下面看看Clang的分步编译是怎么搞得
# 预处理(Preprocessing)
clang++ -E hello.cpp -o hello.ii# 编译为汇编代码(Compilation)
clang++ -S hello.ii -o hello.s# 汇编为目标文件(Assembly)
clang++ -c hello.s -o hello.o# 链接为可执行文件(Linking)
clang++ hello.o -o hello
先温习一下几个术语:
源代码(英语:source code),是指一系列人类可读的计算机语言指令
目标代码(英语:Object code)指计算机科学中编译器或汇编器处理源代码后所生成的代码,它一般由机器代码或接近于机器语言的代码组成。[1]目标文件(英语:Object file)即存放目标代码的计算机文件,它常被称作二进制文件(Binaries)。
字节码(英语:Bytecode)通常指的是已经经过编译,但与特定机器代码无关,需要解释器转译后才能成为机器代码的中间代码。字节码通常不像源码一样可以让人阅读,而是编码后的数值常量、引用、指令等构成的序列。
机器语言(machine language)是一种指令集的体系。这种指令集称为机器代码(machine code),是计算机的CPU或GPU可直接解读的资料。
预处理器(Preprocessor)
在计算机科学中,预处理器是程序中处理输入数据,产生能用来输入到其他程序的数据的程序。继承于C语言的C++的C预处理器,是将采用以’#'为行首的指示,将相关的代码包含进来。
main.cpp
// 将 #include <iostream> 替换为iostream头文件的全部内容
// 可能从几行代码变成几千行代码
#include <iostream>
void a()
{std::cout << " a here " << '\n';
}void b()
{a();
}void c()
{a();b();
}void d()
{a();b();c();
}int main()
{d();return 0;
}
使用
clang++ -E main.cpp -o main.ii
调用预处理器处理源文件main.cpp之后,我打开处理之后的main.ii看了看,加上原来的总共得有37040行代码,下面截取了开头和末尾一部分代码,应该是#include 告诉编译器把C++标准库iostream里面的内容包含进来了。
main.ii
# 1 "main.cpp"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 468 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "main.cpp" 2
# 1 "/usr/lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14/iostream" 1 3
# 37 "/usr/lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14/iostream" 3# 1 "/usr/lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14/bits/requires_hosted.h" 1 3
# 31 "/usr/lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14/bits/requires_hosted.h" 3
# 1 "/usr/lib/gcc/x86_64-linux-gnu/14/../../../../include/x86_64-linux-gnu/c++/14/bits/c++config.h" 1 3
# 34 "/usr/lib/gcc/x86_64-linux-gnu/14/../../../../include/x86_64-linux-gnu/c++/14/bits/c++config.h" 3
# 308 "/usr/lib/gcc/x86_64-linux-gnu/14/../../../../include/x86_64-linux-gnu/c++/14/bits/c++config.h" 3
......
void a()
{std::cout << " a here " << '\n';
}void b()
{a();
}void c()
{a();b();
}void d()
{a();b();c();
}int main()
{d();return 0;
}
我注意到 iostream 头文件的实际路径是 “/usr/lib/gcc/x86_64-linux-gnu/14/…/…/…/…/include/c++/14/iostream”。然后我尝试切换工作目录(cd)到了 “/usr/lib/gcc/x86_64-linux-gnu/14/include/”,但我发现这个路径下并没有我要找的东西,或者这个路径本身不存在。我感到困惑。经过查询原来中间的…也代表当前目录的父目录,于是我算了一下,有4组…,从/14推到了/usr下,然后输入后面的信息,终于找到了iostream
iostream (only read) 共计88行
1 // Standard iostream objects -*- C++ -*-23 // Copyright (C) 1997-2024 Free Software Foundation, Inc.4 //5 // This file is part of the GNU ISO C++ Library. This library is free6 // software; you can redistribute it and/or modify it under the7 // terms of the GNU General Public License as published by the8 // Free Software Foundation; either version 3, or (at your option)......33 #ifndef _GLIBCXX_IOSTREAM34 #define _GLIBCXX_IOSTREAM 13536 #pragma GCC system_header3738 #include <bits/requires_hosted.h> // iostreams3940 #include <bits/c++config.h>41 #include <ostream>42 #include <istream>......67 #ifdef _GLIBCXX_USE_WCHAR_T68 extern wistream wcin; ///< Linked to standard input69 extern wostream wcout; ///< Linked to standard output70 extern wostream wcerr; ///< Linked to standard error (unbuffered)71 extern wostream wclog; ///< Linked to standard error (buffered)72 #endif73 ///@}7475 // For construction of filebuffers for cout, cin, cerr, clog et. al.76 // When the init_priority attribute is usable, we do this initialization77 // in the compiled library instead (src/c++98/globals_io.cc).78 #if !(_GLIBCXX_USE_INIT_PRIORITY_ATTRIBUTE \79 && __has_attribute(__init_priority__))80 static ios_base::Init __ioinit;81 #elif defined(_GLIBCXX_SYMVER_GNU) && defined(__ELF__)82 __extension__ __asm (".globl _ZSt21ios_base_library_initv");83 #endif8485 _GLIBCXX_END_NAMESPACE_VERSION86 } // namespace8788 #endif /* _GLIBCXX_IOSTREAM */....
从38-42,iostream文件还有其他的依赖库,或许闲了可以探索一下这些库之间的依赖关系。接下来看看编译器又将main.ii带向何方?
编译器(compiler)
编译器(compiler)是一种计算机程序,它会将某种编程语言写成的源代码(原始语言)转换成另一种编程语言(目标语言)。
它主要的目的是将便于人编写、阅读、维护的高级计算机语言所写作的源代码程序,翻译为计算机能解读、运行的低阶机器语言的程序,也就是可执行文件。编译器将原始程序(source program)作为输入,翻译产生使用目标语言(target language)的等价程序。源代码一般为高级语言(High-level language),如Pascal、C、C++、C# 、Java等,而目标语言则是汇编语言或目标机器的目标代码(Object code),有时也称作机器代码(Machine code)。
使用
clang++ -S main.ii -o main.s
将预处理的main.ii文件编译成汇编代码main.s,在shell上打开main.s看看汇编是怎么样的,它是一种符号语言。
main.s
1 .text2 .file "main.cpp"3 # Start of file scope inline assembly4 .globl _ZSt21ios_base_library_initv56 # End of file scope inline assembly7 .globl _Z1av # -- Begin function _Z1av8 .p2align 4, 0x909 .type _Z1av,@function10 _Z1av: # @_Z1av11 .cfi_startproc12 # %bb.0:13 pushq %rbp14 .cfi_def_cfa_offset 1615 .cfi_offset %rbp, -1616 movq %rsp, %rbp17 .cfi_def_cfa_register %rbp18 movq _ZSt4cout@GOTPCREL(%rip), %rdi19 leaq .L.str(%rip), %rsi20 callq _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@PLT21 movq %rax, %rdi22 movl $10, %esi23 callq _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_c@PLT24 popq %rbp25 .cfi_def_cfa %rsp, 826 retq27 .Lfunc_end0:28 .size _Z1av, .Lfunc_end0-_Z1av29 .cfi_endproc30 # -- End function31 .globl _Z1bv # -- Begin function _Z1bv32 .p2align 4, 0x9033 .type _Z1bv,@function34 _Z1bv: # @_Z1bv35 .cfi_startproc36 # %bb.0:37 pushq %rbp38 .cfi_def_cfa_offset 1639 .cfi_offset %rbp, -1640 movq %rsp, %rbp41 .cfi_def_cfa_register %rbp42 callq _Z1av43 popq %rbp44 .cfi_def_cfa %rsp, 845 retq46 .Lfunc_end1:47 .size _Z1bv, .Lfunc_end1-_Z1bv48 .cfi_endproc49 # -- End function50 .globl _Z1cv # -- Begin function _Z1cv51 .p2align 4, 0x9052 .type _Z1cv,@function53 _Z1cv: # @_Z1cv54 .cfi_startproc55 # %bb.0:56 pushq %rbp57 .cfi_def_cfa_offset 1658 .cfi_offset %rbp, -1659 movq %rsp, %rbp60 .cfi_def_cfa_register %rbp61 callq _Z1av62 callq _Z1bv63 popq %rbp64 .cfi_def_cfa %rsp, 865 retq66 .Lfunc_end2:67 .size _Z1cv, .Lfunc_end2-_Z1cv68 .cfi_endproc69 # -- End function70 .globl _Z1dv # -- Begin function _Z1dv71 .p2align 4, 0x9072 .type _Z1dv,@function73 _Z1dv: # @_Z1dv74 .cfi_startproc75 # %bb.0:76 pushq %rbp77 .cfi_def_cfa_offset 1678 .cfi_offset %rbp, -1679 movq %rsp, %rbp80 .cfi_def_cfa_register %rbp81 callq _Z1av82 callq _Z1bv83 callq _Z1cv84 popq %rbp85 .cfi_def_cfa %rsp, 886 retq87 .Lfunc_end3:88 .size _Z1dv, .Lfunc_end3-_Z1dv89 .cfi_endproc90 # -- End function91 .globl main # -- Begin function main92 .p2align 4, 0x9093 .type main,@function94 main: # @main95 .cfi_startproc96 # %bb.0:97 pushq %rbp98 .cfi_def_cfa_offset 1699 .cfi_offset %rbp, -16
100 movq %rsp, %rbp
101 .cfi_def_cfa_register %rbp
102 subq $16, %rsp
103 movl $0, -4(%rbp)
104 callq _Z1dv
105 xorl %eax, %eax
106 addq $16, %rsp
107 popq %rbp
108 .cfi_def_cfa %rsp, 8
109 retq
110 .Lfunc_end4:
111 .size main, .Lfunc_end4-main
112 .cfi_endproc
113 # -- End function
114 .type .L.str,@object # @.str
115 .section .rodata.str1.1,"aMS",@progbits,1
116 .L.str:
117 .asciz " a here "
118 .size .L.str, 9
119
120 .ident "Debian clang version 19.1.7 (3+b1)"
121 .section ".note.GNU-stack","",@progbits
122 .addrsig
123 .addrsig_sym _Z1av
124 .addrsig_sym _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_c
125 .addrsig_sym _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
126 .addrsig_sym _Z1bv
127 .addrsig_sym _Z1cv
128 .addrsig_sym _Z1dv
129 .addrsig_sym _ZSt4cout
上面有很多代码,但是基本都是重复的,提取出来就是下面的代码,根据ai和自己的理解查了查了意思。
1 .text # 将后续代码放入可执行代码段(text section)
2 .file "main.cpp" # 指明源文件名,用于调试信息
3 # Start of file scope inline assembly
4 .globl _ZSt21ios_base_library_initv # 声明全局符号(通常是C++运行时库初始化例程)
5
6 # End of file scope inline assembly
7 .globl _Z1av # 声明函数a()为全局符号,允许其他文件调用
8 .p2align 4, 0x90 # 16字节对齐(2^4=16),用0x90(NOP指令)填充空隙
9 .type _Z1av,@function # 声明符号_Z1av的类型是函数
10 _Z1av: # 函数a()的入口点(标签)
11 .cfi_startproc # 开始生成调用帧信息(CFI),用于调试和异常处理
12 # %bb.0: # 基本块0的标签(由编译器生成的控制流标记)
13 pushq %rbp # 保存调用者的栈帧指针到栈上
14 .cfi_def_cfa_offset 16 # CFI: 规范帧地址(CFA)现在在原始SP+16的位置
15 .cfi_offset %rbp, -16 # CFI: 旧的RBP值保存在CFA-16的位置
16 movq %rsp, %rbp # 建立新的栈帧:将当前栈指针复制到RBP
17 .cfi_def_cfa_register %rbp # CFI: 现在使用RBP作为计算CFA的基准寄存器
18 movq _ZSt4cout@GOTPCREL(%rip), %rdi # 获取std::cout地址放入RDI(第一个参数)
19 leaq .L.str(%rip), %rsi # 计算字符串" a here "的地址放入RSI(第二个参数)
20 callq _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@PLT # 调用operator<<输出字符串
21 movq %rax, %rdi # 将返回值(cout引用)作为下一次调用的第一个参数
22 movl $10, %esi # 将数字10(换行符'\n'的ASCII码)放入ESI(第二个参数)
23 callq _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_c@PLT # 调用operator<<输出字符
24 popq %rbp # 从栈中恢复调用者的栈帧指针
25 .cfi_def_cfa %rsp, 8 # CFI: 现在CFA在RSP+8的位置(指向返回地址之前)
26 retq # 从函数返回
27 .Lfunc_end0: # 函数结束的标签
28 .size _Z1av, .Lfunc_end0_Z1av # 定义函数大小(结束地址-开始地址)
29 .cfi_endproc # 结束调用帧信息(CFI)记录
7行.globl是全局可见的意思,作用类似于c++中的全局变量,就是什么地方都可以访问它,_Z1av 就是指代标识符a,这就是为什么同一个作用域中函数名,变量名要唯一,目的是方便编译器识别。
9行 .type _Z1av,@function是关键字.type进一步告诉编译器_Z1av(a)是一个函数。
10行和27行是a函数开始和结束的地方,对应着a函数的左边花括号‘{’ 和右花括号‘}’的位置。
11行和27行是调用栈开始和结束的地方。我们听说一个函数就是一个栈,能够自动创建和销毁。而new出来的就是堆上面,需要手动创建和销毁,因此常常有跟指针相关的问题。
//todo 这里对汇编代码有点问题,稍后解决。
汇编器(assembler)
汇编语言(英语:assembly language或assembler language)是任何一种用于电子计算机、微处理器、微控制器,或其他可编程器件的低级语言。在不同的设备中,汇编语言对应着不同的机器语言指令集。
使用汇编语言编写的源代码,然后通过相应的汇编程序将它们转换成可执行的机器代码(或者称为目标代码)。这一过程被称为汇编过程。
典型的现代汇编器(assembler)建造目标代码,由解译组语指令集的助记符(Mnemonics)到操作码,并解析符号名称(Symbolic names)成为存储器地址以及其它的实体。
目标代码(英语:Object code)指计算机科学中编译器或汇编器处理源代码后所生成的代码,它一般由机器代码或接近于机器语言的代码组成。[1]目标文件(英语:Object file)即存放目标代码的计算机文件,它常被称作二进制文件(Binaries)。
在shell上使用命令行
clang++ -c main.s -o main.o
汇编语言main.s 就会编译成目标代码main.o,下面打开main.o,我也懵逼了,这是啥东西,我用vim显示行数为7行,但是我连它怎么分行的都不清楚。
main.o
1 ^?ELF^B^A^A^@^@^@^@^@^@^@^@^@^A^@>^@^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ ^E^@^@^@^@^@^@^@^@^@^@@^@^@^@^@^@@^@^K^ @^A^@UH<89>åH<8b>=^@^@^@^@H<8d>5^@^@^@^@è^@^@^@^@H<89>Ǿ2 ^@^@^@è^@^@^@^@]Ãf.^O^_<84>^@^@^@^@^@UH<89>åè^@^@^@^@]Ã^O^_D^@^@UH<89>åè^@^@^@^@è^@^@^@^@]ÃUH<89>åè^@^@^@^@è^@^@^@^@ è^@^@^@^@]Ãf.^O^_<84>^@^@^@^@^@<90>UH<89>åH<83>ì^PÇEü^@^@^@^@è^@^@^@^@1ÀH<83>Ä^P]à a here ^@^@Debian clang version 1 9.1.7 (3+b1)^@^@^@^@^@^@^@^@^T^@^@^@^@^@^@^@^AzR^@^Ax^P^A^[^L^G^H<90>^A^@^@^\^@^@^@^\^@^@^@^@^@^@^@&^@^@^@^@A^N^P<86 >^BC^M^Fa^L^G^H^@^@^@^\^@^@^@<^@^@^@^@^@^@^@^K^@^@^@^@A^N^P<86>^BC^M^FF^L^G^H^@^@^@^\^@^@^@\^@^@^@^@^@^@^@^P^@^@^@^@ A^N^P<86>^BC^M^FK^L^G^H^@^@^@^\^@^@^@|^@^@^@^@^@^@^@^U^@^@^@^@A^N^P<86>^BC^M^FP^L^G^H^@^@^@^\^@^@^@<9c>^@^@^@^@^@^@^ @^\^@^@^@^@A^N^P<86>^BC^M^FW^L^G^H^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@[^@^@^@^D^@ñÿ^@^@^@^@^@^@^@^ @^@^@^@^@^@^@^@^@^@^@^@^@^C^@^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@T^@^@^@^A^@^D^@^@^@^@^@^@^@^@^@ ^@^@^@^@^@^@^@^A ^@^@^@^P^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@0^@^@^@^R^@^B^@^@^@^@^@^@^@^@^@&^@^@^@^@^@^@^@A^@^@^@^P^@^@^@^@^@^@^@^ @^@^@^@^@^@^@^@^@^@^@^@Ì^@^@^@^P^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@<96>^@^@^@^P^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ ^@^@*^@^@^@^R^@^B^@0^@^@^@^@^@^@^@^K^@^@^@^@^@^@^@$^@^@^@^R^@^B^@@^@^@^@^@^@^@^@^P^@^@^@^@^@^@^@^^^@^@^@^R^@^B^@P^@^ @^@^@^@^@^@^U^@^@^@^@^@^@^@d^@^@^@^R^@^B^@p^@^@^@^@^@^@^@^\^@^@^@^@^@^@^@^G^@^@^@^@^@^@^@*^@^@^@^F^@^@^@üÿÿÿÿÿÿÿ^N^@ ^@^@^@^@^@^@^B^@^@^@^C^@^@^@üÿÿÿÿÿÿÿ^S^@^@^@^@^@^@^@^D^@^@^@^G^@^@^@üÿÿÿÿÿÿÿ ^@^@^@^@^@^@^@^D^@^@^@^H^@^@^@üÿÿÿÿÿÿÿ5 ^@^@^@^@^@^@^@^D^@^@^@^E^@^@^@üÿÿÿÿÿÿÿE^@^@^@^@^@^@^@^D^@^@^@^E^@^@^@üÿÿÿÿÿÿÿJ^@^@^@^@^@^@^@^D^@^@^@ ^@^@^@üÿÿÿÿÿ ÿÿU^@^@^@^@^@^@^@^D^@^@^@^E^@^@^@üÿÿÿÿÿÿÿZ^@^@^@^@^@^@^@^D^@^@^@ ^@^@^@üÿÿÿÿÿÿÿ_^@^@^@^@^@^@^@^D^@^@^@3 ^@^@^@üÿÿÿÿÿÿÿ<80>^@^@^@^@^@^@^@^D^@^@^@^K^@^@^@üÿÿÿÿÿÿÿ ^@^@^@^@^@^@^@^B^@^@^@^B^@^@^@^@^@^@^@^@^@^@^@@^@^@^@^@^@^@ ^@^B^@^@^@^B^@^@^@0^@^@^@^@^@^@^@`^@^@^@^@^@^@^@^B^@^@^@^B^@^@^@@^@^@^@^@^@^@^@<80>^@^@^@^@^@^@^@^B^@^@^@^B^@^@^@P^@ ^@^@^@^@^@^@ ^@^@^@^@^@^@^@^B^@^@^@^B^@^@^@p^@^@^@^@^@^@^@^E^H^G4 ^K^F^@_ZSt21ios_base_library_initv^@_Z1dv^@_Z1cv^@_Z1bv^@_Z1av^@.rela.text^@_ZSt4cout^@.comment^@.L.str^@main.cpp^@m ain^@.note.GNU-stack^@.llvm_addrsig^@.rela.eh_frame^@_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_c^@_ZStlsIS t11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc^@.strtab^@.symtab^@.rodata.str1.1^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^ @^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^D^A^@^@^ C^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@w^D^@^@^@^@^@^@#^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^ @;^@^@^@^A^@^@^@^F^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@@^@^@^@^@^@^@^@<8c>^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^P^@^@^@^@^@^@^@^@^ @^@^@^@^@^@^@6^@^@^@^D^@^@^@@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ð^B^@^@^@^@^@^@^H^A^@^@^@^@^@^@@ @ @ 5 ^@^@^@^B^@^@^@^H^@^@^@^@^@^@^@^X^@^@^@^@^@^@^@^T^A^@^@^A^@^@^@2^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@Ì^@^@^@^@^@^@^@ ^@^@ ^@^@^@^@^@^@^@^@^@^@^@^@^@^A^@^@^@^@^@^@^@^A^@^@^@^@^@^@^@K^@^@^@^A^@^@^@0^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@Õ^@^@^@^@^@^ @^@$^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^A^@^@^@^@^@^@^@^A^@^@^@^@^@^@^@i^@^@^@^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ù^@ ^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@<8c>^@^@^@^A^@^@p^B^@^@^@^@^@^@^@^@^@^@^ @^@^@^@^@^@^A^@^@^@^@^@^@¸^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^H^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@<87>^@^@^@^D^@^@^@@^@^@^@^@^ @^@^@^@^@^@^@^@^@^@^@ø^C^@^@^@^@^@^@x^@^@^@^@^@^@^@6 ^@^@^@^G^@^@^@^H^@^@^@^@^@^@^@^X^@^@^@^@^@^@^@y^@^@^@^CLÿo^@^@^@<80>^@^@^@^@^@^@^@^@^@^@^@^@p^D^@^@^@^@^@^@^G^@^@^@^ @^@^@^@7 ^@^@^@^@^@^@^@^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^L^A^@^@^B^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@¸^A^@^@^@^@^@^@8^A^@^@ ^@^@^@^@^A^@^@^@^D^@^@^@^H^@^@^@^@^@^@^@^X^@^@^@^@^@^@^@
最后一步就是链接器了,它做完最后的处理就会生成可执行文件,放到windows上就是exe文件,当点击exe文件运行时候出现的错误,我们叫做运行错误。
链接器(Linker)
链接器的作用是合并所有目标文件,并生成所需的输出文件(例如,可以运行的可执行文件)。这个过程称为链接。如果链接过程中的任何步骤失败,链接器都会生成一条描述该问题的错误消息,然后中止运行。
首先,链接器读取编译器生成的(经过前面三个步骤)每个目标文件并确保它们有效。
其次,链接器确保所有跨文件依赖关系都得到正确解析。例如,如果您在一个 .cpp 文件中定义了某个内容,然后在另一个 .cpp 文件中使用它,链接器会将这两个内容连接起来。如果链接器无法将对某个内容的引用与其定义连接起来,则会收到链接器错误,并且链接过程将中止。
第三,链接器通常链接一个或多个库文件,这些库文件是已“打包”以供其他程序重用的预编译代码的集合。
最后,链接器输出所需的输出文件。通常,这是一个可以启动的可执行文件(但如果您的项目设置了库文件,也可以是一个库文件)。
在shell上使用命令行
clang++ main.o -o main
链接器就会将目标代码main.o处理成可执行文件main,同样也晦涩难懂。
main
^?ELF^B^A^A^@^@^@^@^@^@^@^@^@^C^@>^@^A^@^@^@`^P^@^@^@^@^@^@@^@^@^@^@^@^@^@89^@^@^@^@^@^@^@^@^@^@@^@8^@^N^@@^@^_^@^^^ @^F^@^@^@^D^@^@^@@^@^@^@^@^@^@^@@^@^@^@^@^@^@^@@^@^@^@^@^@^@^@^P^C^@^@^@^@^@^@^P^C^@^@^@^@^@^@^H^@^@^@^@^@^@^@^C^@^@ ^@^D^@^@^@<94>^C^@^@^@^@^@^@<94>^C^@^@^@^@^@^@<94>^C^@^@^@^@^@^@^\^@^@^@^@^@^@^@^\^@^@^@^@^@^@^@^A^@^@^@^@^@^@^@^A^@ ^@^@^D^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@¨^G^@^@^@^@^@^@¨^G^@^@^@^@^@^@^@^P^@^@^@^@^@^@^A^@^@^@^E ^@^@^@^@^P^@^@^@^@^@^@^@^P^@^@^@^@^@^@^@^P^@^@^@^@^@^@å^A^@^@^@^@^@^@å^A^@^@^@^@^@^@^@^P^@^@^@^@^@^@^A^@^@^@^D^@^@^@ ^@ ^@^@^@^@^@^@^@ ^@^@^@^@^@^@^@ ^@^@^@^@^@^@¬^A^@^@^@^@^@^@¬^A^@^@^@^@^@^@^@^P^@^@^@^@^@^@^A^@^@^@^F^@^@^@<98>-^@^@ ^@^@^@^@<98>=^@^@^@^@^@^@<98>=^@^@^@^@^@^@<88>^B^@^@^@^@^@^@<90>^B^@^@^@^@^@^@^@^P^@^@^@^@^@^@^B^@^@^@^F^@^@^@¨-^@^@ ^@^@^@^@¨=^@^@^@^@^@^@¨=^@^@^@^@^@^@^P^B^@^@^@^@^@^@^P^B^@^@^@^@^@^@^H^@^@^@^@^@^@^@^D^@^@^@^D^@^@^@P^C^@^@^@^@^@^@P ^C^@^@^@^@^@^@P^C^@^@^@^@^@^@ ^@^@^@^@^@^@^@ ^@^@^@^@^@^@^@^H^@^@^@^@^@^@^@^D^@^@^@^D^@^@^@p^C^@^@^@^@^@^@p^C^@^@^@^ @^@^@p^C^@^@^@^@^@^@$^@^@^@^@^@^@^@$^@^@^@^@^@^@^@^D^@^@^@^@^@^@^@^D^@^@^@^D^@^@^@<8c>!^@^@^@^@^@^@<8c>!^@^@^@^@^@^@ <8c>!^@^@^@^@^@^@ ^@^@^@^@^@^@^@ ^@^@^@^@^@^@^@^D^@^@^@^@^@^@^@Såtd^D^@^@^@P^C^@^@^@^@^@^@P^C^@^@^@^@^@^@P^C^@^@^@^@ ^@^@ ^@^@^@^@^@^@^@ ^@^@^@^@^@^@^@^H^@^@^@^@^@^@^@Påtd^D^@^@^@^P ^@^@^@^@^@^@^P ^@^@^@^@^@^@^P ^@^@^@^@^@^@L^@^@^@^@ ^@^@^@L^@^@^@^@^@^@^@^D^@^@^@^@^@^@^@Qåtd^F^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^ @^@^@^@^@^@^@^P^@^@^@^@^@^@^@Råtd^D^@^@^@<98>-^@^@^@^@^@^@<98>=^@^@^@^@^@^@<98>=^@^@^@^@^@^@h^B^@^@^@^@^@^@h^B^@^@^@ ^@^@^@^A^@^@^@^@^@^@^@^D^@^@^@^P^@^@^@^E^@^@^@GNU^@^B<80>^@À^D^@^@^@^A^@^@^@^@^@^@^@^D^@^@^@^T^@^@^@^C^@^@^@GNU^@^X^ h´_qõ×u°^O1Û6PNÓl[G/lib64/ld-linux-x86-64.so.2^@^B^@^@^@ ^@^@^@^A^@^@^@^F^@^@^@^@^@<81>^@^@^@^@^@ ^@^@^@^@^@^@ ^@ÑeÎm^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@Û^@^@^@^R^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@<99>^@^ @^@^R^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@F^@^@^@^R^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@Ñ^@^@^@^Q^@^@^@^@^@^@^@^@^ @^@^@^@^@^@^@^@^@^@^@|^@^@^@^R^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^P^@^@^@ ^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^ A^@^@^@ ^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@,^@^@^@ ^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@í^@^@^@"^@^@^@^@^@^@^@^@ ^@^@^@^@^@^@^@^@^@^@^@^@__gmon_start__^@_ITM_deregisterTMCloneTable^@_ITM_registerTMCloneTable^@_ZStlsISt11char_trai tsIcEERSt13basic_ostreamIcT_ES5_c^@_ZSt21ios_base_library_initv^@_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5 _PKc^@_ZSt4cout^@__libc_start_main^@__cxa_finalize^@libstdc++.so.6^@libm.so.6^@libgcc_s.so.1^@libc.so.6^@GLIBCXX_3.4 .32^@GLIBCXX_3.4^@GLIBC_2.34^@GLIBC_2.2.5^@^@^@^@^C^@^D^@^D^@^D^@^E^@^A^@^A^@^A^@^B^@^@^@^@^@^A^@^B^@ü^@^@^@^P^@^@^@ 0^@^@^@Bø<97>^B^@^@^E^@-^A^@^@^P^@^@^@t)<92>^H^@^@^D^@<^A^@^@^@^@^@^@^A^@^B^@#^A^@^@^P^@^@^@^@^@^@^@´<91><96>^F^@^@^ C^@H^A^@^@^P^@^@^@u^Zi ^@^@^B^@S^A^@^@^@^@^@^@<98>=^@^@^@^@^@^@^H^@^@^@^@^@^@^@@^Q^@^@^@^@^@^@ =^@^@^@^@^@^@^H^@^@^ @^@^@^@^@^@^Q^@^@^@^@^@^@^X@^@^@^@^@^@^@^H^@^@^@^@^@^@^@^X@^@^@^@^@^@^@¸?^@^@^@^@^@^@^F^@^@^@ ^@^@^@^@^@^@^@^@^@^@ ^@À?^@^@^@^@^@^@^F^@^@^@^A^@^@^@^@^@^@^@^@^@^@^@È?^@^@^@^@^@^@^F^@^@^@^D^@^@^@^@^@^@^@^@^@^@^@Ð?^@^@^@^@^@^@^F^@^@^@ ^F^@^@^@^@^@^@^@^@^@^@^@Ø?^@^@^@^@^@^@^F^@^@^@^G^@^@^@^@^@^@^@^@^@^@^@à?^@^@^@^@^@^@^F^@^@^@^H^@^@^@^@^@^@^@^@^@^@^@
在shell上我们来执行一下 main
./main
会输出
a herea herea herea here