Jade Dungeon

GCC

CentOS安装GCC

CentOS 6.6 升级GCC G++ (当前最新GCC/G++版本为v6.1.0)

没有便捷方式,

yum update....   yum install 

或者 添加yum 的 repo 文件 也不行, 只能更新到 4.4.7!

then, 只能手动编译安装了,那么开始第一步下载源代码吧,GO!

获取安装包并解压

wget http://ftp.gnu.org/gnu/gcc/gcc-6.1.0/gcc-6.1.0.tar.bz2
tar -jxvf gcc-6.1.0.tar.bz2

当然,http://ftp.gnu.org/gnu/gcc 里面有所有的gcc版本供下载,最新版本已经有6.1.0啦.

建议下载.bz2的压缩包,文件更小,下载时间更少.

编译

下载供编译需求的依赖项。这个神奇的脚本文件会帮我们下载、配置、安装依赖库, 可以节约我们大量的时间和精力。

cd gcc-6.1.0
./contrib/download_prerequisites 

建立一个目录供编译出的文件存放

mkdir gcc-build-6.1.0
cd gcc-build-6.1.0

生成Makefile文件

../configure -enable-checking=release -enable-languages=c,c++ -disable-multilib

编译

make -j4

-j4选项是make对多核处理器的优化,如果不成功请使用 make。 (注意:此步骤非常耗时,我虚拟机耗时近3小时; 实体机近80分钟,CPU基本是满的,内存也使用不少)

安装(需要root权限!)

make install

查看安装

ls /usr/local/bin | grep gcc

重启,然后查看gcc版本

gcc -v

测试

写个C++11 特性的程序段

tryCpp11.cc代码省略....

g++ -std=c++11 -o tryCpp11 tryCpp11.cc

升级动态链接库

升级gcc,生成的动态库没有替换老版本gcc的动态库

源码编译升级安装了gcc后,编译程序或运行其它程序时, 有时会出现类似问题:

