【Linux】深刻理解动静态库
1.什么是库
- 静态库 .a[Linux]、.lib[windows]
- 动态库 .so[Linux]、.dll[windows]
2.手动制作静态库
如果我们要做一个静态库的话,首先我们需要把我们源文件(.c/.cpp)隐藏起来,头文件(.h)必须暴露出来,1.我们先要把我们所有的.c文件编译成.o文件,因为我们都知道我们正常进行程序翻译的时候,首先要做的工作就是把.c变成.o文件,最后把所有.o都链接起来。
2.把所以.o文件打包指令:ar -rcar是 Unix 和类 Unix 系统下用于创建、修改和提取归档文件的工具,ar -rc是ar命令的一个常用选项组合,其功能如下:
- -r(replace):该选项用于将文件替换或插入到归档文件中。如果归档文件中已存在同名文件,-r选项会用新文件替换旧文件;如果归档文件中不存在同名文件,则将新文件插入到归档文件中。
- -c(create):用于创建一个新的归档文件。若指定的归档文件已存在,-c选项不会报错,而是静默处理,使得ar -rc能够在归档文件不存在时创建新的归档文件,存在时进行更新操作。
语法:ar [选项] 归档文件 目标文件...
- 归档文件:指定要创建或修改的归档文件的名称,通常具有.a扩展名,如libexample.a。
- 目标文件:指定要添加到归档文件中的一个或多个源文件的名称,可以是 C、C++ 源文件,也可以是目标文件(.o文件)等。
- 静态库必须有前缀lib,后缀.a。
这个库制作好了,直接把他安装到系统里面:把我们头文件拷贝到系统特定路径里,Linux系统里默认搜索头文件路径是/usr/include![]()
然后把我们库也要安装到系统里,默认路径一般是/lib64/![]()
这样静态库和头文件都被安装到系统中去了,所以Linux系统中,所有的安装本质就是拷贝。
3.手动调用我们自己的静态库
然后试试别人能不能直接使用头文件中的方法:创建一个other目录:![]()
![]()
#include<my_stdio.h> #include<my_string.h> #include<stdio.h> int main() {const char *a = "abcdf";printf("%s: %d\n",a,my_strlen(a));myFILE *fp =mfopen("./log.txt","a");if(fp ==NULL) return 1;mfwrite(a,my_strlen(a),fp);mfwrite(a,my_strlen(a),fp);mfwrite(a,my_strlen(a),fp);mfclose(fp);return 0; }
为什么会链接报错呢?因为编译器找不到,那同样是库,那为什么那么多库,能找到libc.aC语言的库呢?为什么不要告诉编译器呢?所以那是因为gcc是编C语言的,他默认要认识C标准库的,我们刚才写的库叫第三方库,如何要让编译器认识?刚才头文件和库都拷贝到系统,都能被找到,但是库有那么多,究竟应该链接哪一个库呢,所以
-l 这个l就是link(链接),然后库名libmystdio.a取掉前缀后缀就是stdio,所以这样就能找到库,并且能使用
-l就是引入指定名称的第三方库。我们也可以不用把这些安装到系统里,别人直接给我们提供了头文件和库,这时我们该怎么使用呢?
指令:gcc main.c -o main -L. -lstdio-L 告诉编译器,编译的时候,查找库,除了在系统路径下查找,也要在指定路径下查找
- gcc:是 GNU Compiler Collection 的缩写,是一个广泛使用的开源编译器套件,支持多种编程语言,这里主要用于编译 C 语言程序。
- main.c:是要编译的源文件,通常包含 C 语言的源代码。
- -o main:-o选项用于指定输出文件的名称,这里将编译后的可执行文件命名为main。如果不使用-o选项,默认的输出文件名是a.out。
- -L.:-L选项用于指定库文件的搜索路径。这里的.表示当前目录,即告诉编译器在当前目录下搜索库文件。
- -lstdio:-l选项用于指定要链接的库。这里的stdio是库的名称(去掉lib前缀和.a或.so后缀),编译器会在-L指定的路径(这里是当前目录)下搜索名为libstdio.a或libstdio.so的库文件,并将其链接到可执行文件中。
所以我们把库写好了,库里面应不应该有没有main函数?1.库里面绝对不能有main函数, 如果库中也有main 函数,那么在链接库到其他程序时,就会出现多个main 函数,这会导致链接错误,因为程序不知道该从哪个main 函数开始执行。2.库我们在发布的时候,我们建议,把所有的头文件放在include目录下,把我们库放在lib64目录下。
通过makefile来制作静态库:![]()
libmystdio.a:my_stdio.o my_string.oar -rc $@ $^ %.o:%.cgcc -c $< .PHONY:clean clean:rm -f *.a *.o
发布我们写的库:指令:tartar是 Unix 和类 Unix 系统中常用的归档工具,用于将多个文件或目录打包成一个归档文件,也可以从归档文件中提取文件或目录。以下是关于tar的详细介绍:基本功能
- 打包:可以将多个文件和目录组合成一个单一的归档文件,方便存储、传输和备份。
- 压缩:通常与压缩工具(如gzip、bzip2等)结合使用,在打包的同时进行压缩,减小文件占用的存储空间。
- 解压:从归档文件中恢复出原始的文件和目录结构。
常用命令选项
- -c(create):创建新的归档文件。例如:tar -cvf archive.tar file1 file2 dir1,这会将file1、file2和dir1打包到archive.tar中。
- -x(extract):从归档文件中提取内容。例如:tar -xvf archive.tar,会将archive.tar中的文件和目录提取到当前目录。
- -t(list):列出归档文件中的内容,但不提取。例如:tar -tvf archive.tar,会显示archive.tar中包含的文件和目录列表。
- -f(file):指定归档文件的名称。这个选项必须紧跟在-c、-x或-t之后,后面跟上归档文件的路径和名称。
- -z(gzip):使用gzip进行压缩或解压。例如:tar -czvf archive.tar.gz file1 file2,会创建一个gzip压缩的归档文件archive.tar.gz;tar -xzvf archive.tar.gz,会解压gzip压缩的归档文件。
- -j(bzip2):使用bzip2进行压缩或解压。例如:tar -cjvf archive.tar.bz2 file1 file2,会创建一个bzip2压缩的归档文件archive.tar.bz2;tar -xjvf archive.tar.bz2,会解压bzip2压缩的归档文件。
libmystdio.a:my_stdio.o my_string.oar -rc $@ $^ %.o:%.cgcc -c $< .PHONY:clean clean:rm -rf *.a *.o stdc .PHONY:output output:mkdir -p stdc/includemkdir -p stdc/libcp -f *.h stdc/includecp -f *.o stdc/libtar -czf stdc.tgz stdc
操作使用:
然后怎么使用呢?
- gcc:GNU Compiler Collection 的缩写,是一个广泛使用的编译器,用于编译 C 语言等程序。
- main.c:要编译的源文件,其中包含了 C 语言的源代码。
- -o main:-o选项用于指定输出文件的名称,这里将编译后的可执行文件命名为main。如果不使用-o选项,默认的输出文件名是a.out。
- -I stdc/include:-I(大写的 i)选项用于指定头文件的搜索路径。这里告诉编译器在stdc/include目录下搜索main.c中#include指令所包含的头文件。例如,如果main.c中有#include "myheader.h",编译器会在stdc/include目录中查找myheader.h文件。
- -L stdc/lib:-L选项用于指定库文件的搜索路径。这意味着编译器会在stdc/lib目录下搜索后续-l选项指定的库文件。
- -l mystdio:-l(小写的 L)选项用于指定要链接的库。这里的mystdio是库的名称(去掉lib前缀和.a或.so后缀),编译器会在-L指定的路径(即stdc/lib)下搜索名为libmystdio.a或libmystdio.so的库文件,并将其链接到可执行文件main中。
![]()
4.手动制作动态库
动态库前缀也要加lib,后缀加.sogcc/g++不仅能形成可执行程序,还能形成动态库gcc -shared 用于生成共享库(shared library),在 Linux 和类 Unix 系统中,共享库是一种可执行代码的二进制形式,能被多个程序同时使用,以节省内存并实现代码复用。形成动态库时,形成.o的过程要新增一个选项 -fPIC,形成与位置无关码-fPIC是 GCC 编译器中的一个重要选项,全称为 “Position - Independent Code”,即生成位置无关代码。以下从多个方面详细介绍它:
1. 作用原理
在编译生成共享库(shared library)或可执行文件时,程序的代码和数据在内存中的加载位置会影响指令的执行。传统的可执行文件在编译时,代码中的内存地址引用是基于固定的加载地址计算的。但共享库比较特殊,它可能被多个不同的程序加载到不同的内存地址空间运行。-fPIC选项会让编译器生成的代码,不依赖于特定的内存加载地址。编译器通过使用相对地址而不是绝对地址来引用数据和代码,这样生成的代码可以在内存的任意位置加载执行,而不需要在加载时进行重定位(relocation)。
2. 为何重要
- 共享库的需求:共享库是为多个程序所共享的代码集合。如果没有-fPIC生成位置无关代码,那么每个使用该共享库的程序在加载共享库时,都需要对共享库中的代码进行重定位,这不仅增加了加载时间,而且还可能导致内存使用效率低下。通过-fPIC,共享库可以在不同进程的地址空间中共享相同的物理内存页面,极大地节省了内存资源。
- 动态链接的基础:现代操作系统普遍采用动态链接机制,允许程序在运行时动态加载所需的共享库。-fPIC生成的代码使得动态链接过程更加高效和灵活,能够适应不同的内存布局和加载场景。
![]()
libmystdio.so:my_stdio.o my_string.ogcc -o $@ $^ -shared %.o:%.cgcc -c $< -fPIC .PHONY:clean clean:rm -rf *.so *.o stdc
指令:ldd
ldd是一个在 Linux 和类 Unix 系统中非常有用的命令,主要用于查看程序运行时所依赖的共享库(shared libraries)。
1. 基本语法
ldd [选项] <文件名>其中,<文件名>是要检查的可执行文件或共享库的名称,选项可以用来获取更详细或特定格式的输出。3. 常用选项
- -v(详细模式)
- 提供更详细的信息,包括共享库的版本号、加载的起始地址等。
4.输出解释
ldd的输出通常包含以下信息:
- 库名称:这是程序或共享库所依赖的动态库的名称,通常以
lib开头,以.so结尾。- 库的完整路径:显示了动态库在文件系统中的实际位置。如果显示
not found,表示该动态库未找到,这可能会导致程序无法正常运行。- 库的地址:显示了该动态库在内存中的加载地址。这通常在运行时由动态链接器分配,对于大多数用户来说,这个信息可能不太重要,除非你正在进行一些系统级的调试。
3.如果把动态库删了,可执行程序会怎么样?
- 立即崩溃(在某些情况下)
- 如果可执行程序在启动时就需要加载被删除的动态库,并且系统无法找到合适的替代库来满足依赖关系,那么程序在启动阶段就会崩溃。例如,一个程序依赖于一个特定版本的图形库来进行界面初始化,当这个图形库动态文件被删除后,程序在启动时尝试加载该库就会失败,从而无法正常启动。
- 这是因为动态库中的函数和数据是在程序运行时按需加载的,当找不到所需的动态库时,操作系统无法完成程序的初始化过程
- 运行时出错
- 即使程序成功启动,如果在运行过程中需要调用被删除的动态库中的函数,也会出现错误。例如,一个计算程序在运行过程中需要调用动态库中的数学计算函数,当动态库被删除后,一旦执行到调用该函数的代码部分,就会出现 “无法找到动态库” 或 “无法解析符号(函数名)” 等错误。
- 这种情况通常会导致程序的部分功能失效或者直接崩溃,具体取决于程序对该动态库的依赖程度以及如何处理动态库加载失败的情况。有些程序可能会有一定的错误处理机制,能够在检测到动态库缺失时给出友好的错误提示并尝试恢复或继续执行其他功能,但很多程序没有这样的机制,就会直接异常终止。
如果把静态库删了,可执行程序会怎么样?
- 基本不受影响(一般情况)
- 当可执行程序链接了静态库后,库中的代码和数据会在编译和链接阶段被复制到可执行文件中。所以,在程序已经成功生成后,删除静态库通常不会影响可执行程序的正常运行。
- 例如,一个简单的 C 语言程序main.c链接了一个静态库libmath.a(其中包含了一些数学计算函数)来计算数值。在编译链接过程中,gcc main.c -o main -L. -lmath(假设库在当前目录下),libmath.a中的相关代码被整合进了main这个可执行文件。之后,即使删除了libmath.a,main依然可以正常运行,因为它已经拥有了运行所需的全部代码。
同时存在动静态库,默认用的是动态库,除非加 -static
一、默认使用动态库
- 编译时:
在类 Unix 系统(如 Linux)中,当同时存在一个库的静态版本(例如libexample.a)和动态版本(例如libexample.so),并且在编译链接可执行程序时没有特别指定使用静态库,编译器通常会默认使用动态库。例如,如果你有以下命令:gcc main.c -L. -lexample -o main
这里-L.表示在当前目录查找库文件,-lexample表示链接libexample库,编译器会优先使用libexample.so(如果存在),因为这是默认行为。这是因为使用动态库可以节省磁盘空间和内存,因为多个程序可以共享同一个动态库,并且在更新库时,只需要更新动态库而不需要重新编译程序。二、使用静态库
- 使用
-static选项:
如果你想强制使用静态库,可以使用-static选项。例如:gcc main.c -L. -lexample -o main -static
此命令将强制编译器使用静态库libexample.a而不是libexample.so进行链接。这样,最终生成的可执行程序将包含静态库的代码,而不依赖于外部的动态库,其优点是可执行程序的独立性更强,可在不同环境中运行而不依赖于特定的库版本,但缺点是可执行程序的体积会变大。三、库搜索顺序
- 搜索顺序:
- 编译器首先会搜索动态库。如果没有使用
-static选项,它会优先使用动态库。- 编译器会根据
-L选项指定的路径查找库文件。如果没有使用-L,则会按照系统的默认库搜索路径查找,通常包括/usr/lib、/usr/local/lib等。- 对于动态库,系统会在运行时根据
LD_LIBRARY_PATH环境变量、/etc/ld.so.conf文件和系统默认的库路径来查找所需的动态库。四、注意事项
- 库命名规则:
静态库通常以.a结尾,动态库通常以.so结尾(在 Linux 系统中)。库的名称通常遵循lib<库名>.<后缀>的格式,例如libexample.a和libexample.so。- 性能和空间权衡:
- 动态库:
- 优点:多个程序可以共享同一个动态库,节省磁盘空间和内存,更新库时不需要重新编译程序。
- 缺点:可能存在兼容性问题,如果库版本不兼容,可能导致程序无法正常运行。
- 静态库:
- 优点:可执行程序不依赖于外部库,可在不同环境中稳定运行。
- 缺点:可执行程序体积较大,每个程序都包含一份库的副本。
相关文章:
【Linux】深刻理解动静态库
1.什么是库 库是写好的现有的,成熟的,可以复⽤的代码。现实中每个程序都要依赖很多基础的底层库,不可能每个⼈的代码都从零开始,因此库的存在意义⾮同寻常。本质上来说库是⼀种可执⾏代码的⼆进制形式,可以被操作系统载…...
亚博microros小车-原生ubuntu支持系列:8-脸部检测与人脸特效
前面的都是使用了mediapipe框架。后面的这两节采用了opencv\dlib的框架。 一 脸部检测 核心:opencv detectMultiScale函数 detectMultiScale(image, scaleFactor, minNeighbors, flags, minSize, maxSize) image--待检测图片,一般为灰度图像加快检测…...
代码随想录算法训练营day32
代码随想录算法训练营 —day32 文章目录 代码随想录算法训练营前言一、动态规划理论基础二、509. 斐波那契数动态规划动态规划优化空间版递归法 三、70. 爬楼梯动态规划动态规划空间优化 746. 使用最小花费爬楼梯动态规划空间优化 总结 前言 今天是算法营的第32天,…...
缓存之美:万文详解 Caffeine 实现原理(下)
上篇文章:缓存之美:万文详解 Caffeine 实现原理(上) getIfPresent 现在我们对 put 方法有了基本了解,现在我们继续深入 getIfPresent 方法: public class TestReadSourceCode {Testpublic void doRead() …...
中企出海:从国际投资建厂:投前投中投后重点事项
1. 投前重点事项 1.1 市场调研与分析 在国际投资建厂的投前阶段,市场调研与分析是至关重要的基础工作,它能够帮助企业全面了解目标市场,为后续决策提供有力依据。 市场规模与潜力:通过收集和分析目标国家或地区的经济数据、行业…...
github登录用的TOTP和恢复码都丢失了怎么办
从22年左右开始github的登录就需要用TOTP的一个6位秘钥做二次认证登录,如果在用的TOTP软件失效了,可以用github开启二次认证时下载的恢复码重置认证,但是如果你和我一样这两个东西都没了就只能用邮箱重置了,过程给大家分享一下 一…...
最长递增子序列问题(Longest Increasing Subsequence),动态规划法解决,贪心算法 + 二分查找优化
问题描述:在一个大小乱序的数列中,找到一个最大长度的递增子序列,子序列中的数据在原始数列中的相对位置保持不变,可以不连续,但必须递增。 输入描述: 第一行输入数列的长度 n。(1 < n < 200) 第二…...
Python中采用.add_subplot绘制子图的方法简要举例介绍
Python中采用.add_subplot绘制子图的方法简要举例介绍 目录 Python中采用.add_subplot绘制子图的方法简要举例介绍一、Python中绘制子图的方法1.1 add_subplot函数1.2 基本语法(1)add_subplot的核心语法(2)add_subplot在中编程中的…...
纯 Python、Django、FastAPI、Flask、Pyramid、Jupyter、dbt 解析和差异分析
一、纯 Python 1.1 基础概念 Python 是一种高级、通用、解释型的编程语言,以其简洁易读的语法和丰富的标准库而闻名。“纯 Python” 在这里指的是不依赖特定的 Web 框架或数据分析工具,仅使用 Python 原生的功能和标准库来开发应用程序或执行任务。 1.…...
C++实现有限元二维杆单元计算 Bar2D2Node类(纯自研 非套壳)
本系列文章致力于实现“手搓有限元,干翻Ansys的目标”,基本框架为前端显示使用QT实现交互,后端计算采用Visual Studio C。 QT软件界面 具体软件操作可查看下方视频哦。也可以点击这里直接跳转。 直接干翻Ansys?小伙自研有限元 1、…...
wx036基于springboot+vue+uniapp的校园快递平台小程序
开发语言:Java框架:springbootuniappJDK版本:JDK1.8服务器:tomcat7数据库:mysql 5.7(一定要5.7版本)数据库工具:Navicat11开发软件:eclipse/myeclipse/ideaMaven包&#…...
Unity中两个UGUI物体的锚点和中心点设置成不一样的,然后怎么使两个物体的位置一样?
一、问题复现 需求:go1物体和我想把go1的位置跟go2的位置一样,但是我通过物体的anchoredPosition以及position还有localposiiton都没有解决问题,使用上面的这三个属性的效果如下: 运行之后,可以看出,go1的…...
兼职全职招聘系统架构与功能分析
2015工作至今,10年资深全栈工程师,CTO,擅长带团队、攻克各种技术难题、研发各类软件产品,我的代码态度:代码虐我千百遍,我待代码如初恋,我的工作态度:极致,责任ÿ…...
HTML5 History API
在 HTML5 的 History API 中,pushState 和 replaceState 方法也可以接受一个 state 对象作为参数。这些方法允许你在改变浏览器路由时不重新加载页面,并且可以附加一些自定义数据。 state 返回在 history 栈顶的 任意 值的拷贝。 let currentState h…...
2025_1_22打卡
402. 移掉 K 位数字 - 力扣(LeetCode) 279. 完全平方数 - 力扣(LeetCode)...
Formality:不可读(unread)的概念
相关阅读 Formalityhttps://blog.csdn.net/weixin_45791458/category_12841971.html?spm1001.2014.3001.5482https://blog.csdn.net/weixin_45791458/category_12841971.html?spm1001.2014.3001.5482 在Formality中有时会遇到不可读(unread)这个概念,本文就将对此…...
stm32f103C8T6和AT24C256链接
模拟IIC总线 myiic.c #ifndef __24CXX_H #define __24CXX_H #include "myiic.h" #define AT24C01 127 //1kbit1*1024/8128byte地址寻址范围为0-127 #define AT24C02 255 #define AT24C04 511 #define AT24C08 1023 #define AT24C16 2047 #define AT24C32 …...
5.SQLAlchemy对两张有关联关系表查询
问题 例如,一个用户可以有多个收获地址。 定义表如下: 用户表 地址表 一般情况,我们会先查询用户表,拿到用户id后,再到地址表中查询关联的地址数据。这样就要执行两次查询。 仅仅为了方便查询,需要一些属…...
2.2.1 语句结构
ST(Structured Text)语言是一种基于IEC 61131-3标准的高级文本编程语言,其语法规则严格且清晰。以下是ST语言中关于分号、注释和代码块的详细语法规则说明: 分号(;)作用:分号用于表示语句的结束。语法规则: 每个独立的语句必须以分号结尾。分号是语句的终止符,用于分隔…...
安当二代TDE透明加密技术与SMS凭据管理系统相结合的数据库安全解决方案
安当二代TDE透明加密技术与安当SMS凭据管理系统的结合,为企业提供了一套完整的数据库安全解决方案,涵盖字段级加密脱敏和动态凭据管理两大核心功能。以下是其实现方式和技术特点的详细说明: 一、安当二代TDE透明加密技术:字段级加…...
RestClient
什么是RestClient RestClient 是 Elasticsearch 官方提供的 Java 低级 REST 客户端,它允许HTTP与Elasticsearch 集群通信,而无需处理 JSON 序列化/反序列化等底层细节。它是 Elasticsearch Java API 客户端的基础。 RestClient 主要特点 轻量级ÿ…...
Python:操作 Excel 折叠
💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Python 操作 Excel 系列 读取单元格数据按行写入设置行高和列宽自动调整行高和列宽水平…...
Cesium1.95中高性能加载1500个点
一、基本方式: 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...
从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路
进入2025年以来,尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断,但全球市场热度依然高涨,入局者持续增加。 以国内市场为例,天眼查专业版数据显示,截至5月底,我国现存在业、存续状态的机器人相关企…...
学校招生小程序源码介绍
基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码,专为学校招生场景量身打造,功能实用且操作便捷。 从技术架构来看,ThinkPHP提供稳定可靠的后台服务,FastAdmin加速开发流程,UniApp则保障小程序在多端有良好的兼…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
Spring AI与Spring Modulith核心技术解析
Spring AI核心架构解析 Spring AI(https://spring.io/projects/spring-ai)作为Spring生态中的AI集成框架,其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似,但特别为多语…...
JS设计模式(4):观察者模式
JS设计模式(4):观察者模式 一、引入 在开发中,我们经常会遇到这样的场景:一个对象的状态变化需要自动通知其他对象,比如: 电商平台中,商品库存变化时需要通知所有订阅该商品的用户;新闻网站中࿰…...
MySQL JOIN 表过多的优化思路
当 MySQL 查询涉及大量表 JOIN 时,性能会显著下降。以下是优化思路和简易实现方法: 一、核心优化思路 减少 JOIN 数量 数据冗余:添加必要的冗余字段(如订单表直接存储用户名)合并表:将频繁关联的小表合并成…...
C#学习第29天:表达式树(Expression Trees)
目录 什么是表达式树? 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持: 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...

