gcc命令行观察c程序的编译过程
文章目录
gcc命令
将要遭遇的概念
GCC:(GNU Compiler Collection)GNU编译器集合
gcc和g++都属于"编译器驱动程序"(driver),实际上编译器是cc1(C语言),cc1plus(C++语言)
GAS:GNU汇编器(GNU Assembler),简称为GAS.使用gcc命令时汇编器(as)和链接器(ld)都是GAS提供的
gcc和g++的区别
包括但是不止下面两条
gcc对于.c文件调用cc1编译器,对于.cpp文件调用cc1plus编译器
g++不管是.c和.cpp都会调用cc1plus编译器
在链接时gcc不会传递给链接器链接C++标准库的命令但是g++会
#include <vector>
using namespace std;
int main(){
vector<int>v;//此处需要使用STL中的vector
return 0;
}
比如这样一个test.cpp文件
使用gcc命令编译则会报错:
root@deutschball-virtual-machine:/home/deutschball/mydir# gcc test.cpp -o test.out
/usr/bin/ld: /tmp/ccfXp0Kz.o: in function `__gnu_cxx::new_allocator<int>::deallocate(int*, unsigned long)':
test.cpp:(.text._ZN9__gnu_cxx13new_allocatorIiE10deallocateEPim[_ZN9__gnu_cxx13new_allocatorIiE10deallocateEPim]+0x20): undefined reference to `operator delete(void*)'
/usr/bin/ld: /tmp/ccfXp0Kz.o:(.data.rel.local.DW.ref.__gxx_personality_v0[DW.ref.__gxx_personality_v0]+0x0): undefined reference to `__gxx_personality_v0'
collect2: error: ld returned 1 exit status
但是使用g++命令编译则不会报错
如果想让gcc命令编译时让链接器可以链接标准库可以使用命令行参数-lstdc++
root@deutschball-virtual-machine:/home/deutschball/mydir# gcc test.cpp -o test.out -lstdc++
但是即使加上该参数,使用gcc和g++对于.cpp的编译还是有区别的.
啥区别我现在不知道,也不想知道
因此现阶段在编译.c源代码时就用gcc命令,编译.cpp源代码时就用g++命令
gcc命令行参数和.c到.exe的过程
预编译-E
预编译命令只能作用于源代码文件(.c,.cpp)
gcc -E balabala.c
或者
cpp balabala.c
1.将所有include(包括库文件和自己写的文件)展开
2.替换所有的宏定义
比如
test.c
#include <stdio.h>
#define N 10
typedef int word;
int main(){
int a=N;
word b=N;
return 0;
}
使用gcc test.c -E
(使用cpp test.c
作用相同)之后会将预编译内容打印到屏幕,但是不会生成.i文件
(截图仅为一小部分)
观察到#define N 10
消失,N被10替换
typedef
起别名并不会被替换
使用-o命令行参数指定预编译生成文件
cpp test.c -o test.i
然后使用ls -sh -l
名令以列表方式查看当前目录下文件大小
可见.i文件明显比.c文件大
-I命令行参数指定自定义头文件
如果需要包含的头文件和就在当前目录下则自动包含,
比如当前目录(mydir/)下
有一个自定义头文件myheader.h
里面只有一个变量a的定义
有一个test.c里面没有定义a直接拿来用
此时预编译是可以通过的
root@deutschball-virtual-machine:/home/deutschball/mydir# ls -l
总用量 8
-rw-r--r-- 1 root root 10 3月 31 22:43 myheader.h
-rw-r--r-- 1 root root 51 3月 31 22:44 test.c
root@deutschball-virtual-machine:/home/deutschball/mydir# cpp test.c -o test.i
root@deutschball-virtual-machine:/home/deutschball/mydir# cat test.i
# 1 "test.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "test.c"
# 1 "myheader.h" 1
int a=10;
# 2 "test.c" 2
int main(){
a;
return 0;
}
如果在其他目录则需要- I <directory>
指定包含文件所在的目录
root@deutschball-virtual-machine:/home/deutschball/mydir# ls -l
总用量 8
-rw-r--r-- 1 root root 10 3月 31 22:43 myheader.h
-rw-r--r-- 1 root root 51 3月 31 22:44 test.c
root@deutschball-virtual-machine:/home/deutschball/mydir# mv myheader.h ..
root@deutschball-virtual-machine:/home/deutschball/mydir# ls
test.c
root@deutschball-virtual-machine:/home/deutschball/mydir# cpp test.c
# 1 "test.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "test.c"
test.c:1:10: fatal error: myheader.h: 没有那个文件或目录
1 | #include "myheader.h"
| ^~~~~~~~~~~~
compilation terminated.
将原本与test.c同目录的myheader.h移动到上级目录(…)中,此时使用cpp命令则在当前目录下找不到myheader.h报错了
此时使用-I <directory>
指定上级目录(…)为包含路径则预编译通过
root@deutschball-virtual-machine:/home/deutschball/mydir# cpp test.c -I ..
# 1 "test.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "test.c"
# 1 "../myheader.h" 1
int a=10;
# 2 "test.c" 2
int main(){
a;
return 0;
}
编译(Compilation)-S
编译命令可以应用于前面所有类型的文件(.c,.i)
gcc -S balabala.c
作用是将源代码(或者说预编译之后的源代码)编译成汇编语言
将一个全空的c程序(一个字都没写的,这样写当然不对,但是是在后来的某一阶段报错)test.c编译成汇编语言,会在同一目录下生成test.s文件
root@deutschball-virtual-machine:/home/deutschball/mydir# gcc test.c -S
root@deutschball-virtual-machine:/home/deutschball/mydir# cat test.s
.file "test.c"
.text
.ident "GCC: (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:
关于汇编语言后来会学,但不是现在
汇编(Assembly)-c
gcc balabala.c -c
或
as balabala.c
汇编命令可以应用于前面过程中生成的所有文件(.c,.i,.s)
对于一个啥也没写的test.c文件,预编译,编译,汇编都是可以通过的
root@deutschball-virtual-machine:/home/deutschball/mydir# echo > test.c
root@deutschball-virtual-machine:/home/deutschball/mydir# ls -l
总用量 4
-rw-r--r-- 1 root root 1 3月 31 23:04 test.c
root@deutschball-virtual-machine:/home/deutschball/mydir# cat test.c
root@deutschball-virtual-machine:/home/deutschball/mydir# cpp test.c -o test.i
root@deutschball-virtual-machine:/home/deutschball/mydir# gcc test.i -S
root@deutschball-virtual-machine:/home/deutschball/mydir# ls -l
总用量 12
-rw-r--r-- 1 root root 1 3月 31 23:04 test.c
-rw-r--r-- 1 root root 149 3月 31 23:04 test.i
-rw-r--r-- 1 root root 298 3月 31 23:04 test.s
root@deutschball-virtual-machine:/home/deutschball/mydir# gcc test.s -c
root@deutschball-virtual-machine:/home/deutschball/mydir# ls -l
总用量 16
-rw-r--r-- 1 root root 1 3月 31 23:04 test.c
-rw-r--r-- 1 root root 149 3月 31 23:04 test.i
-rw-r--r-- 1 root root 1072 3月 31 23:06 test.o
-rw-r--r-- 1 root root 298 3月 31 23:04 test.s
root@deutschball-virtual-machine:/home/deutschball/mydir# cat test.o
ELF>�@@
GCC: (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0GNU���test.c.symtab.strtab.shstrtab.text.data.bss.comment.note.GNU-stack.note.gnu.property!@@,0@,5lEp�� PXX
到此为止,我们完成了下图中红框中的部分
下面来到了链接阶段对应图中load time
链接(Linking)
ld负责将程序的目标文件与所需的所有附加的目标文件连接起来,最终生成可执行文件。
附加的目标文件包括静态连接库和动态连接库。
还是一个字也没写的test
root@deutschball-virtual-machine:/home/deutschball/mydir# gcc test.o -o test.out
/usr/bin/ld: /usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/Scrt1.o: in function `_start':
(.text+0x24): undefined reference to `main'
collect2: error: ld returned 1 exit status
root@deutschball-virtual-machine:/home/deutschball/mydir# ld test.o
ld: 警告: 无法找到项目符号 _start; 缺省为 0000000000401000
在链接阶段终于报错了
报错原因是程序总要有一个main函数入口,一个空的test自然没有main函数
库
库就是现成的可以复用的"代码".
这里"代码"加了引号,因为库不是我们使用的高级语言代码,而是机器码
到此我们知道了多个文件是如何互相找到,在何时互相找到的,也就是链接要做的事情
下面为了更清楚地理解库的作用,我们需要亲自写几个库试试
然后我查阅了这个博客https://www.cnblogs.com/skynet/p/3372855.html
库有两种,一种是静态库,一种是动态库
静态库(.a,.lib)
静态库会在链接时与我们自己编译生成的.o文件一起链接打包到可执行文件,这种链接方式称为"静态链接"
静态库可以看作一组目标文件(.o)的集合
静态库对函数库的链接是在编译链接时期完成的
程序运行时与函数库已经没有关系,方便移植
浪费空间,不容易更新
动态库(.so.dll)
图片来自播客
动态库的出现是为了解决两个问题
1.静态库占用空间,多个程序可能有相同的静态库
2.更新时,静态库即使静态库稍微改动一点,也需要全部重新编译(全量更新)
动态库相对这两点的特性
1.多个程序复用同一个库
2.增量更新,哪里更新就编译哪里
这就要求动态库在运行时才会装载
静态库的使用
在/home/deutschball/mydir
文件夹下写了三个文件
root@deutschball-virtual-machine:/home/deutschball/mydir# ls -l
总用量 12
-rw-r--r-- 1 root root 156 4月 1 09:44 main.cpp
-rw-r--r-- 1 root root 985 4月 1 09:46 Point.cpp
-rw-r--r-- 1 root root 872 4月 1 09:43 Point.h
point.h
#pragma once
#include <iostream>
#include <string>
#include <cmath>
using std::string;
using std::cout;
using std::ostream;
class Point {
private:
double x, y;
string name;
public:
Point(const double &, const double &);
Point(const Point &);
Point();
Point(const string &);
Point(const string &, const double &, const double &);
void setX(const double &);
void setY(const double &);
void setName(const string &);
double getX()const;
double getY()const;
string getName()const;
double getDistance(const Point &);
friend ostream &operator<<(ostream &, const Point &);
};
point.cpp
#include "Point.h"
Point::Point(const double &x, const double &y) {
name = "";
this->x = x;
this->y = y;
}
Point::Point(const Point &p) {
this->name = p.name;
this->x = p.x;
this->y = p.y;
}
Point::Point() {
name = "";
x = y = 0;
}
Point::Point(const string &n) {
name = n;
x = y = 0;
}
Point::Point(const string &name, const double &x, const double &y) {
this->name = name;
this->x = x;
this->y = y;
}
void Point::setX(const double &x) {
this->x = x;
}
void Point::setY(const double &y) {
this->y = y;
}
void Point::setName(const string &name) {
this->name = name;
}
double Point:: getX()const {
return x;
}
double Point:: getY()const {
return y;
}
string Point:: getName()const {
return name;
}
double Point::getDistance(const Point &p) {
return sqrt((x - p.x) * (x - p.x) + (y - p.y) * (y - p.y));
}
ostream &operator<<(ostream &os, const Point &p) {
os << p.name << "(" << p.x << "," << p.y << ")";
return os;
}
main.cpp
#include <iostream>
#include <cmath>
#include "Point.h"
using namespace std;
int main() {
Point p("A", 5.0, 4.0);
cout << p << endl;
return 0;
}
图片来自博客
准备工作完毕,下面开始创建静态库
main.cpp为入口,Point.h是头文件,我们需要将Point.cpp创建为静态库
1.将Point.cpp编译成目标文件.o
root@deutschball-virtual-machine:/home/deutschball/mydir# g++ Point.cpp -c
root@deutschball-virtual-machine:/home/deutschball/mydir# ls -l
总用量 20
-rw-r--r-- 1 root root 156 4月 1 09:44 main.cpp
-rw-r--r-- 1 root root 985 4月 1 09:46 Point.cpp
-rw-r--r-- 1 root root 872 4月 1 09:43 Point.h
-rw-r--r-- 1 root root 7704 4月 1 10:01 Point.o
2.使用ar
工具将刚才生成的目标文件打包成.a静态库文件
root@deutschball-virtual-machine:/home/deutschball/mydir# ar -crv libpoint.a Point.o
a - Point.o
root@deutschball-virtual-machine:/home/deutschball/mydir# ls -l
总用量 32
-rw-r--r-- 1 root root 8540 4月 1 10:02 libpoint.a
-rw-r--r-- 1 root root 156 4月 1 09:44 main.cpp
-rw-r--r-- 1 root root 985 4月 1 09:46 Point.cpp
-rw-r--r-- 1 root root 872 4月 1 09:43 Point.h
-rw-r--r-- 1 root root 7704 4月 1 10:01 Point.o
我们没有指定libpoint.a的目录,因此在当前文件夹下形成
到此,静态库libpoint.a建立完毕
下面我们在编译main.cpp
时使用静态库
-L
指定静态库目录
-l
指定静态库和动态库的名字
root@deutschball-virtual-machine:/home/deutschball/mydir# g++ main.cpp -L ./ -l point -o main
.out
root@deutschball-virtual-machine:/home/deutschball/mydir# ls -l
总用量 52
-rw-r--r-- 1 root root 8540 4月 1 10:02 libpoint.a
-rw-r--r-- 1 root root 156 4月 1 09:44 main.cpp
-rwxr-xr-x 1 root root 19824 4月 1 10:07 main.out
-rw-r--r-- 1 root root 985 4月 1 09:46 Point.cpp
-rw-r--r-- 1 root root 872 4月 1 09:43 Point.h
-rw-r--r-- 1 root root 7704 4月 1 10:01 Point.o
可执行文件main.out就生成了
动态库的使用
linux上动态库的命令规则libbalabala.so,前缀lib后缀.so
windows上动态库使用比较复杂,不管他了
创建动态库
首先生成目标文件,注意使用-fPIC命令行参数
root@deutschball-virtual-machine:/home/deutschball/mydir# g++ -fPIC -c Point.cpp
-fPIC
(position independent code)作用是创建与地址无关的编译程序,为了能够在多个应用程序间共享
然后生成动态链接库
root@deutschball-virtual-machine:/home/deutschball/mydir# g++ -shared -o libpoint.so Point.o
root@deutschball-virtual-machine:/home/deutschball/mydir# ls -l
总用量 60
-rwxr-xr-x 1 root root 18712 4月 1 12:12 libpoint.so
-rw-r--r-- 1 root root 156 4月 1 09:44 main.cpp
-rw-r--r-- 1 root root 985 4月 1 09:46 Point.cpp
-rw-r--r-- 1 root root 872 4月 1 09:43 Point.h
-rw-r--r-- 1 root root 7704 4月 1 12:12 Point.o
-rw-r--r-- 1 root root 16441 4月 1 11:46 Point.s
生成了libpoint.so
到此动态库创建完毕,下面使用动态库
尝试用使用静态库的方法使用动态库
root@deutschball-virtual-machine:/home/deutschball/mydir# g++ main.cpp -L ./ -l point -o main.out
root@deutschball-virtual-machine:/home/deutschball/mydir# ls -l
总用量 80
-rwxr-xr-x 1 root root 18712 4月 1 12:12 libpoint.so
-rw-r--r-- 1 root root 156 4月 1 09:44 main.cpp
-rwxr-xr-x 1 root root 18064 4月 1 12:17 main.out
-rw-r--r-- 1 root root 985 4月 1 09:46 Point.cpp
-rw-r--r-- 1 root root 872 4月 1 09:43 Point.h
-rw-r--r-- 1 root root 7704 4月 1 12:12 Point.o
-rw-r--r-- 1 root root 16441 4月 1 11:46 Point.s
root@deutschball-virtual-machine:/home/deutschball/mydir# ./main.out
./main.out: error while loading shared libraries: libpoint.so: cannot open shared object file: No such file or directory
可以通过编译但是out文件执行出错,说是找不到libpoint.so
确实,我们使用-L命令只能指定静态库的目录,使用-l可以指定静态库和动态库的名字
但是就是没有指定动态库的目录
那么动态库到底在哪里呢?
使用第一种方法,将我们自己编写的动态库放在/usr/lib下面
root@deutschball-virtual-machine:/home/deutschball/mydir# cp libpoint.so /usr/lib
root@deutschball-virtual-machine:/home/deutschball/mydir# ./main.out
A(5,4)
发现可以正常运行了
参考文档
How programs are prepared to run on z/OS
参考博客
https://www.cnblogs.com/skynet/p/3372855.html
https://www.runoob.com/w3cnote/cpp-header.html