4

C语言学习之编译

 4 weeks ago
source link: https://www.biaodianfu.com/c-compiler.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

C程序的完整生命周期

一个C程序的完整生命周期包括以下步骤:

  • 编写代码:C程序的生命周期从我们编写源代码开始。源代码通常使用文本编辑器编写,并保存为.c扩展名的文件。
  • 预处理:预处理器接收源代码作为输入,处理预处理指令,如#define,#include等,并输出修改后的源代码。预处理器执行宏替换,包括文件,条件编译和宏定义等。
  • 编译:编译器将预处理的代码转换为汇编语言代码。此步骤还检查语法错误,并在遇到错误时报告。
  • 汇编:汇编器将汇编代码转换为机器语言代码,生成目标文件。目标文件包含了机器指令及程序所需的各种信息。
  • 链接:链接器将一个或多个目标文件链接成一个可执行文件。链接器还解决程序中的符号引用,即将每个符号引用与一个符号定义关联起来。
  • 加载:当你运行程序时,加载器将可执行文件加载到内存中,准备执行。
  • 执行:最后,CPU开始执行加载器加载到内存中的机器指令。

以上就是一个C程序从编写到执行的完整生命周期。

程序的编译环境与执行环境

编译环境和执行环境是编程中两个非常重要的概念:

  • 编译环境是指用于编译源代码到可执行文件的环境,包括操作系统、编译器、库和开发工具等。例如,你可能在Linux操作系统上,使用GCC编译器和glibc库来编译你的C程序。编译环境的设置会影响到编译过程和生成的可执行文件。例如,不同的操作系统和编译器可能支持不同的语言特性和编译选项;不同的库版本可能提供了不同的API。
  • 执行环境是指用于运行可执行文件的环境,包括操作系统、库、环境变量和输入输出设备等。例如,你可能在Windows操作系统上,使用cmd或powershell终端,通过键盘输入和屏幕输出来运行你的程序。执行环境的设置会影响到程序的运行。例如,不同的操作系统可能提供了不同的系统调用和API;环境变量的设置可能影响到程序的行为;不同的输入输出设备可能支持不同的特性。

编译环境和执行环境可能完全不同。例如,你可以在Linux系统上编译一个程序,然后将其转移到Windows系统上运行(当然,这需要你使用适合目标系统的交叉编译器和库)。然而,在实际开发中,为了简单起见,编译环境和执行环境往往被设置为相同或者相似。

C语言的编译过程

C语言的编译过程通常包括四个步骤:预处理、编译、汇编和链接。

c-compiler.png

以下是它们的详细解释:

预处理(Preprocessing)

预处理是编译过程中的第一步,主要处理C程序中的预处理指令。C预处理器(cpp)不关心C语法,只是一个文本替换工具。预处理器的主要任务包括:

  • 处理#include指令,包含其他的头文件或者源文件
  • 处理#define指令,进行宏定义
  • 处理条件编译指令,如#ifdef, #ifndef, #endif, #else等
  • 删除程序中的所有注释

编译(Compilation)

编译是将预处理后的源程序转换为汇编语言程序。编译过程主要包括四个步骤:

  • 词法分析:将源程序分解为一个个的单词
  • 语法分析:检查程序的语法正确性,构造出一颗语法树
  • 语义分析与中间代码生成:进行类型检查等语义分析,如果没有语义错误,就生成中间代码
  • 优化:对中间代码进行优化处理,以提高目标代码的效率

汇编(Assembly)

汇编过程是将编译生成的汇编语法程序转换为目标机器可以解析的机器语言程序。汇编器(as)是完成这个过程的工具,它的主要任务是将汇编语言翻译成机器语言,并生成可重定位的目标文件。

链接(Linking)

链接是将一个或多个目标文件以及所需的库文件链接生成最终的可执行文件。链接器(ld)的主要任务包括:

  • 地址和空间的分配:确定每个符号(变量和函数)的最终地址,确定程序的布局
  • 符号解析:找到每个未定义标识符的定义,如果找不到,就报错
  • 重定位:修改对符号的引用,使其指向正确的地址
  • 运行库链接:链接系统的运行库和用户指定的库

以上就是C语言编译过程中的四个主要步骤:预处理、编译、汇编和链接的详细讲解。

C语言编译器

