【Linux系统编程】Linux动态库详解

00. 目录

01. 概述

所谓“程序库”,简单说,就是包含了数据和执行码的文件。其不能单独执行,可以作为其它执行程序的一部分来完成某些功能。

库的存在可以使得程序模块化,可以加快程序的再编译,可以实现代码重用,可以使得程序便于升级。

程序库可分静态库(static library)共享库(shared library)

不管是Linux还是Windows中的库文件其本质和工作模式都是相同的, 只不过在不同的平台上库对应的文件格式和文件后缀不同。程序中调用的库有两种 静态库动态库,不管是哪种库文件本质是还是源文件,只不过是二进制格式只有计算机能够识别。

在项目中使用库一般有两个目的,一个是为了使程序更加简洁不需要在项目中维护太多的源文件,另一方面是为了源代码保密,毕竟不是所有人都想把自己编写的程序开源出来。

当我们拿到了库文件(动态库、静态库)之后要想使用还必须有这些库中提供的API函数的声明,也就是头文件,把这些都添加到项目中,就可以我们的功能啦。

动态链接库是程序运行时加载的库,当动态链接库正确部署之后,运行的多个程序可以使用同一个加载到内存中的动态库,因此在Linux中动态链接库也可称之为共享库。

动态链接库是目标文件的集合,目标文件在动态链接库中的组织方式是按照特殊方式形成的。库中函数和变量的地址使用的是相对地址(静态库中使用的是绝对地址),其真实地址是在应用程序加载动态库时形成的。

02. 动态库文件格式

共享库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入。不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例,规避了空间浪费问题。

动态库在程序运行是才被载入,也解决了静态库对程序的更新、部署和发布页会带来麻烦。用户只需要更新动态库即可,增量更新。

按照习惯,一般以“.so”做为文件后缀名。共享库的命名一般分为三个部分:

  • 前缀:lib
  • 库名称:自己定义即可
  • 后缀:.so

所以最终的动态库的名字应该为:libxxx.so

【温馨提示】

  • 在Linux中动态库以lib作为前缀, 以.so作为后缀, 中间是库的名字自己指定即可, 即: libxxx.so
  • 在Windows中动态库一般以lib作为前缀, 以dll作为后缀, 中间是库的名字需要自己指定, 即: libxxx.dll

03. 动态库生成过程

生成动态链接库是直接使用gcc命令并且需要添加-fPIC(-fpic) 以及-shared 参数。

  • -fPIC 或 -fpic 参数的作用是使得 gcc 生成的代码是与位置无关的,也就是使用相对位置。
  • -shared参数的作用是告诉编译器生成一个动态链接库。

在这里插入图片描述

04. 动态库制作

4.1 源码结构如下所示


deng@local:~/tmp$ tree lib
lib
├── add.c
├── add.h
├── mdiv.c
├── mdiv.h
├── mul.c
├── mul.h
├── sub.c
└── sub.h

0 directories, 8 files
deng@local:~/tmp$

4.2 将所有的.c文件生成对应的.o文件

生成目标文件,此时要加编译选项:-fPIC(fpic)

deng@local:~/tmp/lib$ gcc -fPIC -c add.c
deng@local:~/tmp/lib$ gcc -fPIC -c sub.c
deng@local:~/tmp/lib$ gcc -fPIC -c mul.c
deng@local:~/tmp/lib$ gcc -fPIC -c mdiv.c
deng@local:~/tmp/lib$ ls
add.c  add.o   mdiv.h  mul.c  mul.o  sub.h
add.h  mdiv.c  mdiv.o  mul.h  sub.c  sub.o
deng@local:~/tmp/lib$




# 或者以下方式
deng@local:~/tmp/lib$ gcc -fPIC -c add.c sub.c mul.c mdiv.c
deng@local:~/tmp/lib$ ls
add.c  add.o   mdiv.h  mul.c  mul.o  sub.h
add.h  mdiv.c  mdiv.o  mul.h  sub.c  sub.o
deng@local:~/tmp/lib$


参数:-fPIC 创建与地址无关的编译程序(pic,position independent code),是为了能够在多个应用程序间共享。

4.3 生成动态库

