SanXian

简单.确定.稳定.三联生活

0%

上文我们使用apt以及docker提供的源,安装了docker相关软件。生产环境的有些机器无法访问外网,所以上述方法不适用。Docker官方给我们提供了除apt在线安装之外,基于deb包离线安装Docker的方法。

前面几篇博文,我们成功安装了数据库,本文开始我们介绍使用C和C++的开发接口,连接数据库和读取数据的方法。

出了使用apt包管理器安装PostgreSQL之外,我们还可以自己手工编译源代码来安装。虽然自己编译,比较复杂,但我们却可以使用最新的版本。

本文记录使用源码编译的方法,安装PostgreSQL的过程。

使用apt安装PostgreSQL是Ubuntu上最为简便的方式,但安装的版本却不一定是最新版本。比如当前PostgreSQL最新版本为16.1,而Ubuntu的仓库中为14.2。如果对版本没有严格的要求,我们最好使用apt来帮忙安装,如果确实需要最新版本,请参考后续博文,教大家如何使用最新版本源代码编译安装。

PostgreSQL是以加州大学计算机系开发的POSTGRES4.2版本为基础的对象关系型数据库管理系统。PostgreSQL支持大部分的SQL标准并且提供了很多其他现代特性,如复杂查询、外键、触发器、视图、事务完整性、多版本并发控制等。因为许可证的灵活,任何人都可以以任何目的免费使用、修改和分发PostgreSQL。

记录在Ubuntu22.04服务器上,使用apt安装mysql-server、mysql-client、mysql-dev开发库等组件的方法。

MySQL由瑞典MySQL AB公司开发,属于Oracle旗下产品。其具有体积小、速度快、成本低、开放源码等众多特点,无疑是当下互联网企业最为流行的关系型数据库。

它采用双授权政策,分为社区版和商业版。

后续将深度学习MySQL的使用、管理以及源代码等相关知识。按照管理,我们先梳理MySQL的重要学习资料。

参考图书

书名ISBN作者出版社出版日期备注
高性能MySQL(第4版)9787121442575[美]Silvia Botros电子工业出版社2022-09-01P,W
MySQL技术内幕(第5版)9787115388445[美]Paul DuBois人民邮电出版社2015-07-01P,W
MySQL高可用解决方案——从主从复制到InnoDB Cluster架构9787121441417徐轶韬电子工业出版社2022-09-01P,W
MySQL技术内幕:InnoDB存储引擎(第2版)9787111422068姜承尧机械工业出版社2021-10-01P,W
MySQL是怎样运行的 从根儿上理解MySQL9787115191120小孩子4919人民邮电出版社2020-11-01
MySQL是怎样使用的 快速入门MySQL9787115574961小孩子4919人民邮电出版社2021-12-01P,W
跟老男孩学Linux运维:MySQL入门与提高实践9787111613671老男孩机械工业出版社2019-01-01P,W
MySQL必知必会9787115191120[英]Ben Forta人民邮电出版社2020-03-01P,W

我们知道SQLite不对数据加密,如需加密数据,通常采用SQLCipher方案。

SQLCipher在SQLite基础上,基于AES-256算法对数据库文件整体加密,包括数据页和元数据。通过SQLite的hooks功能注入加密逻辑,实现数据写入磁盘前加密、读取时自动解密,开发者无需大幅修改现有SQL逻辑即可增强数据安全性。

本文描述SQLCipher下载、编译、安装、命令行、编码开发等简单操作和基础知识。

SQLite开发库在Ubuntu/Debian系统上可以使用apt安装,也可以使用源代码编译安装。apt仓库一般并非最新版本,采用源码可按需定制编译,亦可使用最新版本特性。

本文主要记录apt安装方法。

毫无疑问,世界上使用最为广泛的数据库应该是SQLite。它是一个嵌入式数据库,常被各种应用比如安卓或者iOS应用用作本地数据库,所以最为广泛实至名归。SQLite是一款轻型的数据库,是遵守ACID的关系型数据库管理系统,它包含在一个相对小的C库中。

如果要学习一个工业级数据库的实现原理,SQLite是一个非常不错的入手对象。

在Linux系统上我们可以采用编译源代码或者使用Ubuntu软件源的方式来安装Redis,本文主要记录编译安装的方法。

下载源代码

该地址 https://redis.io/download 提供了Redis最新版本,如果要安装历史版本,可以到 https://download.redis.io/releases 下载。

我们使用截止当前最新的7.2.3版本演示安装。

