当前位置: 首页 > news >正文

【Linux】21.基础IO(3)

文章目录

  • 3. 动态库和静态库
    • 3.1 静态库与动态库
    • 3.2 静态库的制作和使用原理
    • 3.3 动态库的制作和使用原理
      • 3.3.1 动态库是怎么被加载的
    • 3.4 关于地址


3. 动态库和静态库

3.1 静态库与动态库

  • 静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库

  • 动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。

  • 一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码

  • 在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接(dynamic linking

  • 动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间。


3.2 静态库的制作和使用原理

mymath.h

#pragma once#include <stdio.h>int add(int x, int y);//加
int sub(int x, int y);//减
int mul(int x, int y);//乘
int div(int x, int y);//除

mymath.c

#include "mymath.h"int myerrno = 0;int add(int x, int y){return x + y;
}int sub(int x, int y){return x - y;
}int mul(int x, int y){return x * y;
}int div(int x, int y){if(y == 0){myerrno = 1;return -1;}return x / y;
}

makefile

lib=libmymath.a$(lib):mymath.oar -rc $@ $^
mymath.o:mymath.cgcc -c $^.PHONY:clean
clean:rm -rf *.o *.a lib.PHONY:output
output:mkdir -p lib/includemkdir -p lib/mymathlibcp *.h lib/includecp *.a lib/mymathlib

make后:

ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson23$ ll
total 28
drwxrwxr-x  2 ydk_108 ydk_108 4096 Jan 24 22:18 ./
drwxrwxr-x 17 ydk_108 ydk_108 4096 Jan 24 22:07 ../
-rw-rw-r--  1 ydk_108 ydk_108 2024 Jan 24 22:18 libmymath.a
-rw-rw-r--  1 ydk_108 ydk_108  229 Jan 24 22:18 makefile
-rw-rw-r--  1 ydk_108 ydk_108  276 Jan 24 22:13 mymath.c
-rw-rw-r--  1 ydk_108 ydk_108  147 Jan 24 22:12 mymath.h
-rw-rw-r--  1 ydk_108 ydk_108 1848 Jan 24 22:18 mymath.o
ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson23$ 

make output后:

ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson23$ make output
mkdir -p lib/include
mkdir -p lib/mymathlib
cp *.h lib/include
cp *.a lib/mymathlib
ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson23$ ll
total 32
drwxrwxr-x  3 ydk_108 ydk_108 4096 Jan 24 22:22 ./
drwxrwxr-x 17 ydk_108 ydk_108 4096 Jan 24 22:07 ../
drwxrwxr-x  4 ydk_108 ydk_108 4096 Jan 24 22:22 lib/
-rw-rw-r--  1 ydk_108 ydk_108 2024 Jan 24 22:18 libmymath.a
-rw-rw-r--  1 ydk_108 ydk_108  229 Jan 24 22:18 makefile
-rw-rw-r--  1 ydk_108 ydk_108  276 Jan 24 22:13 mymath.c
-rw-rw-r--  1 ydk_108 ydk_108  147 Jan 24 22:12 mymath.h
-rw-rw-r--  1 ydk_108 ydk_108 1848 Jan 24 22:18 mymath.o
ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson23$ tree lib
lib
├── include
│   └── mymath.h
└── mymathlib└── libmymath.a2 directories, 2 files
ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson23$ 

然后在lesson23这个目录下创建test目录,把lib复制到test目录下

同时在test目录下创建main.c文件

#include "lib/include/mymath.h"int main(){printf("1+1=%d\n",add(1,1));return 0;
}

如果main.c文件的头文件写的是#include "mymath.h"

那么直接gcc是无法编译的,可以用gcc main.c -I ./lib/include/

当然如果头文件是#include "lib/include/mymath.h"就可以直接gcc了。

gcc编译后:

ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson23/test$ gcc main.c
/usr/bin/ld: /tmp/ccErjU5m.o: in function `main':
main.c:(.text+0x13): undefined reference to `add'
collect2: error: ld returned 1 exit status
ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson23/test$

因为gcc默认去系统的默认库里面找的,所以我们要加点东西:

gcc main.c -L ./lib/mymathlib/ -lmymath

这里的-L表示的是在lib库里面,