C语言编译器有很多种,以下是最常见的一些:

  • GCC(GNU Compiler Collection):GCC是GNU开源组织发布的开源编译器,它可以编译C、C++和其他语言的代码。GCC在开源世界中非常流行,并且它是Linux内核的默认编译器。
  • Clang:Clang是一个基于LLVM的C、C++和Objective-C的编译器。它的主要目标是提供一个更友好的编译错误信息,以及更好的性能和内存使用。
  • Microsoft Visual C++(MSVC):这是Microsoft Visual Studio中的C和C++编译器。它只有Windows版本。
  • Intel C++ Compiler(ICC):Intel发布的C和C++编译器,主要优化了在Intel平台上的性能。
  • MinGW(Minimalist GNU for Windows):这是GCC的Windows移植版本。它可以在Windows环境下生成原生的Windows程序,不需要Cygwin环境。
  • TinyCC(TCC):这是一款小型的C语言编译器,它的目标是提供最小的编译时间和生成尽可能小的可执行文件。
  • Digital Mars C/C++ Compiler(DMC):这是一种Windows和DOS的C和C++编译器,之前被广泛用于游戏开发。
  • Open Watcom:一个开源的C/C++编译器,支持多种平台和操作系统,拥有自己的IDE。

这些编译器各有优缺点,适用于不同的开发需求和环境。

GCC编译器

GCC(GNU Compiler Collection,GNU编译器套件)是一个开源的编译器套件,它支持多种编程语言,包括C、C++、Objective-C、Fortran、Ada以及Go等。最初,GCC只是GNU C Compiler(GNU C编译器)的简称,但随着其支持的语言数量增长,现在的GCC已经成为一个多语言编译器集合。

以下是GCC的一些主要特性:

  • 跨平台:GCC可以在各种类型的计算机和操作系统上编译代码,包括Unix和类Unix系统(如Linux),以及Windows、macOS等。GCC也广泛用于各种嵌入式系统开发。
  • 优化:GCC包含了一套强大的代码优化工具,能够自动进行各种性能优化,如循环展开、函数内联、常量传播、死代码删除等。这使得GCC生成的代码在运行时能够有非常好的性能。
  • 可扩展:GCC是模块化设计的,可以通过插件来扩展其功能。用户可以编写自己的插件来改变GCC的行为,或者添加新的优化策略。
  • 警告和错误检查:GCC有众多的编译选项,能够进行严格的代码检查,警告或阻止各种可能的问题,如类型不匹配、未初始化的变量、未使用的变量等。这有助于开发者发现并修复代码中的错误。

GCC的主要组成部分包括预处理器、编译器、汇编器和链接器。用户可以通过命令行选项来控制GCC的各个步骤,例如只进行预处理、只编译不链接等。

值得注意的是,GCC是命令行工具,本身并不包含集成的开发环境。然而,许多IDE(如Eclipse,Code::Blocks等)都支持GCC,并提供了图形化的界面来使用GCC。

GCC一步编译C

在GCC中,一步编译C程序是非常常见的操作。假设你有一个C语言源文件名为”program.c”,你可以使用下面的命令来编译它:

gcc program.c -o program
gcc program.c -o program

这个命令将源代码文件”program.c”编译为一个可执行文件”program”。这个命令的组成部分解释如下:

  • gcc:这是GCC编译器的命令名。
  • c:这是要编译的源代码文件名。
  • -o program:这是一个选项,指定输出的可执行文件名。如果你省略了这个选项,GCC会将输出文件名默认为”a.out”。

在运行这个命令之后,如果源代码没有错误,你会得到一个可执行文件”program”。你可以通过在命令行输入下面的命令来运行它:

./program
./program

这将执行你的程序。如果程序需要命令行参数,你可以在程序名后面添加它们。例如:

./program arg1 arg2
./program arg1 arg2

这将把”arg1″和”arg2″作为命令行参数传递给你的程序。

GCC分布编译C

GCC分步编译C程序的过程涉及到四个步骤:预处理、编译、汇编和链接。

gcc.png

下面是这些步骤及其相应的GCC命令:

预处理(Pre-processing)