使用如下命令下载Redis到本地服务器。

1
$ wget https://github.com/redis/redis/archive/7.2.3.tar.gz

上文提到Redis可以采用编译源代码或者使用Ubuntu软件源的方式来安装Redis,我们已就Redis源码编译安装方法做了分析,本文我们采用APT源的方式来安装官方为我们编译好的deb包。

APT仓库说明

Ubuntu默认的Redis包,并不在main仓库,而是在universe仓库中,并不是Redis官方发布的最新版本。

Redis官方也是建议我们使用官方源来安装,所以首先我们将官方源加入APT中。

You can install recent stable versions of Redis from the official packages.redis.io APT repository.


在添加APT源之前,首先安装相关依赖:

1
$ sudo apt install lsb-release curl gpg

参考图书

书名ISBN作者出版社出版日期备注
Redis设计与实现9787111464747黄健宏机械工业出版社2014-06-01P,W
Redis开发与运维9787111557975付磊机械工业出版社2022-09-01P
Redis深度历险:核心原理与应用实践9787111557975钱文品电子工业出版社2018-12-01P
Redis5设计与源码分析9787111632788陈雷机械工业出版社2019-08-01P
Redis核心原理与实践9787121415487梁国斌电子工业出版社2021-08-01P
Redis入门指南 第3版9787115569899李子骅人民邮电出版社2021-10-01W
Redis使用手册9787111636526黄健宏机械工业出版社2019-09-01P,W
深入理解Redis9787121312014[美]Jeremy Nelson电子工业出版社2017-04-01

数据库理论

书名ISBN作者出版社出版日期备注
数据库查询优化器的艺术:原理解析与SQL性能优化9787111447467李海翔机械工业出版社2014-01-01W
事务处理:概念与技术9787111126416[美]Jim Gray,Andreas Reuter机械工业出版社2004-01-01P
数据库索引设计与优化9787121260544[美]拉赫登迈奇电子工业出版社2015-06-01P
数据库系统内幕9787111655169Alex Petrov机械工业出版社2020-06-01P,Z

图像处理(image processing),用计算机对图像进行分析,以达到所需结果的技术。又称影像处理。图像处理一般指数字图像处理。数字图像是指用工业相机、摄像机、扫描仪等设备经过拍摄得到的一个大的二维数组,该数组的元素称为像素,其值称为灰度值。图像处理技术一般包括图像压缩,增强和复原,匹配、描述和识别3个部分。

OpenCV是一个基于Apache2.0许可(开源)发行的跨平台计算机视觉和机器学习软件库,可以运行在Linux、Windows、Android和Mac OS操作系统上。它轻量级而且高效:由一系列C 函数和少量C++类构成,同时提供了Python、Ruby、MATLAB等语言的接口,实现了图像处理和计算机视觉方面的很多通用算法。

OpenCV用C++语言编写,它具有C++,Python,Java和MATLAB接口,并支持Windows,Linux,Android和Mac OS,OpenCV主要倾向于实时视觉应用,并在可用时利用MMX和SSE指令。

创建Windows可执行程序之后,在有些使用场景下不需做成安装包,而是期待直接双击即可运行。

我们可以将exe、dll等资源打包成压缩包,用户解压之后再运行exe文件;或者使用WinRAR软件将所有资源打包成自解压可执行程序。

系统编程

系统编程是指编写系统软件,其代码在底层运行,直接跟内核和核心系统库对话。

书名ISBN作者出版社出版日期备注
Linux/UNIX系统编程手册 上下册9787115328670[德]Michael Kerrisk人民邮电出版社2022年04月P
Linux系统编程 第2版9787115346353[美]Robert Love人民邮电出版社2022年03月P
Linux系统编程9787111716617[瑞]Jack-Benny Persson机械工业出版社2023年01月
Linux内核编程指南 第三版9787302092940[美]拜克/鲍姆清华大学出版社2004年11月P
Linux内核编程9787115251947[美]罗德里格斯人民邮电出版社2011年05月P
Linux环境编程:从应用到内核9787111536109高峰机械工业出版社2016年06月P

我们知道一个程序可以包含多个进程,每个进程中可以创建多个线程,在线程中又可以创建成千上万甚至更多个协程。进程和线程的创建以及调度需要在内核态和用户态之间切换;而协程的创建和调度都在用户态,不需要和内核态进行交互。所以这就注定创建和维持协程运行所牺牲的性能,要远小于进程和线程。另外,协程都是以一组的形态存在于一个特定的线程内,那么对于数据的共享,不必使用互斥锁或者条件变量,来保证互斥和同步,应用程序性能上也有了很大的提升。这就是我们使用协程的原因。

协程适用于IO密集型,而不适用于计算密集型的程序。对于IO密集型程序,无论是读取socket还是硬盘,这些操作基本上都是阻塞式调用,当协程遇到阻塞时,当前协程显式或者隐式主动放弃控制权,保存当前协程的硬件上下文和栈,然后调度器切换到其他就绪的协程继续执行,而当阻塞IO完成后,调度器获得通知,恢复原来协程的硬件上下文以及栈,再切换回来运行。而对于计算密集型的程序,当前协程除非显式切换协程或者设置定时器,由定时器主动引起切换,否则通常不会主动放弃控制权,其他协程可能会一直等待调度,得不到运行。

一组协程运行在一个线程内,它们是串行运行的,而非并行,即是运行在一个CPU核上,那么协程就无法利用多核CPU资源。如果我们既想使用协程,又想利用多核CPU,一般我们就采用”多进程+协程“的方式。

目前网上有很多协程的实现例子,本文主要分析云风的协程库,来探究协程的实现原理。大家也可以直接看协程库的 注释版

信号是一种软件中断。常驻程序尤其需要注意处理这些信号,如果没有处理,同时也没有了解信号的默认动作,进程可能会莫名其妙的退出或者core。信号早已有之,但在老的操作系统中,可能会出现信号丢失。4.3BSD和SVR3之后增加了可靠信号机制,我们可以放心使用信号机制。本文根据阅读Nginx代码,参考其信号处理机制,总结了信号使用方法和注意事项。

关于pthread条件变量

1
$ man pthread_cond_init | col -b > pthread_cond.man

得到manual中的描述:

A condition (short for ‘‘condition variable’’) is a synchronization device that allows threads to suspend execution and relinquish the processors until some predicate on shared data is satisfied. The basic operations on conditions are: signal the condition(when the predicate becomes true), and wait for the condition, suspending the thread execution until another thread signals the condition.

条件变量是同步线程的一种机制,它允许线程挂起,让出处理器等待其他线程向它发送信号,该线程收到该信号后被唤醒继续执行程序。对条件变量基本的操作就是:a)向条件变量发送信号,唤醒等待的线程;b)等待条件变量并挂起直至其他线程向该条件变量发送信号。为了防止竞争,条件变量总是和一个互斥锁同时使用。

关于Thrift和OpenSSL的安全通信,上篇我们描述了数字证书的生成方法,本文在此基础上编写单向验证的测试代码。

  • 01#编译OpenSSL库;
  • 02#编译Boost库;
  • 03#编译zlib库;
  • 04#编译libevent库;
  • 05#编译Thrift库;
  • 06#生成客户端和服务端通信所用的数字证书;
  • 07#编写基于Linux系统的测试代码(单向验证:客户端验证服务端);
  • 08#编写基于Windows系统的测试代码(单向验证:客户端验证服务端)
  • 09#编写基于Linux系统的测试代码(双向验证:客户端验证服务端+服务端验证客户端);
  • 10#编写基于Windows系统的测试代码(双向验证:客户端验证服务端+服务端验证客户端);
  • 11#自定义数字证书的验证策略;

关于Thrift和OpenSSL的安全通信,上篇我们描述了zlib的编译方法,本文编译libevent库。

  • 01#编译OpenSSL库;
  • 02#编译Boost库;
  • 03#编译zlib库;
  • 04#编译libevent库;
  • 05#编译Thrift库;
  • 06#生成客户端和服务端通信所用的数字证书
  • 07#编写基于Linux系统的测试代码(单向验证:客户端验证服务端);
  • 08#编写基于Windows系统的测试代码(单向验证:客户端验证服务端);
  • 09#编写基于Linux系统的测试代码(双向验证:客户端验证服务端+服务端验证客户端);
  • 10#编写基于Windows系统的测试代码(双向验证:客户端验证服务端+服务端验证客户端);
  • 11#自定义数字证书的验证策略;

我们知道x509数字证书有V1和V3版本,对于我们来说采用V1版本即可,考虑到证书生成的便捷性,推荐使用如下的第二种方法生成我们需要的证书。