-lmymath表示是在./lib/mymathlib/库里面的libmymath.a库文件,这里要去掉lib前缀和.a后缀。-l表示库名称。

ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson23/test$ gcc main.c -L ./lib/mymathlib/ -lmymath
ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson23/test$ ll
total 36
drwxrwxr-x 3 ydk_108 ydk_108  4096 Jan 24 22:40 ./
drwxrwxr-x 3 ydk_108 ydk_108  4096 Jan 24 22:24 ../
-rwxrwxr-x 1 ydk_108 ydk_108 16872 Jan 24 22:40 a.out*
drwxrwxr-x 4 ydk_108 ydk_108  4096 Jan 24 22:22 lib/
-rw-rw-r-- 1 ydk_108 ydk_108    90 Jan 24 22:26 main.c
ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson23/test$ 

可是gcc main.c -L ./lib/mymathlib/ -lmymath这个东西太长了,我们可不可以不写这么长的呢?

我们可以把mymath.hlibmymath.a放到系统默认路径下,这个操作也叫库的安装

这样只需要gcc main.c -lmymath就可以了。

ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson24/test$ ll
total 36
drwxrwxr-x 3 ydk_108 ydk_108  4096 Jan 25 10:26 ./
drwxrwxr-x 3 ydk_108 ydk_108  4096 Jan 25 10:27 ../
-rwxrwxr-x 1 ydk_108 ydk_108 16872 Jan 25 10:26 a.out*
drwxrwxr-x 4 ydk_108 ydk_108  4096 Jan 25 10:26 lib/
-rw-rw-r-- 1 ydk_108 ydk_108    90 Jan 25 10:26 main.c
ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson24/test$ sudo cp lib/include/mymath.h /usr/include/
ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson24/test$ ls /usr/include/mymath.h
/usr/include/mymath.h
ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson24/test$ sudo cp lib/mymathlib/libmymath.a /lib64/
ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson24/test$ ls /lib64/libmymath.a
/lib64/libmymath.a
ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson24/test$ gcc main.c
/usr/bin/ld: /tmp/ccDIK8p6.o: in function `main':
main.c:(.text+0x13): undefined reference to `add'
collect2: error: ld returned 1 exit status
ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson24/test$ gcc main.c -lmymath
ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson24/test$ ll
total 36
drwxrwxr-x 3 ydk_108 ydk_108  4096 Jan 25 10:52 ./
drwxrwxr-x 3 ydk_108 ydk_108  4096 Jan 25 10:27 ../
-rwxrwxr-x 1 ydk_108 ydk_108 16872 Jan 25 10:52 a.out*
drwxrwxr-x 4 ydk_108 ydk_108  4096 Jan 25 10:26 lib/
-rw-rw-r-- 1 ydk_108 ydk_108    90 Jan 25 10:26 main.c
ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson24/test$ 

不过不建议我们自己乱加文件,这样可能会在以后背刺自己,造成冲突。


注意:如果我们把main.c写成这样

#include "lib/include/mymath.h"int main(){printf("10/0=%d, error=%d\n",div(10,0),myerrno);return 0;
}       

那么运行结果就是

10/0=-1, error=0

为什么会这样呢?这里的运算一看就出错了,为什么error不是1呢?

因为C语言默认是从右向左运算的,这里先把myerrno的值传进来才传div(10,0)的值。

我们可以修改一下代码:

#include "lib/include/mymath.h"int main(){int n = div(10,0);printf("10/0=%d, error=%d\n",n,myerrno);return 0;
}       

打印:

10/0=-1, error=1

把我们提供的方法,给别人用有两个方法:

  1. 我把源文件直接给他

  2. 把我们的源代码想办法打包成库 =库+.h

  1. 第三方库,往后使用的时候,必定要是用gcc-l

  2. 深刻理解errno的本质

  3. 如果系统中只提供静态链接,gcc则只能对该库进行静态链接

  4. 如果系统中需要链接多个库,则gcc可以链接多个库

  5. 不带static有动态库就动态编译,只有静态库才静态编译。带static只静态编译。


3.3 动态库的制作和使用原理

makefile

# 定义动态库名称
dy-lib=libmymethod.so
# 定义静态库名称
static-lib=libmymath.a# 声明all为伪目标(不是实际文件)
.PHONY:all
# 默认目标 - 构建动态库和静态库
all: $(dy-lib) $(static-lib)# 从mymath.o创建静态库的规则
$(static-lib):mymath.oar -rc $@ $^     # 创建归档文件,如存在则替换,创建索引
mymath.o:mymath.cgcc -c $^        # 将C文件编译为目标文件# 从mylog.o和myprint.o创建动态库的规则
$(dy-lib):mylog.o myprint.ogcc -shared -o $@ $^    # 将目标文件链接成共享库
mylog.o:mylog.cgcc -fPIC -c $^         # 使用位置无关代码编译
myprint.o:myprint.cgcc -fPIC -c $^         # 使用位置无关代码编译# 清理目标 - 删除所有生成的文件
.PHONY:clean
clean:rm -rf *.o *.a *.so mylib# 输出目标 - 创建发布目录结构
.PHONY:output
output:mkdir -p mylib/include  # 创建头文件目录mkdir -p mylib/lib      # 创建库文件目录cp *.h mylib/include    # 复制头文件cp *.a mylib/lib        # 复制静态库cp *.so mylib/lib       # 复制动态库

myprint.h

#pragma once#include <stdio.h>void Print();

myprint.c

#include "myprint.h"void Print()
{printf("hello new world!\n");printf("hello new world!\n");printf("hello new world!\n");printf("hello new world!\n");
}

mylog.h

#pragma once#include <stdio.h>void Log(const char*);

mylog.c

#include "mylog.h"void Log(const char*info)
{printf("Warning: %s\n", info);
}

然后编译:make ; make output

ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson24$ make ; make output
gcc -fPIC -c mylog.c
gcc -fPIC -c myprint.c
gcc -shared -o libmymethod.so mylog.o myprint.o
gcc -c mymath.c
ar -rc libmymath.a mymath.o
mkdir -p mylib/include
mkdir -p mylib/lib
cp *.h mylib/include
cp *.a mylib/lib
cp *.so mylib/lib
ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson24$ ll
total 76
drwxrwxr-x  4 ydk_108 ydk_108  4096 Jan 25 13:50 ./
drwxrwxr-x 18 ydk_108 ydk_108  4096 Jan 25 10:26 ../
-rw-rw-r--  1 ydk_108 ydk_108  2024 Jan 25 13:50 libmymath.a
-rwxrwxr-x  1 ydk_108 ydk_108 16312 Jan 25 13:50 libmymethod.so*
-rw-rw-r--  1 ydk_108 ydk_108   448 Jan 25 13:34 makefile
drwxrwxr-x  4 ydk_108 ydk_108  4096 Jan 25 13:50 mylib/
-rw-rw-r--  1 ydk_108 ydk_108    85 Jan 25 13:34 mylog.c
-rw-rw-r--  1 ydk_108 ydk_108    58 Jan 25 13:34 mylog.h
-rw-rw-r--  1 ydk_108 ydk_108  1704 Jan 25 13:50 mylog.o
-rw-rw-r--  1 ydk_108 ydk_108   276 Jan 25 10:26 mymath.c
-rw-rw-r--  1 ydk_108 ydk_108   147 Jan 25 10:26 mymath.h
-rw-rw-r--  1 ydk_108 ydk_108  1848 Jan 25 13:50 mymath.o
-rw-rw-r--  1 ydk_108 ydk_108   176 Jan 25 13:35 myprint.c
-rw-rw-r--  1 ydk_108 ydk_108    49 Jan 25 13:35 myprint.h
-rw-rw-r--  1 ydk_108 ydk_108  1864 Jan 25 13:50 myprint.o
drwxrwxr-x  3 ydk_108 ydk_108  4096 Jan 25 10:52 test/
ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson24$ tree mylib
mylib
├── include
│   ├── mylog.h
│   ├── mymath.h
│   └── myprint.h
└── lib├── libmymath.a└── libmymethod.so2 directories, 5 files
ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson24$ 

这里的执行过程是:

  1. 首先执行 make,因为没有指定目标,所以默认执行 Makefile 中的第一个目标 all,完成所有库文件的编译和创建
  2. 然后执行 make output,创建目录结构并复制文件

这样写的原因是:

  • 必须先执行 make 生成所有的库文件(.so.a)
  • 再执行 make output 进行文件整理和复制
  • 如果直接执行 make output,可能会因为库文件还未生成而导致复制失败