这个步骤将C源文件中的预处理指令(如#include和#define)进行处理,并准备好编译。你可以使用以下命令进行预处理:

gcc -E program.c -o program.i
gcc -E program.c -o program.i

这个命令将生成一个预处理后的文件”program.i”。

编译(Compilation)

这个步骤将预处理后的源文件转换为汇编语言代码。你可以使用以下命令进行编译:

gcc -S program.i -o program.s
gcc -S program.i -o program.s

这个命令将预处理后的文件”program.i”编译为一个汇编语言文件”program.s”。

汇编(Assembly)

这个步骤将汇编语言代码转换为机器语言代码(目标文件)。你可以使用以下命令进行汇编:

gcc -c program.s -o program.o
gcc -c program.s -o program.o

这个命令将汇编语言文件”program.s”汇编为一个目标文件”program.o”。

链接(Linking)

这个步骤将一个或多个目标文件以及需要的库文件链接成一个可执行文件。你可以使用以下命令进行链接:

gcc program.o -o program
gcc program.o -o program

这个命令将目标文件”program.o”链接为一个可执行文件”program”。

以上就是GCC进行分步编译的过程。在实际开发中,我们通常会把这些步骤结合在一起进行,因为GCC可以在一次命令中完成所有的步骤。但是,了解这些步骤可以帮助我们更好地理解C程序的编译过程。

构建工具make

Make 是一种经典的构建工具,它使用名为 Makefile 的文件来描述如何构建你的应用。一个 Makefile 文件包含着一系列规则,这些规则定义了源文件和目标文件(通常是可执行文件或库)之间的依赖关系以及如何从源文件生成目标文件。

Make 的主要功能包括:

  • 依赖性检查:Make 可以检查源代码或头文件是否更改过,如果有更改,只编译更改过的文件,避免了不必要的重复编译。
  • 命令执行:在 Makefile 中,用户可以编写编译命令,Make 会按照指定的顺序执行这些命令。
  • 并行构建:如果在系统中有多个处理器或处理器核心,Make 可以利用这些资源并行编译文件,提高编译速度。

然而,Make的一个主要挑战在于编写 Makefile 文件,尤其是在大型或跨平台的项目中,因为不同的系统可能需要不同的编译器或不同的编译选项。

Makefile

Makefile是一个用于描述构建软件项目的文件,通常用于C/C++项目,但也可以用于其他类型的项目。Makefile描述了项目中各个文件之间的依赖关系,以及如何从源代码生成可执行文件或库。当源代码文件被修改后,使用Makefile可以只重新编译被修改的文件和依赖于它的文件,而不是重新编译整个项目,从而大大提高了构建效率。

一个典型的Makefile包含一系列的规则(rules)。每条规则包括一个目标(target)、依赖项(dependencies)和命令(commands)。其基本形式如下:

makefile
target: dependencies
commands
makefile
target: dependencies
    commands
  • 目标(target)可以是一个输出文件,也可以是一个动作名称(比如“clean”)。
  • 依赖项(dependencies)是一系列的文件或其他目标,这些文件或目标需要在执行命令之前被创建或更新。
  • 命令(commands)是一个或多个shell命令,这些命令说明了如何从依赖项生成目标。

假设我们有两个 C 源文件,main.c 和 hello.c,我们想要编译成一个可执行文件,名为 hello。

我们的 Makefile 可能如下所示:

# 变量定义
CC=gcc # 使用的编译器
CFLAGS=-I. # 编译选项,这里是设置 include 路径
# 目标文件定义
OBJ = main.o hello.o
# 生成目标文件的规则
%.o: %.c
$(CC) -c -o $@ $< $(CFLAGS)
# 生成可执行文件的规则
hello: $(OBJ)
$(CC) -o $@ $^ $(CFLAGS)
# 清除生成的文件
clean:
rm -f $(OBJ) hello
# 变量定义
CC=gcc               # 使用的编译器
CFLAGS=-I.           # 编译选项,这里是设置 include 路径

# 目标文件定义
OBJ = main.o hello.o 

# 生成目标文件的规则
%.o: %.c
    $(CC) -c -o $@ $< $(CFLAGS)

# 生成可执行文件的规则
hello: $(OBJ)
    $(CC) -o $@ $^ $(CFLAGS)

# 清除生成的文件
clean:
    rm -f $(OBJ) hello

在这个 Makefile 中:

  • CC 和 CFLAGS 是变量。它们分别表示要使用的 C 编译器和编译选项。
  • OBJ 是一个变量,包含了所有的目标文件名。
  • %.o: %.c 是一个模式规则,它告诉 make 如何从 .c 文件生成 .o 文件。$@ 表示目标文件名,$< 表示第一个依赖文件名。
  • hello: $(OBJ) 定义了名为 hello 的目标,它依赖于 $(OBJ) 中列出的所有 .o 文件。如果任何一个 .o 文件更新了,hello 就会被重新构建。
  • clean 是一个特殊的目标,用于删除所有被 make 生成的文件。

你可以通过在命令行中输入 make 来构建 hello 目标,输入 make clean 来删除所有生成的文件。

构建工具cmake

CMake 是一个跨平台的、开源的、自动化构建系统。它用于控制软件编译过程,使用一个编译器独立的配置文件,并且可以生成一个运行在许多操作系统和编译器环境中的标准的构建环境。

CMake 表述编译过程的方式不同于传统的 Makefile。虽然 CMake 可以生成 Makefile,但你也可以使用 CMake 的图形化接口选择生成例如 Visual Studio 的工程文件。

以下是一个基本的 CMake 的使用过程:

创建 CMakeLists.txt 文件 :在你的项目中创建一个名为 CMakeLists.txt 的文件。这个文件会描述项目中所有的源文件和头文件,以及如何编译它们。

例如,你可能有这样的一个项目结构:

/project
main.c
CMakeLists.txt
/project
    /src
        main.c
    CMakeLists.txt

你的 CMakeLists.txt 文件可能长这样:

cmake_minimum_required(VERSION 3.10) # 指定最小 CMake 版本
project(my_project) # 项目名称
add_executable(my_project src/main.c) # 添加可执行文件目标
cmake_minimum_required(VERSION 3.10)         # 指定最小 CMake 版本
project(my_project)                          # 项目名称
add_executable(my_project src/main.c)        # 添加可执行文件目标

配置和生成构建文件:运行 CMake 工具,它会读取你的 CMakeLists.txt 文件,并生成适合你的编译环境的构建文件。你可以通过命令行或者 CMake 的图形化接口进行这个操作。

如果你在命令行中,你可能会执行以下命令:

mkdir build # 创建一个构建目录
cd build # 进入构建目录
cmake .. # 运行 CMake
mkdir build                   # 创建一个构建目录
cd build                      # 进入构建目录
cmake ..                      # 运行 CMake

编译和构建项目:使用生成的构建文件来编译和构建你的项目。如果你的构建文件是 Makefile,你会使用 make 命令。如果你的构建文件是 Visual Studio 工程文件,你会在 Visual Studio 中打开它。

如果你在命令行中,你可能会执行以下命令:

make # 编译项目
make                          # 编译项目

CMake 的优点包括跨平台、能处理大型项目、能够很好地处理复杂的依赖关系、提供了许多强大的功能,如测试、打包、安装和生成包配置文件等。

静态库和动态库

链接是编译过程的最后一步,这一步骤由链接器(Linker)完成。链接器将编译器生成的一个或多个对象文件以及库合并成一个单一的可执行文件或者库。以下是链接过程中的主要步骤:

  • 符号解析:链接器查找所有未定义的符号(例如函数或全局变量的名称)并将这些符号与他们的定义关联起来。这些定义可能来自于其他的对象文件或者库。如果链接器无法找到某个符号的定义,它就会报告一个未解析的符号错误。
  • 地址分配:链接器为每个符号分配一个运行时地址。链接器将所有的相同类型的节(比如代码节或数据节)聚合在一起,并且为每个聚合后的节分配一个地址。
  • 重定位:链接器更新代码中对符号的所有引用,使得这些引用指向正确的运行时地址。这个过程称为重定位。
  • 构建输出文件:链接器生成一个可执行文件或者库。这个文件包含了程序的所有代码和数据,以及需要运行程序的其他信息,如入口点地址(程序开始执行的位置)。

这就是链接过程的基本步骤。值得注意的是,链接可以在编译时进行(静态链接),也可以在程序运行时进行(动态链接)。静态链接的结果是一个完全独立的可执行文件,包含了程序执行所需要的所有代码和数据。动态链接的结果是一个依赖于一个或多个动态链接库的可执行文件,这些库在程序运行时被加载到内存并链接。动态链接可以节省磁盘空间和内存,并允许程序共享库的代码,但是它需要更复杂的运行时支持。

在计算机编程中,库(Library)是一组预先编译好的代码,它们被打包起来供其他程序使用。库中的代码通常都是一些已经被编写并且经过测试的常用函数或类,它们可以被多个不同的程序复用。

使用库有以下一些主要优点:

  • 代码复用:库让我们可以复用已经被编写和测试过的代码,这样可以节省大量的开发和测试时间。
  • 模块化:库的使用促进了代码的模块化,我们可以将复杂的问题分解成几个更小的模块,然后为每一个模块使用或编写一个库。
  • 维护和更新:当库的代码被更新时,所有使用这个库的程序都可以受益。这样就避免了每个程序都需要单独更新代码的问题。

库通常分为静态库和动态库:

  • 静态库:在程序编译时,静态库的代码会被包含在最终的可执行文件中。这意味着程序运行时不再需要库文件,但会使得可执行文件变大。在Unix/Linux系统上,静态库通常使用 .a 扩展名,在Windows系统上,通常使用 .lib 扩展名。
  • 动态库:动态库的代码在程序运行时才会被载入。这可以减小可执行文件的大小,并允许多个程序共享同一份库代码,但程序运行时需要有对应的库文件。在Unix/Linux系统上,动态库通常使用 .so 扩展名,在Windows系统上,通常使用 .dll 扩展名,而在macOS系统上,通常使用 .dylib 扩展名。

回顾一下,将一个程序编译成可执行程序的步骤:

lib.png

静态库是一种包含了多个对象文件(即编译过的源代码文件)的库,这些对象文件被打包成一个单一文件。在链接阶段,编译器或链接器会把静态库中被程序引用到的部分复制到最终的可执行文件中,形成程序的一部分。

以下是静态库的一些特点:

  • 可移植性:由于静态库中的所有代码都被包含在最终的可执行文件中,因此生成的程序不依赖于任何外部的动态库。这使得程序更容易在不同的系统上运行,因为你不需要担心目标系统上是否存在所需的库版本。
  • 空间消耗:虽然静态链接增加了二进制文件的可移植性,但是它也使得二进制文件变大,因为所有引用的库代码都被复制到了可执行文件中。如果多个程序使用同一个静态库,那么每个程序的可执行文件中都会包含一份库的副本。
  • 更新困难:如果静态库有更新或者修复,你需要重新编译和链接你的程序才能使用新版本的静态库。它不像动态库那样,只需要替换库文件就可以使用新版本的库。
  • 链接时的错误检查:与动态库不同,使用静态库链接的程序在编译阶段就会检查到所有的链接错误。这有助于在程序运行之前发现和修正问题。

在Unix-like系统(如Linux)中,静态库文件通常以.a为扩展名,这些文件可以通过ar(归档)程序创建和管理。在Windows系统中,静态库通常使用.lib扩展名。

之所以叫做静态库,是因为在链接阶段,会将汇编生成的目标文件(.o)与引用的库一起链接打包到可执行文件中。因此对应的链接方式称为静态链接。

静态库与汇编生成的目标文件一起链接为可执行文件,那么静态库必定跟.o文件格式相似。其实一个静态库可以简单看成是一组目标文件(.o/.obj文件)的集合,即很多目标文件经过压缩打包后形成的一个文件。

静态库特点总结:

  • 静态库对函数库的链接是放在编译时期完成的。
  • 程序在运行时与函数库再无瓜葛,移植方便。
  • 浪费空间和资源,因为所有相关的目标文件与牵涉到的函数库被链接合成一个可执行文件。
lib-1.png

Linux下创建与使用静态库

Linux静态库命名规则

Linux静态库命名规范,必须是”lib[your_library_name].a”:lib为前缀,中间是静态库名,扩展名为.a。

创建静态库(.a)

通过上面的流程可以知道,Linux创建静态库过程如下:

  • 首先,将代码文件编译成目标文件.o(o)gcc -c demo.c ,注意带参数-c,否则直接编译为可执行文件
  • 然后,通过ar工具将目标文件打包成.a静态库文件,ar -crv libstaticdemo.a demo.o ,生成静态库a

大一点的项目会编写makefile文件(CMake等等工程管理工具)来生成静态库,输入多个命令太麻烦了。

使用静态库

Linux下使用静态库,只需要在编译的时候,指定静态库的搜索路径(-L选项)、指定静态库名(不需要lib前缀和.a后缀,-l选项)。

gcc main.c -L../StaticLibrary -lstaticdemo
gcc main.c -L../StaticLibrary -lstaticdemo
  • -L:表示要连接的库所在目录
  • -l:指定链接时需要的动态库,编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.a或.so来确定库的名称。

Windows下创建与使用静态库

在Windows环境下,创建和使用C语言静态库通常使用Microsoft的Visual Studio环境或者GCC编译器。MinGW(Minimalist GNU for Windows)的流程和Linux类似,下面主要介绍如何使用Microsoft Visual Studio来创建和使用静态库。以下是一个简单的步骤:

创建静态库:

打开Visual Studio,点击 File -> New Project… 来创建一个新的项目。

  • 在 Create a new project 对话框中,选择 Static Library (.lib) 模板,然后点击 Next 按钮。
  • 在 Configure your new project 对话框中,输入项目的名称和位置,然后点击 Create 按钮。
  • Visual Studio将创建一个新的静态库项目,其中会包含一个预编译头文件(h、pch.cpp)和一个示例类文件(framework.h、framework.cpp)。你可以在这些文件中添加你的代码,或者添加新的源文件到项目中。
  • 在完成代码编写后,你可以点击 Build -> Build Solution 来编译静态库。编译完成后,静态库文件(.lib 文件)将会在项目的 Debug 或 Release 目录下生成。

使用静态库:

  • 创建一个新的应用程序项目,或者打开一个已有的项目。
  • 在项目的属性页面中,点击 Linker -> Input,然后在 Additional Dependencies 项中添加你的静态库文件(.lib 文件)。你需要提供静态库文件的完整路径,或者如果静态库文件在项目的目录下,你可以提供相对于项目的相对路径。
  • 点击 C/C++ -> General,然后在 Additional Include Directories 项中添加你的静态库的头文件所在的目录。这样你就可以在你的代码中直接引用静态库的头文件了。
  • 点击 OK 按钮关闭项目属性页面。现在你就可以在你的程序中使用静态库中的函数或类了。
  • 在完成代码编写后,你可以点击 Build -> Build Solution 来编译你的程序。如果一切顺利,你的程序应该可以正常编译并链接到静态库。

以上就是在Windows下使用Visual Studio创建和使用静态库的基本步骤。注意,不同版本的Visual Studio可能会有一些小的差别。如果你遇到问题,你应该参考你使用的Visual Studio版本的官方文档。

上面提到 空间浪费是静态库的一个问题,另一个问题是静态库对程序的更新、部署和发布页会带来麻烦。如果静态库liba.lib更新了,所以使用它的应用程序都需要重新编译、发布给用户(对于玩家来说,可能是一个很小的改动,却导致整个程序重新下载,全量更新)

动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入。不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例,规避了空间浪费问题。动态库在程序运行是才被载入,也解决了静态库对程序的更新、部署和发布页会带来麻烦。用户只需要更新动态库即可,增量更新。

动态库也被称为共享库,是一种包含函数和数据的二进制文件,可以被多个程序在运行时共享使用。动态库的主要优点是可以节省系统的内存和磁盘空间,因为多个正在运行的程序可以共享同一份库代码和数据,而不是每个程序都包含自己的库代码和数据。

以下是动态库的一些特点:

  • 内存效率:当多个程序同时使用同一个动态库时,库代码和数据只需要在内存中有一份拷贝,这可以节省内存和提高效率。
  • 磁盘空间:动态库通常只需要在磁盘上存储一次,即使多个程序都使用这个库。这与静态库不同,静态库的代码会被复制到每一个使用它的程序中。
  • 易于更新:当动态库的代码需要更新时,只需要替换磁盘上的库文件,然后重新启动使用该库的程序,就可以使用新版本的库了。而静态库则需要重新编译链接所有使用它的程序。
  • 延迟绑定:动态库中的函数在程序运行时才会被载入内存,这被称为延迟绑定或动态链接。这意味着程序在启动时不需要加载所有的库函数,只有当函数真正被调用时才会加载。这可以减小程序的初始内存占用,提高启动速度。

然而,动态库也有一些缺点。例如,它增加了程序的复杂性,因为需要处理动态链接和库版本问题。此外,由于动态库在程序运行时才被加载,所以如果库文件不可用或版本不兼容,可能会导致程序无法运行或运行错误。

在Unix-like系统中,动态库通常有.so(共享对象)扩展名。在Windows系统中,动态库通常有.dll(动态链接库)扩展名,在macOS系统中,动态库通常有.dylib扩展名。

lib-2.png

动态库特点总结:

  • 动态库把对一些库函数的链接载入推迟到程序运行的时期。
  • 可以实现进程之间的资源共享(因此动态库也称为共享库)
  • 将一些程序升级变得简单。
  • 可以做到链接载入完全由程序员在程序代码中控制(显示调用)。

Window与Linux执行文件格式不同,在创建动态库的时候有一些差异。

  • 在Windows系统下的执行文件格式是PE格式,动态库需要一个DllMain函数做出初始化的入口,通常在导出函数的声明时需要有_declspec(dllexport)关键字。
  • Linux下gcc编译的执行文件默认是ELF格式,不需要初始化入口,亦不需要函数做特别的声明,编写比较方便。

与创建静态库不同的是,不需要打包工具(ar、lib.exe),直接使用编译器即可创建动态库。

Linux下创建与使用动态库

linux动态库的命名规则

动态链接库的名字形式为 libxxx.so,前缀是lib,后缀名为”.so”。

  • 针对于实际库文件,每个共享库都有个特殊的名字”soname”。在程序启动后,程序通过这个名字来告诉动态加载器该载入哪个共享库。
  • 在文件系统中,soname仅是一个链接到实际动态库的链接。对于动态库而言,每个库实际上都有另一个名字给编译器来用。它是一个指向实际库镜像文件的链接文件(lib+soname+.so)。

创建动态库(.so)

首先,生成目标文件,此时要加编译器选项-fpic, gcc -fPIC -c DynamicMath.c 。fPIC 创建与地址无关的编译程序(pic,position independent code),是为了能够在多个应用程序间共享。

然后,生成动态库,此时要加链接器选项-shared, gcc -shared -o libdynmath.so DynamicMath.o

使用动态库

引用动态库编译成可执行文件(跟静态库方式一样):

gcc TestDynamicLibrary.c -L../DynamicLibrary -ldynmath
gcc TestDynamicLibrary.c -L../DynamicLibrary -ldynmath

然后运行:./a.out,发现竟然报错了!

error-1.png

可能大家会猜测,是因为动态库跟测试程序不是一个目录,那我们验证下是否如此:

error-2.png

发现还是报错!!!那么,在执行的时候是如何定位共享库文件的呢?

  • 当系统加载可执行代码时候,能够知道其所依赖的库的名字,但是还需要知道绝对路径。此时就需要系统动态载入器(dynamic linker/loader)。
  • 对于elf格式的可执行程序,是由ld-linux.so*来完成的,它先后搜索elf文件的DT_RPATH段—环境变量LD_LIBRARY_PATH—/etc/ld.so.cache文件列表—/lib/,/usr/lib 目录找到库文件后将其载入内存。

如何让系统能够找到它:

  • 如果安装在/lib或者/usr/lib下,那么ld默认能够找到,无需其他操作。
  • 如果安装在其他目录,需要将其添加到/etc/ld.so.cache文件中,步骤如下:
    • 编辑/etc/ld.so.conf文件,加入库文件所在目录的路径
    • 运行ldconfig,该命令会重建/etc/ld.so.cache文件

我们将创建的动态库复制到/usr/lib下面,然后运行测试程序。

Windows下创建与使用动态库

在Windows环境下,创建和使用C语言动态库通常使用Microsoft的Visual Studio环境或者GCC编译器。使用MinGW(Minimalist GNU for Windows)的步骤与Linux环境基本一致。以下是使用Microsoft的Visual Studio来创建和使用C语言动态库。

创建动态库:

  • 打开Visual Studio,选择 File > New > Project。
  • 在新项目对话框中,选择 Empty Project,然后给你的项目命名(例如:MyLibrary)。
  • 右键点击源文件,选择 Add > New Item。
  • 在新项目对话框中,选择 C++ File (.cpp),然后命名你的文件(例如:MyLibrary)。
  • 在新建的文件中,你可以编写你的库函数。注意,由于我们正在创建动态库,所以需要使用__declspec(dllexport)关键字来声明需要导出的函数。例如:
__declspec(dllexport) void MyFunction()
// Your code here
__declspec(dllexport) void MyFunction()
{
    // Your code here
}
  • 右键点击项目,选择 Properties,在打开的属性页中,配置项目为动态库。在 Configuration Type 下拉菜单中选择 Dynamic Library (.dll)。
  • 点击 OK,然后选择 Build > Build Solution 来构建你的动态库。生成的dll文件通常在 Debug 或者Release文件夹下。

使用动态库:

  • 创建一个新的项目(例如,一个控制台应用)。
  • 在你的代码中,你需要使用 __declspec(dllimport) 来声明你要使用的库函数。你还需要包含库函数的原型。例如:
_declspec(dllimport) void MyFunction();
_declspec(dllimport) void MyFunction();
  • 在你的代码中调用库函数,就像调用普通函数一样。
  • 在项目属性中,需要配置链接器知道动态库的位置。在链接器的输入属性页中,添加你的.lib文件到 Additional Dependencies。在常规属性页中,添加你的动态库目录到 Additional Library Directories。
  • 点击 OK,然后选择 Build > Build Solution 来构建你的应用。
  • 运行你的应用时,需要保证动态库的.dll文件在可访问的路径中,通常是应用的Debug或Release目录,或者是系统的PATH环境变量所包含的目录。

以上就是在Windows下使用Microsoft的Visual Studio创建和使用C语言动态库的基本步骤。

C语言IDE推荐

Visual Studio

Visual Studio是微软(Microsoft)为以Windows为主的平台开发的一套功能全面而强大的IDE(集成开发环境),支持C#、F#、VB、C/C++等多种语言的开发。

作者个人推荐使用Visual Studio 2017的最新版本。除非还有使用C#和VB两门语言的需求,或者电脑系统是2015年以前的版本,否则不推荐VS 2015以前的版本(因为会强制附带C#和VB的组件,且安装时会设置系统还原点速度极慢,装好的占用还要多出几GB;另外,2015以前的版本对C/C++语言标准的支持非常不完善,非常不现代)。最新的VS 2019版也不错,但新建项目的页面相较前代做了大改,容易劝退部分依赖老教程的萌新。

Visual Studio Code

VS Code是由Microsoft开发的一款轻量级的代码编辑器,它支持几乎所有主要的开发语言,包括C语言。它非常适合那些希望使用一个简约但功能强大的开发工具的开发者。

为了在VS Code中编写C语言代码,你还需要安装一个名为“C/C++”的扩展,这个扩展由Microsoft开发,可以提供C和C++的语法高亮显示、代码提示、代码格式化、定义跳转、错误检查等功能。

安装步骤如下:

  • 打开VS Code。
  • 点击侧边栏的Extensions图标,或者按下快捷键Ctrl+Shift+X打开扩展面板。
  • 在搜索框中输入“c++”,然后在搜索结果中找到“C/C++”扩展,点击Install按钮进行安装。

安装编译器:

  • 你还需要在你的电脑上安装一个C语言编译器。对于Windows用户,你可以使用MinGW或Cygwin;对于macOS用户,你可以使用Xcode命令行工具;对于Linux用户,你可以使用GCC。

编写和运行C语言代码:

  • 在VS Code中新建一个.c文件,然后你就可以开始编写C语言代码了。
  • 要运行你的代码,你需要首先将其编译成可执行文件。你可以在终端中手动运行编译命令,也可以在VS Code中配置任务(Task)自动执行编译命令。
  • 编译完成后,你可以在终端中运行生成的可执行文件,或者在VS Code中配置调试器(Debugger)进行调试运行。

以上就是在Visual Studio Code中开发C语言代码的基本步骤。如果你需要更详细的指导,你可以查阅VS Code官方文档的C/C++部分,该文档提供了详细的配置指南和教程。

Dev-C++

Dev-C++ 是Windows环境下的轻量级C/C++ 集成开发环境。遵循C++ 11标准,同时兼容C++98标准。

Dev-C++ 功能简洁,易于学习和使用,但同时该软件功能不完善,存在一些问题,并且从未在商业级的软件开发中使用过,只适合于初学者和教学使用。遗憾的是,我的教材使用该软件进行教学。

Dev-C++ 由Orwell公司继续更新开发(现已停止),最新版本为5.11,使用GCC 4.9,上次更新时间为2015年4月。这是官方博客以及下载链接

Dev-C++现在被一些公司或者第三方开发者接手:

小熊猫C++

小熊猫C++(Panda C++)是一款用于中小学生学习C++编程的IDE,它设计得非常简单易用,并且提供了许多适合初学者的功能。

以下是一些小熊猫C++的主要特性:

  • 简洁的用户界面:小熊猫C++的用户界面设计得非常简洁明了,初学者可以很容易地找到需要的功能。
  • 集成编译器:小熊猫C++内置了Mingw编译器,用户无需额外安装编译器就可以编写并运行C++程序。
  • 代码高亮:小熊猫C++支持C++的语法高亮显示,有助于提高编程效率。
  • 运行结果显示:编译并运行程序后,小熊猫C++会在下方的控制台窗口显示运行结果,用户可以直观地看到程序的输出。
  • 错误提示:如果程序中有编译错误,小熊猫C++会在控制台窗口中显示错误信息,并在代码编辑区域高亮显示出错的代码行。

对于初学者来说,小熊猫C++是一个很好的选择。它让用户可以专注于学习编程,而不需要去熟悉复杂的开发环境。然而,对于高级用户来说,小熊猫C++可能会显得功能不够强大,这时候可以考虑使用Visual Studio Code、CLion等更专业的IDE。

Code::Blocks

Code::Blocks 是一个免费的,开源的 C, C++ 和 Fortran 集成开发环境(IDE),适用于多种平台(Windows,Linux,Mac OS)和编译器(GCC,Clang,Visual C++等)。

以下是一些 Code::Blocks 的主要特性:

  • 易用性: Code::Blocks 设计为尽可能的灵活和可定制,同时保持了友好的用户界面,使得它对初学者和专业人士都十分友好。
  • 多编译器支持: Code::Blocks 支持多种编译器,包括 GCC,MinGW,Digital Mars,Microsoft Visual C++,Borland C++,WATCOM,LCC等。
  • 快速构建: Code::Blocks 采用了并行编译,能大大加快构建速度。
  • 无需 Makefile: Code::Blocks 提供了非常强大的构建系统,你无需去编写 Makefile,但如果你需要,它也支持使用 Makefile。
  • 调试: Code::Blocks 内置了对 GDB 和 CDB 的支持,让你能够方便地进行调试和调试步骤。
  • 扩展: Code::Blocks 提供了插件系统,使你可以方便地扩展 IDE 的功能。许多 Code::Blocks 的功能,如编译器和调试器支持,实际上是通过插件实现的。
  • 移植性: Code::Blocks 可以在 Windows,Linux,macOS 等多种平台上运行。

Code::Blocks 是一个功能齐全的,足够强大的开发环境,对于 C 和 C++ 开发者来说,它是一个值得考虑的选择。

JetBrains Clion

CLion 是一款由 JetBrains 开发的集成开发环境(IDE),专门为 C 和 C++ 开发设计。CLion 具有许多强大的特性,包括:

  • 智能编辑: CLion 提供很多有助于提高编程效率的功能,如实时代码分析、快速修复、代码生成以及重构等。
  • 导航 & 搜索: CLion 提供强大的代码导航和搜索功能,你可以轻松地在文件、类、符号、路径等之间进行切换和查找。
  • 调试: CLion 内置了 GDB 和 LLDB 调试器的前端,提供了一个友好的用户界面来进行调试操作。你可以设置断点、单步执行代码、检查变量值、评估表达式等。
  • 构建 & 运行: CLion 支持 CMake 构建系统,并且可以与其他流行的构建系统(如 Make、Gradle 等)集成。你可以直接在 IDE 中构建、运行和调试你的应用。
  • 集成开发环境: CLion 提供了其他 JetBrains IDE 的一些常见特性,如版本控制集成、支持 Docker 和 WSL、数据库工具等。
  • 需要注意的是,CLion 并非免费软件,需要购买许可证才能使用。然而,JetBrains 提供了免费的教育许可证,对于学生和教育工作者是免费的。此外,JetBrains 还提供 30 天的免费试用期。

使用 CLion 需要有一些基本的 C 或 C++ 和构建系统(如 CMake)的知识。如果你是初学者,使用 CLion 可能会有一些困难。但一旦你熟悉了它,你会发现 CLion 是一个非常强大和高效的开发工具。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK