没有输出的输入是不完整的

0%

再探c++编译过程

在科研项目中碰到了一个c++的程序,需要自己进行编译,所以就顺带着复习了一波c++的编译过程。
本篇文章探究c++编译过程的第二篇,主要解释了第一篇中的一些现象,同时尝试编译出预处理文件、汇编文件、对象文件等编译过程中的中间产物。

编译原理解释

编译器的作用

编译器 g++ 通过检查命令行中指定的文件的后缀名可识别其为 C++ 源代码文件。

编译器默认的动作:编译源代码文件生成对象文件(object file),将对象文件libstdc++ 库中的函数进行链接得到可执行程序。

c++编程中常见文件后缀

.a 静态库 (archive)
.C
.c
.cc
.cp
.cpp
.cxx
.c++
C++源代码(需要编译预处理)
.h C或者C++源代码头文件
.ii C++源代码(不需编译预处理)
.o 对象文件
.s 汇编语言代码
.so 动态库
<none> 标准C++系统头文件

初探解疑

  1. 初探过程中,当我们直接通过如下命令编译出最后的执行文件的过程中,
1
g++ helloworld.cpp -o helloworld

其实是存在中间状态文件的,也就是编译过程中所说到的对象文件,查表可知应该是以.o为后缀的文件。
2. 初探过程中,当我们将头文件talk.h和实现源文件talk.cpp分开之后,然后只编译测试文件talktest.cpp的时候是报错的,那么现在我们就可以理解为什么报错了,原因在于最终的可执行文件是要链接对象文件和标准库中的文件形成的,而对象文件是必须要通过cpp等c++源代码编译形成的,所以对于我们自己编写的cpp文件是一定要经过自己手动编译才能参与到后续的链接过程中的。

再探c++编译过程

生成对象文件

1.利用初探中写好的talk.h,talk.cpp,talktest.cpp文件

1
2
3
4
5
6
7
/*
talk.h
*/
class Talk{
public:
void say(const char*);
};
1
2
3
4
5
6
7
8
/*
talk.cpp
*/
#include "talk.h"
#include <iostream>
void Talk::say(const char* str){
std::cout<<"talk with"<<'\t'<<str<<std::endl;
}
1
2
3
4
5
6
7
8
9
/*
talktest.cpp
*/
#include "talk.h"
int main(){
Talk talk;
talk.say("kingwen");
return 0;
}
  1. 直接编译出对象文件
1
2
g++ -c talk.cpp
g++ -c talktest.cpp

3.查看当前文件

1
ls

可以发现多了两个文件

1
talk.o talktest.o
  1. 编译对象文件生成可执行文件
1
g++ talk.o talktest.o -o talktest1
  1. 执行talktest1
1
./talktest1

结果和我们执行直接生成编译结果是一样的,结果如下所示。

1
talk with   kingwen
  1. 对象文件输出
    我们在第二步中是通过gcc -c talk.cpp直接编译出了talk.o这个对象。
    这里会使得编译结果的默认名称和源码名称一致。但是我们其实可以自定义。
1
2
g++ -c talk.cpp -o talker.o
g++ -c talktest.cpp -o talkertest.o

然后编译运行方式和前面说明的一致即可。

1
2
g++ talker.o talkertest.o -o talkertest
./talkertest

最后得到的结果也和之前是一样的。

1
talk with   kingwen

PS:所以如果以后在编码过程中如果碰见说缺少对象文件,那么就是对应的cpp源代码文件没有编译,我们可以通过g++命令对其进行编译即可解决问题。

生成预处理文件

  1. 找到初探中的helloworld.cpp代码
1
2
3
4
5
6
7
8
9
/* 
helloworld.cpp
用于测试g++编译单个源文件生成可执行程序
*/
#include <iostream>
int main(){
std::cout<<"hello world"<<std::endl;
return 0;
}

通过wc命令来查看helloworld.cpp文件的行数

1
2
➜  compilec++ wc -l helloworld.cpp
8 helloworld.cpp
  1. 代码预处理
    通过如下命令会将预处理的结果在标准输出中进行输出。
1
g++ -E helloworld.cpp

当然我们也可以将结果进行输出保存

1
g++ -E helloworld.cpp -o helloworld.ii

通过wc命令来查看预处理结果helloworld.ii的行数

1
2
compilec++ wc -l helloworld.ii
41484 helloworld.ii

可以发现文件大小有了翻天覆地的变化,这里主要就是将头文件等引入了我们的文件中。

生成汇编文件

  1. 同样还是之前的helloworld.cpp代码
1
2
3
4
5
6
7
8
9
/* 
helloworld.cpp
用于测试g++编译单个源文件生成可执行程序
*/
#include <iostream>
int main(){
std::cout<<"hello world"<<std::endl;
return 0;
}
  1. 直接编译生成汇编代码
1
g++ -S helloworld.cpp -o helloworld.s
  1. 查看生成的汇编代码
    其实可以使用cat或者head或者tail进行查看,
    这里我用的是sed命令查看了从1360到1372行,因为我觉得这块代码更加符合我对汇编的想象。
1
sed -n '1360,1372p' helloworld.s

结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
compilec++ sed -n '1360,1372p' helloworld.s
movq %rsp, %rbp
.cfi_def_cfa_register %rbp
movl %edi, -4(%rbp)
movl %esi, -8(%rbp)
movl -4(%rbp), %esi
cmpl -8(%rbp), %esi
sete %al
andb $1, %al
movzbl %al, %eax
popq %rbp
retq
.cfi_endproc
## -- End function

参考

GCC编译C++