然后我们把库复制进test文件夹里面,并且把main.c文件改一下:

#include "mylog.h"
#include "myprint.h"int main()
{Print();Log("hello log function");return 0;
}

运行

ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson24/test$ ll
total 40
drwxrwxr-x 4 ydk_108 ydk_108  4096 Jan 25 14:00 ./
drwxrwxr-x 4 ydk_108 ydk_108  4096 Jan 25 13:50 ../
-rwxrwxr-x 1 ydk_108 ydk_108 16872 Jan 25 10:52 a.out* # 之前静态编译留下的
drwxrwxr-x 4 ydk_108 ydk_108  4096 Jan 25 10:26 lib/
-rw-rw-r-- 1 ydk_108 ydk_108   116 Jan 25 13:57 main.c
drwxrwxr-x 4 ydk_108 ydk_108  4096 Jan 25 14:00 mylib/
ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson24/test$ gcc main.c -I mylib/include/ -L mylib/lib -lmymethod
ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson24/test$ ll
total 40
drwxrwxr-x 4 ydk_108 ydk_108  4096 Jan 25 14:00 ./
drwxrwxr-x 4 ydk_108 ydk_108  4096 Jan 25 13:50 ../
-rwxrwxr-x 1 ydk_108 ydk_108 16712 Jan 25 14:00 a.out* # 现在动态编译新生成的
drwxrwxr-x 4 ydk_108 ydk_108  4096 Jan 25 10:26 lib/
-rw-rw-r-- 1 ydk_108 ydk_108   116 Jan 25 13:57 main.c
drwxrwxr-x 4 ydk_108 ydk_108  4096 Jan 25 14:00 mylib/
ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson24/test$ 

虽然编译过了,但是一运行就这样了:

ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson24/test$ ./a.out
./a.out: error while loading shared libraries: libmymethod.so: cannot open shared object file: No such file or directory

为什么呢?

我们之前编译的时候告诉编译器我们的动态库在哪里了,但是我们编译后,并没有告诉系统(即加载器)我们的动态库在哪里。

可以将自己库所在的路径,添加到系统的环境变量LD_LIBRARY_PATH中。

ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson24/test$ ldd a.outlinux-vdso.so.1 (0x00007ffc9cd79000)libmymethod.so => not foundlibc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fa1947d3000)/lib64/ld-linux-x86-64.so.2 (0x00007fa1949d3000)
ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson24/test$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/ydk_108/108/lesson24/test/mylib/lib
ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson24/test$ echo $LD_LIBRARY_PATH
::/home/ydk_108/108/lesson24/test/mylib/lib
ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson24/test$ ldd a.outlinux-vdso.so.1 (0x00007ffd6835f000)libmymethod.so => /home/ydk_108/108/lesson24/test/mylib/lib/libmymethod.so (0x00007f105f59b000)libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f105f3a2000)/lib64/ld-linux-x86-64.so.2 (0x00007f105f5a7000)
ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson24/test$ ./a.out
hello new world!
hello new world!
hello new world!
hello new world!
Warning: hello log function
ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson24/test$ 

ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson24/test$ su
Password: 
root@iZuf68hz06p6s2809gl3i1Z:/home/ydk_108/108/lesson24/test# cd /etc/ld.so.conf.d
root@iZuf68hz06p6s2809gl3i1Z:/etc/ld.so.conf.d# ll
total 20
drwxr-xr-x  2 root root 4096 Jun  3  2024 ./
drwxr-xr-x 89 root root 4096 Jan 14 19:45 ../
-rw-r--r--  1 root root   38 Sep  7  2019 fakeroot-x86_64-linux-gnu.conf
-rw-r--r--  1 root root   44 Apr 15  2020 libc.conf
-rw-r--r--  1 root root  100 Apr 15  2020 x86_64-linux-gnu.conf
root@iZuf68hz06p6s2809gl3i1Z:/etc/ld.so.conf.d# touch ydk_108.conf
root@iZuf68hz06p6s2809gl3i1Z:/etc/ld.so.conf.d# ll
total 20
drwxr-xr-x  2 root root 4096 Jan 25 14:25 ./
drwxr-xr-x 89 root root 4096 Jan 14 19:45 ../
-rw-r--r--  1 root root   38 Sep  7  2019 fakeroot-x86_64-linux-gnu.conf
-rw-r--r--  1 root root   44 Apr 15  2020 libc.conf
-rw-r--r--  1 root root  100 Apr 15  2020 x86_64-linux-gnu.conf
-rw-r--r--  1 root root    0 Jan 25 14:25 ydk_108.conf
root@iZuf68hz06p6s2809gl3i1Z:/etc/ld.so.conf.d# pwd
/etc/ld.so.conf.d
root@iZuf68hz06p6s2809gl3i1Z:/etc/ld.so.conf.d# vim ydk_108.conf
root@iZuf68hz06p6s2809gl3i1Z:/etc/ld.so.conf.d# ldconfig
root@iZuf68hz06p6s2809gl3i1Z:/etc/ld.so.conf.d# 
# ldconfig前
ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson24/test$ ldd a.outlinux-vdso.so.1 (0x00007ffc8e125000)libmymethod.so => not foundlibc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f4e24c2d000)/lib64/ld-linux-x86-64.so.2 (0x00007f4e24e2d000)
# ldconfig后
ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson24/test$ ldd a.outlinux-vdso.so.1 (0x00007fffa231c000)libmymethod.so => /home/ydk_108/108/lesson24/test/mylib/lib/libmymethod.so (0x00007f9b2d7b8000)libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f9b2d5c6000)/lib64/ld-linux-x86-64.so.2 (0x00007f9b2d7cb000)
ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson24/test$ 

解决加载找不到动态库的方法:

  1. 拷贝到系统默认的库路径 /ib64 /usr/lib64/

  2. 在系统默认的库路径 /ib64 /usr/lib64/下建立软连接

  3. 将自己的库所在的路径,添加到系统的环境变量LD_LIBRARY_PATH中。(重启就消失了)

  4. /etc/ld.so.conf.d 建立自己的动态库路径的配置文件(名字随便写,里面路径写对就行),然后重新ldconfig即可。(重启不会消失)

实际情况,我们用的库都是别人的成熟的库,都采用直接安装到系统的方式。


3.3.1 动态库是怎么被加载的

动态库(共享库)的共享机制:

  1. 内存共享机制
  • 当第一个进程加载动态库时,动态库的代码段被加载到物理内存中
  • 后续进程再使用这个动态库时,不会重新加载代码段,而是直接映射到已加载的物理内存
  • 多个进程共享同一份物理内存中的代码段,节省内存资源
  • 每个进程有自己的数据段副本,确保数据隔离

动态库在进程运行的时候,是要被加载的(静态库没有)

所以,动态库在系统中加载之后,会被所有进程共享。

9e45eb35a7ab2f9f8cb634e75555f4e3

这个共享库一旦被多个进程共享,那么它对应的页就会引入计数2,缺页中断的时候,识别发现是被多个进程共享的就会写时拷贝。


3.4 关于地址

程序没有加载前的地址(程序)

程序编译好之后,内部有地址的概念吗?

有的。(可以联想之前C++多态的虚函数表)

库可以在虚拟内存中,任意位置加载。

怎么做到的呢?

让自己内部函数不要采用绝对编址,只表示每个函数在库中的偏移量即可。

例如:printf的地址0x1122,这个0x1122代表printf相对于库的起始地址的偏移量。

然后加载动态库的时候,在地址空间里随便放。只需要记住这个库在虚拟地址空间的起始地址就可以了。

解释:为什么动态库可以被加载到任意位置,并且多个进程可以共享同一份代码?

  1. 假设有一个动态库 libexample.so:
起始位置: 未知 (加载时才确定)
函数A: +0x100 (偏移量)
函数B: +0x200 (偏移量)
函数C: +0x300 (偏移量)
  1. 不同进程加载时的情况:
进程1:
- 库加载到虚拟地址 0x10000000
- 函数A实际地址 = 0x10000000 + 0x100 = 0x10000100
- 函数B实际地址 = 0x10000000 + 0x200 = 0x10000200进程2:
- 库加载到虚拟地址 0x20000000
- 函数A实际地址 = 0x20000000 + 0x100 = 0x20000100
- 函数B实际地址 = 0x20000000 + 0x200 = 0x20000200
  1. 工作原理:
  • 编译时:函数地址全部用偏移量表示
  • 加载时:
    • 动态链接器选择一个可用的虚拟地址空间
    • 记录库的基地址
    • 所有函数调用时:实际地址 = 基地址 + 偏移量