关于Thrift和OpenSSL的安全通信,上篇我们描述了zlib的编译方法,本文编译libevent库。

  • 01#编译OpenSSL库;
  • 02#编译Boost库;
  • 03#编译zlib库;
  • 04#编译libevent库
  • 05#编译Thrift库;
  • 06#生成客户端和服务端通信所用的数字证书;
  • 07#编写基于Linux系统的测试代码(单向验证:客户端验证服务端);
  • 08#编写基于Windows系统的测试代码(单向验证:客户端验证服务端);
  • 09#编写基于Linux系统的测试代码(双向验证:客户端验证服务端+服务端验证客户端);
  • 10#编写基于Windows系统的测试代码(双向验证:客户端验证服务端+服务端验证客户端);
  • 11#自定义数字证书的验证策略;

关于Thrift和OpenSSL的安全通信,上篇我们描述了Boost的编译方法,本文编译zlib库。

  • 01#编译OpenSSL库;
  • 02#编译Boost库;
  • 03#编译zlib库
  • 04#编译Libevent库;
  • 05#编译Thrift库;
  • 06#生成客户端和服务端通信所用的数字证书;
  • 07#编写基于Linux系统的测试代码(单向验证:客户端验证服务端);
  • 08#编写基于Windows系统的测试代码(单向验证:客户端验证服务端);
  • 09#编写基于Linux系统的测试代码(双向验证:客户端验证服务端+服务端验证客户端);
  • 10#编写基于Windows系统的测试代码(双向验证:客户端验证服务端+服务端验证客户端);
  • 11#自定义数字证书的验证策略;

最近对基于Thrift和OpenSSL的安全通信比较感兴趣,根据自己的研究情况,我将其中的研究过程总结下来,作为备忘。

首先,我们明确一下为达成上述目标,需要做哪些工作?

  • 01#编译OpenSSL库
  • 02#编译Boost库;
  • 03#编译zlib库;
  • 04#编译Libevent库;
  • 05#编译Thrift库;
  • 06#生成客户端和服务端通信所用的数字证书;
  • 07#编写基于Linux系统的测试代码(单向验证:客户端验证服务端);
  • 08#编写基于Windows系统的测试代码(单向验证:客户端验证服务端);
  • 09#编写基于Linux系统的测试代码(双向验证:客户端验证服务端+服务端验证客户端);
  • 10#编写基于Windows系统的测试代码(双向验证:客户端验证服务端+服务端验证客户端);
  • 11#自定义数字证书的验证策略;

明确了安全通信所需要的执行步骤,我们从OpenSSL的编译开始本系列之旅。

关于字节序(大端法、小端法)的定义

《UNXI网络编程》定义:术语“小端”和“大端”表示多字节值的哪一端(小端或大端)存储在该值的起始地址。小端存在起始地址,即是小端字节序(Little-Endian);大端存在起始地址,即是大端字节序(Big-Endian)。 也可以说:

  • 小端法就是低位字节排放在内存的低地址端即该值的起始地址,高位字节排放在内存的高地址端。
  • 大端法就是高位字节排放在内存的低地址端即该值的起始地址,低位字节排放在内存的高地址端。

举个简单的例子,对于整形0x12345678。它在大端法和小端法的系统内存中,分别如图1所示的方式存放。

头文件:

1
2
3
4
5
/usr/include/x86_64-linux-gnu/bits/sockaddr.h

/usr/include/x86_64-linux-gnu/bits/socket.h

