一、前言

读研一个学期了,因为本科并非计算机专业的,跑别人的代码的时候,总是报一些编译相关的错误,今天集中记录一下。

二、g++编译

1.gcc的编译流程(仅仅只有一个cpp文件,编译不依赖其他文件)

(1)预处理: 在这个阶段主要做了三件事: 展开头文件 、宏替换 、去掉注释行

(2)编译: 这个阶段需要GCC调用编译器对文件进行编译, 最终得到一个汇编文件

(3)汇编: 这个阶段需要GCC调用汇编器对文件进行汇编, 最终得到一个二进制文件

(4)链接: 这个阶段需要GCC调用链接器对程序需要调用的库进行链接, 最终得到一个可执行的二进制文件

比如我现在有一个test.cpp文件, 从这个文件到编译后的可执行文件,代码如下。

#1.预处理
g++ -E test.cpp -o test.i
#2.生成汇编文件
g++ -S test.i -o test.s
#3.生成二进制文件
g++ -c test.s -o test.o
#4.生成可执行文件
g++ test.o -o test
######也可以一步直接生成可执行文件
g++ test.cpp -o test

2. 编译多个文件

现有文件树如下,各个文件代码如下,我们的目标是生成一个可执行文件,可以输出main.cpp文件的内容。

helloworld.cpp

#include <iostream>
#include "headfile.h" // 包含头文件

void printHelloWorld() {
    std::cout << "Hello, World!" << std::endl;
}

hellolinux.cpp

#include <iostream>
#include "headfile.h" // 包含头文件

void printHelloLinux() {
    std::cout << "Hello, Linux!" << std::endl;
}

main.cpp

#include <iostream>
#include "headfile.h" // 包含头文件

int main() {
    printHelloWorld();
    printHelloLinux();
    return 0;
}

headfile.h

#ifndef HEADFILE_H
#define HEADFILE_H

// 声明函数
void printHelloWorld();
void printHelloLinux();

#endif // HEADFILE_H
        

开始编译,当执行g++ -c helloworld.cpp -o helloworld.o,会提示找不到"headfile.h"这个文件,这是因为headfile.h和当前的路径并不一致,在ubantu编译c++代码寻找头文件,一般的路径如下

  1. /usr/local/include:本地安装的头文件目录。
  2. /usr/include:系统标准头文件目录。
  3. /usr/local/include/x86_64-linux-gnu:本地安装的64位头文件目录(在64位系统上)。
  4. /usr/include/x86_64-linux-gnu:系统64位标准头文件目录(在64位系统上)。
  5. 当前路径

当然可以在编译的时候指定添加头文件的路径,代码如下

g++ -c helloworld.cpp -o helloworld.o -I ./include/

完整的编译代码如下

// 生成目标文件
g++ -c helloworld.cpp -o helloworld.o -I ./include/
g++ -c hellolinux.cpp -o hellolinux.o -I ./include/
g++ -c main.cpp -o main.o -I ./include/
// 将目标文件链接在一起
g++ main.o hellolinux.o helloworld.o -o myprogram

运行myprogram, 结果如下图

3.建立静态链接库或者动态链接库

        当我们想让其他人使用我们的hellolinux.cpp 和 helloworld.cpp文件的时候,但是我们又不想发布源码给他们,那么就可以创建静态链接库或者动态链接库。

        我们给别人的内容包括两个部分,第一部分是头文件,第二部分是静态链接库和动态链接库,当别人写一个main.cpp文件的时候,可以依赖我们提供的链接库。

        在linux系统中,静态库的前缀是lib,后缀是.a。动态库的前缀是lib,后缀是.so

(1)创建静态库

将我们上面生成的.o文件通过ar工具打包成静态库,其中libprint.a表示要生成的静态库文件。

ar rcs libprint.a *.o 

(2)使用静态库

新建一个文件夹,将静态库和头文件,以及,main.cpp放进去。文件树如下图

编译代码为

g++ -o main libprint.a 

运行结果为

注意:编译时候,这个静态库有默认搜索路径如下,也可以指静态库的搜索路径,其中-L指定静态库所属的文件夹,-l指定编译依赖的静态库。

gcc -o main main.o -L /path/to/libs -l mylib
  1. /usr/local/lib:这是本地安装的库文件的默认目录。当你手动编译并安装软件时,库文件通常会被安装到这个目录。

  2. /usr/lib:这是系统标准库文件的目录。大多数系统自带的库文件都位于这个目录。

  3. /lib:这也是系统库文件的目录,但通常包含一些基本的、核心的库文件。

  4. /usr/local/lib64:在64位系统上,这是本地安装的64位库文件的默认目录。

  5. /usr/lib64:在64位系统上,这是系统标准的64位库文件的目录。

  6. /lib64:在64位系统上,这也是系统库文件的目录,但通常包含一些基本的、核心的64位库文件。

  7. 当前目录

(3) 关于动态库

        动态库也可以由一系列.o文件生成,他的区别在于,他的内容是动态加载的。意思是即使我编译成功了,但是他在运行的时候也是在不断的依赖这个动态库,好处在于省存储了,坏处在于需要指定依赖动态库的路径。

        动态库的默认搜索路径如下,也可以使用来临时指定动态库的搜索路径。

export LD_LIBRARY_PATH=/path/to/libs:$LD_LIBRARY_PATH
  1. /lib:这是系统库文件的目录,包含一些基本的、核心的库文件。

  2. /usr/lib:这是系统标准库文件的目录,大多数系统自带的库文件都位于这个目录。

  3. /usr/local/lib:这是本地安装的库文件的默认目录。当你手动编译并安装软件时,库文件通常会被安装到这个目录。

  4. /lib64:在64位系统上,这也是系统库文件的目录,但通常包含一些基本的、核心的64位库文件。

  5. /usr/lib64:在64位系统上,这是系统标准的64位库文件的目录。

  6. /usr/local/lib64:在64位系统上,这是本地安装的64位库文件的默认目录。

  7. /etc/ld.so.conf 中指定的目录:这个文件中列出了额外的库搜索路径。你可以通过编辑这个文件并运行 sudo ldconfig 来更新动态链接器的缓存。

(4)关于/usr/bin/ld

这个可执行文件的作用就是将.o文件,.a文件,.so文件,这些源文件和动态链接库或者静态链接库全部搞到一起。最后生成一个动态链接库,所以在使用它的时候,他的参数动态链接库需要在上面的搜索路径里面,如果没在,那就整个软链接,链接过去。

三、关于cmake和makefile

1.关于makefile

如果我们的项目比较大的话,那么使用g++使用命令行编译那就很烦了,一个比较好的解决策略就是将编译写成脚本,makefile就是这个东西。得到makefile文件之后,执行make指令,他就会自动在当前文件夹下找makefile这个文件,之后执行这个文件。

2.关于cmakelist.txt

当跨系统的时候,makefile可能不怎么好用了,所以现在新的技术是cmakelist.txt文件,利用cmake可以将这个.txt文件转化成makefile。

参考链接:

Linux 静态库和动态库 | 爱编程的大丙

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