这就好比:

  • 一本书的目录不用页码,而用"距离书开始的页数"
  • 无论这本书放在书架的哪个位置(起始地址)
  • 只要知道书在哪(基地址),就能通过偏移量找到每个章节

这也就是为什么fPIC叫做产生位置无关码

  1. 静态库为什么不谈加载呢?

因为静态库会直接把程序拷贝到可执行程序里面,就谈不上加载了。

  1. 静态库为什么不说与位置无关呢?

因为库方法拷贝到可执行程序里后,就不谈偏移量了。他在什么位置就是确定的了。

相关文章:

【Linux】21.基础IO(3)

文章目录 3. 动态库和静态库3.1 静态库与动态库3.2 静态库的制作和使用原理3.3 动态库的制作和使用原理3.3.1 动态库是怎么被加载的 3.4 关于地址 3. 动态库和静态库 3.1 静态库与动态库 静态库&#xff08;.a&#xff09;&#xff1a;程序在编译链接的时候把库的代码链接到可…...

深度学习算法:从基础到实践

简介 深度学习作为人工智能领域的一个重要分支&#xff0c;近年来在多个领域取得了显著的成就。本文将从基础概念出发&#xff0c;探讨深度学习算法的核心原理&#xff0c;并介绍一些实际应用案例。 深度学习算法的核心概念 深度学习算法基于人工神经网络&#xff0c;通过构…...

27. 【.NET 8 实战--孢子记账--从单体到微服务】--简易报表--报表服务

报表是每个记账应用所具备的功能&#xff0c;要实现报表功能就需要把账本的核心功能&#xff08;记账&#xff09;完成&#xff0c;因此报表服务作为本专栏第一部分单体应用开发中最后一个要实现的功能&#xff0c;这一篇文章很简单&#xff0c;我们一起来实现一个简单的报表服…...

coffee销售数据集分析:基于时间趋势分析的实操练习

**文章说明&#xff1a;**对coffee销售数据集的简单分析练习&#xff08;时间趋势分析练习&#xff09;&#xff0c;主要是为了强化利用python进行数据分析的实操能力。属于个人的练习文章。 **注&#xff1a;**这是我第一次使用md格式编辑博客文章&#xff0c;排版上还是不是很…...

【转帖】eclipse-24-09版本后,怎么还原原来版本的搜索功能

【1】原贴地址&#xff1a;eclipse - 怎么还原原来版本的搜索功能_eclipse打开类型搜索类功能失效-CSDN博客 https://blog.csdn.net/sinat_32238399/article/details/145113105 【2】原文如下&#xff1a; 更新eclipse-24-09版本后之后&#xff0c;新的搜索功能&#xff08;CT…...

Centos 修改历史读录( HISTSIZE)

history命令 -c #清空命令历史 -r #读历史文件附加到历史列表 -w #保存历史列表到指定的历史文件 命令历史相关环境变量 HISTSIZE #命令历史记录的条数 HISTFILE #指定历史文件&#xff0c;默认为~/.bash_history HISTFILESIZE #命令历史文件记录历史的条数 以上变量可以 exp…...

lwIP——4 网络接口

1.lwIP网络接口 网络接口&#xff08;网卡&#xff09;&#xff1a;个人理解是处理网络层和数据传输关系的接口&#xff08;tcp/ip协议栈中的网络接口层部分&#xff09;&#xff0c;直接与硬件平台打交道 lwIP协议栈支持多种不同的网络接口&#xff08;网卡&#xff09;&#…...

pytest自动化测试 - pytest夹具的基本概念

<< 返回目录 1 pytest自动化测试 - pytest夹具的基本概念 夹具可以为测试用例提供资源(测试数据)、执行预置条件、执行后置条件&#xff0c;夹具可以是函数、类或模块&#xff0c;使用pytest.fixture装饰器进行标记。 1.1 夹具的作用范围 夹具的作用范围&#xff1a; …...

FreeRtos的使用教程