/usr/include/x86_64-linux-gnu/bits/in.h

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
/* Protocol families.  */
#define PF_UNSPEC      0           /* Unspecified.  */
#define PF_LOCAL       1           /* Local to host (pipes and file-domain).  */
#define PF_UNIX        PF_LOCAL    /* POSIX name for PF_LOCAL.  */
#define PF_FILE        PF_LOCAL    /* Another non-standard name for PF_LOCAL.  */
#define PF_INET        2           /* IP protocol family.  */
#define PF_AX25        3           /* Amateur Radio AX.25.  */
#define PF_IPX         4           /* Novell Internet Protocol.  */
#define PF_APPLETALK   5           /* Appletalk DDP.  */
#define PF_NETROM      6           /* Amateur radio NetROM.  */
#define PF_BRIDGE      7           /* Multiprotocol bridge.  */
#define PF_ATMPVC      8           /* ATM PVCs.  */
#define PF_X25         9           /* Reserved for X.25 project.  */
#define PF_INET6       10          /* IP version 6.  */
#define PF_ROSE        11          /* Amateur Radio X.25 PLP.  */
#define PF_DECnet      12          /* Reserved for DECnet project.  */
#define PF_NETBEUI     13          /* Reserved for 802.2LLC project.  */
#define PF_SECURITY    14          /* Security callback pseudo AF.  */
#define PF_KEY         15          /* PF_KEY key management API.  */
#define PF_NETLINK     16
#define PF_ROUTE       PF_NETLINK  /* Alias to emulate 4.4BSD.  */
#define PF_PACKET      17          /* Packet family.  */
#define PF_ASH         18          /* Ash.  */
#define PF_ECONET      19          /* Acorn Econet.  */
#define PF_ATMSVC      20          /* ATM SVCs.  */
#define PF_RDS         21          /* RDS sockets.  */
#define PF_SNA         22          /* Linux SNA Project */
#define PF_IRDA        23          /* IRDA sockets.  */
#define PF_PPPOX       24          /* PPPoX sockets.  */
#define PF_WANPIPE     25          /* Wanpipe API sockets.  */
#define PF_LLC         26          /* Linux LLC.  */
#define PF_IB          27          /* Native InfiniBand address.  */
#define PF_MPLS        28          /* MPLS.  */
#define PF_CAN         29          /* Controller Area Network.  */
#define PF_TIPC        30          /* TIPC sockets.  */
#define PF_BLUETOOTH   31          /* Bluetooth sockets.  */
#define PF_IUCV        32          /* IUCV sockets.  */
#define PF_RXRPC       33          /* RxRPC sockets.  */
#define PF_ISDN        34          /* mISDN sockets.  */
#define PF_PHONET      35          /* Phonet sockets.  */
#define PF_IEEE802154  36          /* IEEE 802.15.4 sockets.  */
#define PF_CAIF        37          /* CAIF sockets.  */
#define PF_ALG         38          /* Algorithm sockets.  */
#define PF_NFC         39          /* NFC sockets.  */
#define PF_VSOCK       40          /* vSockets.  */
#define PF_KCM         41          /* Kernel Connection Multiplexor.  */
#define PF_QIPCRTR     42          /* Qualcomm IPC Router.  */
#define PF_SMC         43          /* SMC sockets.  */
#define PF_XDP         44          /* XDP sockets.  */
#define PF_MCTP        45          /* Management component transport protocol.  */
#define PF_MAX         46          /* For now..  */

/* Address families.  */
#define AF_UNSPEC      PF_UNSPEC
#define AF_LOCAL       PF_LOCAL
#define AF_UNIX        PF_UNIX
#define AF_FILE        PF_FILE
#define AF_INET        PF_INET
#define AF_AX25        PF_AX25
#define AF_IPX         PF_IPX
#define AF_APPLETALK   PF_APPLETALK
#define AF_NETROM      PF_NETROM
#define AF_BRIDGE      PF_BRIDGE
#define AF_ATMPVC      PF_ATMPVC
#define AF_X25         PF_X25
#define AF_INET6       PF_INET6
#define AF_ROSE        PF_ROSE
#define AF_DECnet      PF_DECnet
#define AF_NETBEUI     PF_NETBEUI
#define AF_SECURITY    PF_SECURITY
#define AF_KEY         PF_KEY
#define AF_NETLINK     PF_NETLINK
#define AF_ROUTE       PF_ROUTE
#define AF_PACKET      PF_PACKET
#define AF_ASH         PF_ASH
#define AF_ECONET      PF_ECONET
#define AF_ATMSVC      PF_ATMSVC
#define AF_RDS         PF_RDS
#define AF_SNA         PF_SNA
#define AF_IRDA        PF_IRDA
#define AF_PPPOX       PF_PPPOX
#define AF_WANPIPE     PF_WANPIPE
#define AF_LLC         PF_LLC
#define AF_IB          PF_IB
#define AF_MPLS        PF_MPLS
#define AF_CAN         PF_CAN
#define AF_TIPC        PF_TIPC
#define AF_BLUETOOTH   PF_BLUETOOTH
#define AF_IUCV        PF_IUCV
#define AF_RXRPC       PF_RXRPC
#define AF_ISDN        PF_ISDN
#define AF_PHONET      PF_PHONET
#define AF_IEEE802154  PF_IEEE802154
#define AF_CAIF        PF_CAIF
#define AF_ALG         PF_ALG
#define AF_NFC         PF_NFC
#define AF_VSOCK       PF_VSOCK
#define AF_KCM         PF_KCM
#define AF_QIPCRTR     PF_QIPCRTR
#define AF_SMC         PF_SMC
#define AF_XDP         PF_XDP
#define AF_MCTP        PF_MCTP
#define AF_MAX         PF_MAX

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
typedef unsigned short int sa_family_t;