deng@local:~/tmp/lib$ gcc -shared add.o sub.o mul.o mdiv.o -o libtest.so
deng@local:~/tmp/lib$ ls
add.c  add.o       mdiv.c  mdiv.o  mul.h  sub.c  sub.o
add.h  libtest.so  mdiv.h  mul.c   mul.o  sub.h
deng@local:~/tmp/lib$



4.4 动态库发布

静态库制作完成之后,需要将.so文件和头文件一起发布给用户。

05. 动态库测试

5.1 工程结构如下所示


deng@local:~/tmp$ tree test/
test/
├── add.h
├── libtest.a
├── mdiv.h
├── mul.h
├── sub.h
└── test.c

0 directories, 6 files
deng@local:~/tmp$


5.2 编译test.c,生成可执行文件

deng@local:~/tmp/test$ gcc test.c -o test -L./ -I./ -ltest
deng@local:~/tmp/test$ ls
add.h  libtest.so  mdiv.h  mul.h  sub.h  test  test.c
deng@local:~/tmp/test$




参数说明:

  • -L:表示要连接的库所在目录
  • -I./: I(大写i) 表示指定头文件的目录为当前目录
  • -l(小写L):指定链接时需要的库,去掉前缀和后缀

5.3 执行验证

deng@local:~/tmp/test$ ./test
x + y = 18
x - y = 12
x * y = 45
x / y = 5
deng@local:~/tmp/test$

06. 动态库无法加载问题

问题描述:

deng@local:~/tmp/test$ ./test
./test: error while loading shared libraries: libtest.so: cannot open shared object file: No such file or directory
deng@local:~/tmp/test$

6.1 库的工作原理

  • 静态库加载原理

    在程序编译的最后一个阶段也就是链接阶段,提供的静态库会被打包到可执行程序中。当可执行程序被执行,静态库中的代码也会一并被加载到内存中,因此不会出现静态库找不到无法被加载的问题。

  • 动态库加载原理

    • 在程序编译的最后一个阶段也就是链接阶段:
      • 在gcc命令中虽然指定了库路径(使用参数 -L ), 但是这个路径并没有记录到可执行程序中,只是检查了这个路径下的库文件是否存在。
      • 同样对应的动态库文件也没有被打包到可执行程序中,只是在可执行程序中记录了库的名字。
    • 可执行程序被执行起来之后:
      • 程序执行的时候会先检测需要的动态库是否可以被加载,加载不到就会提示上边的错误信息
      • 当动态库中的函数在程序中被调用了, 这个时候动态库才加载到内存,如果不被调用就不加载
      • 动态库的检测和内存加载操作都是由动态连接器来完成的

6.2 动态连接器

动态链接器是一个独立于应用程序的进程, 属于操作系统, 当用户的程序需要加载动态库的时候动态连接器就开始工作了,很显然动态连接器根本就不知道用户通过 gcc 编译程序的时候通过参数 -L指定的路径。

那么动态链接器是如何搜索某一个动态库的呢,在它内部有一个默认的搜索顺序,按照优先级从高到低的顺序分别是:

(1) 可执行文件内部的 DT_RPATH 段

(2) 系统的环境变量 LD_LIBRARY_PATH

(3) 系统动态库的缓存文件 /etc/ld.so.cache

(4) 存储动态库/静态库的系统目录 /lib/, /usr/lib

按照以上四个顺序, 依次搜索, 找到之后结束遍历, 最终还是没找到, 动态连接器就会提示动态库找不到的错误信息。

6.3 动态库无法加载解决办法

可执行程序生成之后, 根据动态链接器的搜索路径, 我们可以提供三种解决方案,我们只需要将动态库的路径放到对应的环境变量或者系统配置文件中,同样也可以将动态库拷贝到系统库目录(或者是将动态库的软链接文件放到这些系统库目录中)。

方式一: 将库路径添加到环境变量 LD_LIBRARY_PATH 中

(1) 找到相关的配置文件

  • 用户级别: ~/.bashrc 设置对当前用户有效
  • 系统级别: /etc/profile 设置对所有用户有效

(2) 在上述任意配置文件中最后添加一下内容

# 动态库的绝对路径
#export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:动态库的绝对路径


export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/deng/tmp/test/