/usr/lib64/libstdc++.so.6: version `GLIBCXX_3.4.21' not found

这是因为升级gcc时,生成的动态库没有替换老版本gcc的动态库导致的, 将gcc最新版本的动态库替换系统中老版本的动态库即可解决。

运行以下命令检查动态库:

strings /usr/lib64/libstdc++.so.6 | grep GLIBC

从输出可以看出,gcc的动态库还是旧版本的。说明出现这些问题,是因为升级gcc时, 生成的动态库没有替换老版本gcc的动态库。

执行以下命令,查找编译gcc时生成的最新动态库:

find / -name "libstdc++.so*"

将上面的最新动态库libstdc++.so.6.0.22复制到/usr/lib64目录下

cd /usr/lib64
cp /root/Downloads/gcc-6.1.0/gcc-build-6.1.0/stage1-x86_64-pc-linux-gnu/libstdc++-v3/src/.libs/libstdc++.so.6.0.22 ./

删除原来软连接:

rm -rf libstdc++.so.6

将默认库的软连接指向最新动态库:

ln -s libstdc++.so.6.0.22 libstdc++.so.6

默认动态库升级完成。重新运行以下命令检查动态库:

strings /usr/lib64/libstdc++.so.6 | grep GLIBC

可以看到 输出有GLIBCXX_3.4.21

GCC基本使用

常用参数

语法检查

  • -Wall:检测所有可能错误。
  • -pendantic:根据 C 标准产生警告,避免程序中有非标准特性。
  • -ansi:禁用 GCC 的非标准特性。
  • -std=c89-std=c99:指定用 C 编译器的版本。

预处理参数

  • -I:参数声明寻找头文件包含的路径。
  • -E:参数声明不编译源代码,只输出预处理器处理过的程序。
  • -D:参数声明一个宏,相当于#define。如果没有明确给出宏的值,那么默认为 1。例: gcc -DDEBUG=1 foo.c
  • -U参数用来声明取消一个宏定义,相当于#undef。例:gcc -UDEBUG foo.c

编译参数

  • -S:只编译、不汇编、不链接,生成汇编代码.s
  • -c:编译、汇编,但是不链接,生成目标文件.o
  • -g:在可执行程序中包含调试信息。
  • -o file:指定输出文件名。

链接库相关参数

  • -static:静态编译为静态链接库,禁止使用动态库。
  • -shared:默认开启,尽可能生成动态链接库;如果找不到动态库,才链接同名静态库。
  • -L dir:添加指定目录到库文件查找列表。
  • -lname:链接名为libname.a的静态库或是libname.so的动态库。 如果两个都有,就按参数是-static还是-shared决定。
  • -fPIC或是-fpic:生成相对地址位置无关的目标代码 (Position Independent Code)。 以便将来使用-static从这个PIC目标文件生成动态库文件。

编译过程概览

在使用 GCC 编译程序时,编译过程可以被细分为四个阶段:

  • 预处理(Pre-Processing),生成预处理后文件*.i(C程序)与*.ii(C++)。
  • 编译(Compiling),生成*.s汇编原始程序。
  • 汇编(Assembling),生成*.o目标代码。
  • 链接(Linking),生成可执行文件。

gcc预处理阶段:

主要对包含的头文件(#include)和宏定义(#define,#ifdef…)进行处理。 可以使用gcc -E让gcc 在预处理之后停止编译过程,生成*.i文件:

gcc -E hello.c -o hello.i

gcc 编译阶段:

gcc 首先要检查代码的规范性,是否有语法错误等。以确定代码实际要做的工作, 在检查无误后,gcc 把代码翻译成汇编语言。用户可以使用-S选项进行查看, 该选项只进行编译而不进行汇编,生成汇编代码。

gcc -S hello.i -o hello.s

gcc 汇编阶段:生成目标代码*.o,有两种方式:

  • 使用 gcc 直接从源代码生成目标代码gcc -c *.s -o *.o
  • 使用汇编器从汇编代码生成目标代码as *.s -o *.o
gcc -c hello.s -o hello.o

或:

as hello.s -o hello.o

也可以直接使用as *.s, 将执行汇编、链接过程生成可执行文件a.out, 可以像上面使用-o选项指定输出文件的格式。

gcc 链接阶段:生成可执行文件,默认的名为: a.out

gcc hello.o     生成可执行文件 a.out

或:

gcc hello.o -o hello        生成可执行文件 hello

静态库与动态库

库的源代码会被编译成一个或多个目标模块,目标模块是二进制文件, 可以被包含在库中并且链接到可执行的二进制中。

  • 对于静态库,标准的文件拓展名是.a
  • 对于动态库,标准的文件拓展名是.so

两种库的文件名都使用前缀lib*进行标识。 两者的差别仅在程序执行时所需的代码是在编译时静态加载的, 还是在运行时动态加载的。

库文件被复制到标准目录下,使得客户程序可以轻松地访问到库。 无论是静态库还是动态库,典型的位置是/usr/lib或者/usr/local/lib, 当然其他位置也是可以的。

代码与例子: ../../../make/sample03/

基础概念

函数范围

  • 函数默认的存储类是extern,它给了函数一个全局域。 一个客户程序可以调用在库中用extern修饰的任意函数。
  • 存储类static将一个函数的的范围限制到函数被定义的文件中。 进而实现了对库的客户程序隐藏gcd函数。

例如源文件libmy_primes.c中:

extern unsigned are_coprimes(unsigned n1, unsigned n2) { ... }
static unsigned gcd(unsigned n1, unsigned n2) { ... }
  • 只有在primes.c文件中的函数可以调用 gcd,
  • 只有are_coprimes函数会调用它。
  • 当静态库和动态库被构建和发布后,其他的程序可以调用外部的(extern)函数, 如are_coprimes,但是不可以调用静态(static)函数gcd。静态(static) 存储类通过将函数范围限制在其他库函数内,进而实现了对库的客户程序隐藏gcd函数。

函数定义与声明

C 语言区分了函数的定义(definition)和声明(declaration),这对库来说很重要。

C语言仅允许命名函数不允许匿名函数,并且每个函数需要定义以下内容:

  • 一个唯一的名字。一个程序不允许存在两个同名的函数。
  • 一个可以为空的参数列表,参数需要指明类型。
  • 一个返回值类型,当没有返回值时设置为空类型(void)。
  • 用一对花括号包围起来的函数主体部分。特殊情况下函数主体部分可以为空。
  • 程序中的每个函数必须要被定义一次。

函数声明不同于定义,其不需要主体部分:

  • 声明在参数列表后用一个分号代表结束,它没有被花括号包围起来的主体部分。
  • 程序中的函数可以被多次声明。

为什么需要声明?在 C 语言中,一个被调用的函数必须对其调用者可见。 有多种方式可以提供这样的可见性,具体依赖于编译器如何实现。

一个必然可行的方式就是当它们二者位于同一个文件中时, 将被调用的函数定义在在它的调用者之前。

void f() {...}     /* f 定义在其被调用前 */
void g() { f(); }  /* ok */

当函数f被在调用前声明,此时函数f的定义可以移动到函数g的下方。

void f();         /* 声明使得函数 f 对调用者可见 */
void g() { f(); } /* ok */
void f() {...}    /* 相较于前一种方式,此方式显得更简洁 */

无论是静态库还是动态库,一个被调用的函数和调用它的函数往往不在同一个文件中。 用头文件声明外部要调用的库,如与libmy_primes.c配套的libmy_primes.h

/** 头文件 primes.h:函数声明 **/
extern unsigned is_prime(unsigned);
extern void prime_factors(unsigned);
extern unsigned are_coprimes(unsigned, unsigned);
extern void goldbach(unsigned);
  • 为了客户程序的便利性,头文件primes.h应该存储在C编译器查找路径下的目录中。 典型的位置有/usr/include/usr/local/include
  • 一个 C 语言客户程序应使用#include包含这个头文件, 并尽可能将这条语句其程序源代码的首部(头文件将会被导入另一个源文件的头部)。

静态库

当你的应用链接了一个静态库,这个库的代码就变成了可执行文件的一部分。 这个动作只在链接过程中执行一次,这些静态库通常以.a扩展符, 结尾意为「目标(object)文件」的「归档(archive)」。

这些目标文件通常是ELF格式(可执行可链接格式: Executable and Linkable Format),可以与多个操作系统兼容。

静态库与汇编生成的目标文件一起链接为可执行文件, 那么静态库必定跟.o文件格式相似。其实一个静态库可以简单看成是一组目标文件 (.o/.obj文件)的集合,即很多目标文件经过压缩打包后形成的一个文件。

静态库特点总结:

  • 静态库对函数库的链接是放在编译时期完成的。
  • 程序在运行时与函数库再无瓜葛,移植方便。
  • 浪费空间和资源,因为所有相关的目标文件与牵涉到的函数库被链接合成一个可执行文件。

创建目标文件

首先,gcc参数-c将代码文件编译成目标文件.oStaticMath.o):

g++ -c StaticMath.cpp

注意带参数-c,否则直接编译为可执行文件

创建新归档文件

然后,通过ar -crv工具将目标文件打包成.a静态库文件:

  • v:详细的信息输出
  • 归档操作参数:
    • c:创建新的归档文件
    • s:目标文件添加到现有的归档文件中
  • 加入归档的方式:
    • r:替换添加,查重复,替换归档中重复的目标文件
    • q:快速添加,不检查重复,直接添加到归档尾部
ar -crv libstaticmath.a StaticMath.o

添加到现有归档文件中

如果有多个文件,可以先分别编译成目标文件:

gcc -c -Wall -o out/libmy_static_a.o src/libmy_static_a.c
gcc -c -Wall -o out/libmy_static_b.o src/libmy_static_b.c

然后多个文件用ar -rsv打包为.a静态文件:

ar -rsv out/libmy_static.a out/libmy_static_a.o out/libmy_static_b.o

file命令的输出可以告诉你静态库libmy_static.aar格式的归档文件类型。

$ file libmy_static.a
libmy_static.a: current ar archive

使用ar -t,你可以看到归档文件的内部。它展示了两个目标文件:

$ ar -t libmy_static.a
libmy_static_a.o
libmy_static_b.o

从归档文件中提取目标文件

你可以用ax -x <archive-file>命令来提取归档文件的文件。 被提出的都是 ELF 格式的目标文件:

$ ar -x libmy_static.a
$ file libmy_static_a.o
libmy_static_a.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

动态链接库

动态链接指的是使用共享库。 共享库通常以.so的扩展名结尾,代表「共享对象(shared object)」的简写。

  • 动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入。
  • 不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例, 规避了空间浪费问题。
  • 动态库在程序运行是才被载入,也解决了静态库对程序的更新、 部署和发布页会带来麻烦。用户只需要更新动态库即可,增量更新。

Window与Linux执行文件格式不同,在创建动态库的时候有一些差异。

  • 在Windows系统下的执行文件格式是PE格式, 动态库需要一个DllMain函数做出初始化的入口, 通常在导出函数的声明时需要有_declspec(dllexport)关键字。
  • Linux下gcc编译的执行文件默认是ELF格式,不需要初始化入口, 亦不需要函数做特别的声明,编写比较方便。

与创建静态库不同的是,不需要打包工具(ar、lib.exe), 直接使用编译器即可创建动态库。

查找库文件路径

GCC 采用搜索目录的办法来查找所需要的文件,大多数函数都默认将:

  • 头文件放到/usr/include/目录下,
  • 库文件则放到/usr/lib/目录下,

通常来说,32 位和 64 位版本的应用有不同的库。 下面列表展示了不同 Linux 发行版库的标准路径:

  32 位 64 位
红帽家族 /usr/lib /usr/lib64
Debian 家族 /usr/lib/i386-linux-gnu /usr/lib/x86_64-linux-gnu
Arch Linux 家族 /usr/lib32 /usr/lib64
FreeBSD /usr/lib32 /usr/lib

指定额外的查找路径:

-I选项可以向 GCC 的头文件搜索路径中添加新的目录:

gcc foo.c -I /home/justin/include -o foo 

-L选项向 GCC 的库文件搜索路径中添加新的目录:

gcc foo.c -L /home/justin/lib -lfoo -o foo

-l选项指示GCC去连接库文件libfoo.so。 Linux 下的库文件在命名时有一个约定, 那就是应该以lib*三个字母开头。

由于所有的库文件都遵循了同样的规范, 因此在用-l选项指定链接的库文件名时可以省去lib三个字母。

也就是说GCC 在对-lfoo进行处理时,会自动去链接名为libfoo.so

默认情况下,GCC 在链接时优先使用动态链接库, 只有当动态链接库不存在时才考虑使用静态链接库, 如果需要的话可以在编译时加上-static选项,强制使用静态链接库。

例如,如果在home/justin/lib/目录下有链接时所需要的库文件libfoo.solibfoo.a ,为了让GCC 在链接时只用到静态链接库,可以使用下面的命令:

gcc foo.c -L /home/justin/lib -static -lfoo -o foo

创建自己的动态库

首先要编译目标文件,使用-fPIC参数指定编译为动态链接目标*.o

gcc -c -fPIC -Wall -Werror -o out/libmy_shared.o src/libmy_shared.c

-fPIC选项的目的是让编译器生成地址无关(position independent)的代码, 这是因为,动态库是在运行期间链接的,变量和函数的偏移量是事先不知道的, 需要链接以后根据offset进行地址重定向。

然后使用-shared参数指定编译为动态链接库*.so

gcc -shared -o out/libmy_shared.so.1.5 out/libmy_shared.o \
	-Wl,-soname,libmy_shared.so,-rpath,./out

-Wl选项是设置传递给ld(链接器)的参数,在这里相当于:

ld -soname libmy_shared.so.1.5 -rpath ./out
  • -o:库文件的名字。一般链接时都指定lib<XYZ>.so.<MAJOR>.<MINOR>格式。 避免同一个库不同版本的文件名冲突。
  • -soname:库的逻辑名字(必选)。将来程序运行时会按照逻辑名查找。 例:GCC使用-lmy_shared参数时只会查找名为libmy_shared.so的文件。

所以一般会按物理名生成一个逻辑名的链接:

cp out/libmy_shared.so.1.5 /opt/lib/
ln -sf /opt/lib/libmy_shared.so.1.5 /opt/lib/libmy_shared.so

逻辑名(符号链接的名字)不应该改变,但是符号链接的目标可以根据需要进行更新, 库的新版本(库文件)实现可以是修复了 bug,提高性能等。

在程序中依赖动态库

程序中调用动态库

编译主程序后把动态链接库添加进去,用-L指定库文件路径,用-l指定库文件:

gcc -c -Wall -Werror -o out/main.o src/main.c

gcc -Wall -Werror -o out/my_app out/main.o out/libmy_static.a \
	-L./out -lmy_shared -lmy_primes -lm      

注意库的顺序,因为libmy_primes信赖数学库libm,所以-lm在放在libmy_primes 后面。

这样程序可以链接成功,但是在运行时会找不到库而无法执行:

$ out/my_app
out/my_app: error while loading shared libraries: 
	libmy_shared.so.1.5: 
		cannot open shared object file: No such file or directory
查看动态库依赖

为了检查一个应用在启动时需要哪些库,你可以使用ldd命令, 它会打印出给定文件所需的动态库:

$ ldd out/my_app
    linux-vdso.so.1 (0x00007ffe2cbe1000)
    libmy_shared.so => not found
    libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f7f5d67f000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007efd0a964000)
    /lib64/ld-linux-x86-64.so.2 (0x00007efd0af57000)

可以注意到libmy_shared.so库是代码仓库的一部分,但是没有被找到。 对比正常找到的库,以libc.so.6为例,对应文件为: /lib/x86_64-linux-gnu/libc.so.6

在我的系统中,libc.so.6也是指向同一目录下的共享对象libc-2.31.so的软链接。

$ file /lib64/libc.so.6
/lib64/libc.so.6: symbolic link to libc-2.31.so
查看链接器信息

想知道哪个链接器被调用了,你可以用file命令:

$ file my_app
my_app: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), 
	dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, 
	BuildID[sha1]=26c677b771122b4c99f0fd9ee001e6c743550fa6, 
	for GNU/Linux 3.2.0, not stripped

链接器/lib64/ld-linux-x86–64.so.2是一个指向ld-2.30.so的软链接, 它也是我的 Linux 发行版的默认链接器:

$ file /lib64/ld-linux-x86-64.so.2
/lib64/ld-linux-x86-64.so.2: symbolic link to ld-2.31.so

运行时动态加载库

之前的程序可以链接成功,但是在运行时会找不到库而无法执行:

$ out/my_app
out/my_app: error while loading shared libraries: 
	libmy_shared.so.1.5: 
		cannot open shared object file: No such file or directory

运行时加载动态库的工作由动态加载器ld.so完成的。 它按特定的机制来检测一个应用的依赖并将其加载进内存中。

ld.so按以下顺序在这些地方寻找共享对象:

  1. 应用的绝对路径或相对路径下(用 GCC 编译器的-rpath选项硬编码的)
  2. 环境变量LD_LIBRARY_PATH
  3. /etc/ld.so.cache文件

需要记住的是,将一个库加到系统库归档/usr/lib64中需要管理员权限。 你可以手动拷贝libmy_shared.so至库归档中来让应用可以运行。

方法一:环境变量LD_LIBRARY_PATH

对新手来说,与常用库(例如bizp2)版本不兼容相关的问题往往十分令人困惑。 一种方法是把该仓库的路径加入到环境变量LD_LIBRARY_PATH 中来告诉链接器去哪里找到正确的版本。在本例中,正确的版本就在这个目录下, 所以你可以导出它至环境变量:

$ LD_LIBRARY_PATH=$(pwd)/out:$LD_LIBRARY_PATH
$ export LD_LIBRARY_PATH

现在动态链接器知道去哪找库了,应用也可以执行了。

$ out/myapp
Press Enter to repeat

Got integer: 5

Got integer: -4
c

你可以再次执行ldd去调用动态链接器,它会检查应用的依赖然后加载进内存。 内存地址会在对象路径后展示:

$ ldd out/my_app
    linux-vdso.so.1 (0x00007fffdb769000)
    libmy_shared.so => /home/sam/out/libmy_shared.so (0x00007fd05f630000)
    libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f7f5d67f000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fd05f220000)
    /lib64/ld-linux-x86-64.so.2 (0x00007fd05fc00000)
方法二:在编译时定制共享库

如果你想你的应用使用你的共享库,你可以在编译时指定一个绝对或相对路径。

在生成动态链接库时,去掉链接参数-Wl

gcc -shared -o out/libmy_shared.so.1.5 out/libmy_shared.o \
	-Wl,-soname,libmy_shared.so.1.5,-rpath,/home/jade/git-repo/study/make/sample03/out

# change to 

gcc -shared  -o out/libmy_shared.so.1.5 out/libmy_shared.o
gcc -Wall -Werror -o out/my_app out/main.o out/libmy_static.a \
	-L/home/jade/git-repo/study/make/sample03/out  -lmy_shared 

# change to 

gcc -Wall -Werror -o out/my_app out/main.o out/libmy_static.a \
	/home/jade/git-repo/study/make/sample03/out/libmy_shared.so.1.5  

然后通过make -B来重新编译程序。ldd输出就会显示libmy_shared.so 它的绝对路径一起被列出来了:

$ ldd out/my_app
    linux-vdso.so.1 (0x00007ffec39ee000)
    /home/jade/git-repo/study/make/sample03/out/libmy_shared.so.1.5 (0x00007f7d9abdc000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fd05f220000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f7d9a7eb000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f7d9afe0000)
方法三:/etc/ld.so.cache文件

新库的路径可以通过写入/etc/ld.so.conf或是在/etc/ld.so.conf.d/ 目录下创建一个包含路径的<library-name>.conf文件来注册至系统。

之后,必须执行ldconfig命令来覆写ld.so.cache文件。 这一步有时候在你装了携带特殊的共享库的程序来说是不可省略的。

$ sudo ldconfig

查看ld.so的手册页 获取更多详细信息。

Python调用动态库

与 C 不同,Python 不是一个静态编译语言,这意味着 Python 程序必须访问动态版本而非静态版本的库。

Python中有众多的支持「外部语言接口(FFI:foreign function interface)」的模块 (标准的或第三方的),它们允许用一种语言编写的程序来调用另一种语言编写的函数。 Python 中的ctypes是一个标准的、相对简单的允许 Python 代码调用 C 函数的FFI。

FFI对接的语言不大可能会具有完全相同的数据类型。例如:

  • Python 并不具有C语言 unsigned int类型;因此ctypes FFI将之映射为 Python 中的int类型。
  • ctypes默认会将C语言中的void替换为Python语言中的int。 当从Python代码中调用返回值为void的函数时会从栈中返回一个随机整数值 (因此,该值无任何意义)。
  • 等等……

例如有一个叫primes的库中有四个extern C函数, 有两个在具有显式ctypes配置的Python中会表现得更好:

extern unsigned is_prime(unsigned);
extern unsigned are_coprimes(unsigned, unsigned);

extern void prime_factors(unsigned);
extern void goldbach(unsigned);

函数prime_factorsgoldbach返回void而不是返回一个具体类型, 当从Python代码中调用返回值为void的函数时会从栈中返回一个随机整数值 (因此,该值无任何意义)。

然而,可以对ctypes进行配置,让这些函数返回None(Python中为null类型)。 下面是对primes库的prime_factors函数的配置:

primes.prime_factors.restype = None

可以用类似的语句处理goldbach函数。

例如在Python3中:

>>> from ctypes import cdll

>>> primes = cdll.LoadLibrary("libshprimes.so") ## 逻辑名

>>> primes.is_prime(13)
1
>>> primes.is_prime(12)
0

>>> primes.are_coprimes(8, 24)
0
>>> primes.are_coprimes(8, 25)
1

>>> primes.prime_factors.restype = None
>>> primes.goldbach.restype = None

>>> primes.prime_factors(72)
2 2 2 3 3

>>> primes.goldbach(32)
32 = 3 + 29
32 = 13 + 19

primes库中的函数只使用一个简单数据类型:unsigned int。 如果这个C语言库使用复杂的类型如结构体,如果库函数传递和返回指向结构体的指针, 那么比ctypes更强大的FFI更适合作为一个在Python语言和C语言之间的平滑接口。 尽管如此,ctypes示例展示了一个Python客户程序可以使用C语言编写的库。

值得注意的是,用作科学计算的流行的Numpy库是用C语言编写的, 然后在高级Python API中公开。

简单的primes库和高级的Numpy库强调了C语言仍然是编程语言中的通用语言。 几乎每一个语言都可以与C语言交互,同时通过C语言也可以和任何其他语言交互。

Python 很容易和C语言交互,作为另外一个例子,当Panama项目成为JNI (Java Native Interface)一个替代品后,Java 语言和 C 语言交互也会变的很容易。