struct sockaddr
{
    sa_family_t    sa_family;

    char           sa_data[14]; /* Address data. */
};

typedef uint32_t in_addr_t;

struct in_addr
{
    in_addr_t      s_addr;
};

struct sockaddr_in
{
    sa_family_t    sin_family;
    in_port_t      sin_port;
    struct in_addr sin_addr;
    unsigned char  sin_zero[sizeof(struct sockaddr) - __SOCKADDR_COMMON_SIZE - sizeof(in_port_t) - sizeof(struct in_addr)];
};

背景

1
2
3
4
#include <sys/types.h>
#include <sys/socket.h>
 
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

connect是socket套接口编程中非常重要的一个函数,它用于客户机连接使用TCP协议打开的服务机。

connect有同步连接和异步连接两种模式:

  • 同步连接指的是,我们设置socket套接字为阻塞模式,调用connect之后,程序一直等待,直到该函数返回成功或者失败。如果连接过程中,发生了超时重传,接口的耗时时间有可能达127秒之久。假如我们的服务器程序只有一个网络线程,同步connect会阻塞该网络线程较长时间,在这段时间内将不能给其他连接提供服务。
  • 异步连接指的是,我们设置socket套接字为非阻塞模式,调用connect之后,该函数会马上返回,如果连接立即成功,那么皆大欢喜,就不用进行下步操作了。如果连接没有立即成功,我们就用select或者epoll等待操作系统给我们通知,接到通知后,我们再判断连接成功与否。在高性能服务器程序中,我们优先使用异步连接这种模式。

参考图书

书名ISBN作者出版社出版日期备注
UNIX网络编程 卷1 套接字联网API 第3版9787115517791[美]W.理查德.史蒂文斯人民邮电出版社2019-10-01P
UNIX网络编程 卷2 进程间通信 第2版9787115517807[美]W.理查德.史蒂文斯人民邮电出版社2019-10-01P
TCP/IP高效编程:改善网络程序的44个技巧9787115249371[美]斯纳德人民邮电出版社2011-04-01P
Linux高性能服务器编程9787111425199游双机械工业出版社2013-06-01P

编程时,有时需要打印版本号或者根据版本号来判断逻辑分支走向,前者一般是字符串形式的版本号,后者一般是整型数表达的版本号。

所以需要设计一个相对通用的版本号定义、升级和生成方法,来方便我们使用。

(extern 变量) (extern 函数) 解析

这种情况下的extern说明变量或者函数声明在其他的源文件里,而不用include头文件的方式来引用该函数,在链接时,链接器在各个模块中搜索这个变量或者函数来进行最终链接。

(extern “C”) 解析

使用这种extern的情况多发生在使用C++调用由C写成的函数库时,此时编译过程中常发生编译器找不到C函数的问题,从而导致链接失败。为了解决这种情况,才引用了extern “C”这种用法。

那为什么又会出现这种链接失败的情况呢?简单来是由于g++和gcc生成函数名称方式的不同造成的。C++语言在编译的时候为了解决函数的多态问题,不会直接使用程序中书写的函数名称,而会使用一种特别的方法经过中间变换过程生成一个全局唯一函数名。C库函数是没有经过函数名称变换得来的,当C++使用经过变换的函数名称去调没有变换过的函数时,肯定会出现链接失败的情况。

这种特殊的转换方法叫做“名称的特殊处理(Name Mangling)”,比如C++将函数名和参数联合起来生成一个中间的函数名称,而C语言则不会,因此会造成链接时找不到对应函数的情况,此时C函数就需要用extern “C”进行链接指定,这告诉编译器,这是一个用C写成的库文件,请用C的方式来链接它们。

printf是一套家族函数,是C语言标准库函数,定义于头文件stdio.h,其定义如下所示:

1
2
3
4
5
6
7
#include <stdio.h>

int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
int dprintf(int fd, const char *format, ...);
int sprintf(char *str, const char *format, ...);
int snprintf(char *str, size_t size, const char *format, ...);

printf的format参数是格式控制字符串,它包含两种字段:一是普通字符串,打印时原样输出;二是控制字段,打印时使用可变变量…指定的参数数据,来替换对应的控制字段。两种字段相结合,形成结果字符串,然后printf将结果字符串打印到标准输出。