(3) 配置文件生效

  • 修改了用户级别的配置文件, 关闭当前终端, 打开一个新的终端配置就生效了
  • 修改了系统级别的配置文件, 注销或关闭系统, 再开机配置就生效了
  • 不想执行上边的操作, 可以执行一个命令让配置重新被加载
deng@local:~/tmp/test$ source ~/.bashrc
deng@local:~/tmp/test$

方式二:更新 /etc/ld.so.cache 文件

(1) 修改 /etc/ld.so.conf 这个文件, 在文件最后添加一下库的路径

/home/deng/tmp/test

(2) 更新 /etc/ld.so.conf中的数据到 /etc/ld.so.cache

deng@local:~/tmp/test$ sudo ldconfig
deng@local:~/tmp/test$


方式三:拷贝动态库文件到系统库目录 /lib/ 或者 /usr/lib 中 (或者将库的软链接文件放进去)

# 库拷贝
#sudo cp /xxx/xxx/libxxx.so /usr/lib

deng@local:~/tmp/test$ sudo cp libtest.so /usr/lib/


# 创建软连接
#sudo ln -s /xxx/xxx/libxxx.so /usr/lib/libxxx.so
deng@local:~/tmp/test$ sudo cp libtest.so /lib/

07. 动态库优缺点

  • 优点:
    • 可实现不同进程间的资源共享
    • 动态库升级简单, 只需要替换库文件, 无需重新编译应用程序
    • 可以控制何时加载动态库, 不调用库函数动态库不会被加载
  • 缺点:
    • 加载速度比静态库慢, 以现在计算机的性能可以忽略
    • 发布程序需要提供依赖的动态库

在这里插入图片描述

08. 技术交流

物联网、嵌入式、C++、C方向等等兴趣爱好者可以添加WX一起交流。 点击主页私信,添加博主微信。