定义&#xff1a; RTOS实时操作系统, (Real Time Operating System), 指的是当外界事件发生时, 能够有够快的响应速度,调度一切可利用的资源, 控制实时任务协调一致的运行。 特点&#xff1a; 支持多任务管理&#xff0c; 处理多个事件&#xff0c; 实现更复杂的逻辑。 与计算…...

yolov11 解读简记

1 文章详细介绍了YOLOv11的架构设计&#xff0c;包括以下几个关键组件&#xff1a; C3k2块&#xff1a;这是YOLOv11引入的一种新型卷积块&#xff0c;替代了之前版本中的C2f块。C3k2块通过使用两个较小的卷积核代替一个大的卷积核&#xff0c;提高了计算效率&#xff0c;同时保…...

实验二 数据库的附加/分离、导入/导出与备份/还原

实验二 数据库的附加/分离、导入/导出与备份/还原 一、实验目的 1、理解备份的基本概念&#xff0c;掌握各种备份数据库的方法。 2、掌握如何从备份中还原数据库。 3、掌握数据库中各种数据的导入/导出。 4、掌握数据库的附加与分离&#xff0c;理解数据库的附加与分离的作用。…...

Kafka常见问题之 `javax.management.InstanceAlreadyExistsException`

文章目录 Kafka常见问题之 javax.management.InstanceAlreadyExistsException1. 概述2. 常见原因3. 具体异常示例4. 解决方案4.1 确保单一 Kafka Producer 实例4.2 配置 Kafka Broker 和 Producer 使用唯一的 JMX 名称&#xff08;对于Producer重点检查 client.id&#xff09;4…...

性能测试丨JVM 性能数据采集

什么是JVM性能数据采集&#xff1f; JVM性能数据采集是指通过一些工具和技术采集与Java虚拟机相关的性能数据。这些数据包括但不限于内存使用、CPU使用、垃圾回收&#xff08;GC&#xff09;行为、线程活动等。合理地分析这些数据&#xff0c;可以帮助我们找出系统的瓶颈&…...

计算机图形学实验练习(实验1.2-4.1AND补充实验12)

实验1.2 OpenGL与着色器编程 1.理论知识 1.1 OpenGL的含义 OpenGL是一种应用程序编程接口(Application Programming Interface,API),它是一种可以对图形硬件设备特性进行访问的软件库。OpenGL最新的4.3版本包含了超过500个不同的命令,可以用于设置所需的对象、图像和操…...

JWT实现单点登录

文章目录 JWT实现单点登录JWT 简介存在问题及解决方案登录流程后端程序实现前端保存Tokenstore存放信息的缺点及解决 校验流程&#xff1a;为gateway增加登录校验拦截器 另一种单点登录方法&#xff1a;Token&#xff0b;Redis实现单点登录 JWT实现单点登录 登录流程&#xff…...

云计算的概念与特点:开启数字化时代的新篇章

在当今数字化时代,云计算(Cloud Computing)已经成为推动技术创新和业务转型的核心力量。无论是大型企业、中小型企业,还是个人用户,云计算都为其提供了高效、灵活和经济的解决方案。本文将深入探讨云计算的概念及其核心特点,帮助读者全面了解这一革命性技术。 © ivw…...

salesforce中如何获取一个profile的18位id

在 Salesforce 中&#xff0c;要获取一个 Profile 的 18 位 ID&#xff0c;可以通过以下几种方式实现&#xff1a; 方法 1&#xff1a;通过 Developer Console 登录 Salesforce。 点击右上角的 头像 或 设置齿轮&#xff0c;选择 “开发者控制台”&#xff08;Developer Conso…...

Vue 3 中的标签 ref 与 defineExpose:模板引用与组件暴露

在 Vue 3 中&#xff0c;ref 不仅可以用于创建响应式数据&#xff0c;还可以用于获取 DOM 节点或组件实例。通过 ref&#xff0c;我们可以直接访问模板中的元素或组件&#xff0c;并在需要时操作它们。此外&#xff0c;defineExpose 用于在 <script setup> 语法中显式暴露…...

FLTK - FLTK1.4.1 - demo - adjuster.exe

