C/C++基础
1. 编译/连接
1.1 生成可执行文件的步骤
预处理
处理所有宏定义、处理所有条件编译、处理包含文件
编译
将预处理完的文件,生成相应的汇编文件,(词法、语法分析优化)
汇编
将汇编代码转换成可执行的机器指令,生成目标文件 (二进制文件)
链接
动态链接库和静态链接库 (相当于寻址操作)
目标文件已经是二进制文件,与可执行文件的组织形式类似,只是有些函数和全局变量的地址还未找到,因此还无法执行。链接的作用就是找到这些目标地址,将所有的目标文件组织成一个可以执行的二进制文件
1.1.1 编译与链接
编译/汇编:通过编译器,识别源文件中的语句,将其转换成计算机能够识别的二进制形式
编译后,生成的是汇编文件,汇编后生成目标文件,不是最终的可执行文件
目标文件(中间文件),是二进制形式(.o
或.obj
)
编译只能编译单个源文件,多个源文件就需要多次编译链接:通过链接器,链接生成可执行文件。编译只是对自己编写的源文件进行编译,链接是将源文件的目标文件和标准库、动态链接库等结合起来,组成最终的可执行文件
即,将所有二进制形式的目标文件和系统组件组合成一个可执行文件
嵌入式编译器:CCS (DSP)
1.1.2 动态链接/静态链接
链接过程:假设一个程序有两个模块 main.c 和 module.c,我们在 module.c 中定义了函数 func(),并在 main.c 中进行了多次调用,当所有模块被编译成一个可执行文件后,每一处对 func() 函数的调用都会被替换为一个绝对地址。但由于每个模块都是单独编译的,编译器在处理 main.c 时并不知道 func() 的地址,所以需要把这些调用 func()的指令的目标地址搁置,等到最后链接的时候再由链接器将这些地址修正。有了链接器,我们可以直接调用其他模块中的函数而无需知道它们的地址
静态链接
在程序之前确定符号地址的过程,叫做静态链接
.a
动态连接
需要等到程序运行期间再确定符号地址,叫做动态连接
.dll
/.so
多文件编译
gcc test.c test2.c -o main.out
file.out
是可执行文件
不建议在头文件中定义变量,可以声明,防止出现重复包含的情况
多文件编译中,只需要引入.h文件中就可以了
全局变量的作用范围不是从变量定义到该文件结束,在其他文件中也有效
#define 用法
#define 直接一个参数
https://blog.csdn.net/ceci_zhou/article/details/19917637
想象#ifndef 定义头文件的时候
在头文件中,#define 后面只有一个参数的用法是为了定义一个符号,用来给#if(n)def判断或者防止头文件被重复包含。这个符号在预处理过程中会被替换为空字符串,即删除。这样做主要是为了标记某些内容,使程序阅读者能够清楚标记表明的意义,同时又不影响被编译的源代码。而在其他情况下,#define 后面需要两个参数,表示用第一个参数代替第二个参数
多文件编程
#include .h头文件
,那么就不需要再进行变量和函数的声明
同名.c文件,也要引入同名.h文件
注
不带
.h
的头文件为C++新标准中的,所有的符号都位于命名空间std
中,使用时需要声明命名空间std
带.h
的头文件,没有使用任何命名空间,所有符号都位于全局作用域
定义指的是将某个符号完整的描述清楚
声明的作用仅仅是告诉编译器该符号的存在,至于该符号的具体含义,只有等链接的时候才知道
一个符号允许被声明多次,定义只能有一次
如果在头文件中存放定义,就等于在多的.cpp文件中出现对同一个符号的定义,即使定义的内容相同,编译器会出现重定义的问题
多文件编译时的头文件https://blog.csdn.net/lyb06/article/details/127501959 重要!!!
见下面的 重复包含 / 重复定义
extern
extern 与.h文件
https://blog.csdn.net/m0_46606290/article/details/119973574
extern dataType var 相当于引用声明,表明声明不是一个定义,告诉编译器该变量的真正定义在别的地方,可以当前文件,也可以是别的文件
extern 关键字修饰的表达式,的变量,不会分配内存空间,他只是用来引用一个已经存在的外部定义
不能利用extern进行变量定义,extern int a=1; 这种事错误的
跨文件调用的时候,要加上extern 虽然有写情况下仍然可以执行(函数不用extern可以,变量不可以)
简单用法
提供一个全局变量的引用,全局变量对所有程序文件都是可见的
使用extern
无法初始化变量,会把变量名指向一个之前定义过的存储位置
其实就是 A文件中定义了一个可以在B文件中使用的全局变量或函数,那么在B文件中就可以使用extern来得到A文件中定义的变量或函数的引用(用在在另一个文件中声明一个全局变量或函数)
进阶:A/B文件可以存在相互的使用extern
// A.c
#include <stdio.h>
int count;
extern void write_extern();
int main()
{
count=5;
write_extern();
}
// B.c
#include <stdid.h>
extern int count;
void write_extern(void)
{
printf("count is %d\n",count)
}
简单用法2
时刻注意先声明后定义
// 在一个文件内
int a; // 之所以下面可以使用extern 是因为此时a相当于一个外部链接变量(外部链接的静态)
// 当在函数内不写extern的时候,那么根据作用域进行判断使用局部变量a或全局变量a
int main() // or some function
{
extern int a; // 使用的是全局变量的a
int a; // 重新定义了一个局部变量a
}
// 下面这个是错误的
int a=10; // 下面再使用extern 就相当于是先定义后声明了,这是错误的
int main() // or some function
{
extern int a; // 使用的是全局变量的a
int a; // 重新定义了一个局部变量a
}
进阶用法
https://blog.csdn.net/m0_46606290/article/details/119973574
/*test.h*/
extern int a; // 引用声明,引用了一个已经存在真正定义的变量,该变量先通过.c文件#include 然后进行了定义!!!
/*test.c*/
#include "test.h"
int a=10;
/*main.c*/
#include <stdio.h>
#include "test.h" // 会连接到
int main()
{
printf("%d\n",a); // 10 使用的是test.c中的a
int a=2000;
printf("%d\n",a); // 2000 就近原则,使用的是局部变量的a
}
进阶用法2
/*test.h*/
void func(); // 函数声明
/*test.c*/
#include <stdio.h>
#include "test.h"
extern int times; // 引用声明,可以引用主文件中的全局变量
// 函数定义
void func()
{
int i=0;
int i=0;
for ( i;i<times;i++)
{
printf("hello world\n");
}
}
/*main.c*/
#include <stdio.h>
#include "test.h"
// 定义全局变量
int times=10; // test.c中extern 引用声明
int main()
{
loop(); // 调用的是test.h 中连接test.c的函数
return 0;
}
vscode 多文件编译
修改tasks.json文件
// "${file}", // 注释掉这个
"-o",
"${fileDirname}\\${fileBasenameNoExtension}.exe",
"${fileDirname}\\*.cpp" // 加一个这个
vscode多文件编译的时候,不能有两个main
2. 头文件/库文件
// 标准输入输入文件
#include <iostream>
#include <stdio.h>
// 标准库文件 包括system函数
#include <stdlib.h>
2.1 头文件
头文件包含若干函数
使用外部函数,要引入对应的头文件
通常,头文件只包含函数的说明,即函数怎么用;而函数本身保存在其他文件,在链接时会找到
#include<file_name.h>
;#include
的作用就是将头文件中的文本复制到当前文件,然后一起被编译器编译
在头文件中,必须:只能包含变量和函数声明,不能包含定义,否则在多次引入时会引起重复定义的错误
#include
不能.c
文件
头文件与命名空间 见C+±基础
多文件编译时的头文件https://blog.csdn.net/lyb06/article/details/127501959 重要!!!
注:
不带
.h
的头文件为C++新标准中的,所有的符号都位于命名空间std
中,使用时需要声明命名空间std
带.h
的头文件,没有使用任何命名空间,所有符号都位于全局作用域
头文件/源文件 中 包含头文件
在头文件中包含头文件和在源文件中包含头文件的区别如下:
编译时间:在头文件中包含头文件会导致宏展开、类型定义、函数声明等被重复编译,增加编译时间;而在源文件中包含头文件只会在编译源文件时被编译一次,不会增加编译时间。
命名空间:在头文件中包含头文件会导致命名空间的嵌套,容易引起命名冲突;而在源文件中包含头文件不会出现这种情况。
依赖性:在头文件中包含头文件可能会产生循环依赖,导致编译错误;而在源文件中包含头文件可以避免这种情况。
根据以上区别,建议在头文件中只包含必要的头文件,而在源文件中包含所有需要的头文件。这样可以减少编译时间,避免命名空间冲突和循环依赖问题。
2.2 库/库函数
库:一系列函数的集合
标准库、第三方库
3.预处理
概念
#
开头的命令为预处理命令
预处理:对源文件进行简单加工的过程
预处理不等于编译,编译之前要先做预处理
预处理 由 预处理程序完成
预处理包括:宏定义、文件包含、条件编译
如#if #elif #endif
预处理命令,在编译之前由预处理程序来执行
文件包含
#include
文件包含
其实也是预处理命令
就是将头文件的内容插入到该命令所在的位置,从而将头文件、当前源文件连接成一个源文件
#include <xxx.h>
在系统路径下查找头文件
#include "xxx.h"
在当前目录下找头文件,如果没有找到,则从系统路径下查找
头文件包含是一个递归循环的过程,如果被包含的头文件中还包含了其他的头文件,预处理器会继续将他们包含进来,直到不会在包含任何头文件
防止重复包含 - ifndef/define/endif
https://blog.csdn.net/m0_48990191/article/details/115007141 – 没看完
ifndef
与ifdef
相反,如果当前的宏未被定义,则对 对应的coding进行编译
被重复引用:有些被重复引用只是增加了编译工作的工作量,只会引起编译效率低下,有些则会引起错误(如在头文件中定义全局变量)
防止头文件被重复引用(一个头文件在同一个c/cpp文件中被include多次 <-- include嵌套),防止被重复编译,使用 宏保护 来防止重复包含
#ifndef _FILE_NAME_H
#define _FILE_NAME_H
// 该头文件的内容
#endif
/*
#ifndef FILE_H // if not define FILE.h 如果不存在file.h (作用:防止file.h被重复引用)
#define FILE_H // 就引入file.h
...
#endif //否则不需要引入
*/
// 等价于
#pragma once
// 头文件的其他内容
#ifndef
条件编译,即预处理功能(宏定义、文件包含、条件编译)中的一种
#ifndef
作用如果
.h
文件中定义了全局变量,一个c文件包含了.h
文件多次,如果不加#ifndef
宏定义,会出现变量重复定义的错误
#define
#endif
第一次包含该头文件,会定义FILE_H,并执行’头文件内容’部分的代码,第二次包含时因为已经定义了该宏FILE_H,则不会重复执行’头文件内容’部分的代码
这样,头文件只在第一次包含时,起作用,再次包含无效
#ifndef FILE_H
#define FILE_H
/*下面是头文件的内容...*/
#include <math.h> // 引入标准库头文件
#include "header.h" // 引入非标准库的头文件
void function(...) //全局函数声明
class C{}; // 类结构声明
#endif
重复包含 / 重复定义
头文件中定义的变量和函数会被编译器当做全局变量和函数处理,每个源文件在编译时都回单独对这些全局变量和函数进行编译
如果同一个全局变量或函数被多个源文件共同引用,就会导致重复定义的问题
头文件(宏定义)保护是为了避免头文件的重复包含,一般情况下会定义一个宏来实现这一个功能
注意:
但是即使添加了头文件保护,如果多个源文件都包含了同一个头文件,而头文件中又定义了全局变量,依然会出现重复定义的问题
这是因为头文件保护只能防止同一个源文件中的重复包含,而无法解决多个源文件同时包含同一个头文件的问题
因此,如果多个源文件都需要包含同一个头文件,且该头文件中包含了全局变量,就需要使用extern和其他一些技巧来避免重复定义的问题
这是因为,每个编译器都会在编译过程中将源代码和头文件打包在一起处理,而多个源文件中包含同一个头文件时,统计这些头文件中变量的数量时会进行累加,从而导致多重定义的错误
编译/链接
从编译、链接角度看
编译节期间,编译器将每个源文件转换为相应的对象文件,即每个源文件都会变异成一个单独的目标文件,在编译期间,编译器会检查代码中是否存在错误或逻辑错误,并且执行必要的预处理工作,编译器会将源文件中的函数和变量声明与头文件进行匹配,编译器不会检查是否存在重复定义或缺少定义的变量和函数
链接器将对象文件组合在一起以创建可执行文件,编译器在此过程中将检查所有的定义和声明,并确保所有函数和变量都有唯一的定义,如果有重复定义,则会导致链接错误,即链接时会将所有文件中的全局变量合并起来,而当多个源文件中包含同一个头文件时,统计这些头文件中变量的数量时会进行累加,从而导致多重定义的错误,也就是全局变量在链接时被重复定义了
static/extern
问题:c++ 在a.h文件中定义了一个全局静态变量,在a.cpp中#include"a.h",为什么这个全局变量在a.cpp中还是不可见的
解决:
在C++中,静态全局变量的作用域仅限于当前源文件。也就是说,在一个源文件中定义的静态全局变量只能在该文件中访问,并且对其他源文件是不可见的
在你的情况下,a.h文件声明了一个静态全局变量。如果其它源文件需要访问这个静态全局变量,那么需要使用extern关键字进行声明而非重新定义。因此,在a.cpp中#include"a.h"后,需要再添加一条extern语句来声明前面在a.h中定义的静态全局变量。这样才能够在a.cpp中访问该静态全局变量。
如果你在a.cpp中重新定义了一个同名的静态全局变量,那么这将会导致链接错误,因为编译器会认为这两个变量表示不同的对象,而不会把它们视为同一个变量的多次定义。
问题: c++ 定义一个静态全局变量,既然这个变量仅对当前文件可见,那么为什么还能用extern访问
在 C++ 中,extern 关键字用来声明一个变量是在程序的其他地方定义的。通常情况下,这意味着它是在另一个源文件中定义的全局变量。
即使是静态全局变量也不例外。虽然静态全局变量的作用域仅限于当前 C++ 源文件中,但是通过使用 extern 关键字在其他文件中声明该变量,就可以访问相同的变量,因为 static 关键字不会改变变量的存储位置。
当别的源文件需要访问当前源文件中定义的静态全局变量时,只需在其他文件中使用 extern 关键字声明该变量,编译器会知道该变量是在其他源文件定义的,并且共享内存地址,从而实现在不同源文件之间共享数据的目的。
在使用静态全局变量时,需要注意的是确保变量定义和声明的一致性,否则可能会导致链接错误。
在.h文件中定义全局变量,然后再多个文件中包含这个.h文件是错误的,会出现宏定义的问题
解决方案之一就是,将这个全局变量定义为static静态全局变量,从而使其只在该.h文件中可见
宏定义
宏定义:用一个标识符来表示一个字符串(这里的字符串指的不是数据类型字符串,而可以是数字、表达式、if语句,函数等),即后面的代码中用该标识符表示字符串
在预处理阶段,对程序中所有出现的宏名,预处理器都会用宏定义中的字符串去代替,即“宏替换”
#undef
表示结束作用域
代码中的宏如果被引号包围,则预处理程序不进行宏替换
宏定义仅仅是字符串替换,不会对表达式进行计算
// 例子1
#define N 100;
printf("%d",N);
// 例子2
#define M (n*n+3*n)
/*结束宏定义的作用域*/
#define N 10
//coding... // 宏定义的N 只能在coding的作用域中使用
#undef N
嵌套宏定义
#define A 10
#define S A*2 // 注意没有分号!!!
宏定义定义变量类型
#define UINT unsigned int
带参宏定义
https://blog.csdn.net/u012507643/article/details/52918287
带参宏定义中,不会为形式参数分配内存,因此不必指明数据类型,但是实参变量或返回值必须指明数据类型
带参数宏定义,不是值传递,仍然是字符串替换,因此需要注意括号的使用
#define FUNC(x) (x*2) // 注意要加括号
int a;
a=FUNC(10); // a=20
常见宏定义
#include <stdlib.h>
__LINE__ // 当前调用的语句在源代码的行号
__FILE__ // 源文件名称
__DATE__ // 当前的编译日期
__TIME__ // 当前的编译事件
#define
与 typedef
的区别
#define
是由预处理器来处理的,在预处理阶段,只是简单的字符序列替换
typedef
是由编译器完成的,在编译阶段,给原来的数据类型起别名,可以作为一种新的数据类型进行使用
条件编译
条件编译:根据不同情况编译不同代码,产生不同目标文件的机制
条件编译是预处理程序的功能,不是编译器的功能
整型常量表达式:表达式中不能包含变量,且结果必须是整数
目的:允许值编译源程序中满足条件的程序段
#if 整型常量表达式
...
#elif 整型常量表达式
...
#else 整型常量表达式
...
#endif
#include <stdio.h>
// 对不同的平台引入不同的头文件
#if _WIN32 // 识别windows平台,_WIN32是专有宏
#include <windows.h>
#elif __linux__ // 识别linux平台 __linux__是专有宏
#include <unistd.h>
#endif
it main(){
#if _WIN32
Sleep(10);
#elif __linux__
sleep(5);
#endif
// code...
puts('hello world')
}
if/ifdef/ifndef的区别
#if 只能是整型常量表达式
#ifdef 只能是一个宏名
#ifndef 只能是一个宏名
- ifdef
如果当前宏已经被定义过,则对 对应的coding1进行编译,否则对coding2进行编译
#ifdef 宏名
// coding1...
#else
// coding2...
#endif
// 例子
#ifdef _DEBUG
printf("debug mode...")
#else
printf("release mode...")
- ifndef
如果当前宏未被定义过,则对 对应的coding1进行编译,否则对coding2进行编译
预编译/stdafx.h
https://blog.csdn.net/follow_blast/article/details/81704460
https://zhuanlan.zhihu.com/p/112799791
https://blog.csdn.net/qingkong8832/article/details/6695123
https://www.cnblogs.com/yhjoker/p/8110684.html
https://blog.csdn.net/follow_blast/article/details/81704460
https://blog.csdn.net/CV_Jason/article/details/87475644
4.路径
绝对路径
在同路径下找头文件
#include "d:\\xxx.h"
#include "d://xxx.h"
相对路径
先在当前目录下找头文件,没有找到,再从系统路径下查找
当前目录指的是:需要引入头文件的源文件所在目录
建议使用相对路径
#include "./xxx.h"
5. assert断言 – 没看
6. 编译链接-进阶
6.1 gcc/g++
GUN编译器套件包括gcc编译器
gcc编译器包含许多组件
g++
指令就等同于gcc -xc++ -lstdc++ -shared-libgcc
指令 ;表示gcc
在编译 C++ 程序时可以链接必要的 C++ 标准库
- gcc
.c
.i
.s
.o
.out
- g++
.cpp
源文件
.ii
只预处理 未编译、汇编、链接
.s
经过编译后的汇编文件
.o
汇编之后的目标文件
.out
可执行文件
6.2 gcc/g++ 示例
gcc main.c # 输出a.out(linux) a.exe(windows)可执行程序
# 使用gcc 编译执行c++程序
# g++ 指令就等同于gcc -xc++ -lstdc++ -shared-libgcc指令 ;表示 gcc 在编译 C++ 程序时可以链接必要的 C++ 标准库
gcc -xc++ -lstdc++ -shared-libgcc main.cpp
g++ main.cpp -save-temps # 保留中间过程的文件 预处理、编译、汇编、链接
-o # 指定要生成的文件名
-E # 预处理,但不编译
-S # 编译,但不汇编
-c # 编译、汇编,但不链接
-llibrary (-l library) #!!! library 表示要搜索的库文件的名称,该选项用于手动指定链接环节中程序可以调用的库文件 -llstdc++
-std # 手动指定变成所遵循的标准
.c
->.i
gcc-E main.c -o main.i
# #include 搜索顺序
# - 首先搜索当前程序文件所在的目录
# - iqunote 命令选项指定的目录
# -I 命令选项指定的目录
# -isystem 命令选项指定的目录
# 默认的系统路径下搜索
# -idirafer 命令选项指定的目录
.i
->.s
gcc -S main.i # 从预处理到编译
gcc -S main.i -o main.s# 从预处理到编译
gcc -S main.c -o main.s # 从源文件到编译
.s
->.o
gcc -c main.s # 从编译到汇编
gcc -c main.s -o main.o
gcc -c main.c -o main.o # 从源文件到汇编
gcc -c main.i -o main.o # 从预处理到汇编
.o
->.out
gcc -c main.o -o main.out # -o main.exe
6.3 链接器
链接器将多个二进制的目标文件
.o
链接成一个单独的可执行文件
链接过程,把符号(函数名、变量名等一系列标识符)用对应的数据的内存地址(变量地址、函数地址等)代替,以完成程序中多个模块的外部引用
链接器必须将程序中用到的所有c标准库函数加入其中
编译/链接
编译:对每个源文件生成对应的目标文件
.o
,每个目标文件都是二进制文件,但是由于他们对互相调用对方的函数或变量,还可能会调用某些链接库文件中的函数或变量,编译器无法跨文件找到他们确切的存储地址,所以这些目标文件无法单独执行(!!!一个目标文件中使用的函数或变量,可能定义在其他的目标文件中,也可能定义在某个链接库文件中)
链接:对于各个目标文件中缺失的函数或变量的存储地址,由链接器负责修复,并最终将所有的目标文件和链接库组织成一个可执行文件
目标文件 / 库(文件)
https://blog.csdn.net/qq_36689800/article/details/78151395
中间文件有多种形式,最普遍的一种是将源代码转换成机器语言代码,并把结果放在目标代码文件中(简称目标文件)。虽然目标文件中包含机器语言代码,但并不能直接运行。因为其中存储的是编译器翻译的源代码,还不是一个完整的程序。
- 目标代码文件缺失启动代码(startup code)。启动代码充当着程序与操作系统之间的接口。
- 目标代码还缺少库函数。几乎所有的C程序都需要使用C标准库中的函数。举例:源码中使用printf()函数,编译过程中的目标文件并不包含该函数的代码,仅仅是包含了使用该函数的指令。真正实现printf()功能的代码存放在‘库’文件中,库文件中包含很多功能函数的目标代码。
- 链接器的作用:将转换成目标代码、系统的标准启动代码、库代码这3部分进行合并,生成一个可执行文件。链接过程中,从库文件中提取的仅仅是程序中需要用到的。
库/链接
库:一系列函数的集合
链接:目标文件.o
已经是二进制文件,与可执行文件的组织形式类似,只是有些函数和全局变量的地址还未找到,因此还无法执行。链接的作用就是找到这些目标地址,将所有的目标文件组织成一个可以执行的二进制文件
链接 库
可以看成是许多目标文件
.o
的集合(静态链接库)(动态链接库不仅仅是多个目标文件的集合)
当程序链接到一个链接库时,只会链接 程序所用到的函数的目标文件.o
链接库一般位于/lib
文件夹下
对于非标准库、第三方库,需要手动添加
标准库
标准库的大部分函数通常放在
libc.a
中,或者放在用于共享的动态连接文件libc.so
文件中
gcc编译和链接程序时,gcc默认会链接libc.a
或libc.so
库(对于其他的库,非标准库、第三方库,就需要手动添加)
手动添加的例子
/*main.c*/
#include <stdio.h>
#include <math.h> // 必须手动添加链接对应的库文件, 对应的库文件为libm.a
int main()
{
// 使用math.h 对应函数
return 0;
}
> gcc main.c # 错误
> gcc main.c -o main.out -lm
# libm.a # lib为前缀;.a为后缀;m为基本名称
# -l 命令行选项 后面紧跟的基本名称 gcc会在其基本名称的基础上自动加上前缀和后缀
/* 链接指定目录下的链接库 */
# version 1 直接指明库的绝对路径
gcc main.c -o main.out abspath[.a]
# version 2 -L 为gcc添加一个搜索链接库的目录 再指明链接库的文件名
gcc main.c -o main.out -L abspathdir -lm
# version 3 直接将所需要链接库的目录添加到环境变量LIBRARYPATH中
6.4 静态链接库/动态链接库
链接: 将同一个项目中各源文件生成的目标文件以及程序中用到的库文件整合为一个可执行文件
补充
库文件:可以理解为压缩包文件,该文件内部通常包含不止一个目标文件.o
;也就是库文件中每个目标文件存储的代码,并非完整的程序,而是一个个使用的功能模块(类或函数的实现)
库文件的作用:不公开实现功能的源码而是制作为库文件,从而提供二进制文件,以及对应的头文件
目标文件 / 库文件 / 头文件
库文件包含压缩多个目标文件,包含的是一些用户没有手动实现的函数或类
头文件:只存储变量、函数或类 这些功能模块的声明部分
库文件:存储对应于头文件各模块具体的实现部分
所有的库文件都提供有响应的头文件作为调用该库文件的接口
6.4.1 静态链接库
静态链接库:
.a
(linux).lib
(windows) 名字:libxxx.a / libxxx.lib
静态链接库的链接实现:gcc将库文件的功能代码直接复制到程序中调用该模块的位置
优点:生成的可执行文件不再需要静态库的支持,就能运行,可移植性强
缺点:代码冗余,体积大
6.4.1.1 linux
静态链接库的创建/使用
# 先要通过.h .cpp 通过-c 编译成目标文件.o
# 然后直接将.o 文件与其他.cpp文件链接生成
# 最后发布静态库时,将静态库的.h文件和.lib(.a)文件打包(实际上发布整个项目后,静态库文件将不再有用)
# 创建静态链接库
ar rcs liblibrary_name.a file1.o file2.o # 生成liblibrary_name.a
rcs:
-c: 创建一个库,不管库是否存在,都将创建
-s: 创建目标文件索引,这在创建较大的库时能加快时间
-r: 在库中插入模块替换,默认新的成员添加在库的结尾,如果模块名已经在库中存在,则替换同名的模块
# 使用
# -L 向gcc致命静态链接库的存储位置
# -l 指明所需静态链接库的名称
gcc main.o -static liblibrary.a [-o main.out]
gcc main.o -static -L [dirpath] -llibrary
gcc main.o -static -L [dirpath] -llibrary -o main.out
> main.out 即可运行
静态链接库的查找顺序
# 以下面这种方式生成可执行程序时
# 仅在当前目录查找静态链接库文件liblibrary.a
# ERROR: 如果找不到则手动将库文件移动到当前路径
gcc main.o -static liblibrary.a [-o main.out]
# 如果以下面的方式生成可执行程序时
# 如果定义了-L 则优先在-L指定的路径下搜索库文件
# 然后再到LIBRARY_PATH环境变量指定的路径中搜索库文件
# 最后到gcc编译器默认的搜索路径 /lib(linux)等搜索等库文件
# ERROR: 如果找不到则 1.指明-L 2.设置环境变量 3.移动到默认的搜索路径
gcc main.o -static [-L [dirpath]] -llibrary -o main.out
6.4.1.2windows
// windows 平台
#include "library.h"
#pragma comment(lib,"library.lib")
6.4.2 动态链接库
动态链接库:
.so
(linux).dll
(windows)
动态链接库的链接实现:gcc不会复制,而是将功能模块的位置信息记录到文件中,直接生成可执行文件,(这样生成的可执行文件无法独立运行,运行时: gcc将对应的动态链接库一同加载到内存中,由于可执行文件中事先记录了所需功能模块的位置信息,结合动态链接库就能够运行)
值得注意的是:首先由静态链接器将所有的目标文件组织长一个可执行文件,运行时将所需要的动态链接库全部载入内存,由动态链接器完成可执行文件和动态库文件的链接工作
优点:共享、节省内存
缺点:强依赖,可移植性差
gcc 优先使用动态连接的方法
6.4.2.1 linux
动态链接库的创建/使用
# 创建动态链接库
# version1
gcc -fpic -shared file1.c file2.c -o liblibrary_name.so# liblibrary_name.so
# version2
gcc -shared file1.o file2.o -o liblibrary_name.so
# 使用
gcc main.c liblibrary_name.so -o main.out
# 注意:但是此时无法直接运行main.out
# 解决方法1
将动态链接库文件移动到标准库目录下
# 解决方法2
设置换将变量 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:xxx
# 修改~/.bashrc 或~/.bash_profile 文件
在程序中显示调用动态链接库 – 还没看
见文档
动态链接库的查找顺序
见文档
6.4.2.2 windows
windows动态链接库
https://blog.csdn.net/qq_36689800/article/details/78151395
对于windows,.dll 之外还需要生成一个引入库文件.lib
引入库文件.lib:动态链接库负责存储定义好的函数和变量,引入库负责记录动态库中哪些函数和变量允许被外界调用,记录的信息包含函数和变量的名称以及它们在动态库中的存储位置(实际上就是告诉 静态链接器哪些函数或者变量的定义位于动态库中,将这些函数和变量的链接工作向后推迟,等程序执行时再进行(即 由动态链接库完成))
dllexport dllimport
在windows平台下,动态链接库中的函数或者变量要想被外界调用,就必须用
__declspec(dllexport)
修饰,注意dllexport表示生成动态链接库时
dllimport表示分布之后,用户能够使用
/* .h */
__declspec(dllexport) void func1(int argc,char* argv[]);
__declspec(dllexport) void func2(int argc,char* argv[]);
dllexport
__declspec(dllexport)
告诉编译器符号(函数、变量等)导出到动态连接库中
当DLL内部使用__declspec(dllexport)
修饰符时,指示编译器将相关的符号添加到DLL的导出表中,这样其他程序可以通过连接到导出表中的符号来使用DLL提供的功能
即在DLL头文件中使用__declspec(dllexport)
来标记你想要的导出的函数和变量,然后再编译DLL时,这些符号将被添加到DLL导出表中
也就是在DLL源码中使用(生成时使用)
dllimport
__declspec(dllimport)
告诉编译器将符号(函数、变量等)从动态链接库中导入到当前的可执行文件或另一个DLL中
当在可执行文件或其他DLL中使用__declspec(dllimport)
修饰符时,指示编译器在链接时会从相应的DLL的导入表中获取符号信息,并将其链接到当前项目中
即在使用DLL的可执行文件或其他DLL中,可以在包含DLL函数声明的头文件中使用__declspec(dllimport)
,然后再链接时编译器将在响应的DLL的导入表中查找符号
在使用DLL模块时使用(使用时)
6.4.3 静态库 / 动态库 创建使用实例
https://subingwen.cn/linux/library/
6.4.3.1 静态库
g++ -c .\staticLibFunc1.cpp .\staticLibFunc2.cpp
ar rcs libstaticLib.a .\staticLibFunc1.o .\staticLibFunc2.o
g++ .\staticLib.cpp -o staticLibApp -static -L ./ -lstaticLib
./staticLibApp.exe
/* staticLibHead.h */
#ifndef __STATICLIBHEAD__H__
#define __STATICLIBHEAD__H__
int func1_inStaticLib(int a,int b);
int func2_inStaticLib(int a,int b);
#endif
/* staticLibFunc1.cpp */
// #include <iostream>
#include "staticLibHead.h"
int func1_inStaticLib(int a,int b)
{
return a+b;
}
/* staticLibFunc2.cpp */
// #include <iostream>
#include "staticLibHead.h"
int func2_inStaticLib(int a,int b)
{
return a*b;
}
/* staticLib.cpp */
#include <iostream>
#include "staticLibHead.h"
using namespace std;
int main()
{
int a=10;
int b=20;
cout<<func1_inStaticLib(a,b)<<endl;
cout<<func2_inStaticLib(a,b)<<endl;
return 0;
}