09. 附录

  • 4
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
目录 历史 前言 I. C语言入门 1. 程序的基本概念 1. 程序和编程语言 2. 自然语言和形式语言 3. 程序的调试 4. 第一个程序 2. 常量、变量和表达式 1. 继续Hello World 2. 常量 3. 变量 4. 赋值 5. 表达式 6. 字符类型与字符编码 3. 简单函数 1. 数学函数 2. 自定义函数 3. 形参和实参 4. 全局变量、局部变量和作用域 4. 分支语句 1. if语句 2. if/else语句 3. 布尔代数 4. switch语句 5. 深入理解函数 1. return语句 2. 增量式开发 3. 递归 6. 循环语句 1. while语句 2. do/while语句 3. for语句 4. break和continue语句 5. 嵌套循环 6. goto语句和标号 7. 结构体 1. 复合类型与结构体 2. 数据抽象 3. 数据类型标志 4. 嵌套结构体 8. 数组 1. 数组的基本概念 2. 数组应用实例:统计随机数 3. 数组应用实例:直方图 4. 字符串 5. 多维数组 9. 编码风格 1. 缩进和空白 2. 注释 3. 标识符命名 4. 函数 5. indent工具 10. gdb 1. 单步执行和跟踪函数调用 2. 断点 3. 观察点 4. 段错误 11. 排序与查找 1. 算法的概念 2. 插入排序 3. 算法的时间复杂度分析 4. 归并排序 5. 线性查找 6. 折半查找 12. 栈与队列 1. 数据结构的概念 2. 堆栈 3. 深度优先搜索 4. 队列与广度优先搜索 5. 环形队列 13. 本阶段总结 II. C语言本质 14. 计算机中数的表示 1. 为什么计算机用二进制计数 2. 不同进制之间的换算 3. 整数的加减运算 3.1. Sign and Magnitude表示法 3.2. 1's Complement表示法 3.3. 2's Complement表示法 3.4. 有符号数和无符号数 4. 浮点数 15. 数据类型详解 1. 整型 2. 浮点型 3. 类型转换 3.1. Integer Promotion 3.2. Usual Arithmetic Conversion 3.3. 由赋值产生的类型转换 3.4. 强制类型转换 3.5. 编译器如何处理类型转换 16. 运算符详解 1. 位运算 1.1. 按位与、或、异或、取反运算 1.2. 移位运算 1.3. 掩码 1.4. 异或运算的一些特性 2. 其它运算符 2.1. 复合赋值运算符 2.2. 条件运算符 2.3. 逗号运算符 2.4. sizeof运算符与typedef类型声明 3. Side Effect与Sequence Point 4. 运算符总结 17. 计算机体系结构基础 1. 内存与地址 2. CPU 3. 设备 4. MMU 5. Memory Hierarchy 18. x86汇编程序基础 1. 最简单的汇编程序 2. x86的寄存器 3. 第二个汇编程序 4. 寻址方式 5. ELF文件 5.1. 目标文件 5.2. 可执行文件 19. 汇编与C之间的关系 1. 函数调用 2. main函数和启动例程 3. 变量的存储布局 4. 结构体和联合体 5. C内联汇编 6. volatile限定符 20. 链接详解 1. 多目标文件的链接 2. 定义和声明 2.1. extern和static关键字 2.2. 头文件 2.3. 定义和声明的详细规则 3. 静态 4. 共享 4.1. 编译、链接、运行 4.2. 动态链接的过程 4.3. 共享的命名惯例 5. 虚拟内存管理 21. 预处理 1. 预处理的步骤 2. 宏定义 2.1. 函数式宏定义 2.2. 内联函数 2.3. #、##运算符和可变参数 2.4. 宏展开的步骤 3. 条件预处理指示 4. 其它预处理特性 22. Makefile基础 1. 基本规则 2. 隐含规则和模式规则 3. 变量 4. 自动处理头文件的依赖关系 5. 常用的make命令行选项 23. 指针 1. 指针的基本概念 2. 指针类型的参数和返回值 3. 指针与数组 4. 指针与const限定符 5. 指针与结构体 6. 指向指针的指针与指针数组 7. 指向数组的指针与多维数组 8. 函数类型和函数指针类型 9. 不完全类型和复杂声明 24. 函数接口 1. 本章的预备知识 1.1. strcpy与strncpy 1.2. malloc与free 2. 传入参数与传出参数 3. 两层指针的参数 4. 返回值是指针的情况 5. 回调函数 6. 可变参数 25. C标准 1. 字符串操作函数 1.1. 初始化字符串 1.2. 取字符串的长度 1.3. 拷贝字符串 1.4. 连接字符串 1.5. 比较字符串 1.6. 搜索字符串 1.7. 分割字符串 2. 标准I/O函数 2.1. 文件的基本概念 2.2. fopen/fclose 2.3. stdin/stdout/stderr 2.4. errno与perror函数 2.5. 以字节为单位的I/O函数 2.6. 操作读写位置的函数 2.7. 以字符串为单位的I/O函数 2.8. 以记录为单位的I/O函数 2.9. 格式化I/O函数 2.10. C标准的I/O缓冲区 2.11. 本节综合练习 3. 数值字符串转换函数 4. 分配内存的函数 26. 链表、二叉树和哈希表 1. 链表 1.1. 单链表 1.2. 双向链表 1.3. 静态链表 1.4. 本节综合练习 2. 二叉树 2.1. 二叉树的基本概念 2.2. 排序二叉树 3. 哈希表 27. 本阶段总结 III. Linux系统编程 28. 文件与I/O 1. 汇编程序的Hello world 2. C标准I/O函数与Unbuffered I/O函数 3. open/close 4. read/write 5. lseek 6. fcntl 7. ioctl 8. mmap 29. 文件系统 1. 引言 2. ext2文件系统 2.1. 总体存储布局 2.2. 实例剖析 2.3. 数据块寻址 2.4. 文件和目录操作的系统函数 3. VFS 3.1. 内核数据结构 3.2. dup和dup2函数 30. 进程 1. 引言 2. 环境变量 3. 进程控制 3.1. fork函数 3.2. exec函数 3.3. wait和waitpid函数 4. 进程间通信 4.1. 管道 4.2. 其它IPC机制 5. 练习:实现简单的Shell 31. Shell脚本 1. Shell的历史 2. Shell如何执行命令 2.1. 执行交互式命令 2.2. 执行脚本 3. Shell的基本语法 3.1. 变量 3.2. 文件名代换(Globbing):* ? [] 3.3. 命令代换:`或 $() 3.4. 算术代换:$(()) 3.5. 转义字符\ 3.6. 单引号 3.7. 双引号 4. bash启动脚本 4.1. 作为交互登录Shell启动,或者使用--login参数启动 4.2. 以交互非登录Shell启动 4.3. 非交互启动 4.4. 以sh命令启动 5. Shell脚本语法 5.1. 条件测试:test [ 5.2. if/then/elif/else/fi 5.3. case/esac 5.4. for/do/done 5.5. while/do/done 5.6. 位置参数和特殊变量 5.7. 函数 6. Shell脚本的调试方法 32. 正则表达式 1. 引言 2. 基本语法 3. sed 4. awk 5. 练习:在C语言中使用正则表达式 33. 信号 1. 信号的基本概念 2. 产生信号 2.1. 通过终端按键产生信号 2.2. 调用系统函数向进程发信号 2.3. 由软件条件产生信号 3. 阻塞信号 3.1. 信号在内核中的表示 3.2. 信号集操作函数 3.3. sigprocmask 3.4. sigpending 4. 捕捉信号 4.1. 内核如何实现信号的捕捉 4.2. sigaction 4.3. pause 4.4. 可重入函数 4.5. sig_atomic_t类型与volatile限定符 4.6. 竞态条件与sigsuspend函数 4.7. 关于SIGCHLD信号 34. 终端、作业控制与守护进程 1. 终端 1.1. 终端的基本概念 1.2. 终端登录过程 1.3. 网络登录过程 2. 作业控制 2.1. Session与进程组 2.2. 与作业控制有关的信号 3. 守护进程 35. 线程 1. 线程的概念 2. 线程控制 2.1. 创建线程 2.2. 终止线程 3. 线程间同步 3.1. mutex 3.2. Condition Variable 3.3. Semaphore 3.4. 其它线程间同步机制 4. 编程练习 36. TCP/IP协议基础 1. TCP/IP协议栈与数据包封装 2. 以太网(RFC 894)帧格式 3. ARP数据报格式 4. IP数据报格式 5. IP地址与路由 6. UDP段格式 7. TCP协议 7.1. 段格式 7.2. 通讯时序 7.3. 流量控制 37. socket编程 1. 预备知识 1.1. 网络字节序 1.2. socket地址的数据类型及相关函数 2. 基于TCP协议的网络程序 2.1. 最简单的TCP网络程序 2.2. 错误处理与读写控制 2.3. 把client改为交互式输入 2.4. 使用fork并发处理多个client的请求 2.5. setsockopt 2.6. 使用select 3. 基于UDP协议的网络程序 4. UNIX Domain Socket IPC 5. 练习:实现简单的Web服务器 5.1. 基本HTTP协议 5.2. 执行CGI程序 A. 字符编码 1. ASCII码 2. Unicode和UTF-8 3. 在Linux C编程中使用Unicode和UTF-8 B. GNU Free Documentation License Version 1.3, 3 November 2008 参考书目 索引
Linux1.0核心游记<br><br>第一部分 基础知识(Basic knowledge ).....................................................................11 <br> 软件部分(Software part)...........................................................................................12 <br> S1.Makefile简介.....................................................................................................12 <br> S1.1Makefile规则.........................................................................................12 <br> S2.汇编简介 ...........................................................................................................17 <br> S2.1 汇编优缺点............................................................................................17 <br> S2.2 汇编语法(AT&T asm VS Intel asm).............................................18 <br> S2.3 Hello world!示例...............................................................................20 <br> S3.实模式向保护模式切换....................................................................................21 <br> S3.1 切换到保护方式的准备工作.................................................................21 <br> S3.2 使用段间指令切换进保护模式............................................................22 <br> S3.3 打开A20 地址线切换进保护模式........................................................23 <br> S4.gcc内嵌汇编......................................................................................................25 <br> S4.1 内嵌汇编格式.........................................................................................25 <br> S4.2 内嵌汇编示例.........................................................................................26 <br> S5.GDB调试器.......................................................................................................28 <br> S5.1 GDB命令................................................................................................29 <br> S5.2 GDB调试样例........................................................................................29 <br> S6.系统调用实现详解............................................................................................34 <br> S6.1 核心中提供的宏....................................................................................34 <br> S6.2 系统调用编号........................................................................................37 <br> S6.3 系统调用入口点及函数表....................................................................40 <br> S6.4 对系统调用调用....................................................................................41 <br> 硬件部分(Hardware part)..........................................................................................44 <br> H1.操作系统的引导...............................................................................................44 <br> H1.1 BIOS的工作...........................................................................................44 <br> H1.2 操作系统的引导块程序.......................................................................44 <br> H2. X86 CPU 寻址简介........................................................................................47 <br> H2.1 实模式...................................................................................................47 <br> H2.2 实模式方式下物理地址的形成............................................................48 <br> H2.3 保护模式...............................................................................................49 <br> H2.4 保护模式方式下物理地址的形成........................................................49 <br> H3. IDT & GDT & LDT .........................................................................................51 <br> H3.1 IDT .........................................................................................................51 <br> H3.2 GDT & LDT ...........................................................................................52 <br> H4.8259A可编程中断控制器................................................................................53 <br> H4.1 8259A芯片简介.....................................................................................53 <br> H4.2 8259A芯片对的中断处理过程.............................................................54 <br> H4.3 8259A编程方式.....................................................................................55 <br> H5.I/O端口及指令..................................................................................................61 <br> H5.1I/O端口.......................................<br><br>A1.模块的编写.............................................................................................................396 <br> A1-1 模块代码及分析..........................................................................................396 <br> A1-2 模块的加载、注销及查看..........................................................................398 <br> A2.系统调用的添加.....................................................................................................400 <br> A2-1 静态添加系统调用......................................................................................400 <br> A2-1-1 讨论Linux系统调用的体系.............................................................400 <br> A2-1-2 修改代码来添加系统调用..............................................................405 <br> A2-2 动态添加系统调用......................................................................................406 <br> A2-2-1 动态添加系统调用的原理..............................................................407 <br> A2-2-2 实现动态添加、修改系统调用......................................................408 <br> A2-2-3 反汇编capturemod.o并分析之........................................................414 <br> A3.函数的编写.........................................................................................................419 <br> A3-1 静态函数的编写.....................................................................................419 <br> A3-1-1 包含算法的各个文件及Makefle .....................................................420 <br> A3-1-2 测试静态函数的程序及Makefile ................................................422 <br> A3-1-3 静态编译情况..............................................................................423 <br> A3-1-3 主程序与静态连接......................................................................425 <br> A3-2 动态函数的编写.....................................................................................425 <br> A3-2-1 动态库编译情况..............................................................................426 <br> A3-2-2 使用动态装载器..............................................................................428 <br> A3-3 动态/静态函数优点 ...............................................................................429 <br> A3-3-1 静态优点......................................................................................429 <br> A3-3-2 动态库优点....................................
Linux系统目录是指在Linux操作系统中存储文件和目录的位置。以下是Linux系统目录的详解: 1. /:根目录,所有其他目录都是从根目录开始的。 2. /bin:二进制文件目录,存储系统启动和运行所需的二进制文件。 3. /boot:启动目录,存储启动Linux所需的文件。 4. /dev:设备文件目录,存储Linux系统中的设备文件。 5. /etc:配置文件目录,存储系统和应用程序的配置文件。 6. /home:用户主目录,存储用户的个人文件和目录。 7. /lib:文件目录,存储系统和应用程序所需的文件。 8. /media:可移动媒体目录,存储可移动设备(如USB驱动器)的挂载点。 9. /mnt:挂载目录,存储临时挂载的文件系统。 10. /opt:可选应用程序目录,存储第三方应用程序。 11. /proc:进程信息目录,存储正在运行的进程信息。 12. /root:root用户主目录,存储root用户的个人文件和目录。 13. /run:运行时目录,存储系统运行时的临时文件。 14. /sbin:系统二进制文件目录,存储系统管理员使用的二进制文件。 15. /srv:服务数据目录,存储系统提供的服务数据。 16. /sys:系统文件目录,存储Linux内核和设备驱动程序的信息。 17. /tmp:临时文件目录,存储临时文件。 18. /usr:用户程序目录,存储用户安装的应用程序和文件。 19. /var:可变数据目录,存储系统和应用程序的可变数据。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值