文章目录 FLTK - FLTK1.4.1 - demo - adjuster.exe概述笔记根据代码&#xff0c;用fluid重建一个adjuster.fl 备注 - fluid生成的代码作为参考代码好了修改后可用的代码END FLTK - FLTK1.4.1 - demo - adjuster.exe 概述 想过一遍 FLTK1.4.1的demo和测试工程&#xff0c;工程…...

单路由及双路由端口映射指南

远程登录总会遇到登陆不上的情况&#xff0c;可能是访问的大门没有打开哦&#xff0c;下面我们来看看具体是怎么回事&#xff1f; 当软件远程访问时&#xff0c;主机需要两个条件&#xff0c;一是有一个唯一的公网IP地址&#xff08;运营商提供&#xff09;&#xff0c;二是开…...

7.4.分块查找

一.分块查找的算法思想&#xff1a; 1.实例&#xff1a; 以上述图片的顺序表为例&#xff0c; 该顺序表的数据元素从整体来看是乱序的&#xff0c;但如果把这些数据元素分成一块一块的小区间&#xff0c; 第一个区间[0,1]索引上的数据元素都是小于等于10的&#xff0c; 第二…...

Docker 运行 Kafka 带 SASL 认证教程

Docker 运行 Kafka 带 SASL 认证教程 Docker 运行 Kafka 带 SASL 认证教程一、说明二、环境准备三、编写 Docker Compose 和 jaas文件docker-compose.yml代码说明&#xff1a;server_jaas.conf 四、启动服务五、验证服务六、连接kafka服务七、总结 Docker 运行 Kafka 带 SASL 认…...

高等数学(下)题型笔记(八)空间解析几何与向量代数

目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...

Linux-07 ubuntu 的 chrome 启动不了

文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了&#xff0c;报错如下四、启动不了&#xff0c;解决如下 总结 问题原因 在应用中可以看到chrome&#xff0c;但是打不开(说明&#xff1a;原来的ubuntu系统出问题了&#xff0c;这个是备用的硬盘&a…...

Swagger和OpenApi的前世今生

Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章&#xff0c;二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑&#xff1a; &#x1f504; 一、起源与初创期&#xff1a;Swagger的诞生&#xff08;2010-2014&#xff09; 核心…...

用机器学习破解新能源领域的“弃风”难题

音乐发烧友深有体会&#xff0c;玩音乐的本质就是玩电网。火电声音偏暖&#xff0c;水电偏冷&#xff0c;风电偏空旷。至于太阳能发的电&#xff0c;则略显朦胧和单薄。 不知你是否有感觉&#xff0c;近两年家里的音响声音越来越冷&#xff0c;听起来越来越单薄&#xff1f; —…...

【从零学习JVM|第三篇】类的生命周期(高频面试题)

前言&#xff1a; 在Java编程中&#xff0c;类的生命周期是指类从被加载到内存中开始&#xff0c;到被卸载出内存为止的整个过程。了解类的生命周期对于理解Java程序的运行机制以及性能优化非常重要。本文会深入探寻类的生命周期&#xff0c;让读者对此有深刻印象。 目录 ​…...

MySQL JOIN 表过多的优化思路

当 MySQL 查询涉及大量表 JOIN 时&#xff0c;性能会显著下降。以下是优化思路和简易实现方法&#xff1a; 一、核心优化思路 减少 JOIN 数量 数据冗余&#xff1a;添加必要的冗余字段&#xff08;如订单表直接存储用户名&#xff09;合并表&#xff1a;将频繁关联的小表合并成…...

4. TypeScript 类型推断与类型组合

一、类型推断 (一) 什么是类型推断 TypeScript 的类型推断会根据变量、函数返回值、对象和数组的赋值和使用方式&#xff0c;自动确定它们的类型。 这一特性减少了显式类型注解的需要&#xff0c;在保持类型安全的同时简化了代码。通过分析上下文和初始值&#xff0c;TypeSc…...

Neko虚拟浏览器远程协作方案:Docker+内网穿透技术部署实践

前言&#xff1a;本文将向开发者介绍一款创新性协作工具——Neko虚拟浏览器。在数字化协作场景中&#xff0c;跨地域的团队常需面对实时共享屏幕、协同编辑文档等需求。通过本指南&#xff0c;你将掌握在Ubuntu系统中使用容器化技术部署该工具的具体方案&#xff0c;并结合内网…...