Menu

  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay
  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay

Native编程知识集锦

17
Nov
2011

Native编程知识集锦

By Alex
/ in C,C++
0 Comments
基础知识
不同编译器创建的库的互操作性问题

不同编译器(甚至同一款编译器的不同版本)创建的目标文件(.o/.obj)、静态库常常不能相互链接,因此常常需要获得源码,从头编译。

动态链接库(DLL)的互操作性则较好,如果DLL是基于C编写的,那么互操作性通常不是问题。即使DLL基于C++编写, 只要通过C接口( extern C )调用,通常也没有问题。例如MinGW就可以正常的链接到Windows的C运行时库。

互操作性问题的根源不仅仅是C++名称改编,更关键的是ABI的不兼容性。因此即使解决了名称改编问题,让链接阶段正常完成,也可能在运行时,调用DLL时出现程序崩溃。ABI的不兼容性可以表现在:

  1. 数据类型的大小、对齐方式。对齐方式一致性可能需要正确的编译器设置(例如 -mms-bitfields )
  2. 调用约定:如何传递参数、如何接受返回值。例如所有参数通过栈传递,还是部分通过寄存器传递;寄存器和参数的对应关系;栈传递的第一个参数位于栈底还是栈顶
  3. 内存模型的不一致性:例如MSVC DLL中的 new/delete、malloc/free 和Cygwin newlib中对应函数不能互操作
  4. 异常处理模型的不一致性:MSVC DLL抛出的异常不能被Cygwin创建的exe文件捕获,反之亦然
  5. 旧的GNU SJLJ异常模型(GCC 3.x-)可以和MSVC++的异常模型兼容;而新的DWARF2则不能兼容
C++异常处理器

使用GCC时,可用的异常处理机制(exception handling,EH)包括:

  1. DWARF (DW2, dwarf-2):该机制不能在64bit的Windows下运行。在32bit的Windows下,异常也不能通过任何非DW2感知的代码传播(propagate )。因此,Windows系统DLL、Visual Studio构建的DLL,都不能和此EH联用
  2. SJLJ (setjmp/longjmp):大部分情况下同时支持32/64bit的Windows,但是该EH不是Zero-cost的,相对之下较慢
  3. SEH (Structured Exception Handling):这是微软的EH,GCC从4.8开始支持它
编译、链接和库

一个现代编译器的主要工作流程:

源代码(source code)→
预处理器(preprocessor)→
编译器(compiler)→
汇编程序(assembler)→
目标代码(object code)→
链接器(Linker)→
可执行文件(executables)
编译(Compile)

把源文件编译成中间代码文件(.o,或者Windows的.obj)文件的过程。通常每个源文件都应该对应于一个中间目标文件。编译阶段,检查语法错误、函数、变量的声明是否正确(需要告知编译器头文件所在位置)。

链接(Link)

把多个中间代码文件合并为一个可执行文件的过程。连接器在.o文件中找到函数的实现,解析未定义的符号引用,将.o文件中的占位符替换为符号的地址。链接分为静态链接、动态链接两种,对于前者,依赖库仅需要在链接期可见;对于后者,依赖库在链接期、运行期都必须可见。

库(Library)

如果源文件太多,则导致有大量的.o文件,链接时需要逐一指出其文件名,这很不方便。库文件(.a,或者Window的.lib)文件相当于把.o文件打包,库文件是预先编译好的函数的集合。

在Linux中,库文件的命名必须遵守格式: lib[libname].[a|so] 。也就是说,库文件必须以lib三个字母开头,其后跟着库的名称,最后是说明库类型的扩展名:

  1. *.a表示传统的静态库,又称归档文件(archive),静态库只是目标文件的简单打包,使用ar命令,可以将多个*.o文件打包为一个静态库
  2. *.so表示共享库。共享库的出现是为了解决多任务系统中静态资源浪费资源的问题:当多个程序同时静态链接一个静态库时,会在内存中出现同一函数的多份副本,静态链接的可执行文件体积也较大
共享库

传统的非共享的“静态”库,其代码段会在链接阶段直接加入到目标可执行文件的代码段中,这意味着如果两个可执行程序依赖于同一个静态库,那么静态库的代码将在内存中出现两次,这会造成浪费。而共享库的代码段则独立存放一份在内存中,程序运行时,由操作系统负责绑定调用到真实的共享库函数地址。虽然共享库一般都延迟到其加载后才链接到程序,但是静态链接也是支持的。

共享库在不同的操作系统中实现的方式不同,但是基本上都使用与可执行文件(Executables)一样的格式。这样做一方面仅需要一个加载器;另一方面则可以把可执行文件当做共享库使用(只要其具有符号表, symbol table)。典型的文件格式,在Windows上是PE(Portable Executable);在Linux上是 ELF(Executable and Linkable Format)。

在Linux中,当一个程序使用共享库,其链接方式如下:

  1. 程序本身不再包含函数代码,而是引用运行时可访问的共享代码
  2. 当编译好的程序载入内存并执行时,上述引用被解析,并产生对共享库的调用,如果有必要共享库才加载到内存

共享库具有以下优点:

  1. 内存中可以只保留一份共享库的副本供所有程序使用;磁盘上也只需要保留一份
  2. 共享库可以独立于依赖它的应用程序进行更新。如果程序依赖libm.so,那么系统中可能存在libm的第6个修订版:/lib/libm.so.6,只需要将/lib/libm.so设置为前者的符号链接,即可改变使用的链接库的版本

在Linux中,负责加载动态库,并解析应用程序引用的程序是ld.so(或者其它名称),用于搜索动态库的额外位置可以通过配置文件:/etc/ld.so.conf指定,如果修改了该文件,需要调用ldconfig命令。

交叉编译(Cross Compile)

所谓交叉编译,就是指编译出在其它体系结构(CPU指令集架构)上运行的程序,比如在x86平台上编译出ARM平台的程序,这种编译方式在异平台移植和嵌入式开发时使用的很普遍。相对于交叉编译的叫本地编译。

用来进行交叉编译的编译器称为交叉编译器,为了不和本地编译器混淆,交叉编译器通常具有特定的前缀,来指明它编译的目标体系结构。

使用GCC交叉编译器时,方式与使用本地编译器差不多,但是必须用-L和-I参数指定编译器使用的目标体系结构的库和头文件。

函数调用约定与名称改编规则

函数命名约定、名称改编规则都属于应用程序二进制接口(Application binary interface,ABI)的组成部分。不同的编译器实现具有微妙的区别,另一方面,各编译器对用作API standard (例如 stdcall) 的实现则相当统一。

函数调用约定(Calling Conventions)

在x86架构处理器上进行编程时,存在多种可用的函数调用约定,他们规定了:

  1. 原子(标量)参数,或者复杂参数的每个部分,其被分配(allocated)的顺序
  2. 参数如何传递(压栈,或者存放到寄存器,或者两者的混合)
  3. 被调用函数必须为调用者保留哪些寄存器
  4. 准备栈、清理栈的工作如何在调用者和被调用函数之间进行分配

下表是常见的调用约定

调用约定   说明
stdcall __stdcal
参数传递:从右到左压栈
栈清理:被调用函数在返回前清理栈(不支持可变长参数)
Win32 API均是使用该调用方式
fastcall __fastcall
参数传递:左边开始的两个大小不大于4个字节(DWORD)的参数分别放在ECX和EDX寄存器,其余参数从右到左压栈
栈清理:被调用函数在返回前清理栈
微软的一种调用约定
cdecl __cdecl
参数传递:从右到左压栈
栈清理:调用者负责弹出参数以清理栈(支持可变长参数)
cdecl(C declaration)是多种x86架构下C语言编译器使用的调用约定。整数和内存地址通过EAX寄存器返回,浮点数则通过ST0寄存器返回。调用者保留EAX、ECX、EDX寄存器,其余寄存器由被调用函数保留。在调用一个函数前,浮点寄存器ST0至ST7必须为空(弹出或释放);退出一个函数时ST1至ST7必须为空
函数名称改编(Name Mangling)

函数名称改编对代码中符号名称到连接器使用的符号名称的映射规则进行约定。现代编程语言提供的诸如函数重载的功能,使单纯使用函数的名称无法进行唯一性鉴别,因此必须按照一定的规则进行函数名改写,以便连接(连接器必须知道函数的名称、参数个数、类型等信息)。

由于C++符号会通过DLL、SO文件导出,供其它人使用,因此名称改编不是单个编译器的内部事务了,不同编译器,甚至同一种编译器的不同版本在进行链接时,经常会出现unresolved externals错误。

改编后的名称,存在于obj文件中,链接阶段将从obj中读取这些名称。

Windows下的C名称修饰规则

这些规则用微软发起,其它编译器(例如: Borland、GNU GCC)在Windows下编译代码时也遵守,甚至其它语言(例如: Delphi、Fortran、C#)也遵守。因此不同语言编写的子例程,可以与使用不同调用约定的Windows库函数相互调用。

 调用约定  func(int x)的名称改编结果(32bit编译器)
_cdecl _func
_stdcall   _func@4   后缀的数字是参数列表的字节数
_fastcall  @func@4  后缀的数字是参数列表的字节数,包括传递给寄存器的

注意:Microsoft C的64bit编译器没有前导的下划线,可能导致某些情况下出现unresolved externals错误

C++的名称改编规则

C++编译器是名称改编最主要的使用者,为了与C的兼容性,C++编译后的符号名称必须服从C的标识符规则。

由于C++语言的复杂性(类、模板、名字空间、操作符重载)、缺乏标准,导致C++名称改编非常混乱复杂,很少有连接器能够连接其他编译器的obj代码。

下表显示多种编译器的改编结果

Compiler void h(int) void h(int, char) void h(void)
GCC 3.x and 4.x _Z1hi _Z1hic _Z1hv
GCC 2.9x h__Fi h__Fic h__Fv
Microsoft Visual C++ v6-v10 ?h@@YAXH@Z ?h@@YAXHD@Z ?h@@YAXXZ
Digital Mars C++ ?h@@YAXH@Z ?h@@YAXHD@Z ?h@@YAXXZ
Borland C++ v3.1 @h$qi @h$qizc @h$qv
Tru64 C++ V6.5 (ARM mode) h__Xi h__Xic h__Xv
Tru64 C++ V6.5 (ANSI mode) __7h__Fi __7h__Fic __7h__Fv
在C++中处理C符号的链接
C++
1
2
3
4
5
6
7
#ifdef __cplusplus
extern "C" {
#endif
    /* 在这里的代码不会进行名称改编 */
#ifdef __cplusplus
}
#endif
应用二进制接口

ABI(application binary interface)指两个程序模块之间的接口,通常其中一个是库、操作系统提供的服务,另一个是用户执行的应用程序。ABI定义了及其代码如何访问数据结构和程序,想当低级且依赖于硬件。

ABI涵盖以下方面的细节:

  1. 数据类型的大小、布局和对齐
  2. 调用约定(控制着函数的参数如何传送以及如何接受返回值)—— 通过栈还是寄存器传递,通过栈传递的时候,首先传递第一个还是最后一个参数
  3. 系统调用的编码、应用如何向操作系统进行系统调用
  4. 目标文件的二进制格式

一些ABI标准化了一些细节,例如C++名称改编。

目标文件格式

目标文件是源代码编译后未进行链接的中间文件(.o/.obj),和可执行文件(ELF/PE)的结构和内容相似。

常见的可执行文件格式主要有 Windows 的 PE(Portable Executable)和 Linux 的 ELF(Executable and Linkable Format)。两者都是通用目标文件格式(COFF,Common Object File Format)的变体。在Windows 下目标文件文件(COFF文件)和可执行文件(PE文件)统称为 PE-COFF 文件,Linux 下则统称为 ELF 文件。

COFF 是由System V Release 3 首次提出并使用的格式规范,Microsoft 在其基础上,制定了 PE格式标准。System V Release 4 在 COFF 的基础上引入了ELF 格式,Linux 系统也是以 ELF 作为基本的可执行文件格式。

动态链接库、静态链接库也都按照可执行文件的格式进行存储。

ELF
格式

一个ELF文件由以下部分组成:

  1. ELF头
    1. 定义了使用32bit还是64bit地址
  2. 文件数据(file data),包含:
    1. 程序头表(Program header table):描述一个或多个段(segments)
    2. 分区头表(Section header table):描述一个或多个分区(sections)
  3. 各种段 —— 程序在执行时所必须的信息
    1. .text 程序需要执行的指令
    2. .data 固定到程序镜像中的初始化数据
    3. .rodata 只读的数据
    4. .bss 固定到程序镜像中的未初始化数据。系统在程序运行时将这些数据初始化为0
    5. .rel.text、 .rel.data、 .rel.rodata 文本、数据段的重定向(relocation)信息
    6. .symtab 符号表
    7. .strtab 字符串
    8. .init 进程初始化代码的指令
    9. .fini 进程终止代码的指令
    10. .debug 符号化的调试信息
    11. .line 符号化调试信息的行号信息,描述程序源代码和机器码之间的对应关系
    12. .comment 存储其它额外信息
  4. 各种分区 ——  链接和重定位所必须的信息
分类

Linux下的ELF文件分为以下几个子类:

类型 说明
可重定位文件/Relocatable File

如目标文件与静态链接库。这种文件包含代码与数据,可以用来连接成可执行文件或共享目标文件

这种文件是连接器的输入,所谓可重定位,是因为这种文件中的函数以及其它符号,仍然存储的是名字,而非地址

共享目标文件/Shared Object File

代码和数据,主要有两种用途

  1. 与目标文件或其它共享目标文件链接成新的共享目标文件
  2. 与可执行文件结合,作为进程映像的一部分来运行
可执行文件/Executable File 可直接执行的程序
核心转储文件/Core Dump File 当进程意外终止时,系统可以将该进程的地址空间的内容及终止时的一些其他信息转储到核心转储文件
Win32编程相关知识
常见预定义宏
宏 说明 
__DATE__ 当前源代码编译的日期,格式 Mmm dd yyyy 
__TIME__ 当前源文件的编译时间,格式 hh:mm:ss 
__TIMESTAMP__ 当前源文件的编译时间戳
__FILE__ 当前源文件的名称
__LINE__ 当前宏所在行号
_CHAR_UNSIGNED 默认char是否无符号
__cplusplus 用于定义一段专属于C++的内容
_CPPRTTI 启用C++运行时类型识别
_CPPUNWIND 启用C++异常处理
_DEBUG 表示当前使用调试的C运行时库,或者创建调试版本的DLL
_DLL 表示与DLL版本的C运行时库链接
__FUNCTION__ 仅在函数内使用,函数名称
__FUNCDNAME__ 仅在函数内使用,改变后的函数名称
_FUNCSIG__ 仅在函数内使用,函数签名
_INTEGRAL_MAX_BITS 整数最大位数
_WIN32 对于Win32/Win64应用,总是定义
_WIN64 对于Win64程序,定义
_UNICODE

控制C运行时库/MFC头文件中和字符集有关宏的处理,如果定义了该宏:

  1. _tcslen 指向 wcslen 而不是 strlen 
  2. _TEXT 指向 L"..." 而不是 "..." 
  3. ……
UNICODE

控制Windows头文件中和字符集有关的宏的指向,如果定义了该宏:

  1. GetWindowText 指向 GetWindowTextW 而不是 GetWindowTextA 
  2. TEXT 指向 L"..." 而不是 "..." 
  3. ……
← 日志组件Log4cplus的使用
我的热带鱼 →

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">

Related Posts

  • GNU Make学习笔记
  • Visual Studio知识集锦
  • MinGW知识集锦
  • AutoTools学习笔记
  • Bazel学习笔记

Recent Posts

  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
  • A Comprehensive Study of Kotlin for Java Developers
  • 背诵营笔记
  • 利用LangChain和语言模型交互
  • 享学营笔记
ABOUT ME

汪震 | Alex Wong

江苏淮安人,现居北京。目前供职于腾讯云,专注容器方向。

GitHub:gmemcc

Git:git.gmem.cc

Email:gmemjunk@gmem.cc@me.com

ABOUT GMEM

绿色记忆是我的个人网站,域名gmem.cc中G是Green的简写,MEM是Memory的简写,CC则是我的小天使彩彩名字的简写。

我在这里记录自己的工作与生活,同时和大家分享一些编程方面的知识。

GMEM HISTORY
v2.00:微风
v1.03:单车旅行
v1.02:夏日版
v1.01:未完成
v0.10:彩虹天堂
v0.01:阳光海岸
MIRROR INFO
Meta
  • Log in
  • Entries RSS
  • Comments RSS
  • WordPress.org
Recent Posts
  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
    In this blog post, I will walk ...
  • A Comprehensive Study of Kotlin for Java Developers
    Introduction Purpose of the Study Understanding the Mo ...
  • 背诵营笔记
    Day 1 Find Your Greatness 原文 Greatness. It’s just ...
  • 利用LangChain和语言模型交互
    LangChain是什么 从名字上可以看出来,LangChain可以用来构建自然语言处理能力的链条。它是一个库 ...
  • 享学营笔记
    Unit 1 At home Lesson 1 In the ...
  • K8S集群跨云迁移
    要将K8S集群从一个云服务商迁移到另外一个,需要解决以下问题: 各种K8S资源的迁移 工作负载所挂载的数 ...
  • Terraform快速参考
    简介 Terraform用于实现基础设施即代码(infrastructure as code)—— 通过代码( ...
  • 草缸2021
    经过四个多月的努力,我的小小荷兰景到达极致了状态。

  • 编写Kubernetes风格的APIServer
    背景 前段时间接到一个需求做一个工具,工具将在K8S中运行。需求很适合用控制器模式实现,很自然的就基于kube ...
  • 记录一次KeyDB缓慢的定位过程
    环境说明 运行环境 这个问题出现在一套搭建在虚拟机上的Kubernetes 1.18集群上。集群有三个节点: ...
  • eBPF学习笔记
    简介 BPF,即Berkeley Packet Filter,是一个古老的网络封包过滤机制。它允许从用户空间注 ...
  • IPVS模式下ClusterIP泄露宿主机端口的问题
    问题 在一个启用了IPVS模式kube-proxy的K8S集群中,运行着一个Docker Registry服务 ...
  • 念爷爷
      今天是爷爷的头七,十二月七日、阴历十月廿三中午,老人家与世长辞。   九月初,回家看望刚动完手术的爸爸,发

  • 6 杨梅坑

  • liuhuashan
    深圳人才公园的网红景点 —— 流花山

  • 1 2020年10月拈花湾

  • 内核缺陷触发的NodePort服务63秒延迟问题
    现象 我们有一个新创建的TKE 1.3.0集群,使用基于Galaxy + Flannel(VXLAN模式)的容 ...
  • Galaxy学习笔记
    简介 Galaxy是TKEStack的一个网络组件,支持为TKE集群提供Overlay/Underlay容器网 ...
TOPLINKS
  • Zitahli's blue 91 people like this
  • 梦中的婚礼 64 people like this
  • 汪静好 61 people like this
  • 那年我一岁 36 people like this
  • 为了爱 28 people like this
  • 小绿彩 26 people like this
  • 彩虹姐姐的笑脸 24 people like this
  • 杨梅坑 6 people like this
  • 亚龙湾之旅 1 people like this
  • 汪昌博 people like this
  • 2013年11月香山 10 people like this
  • 2013年7月秦皇岛 6 people like this
  • 2013年6月蓟县盘山 5 people like this
  • 2013年2月梅花山 2 people like this
  • 2013年淮阴自贡迎春灯会 3 people like this
  • 2012年镇江金山游 1 people like this
  • 2012年徽杭古道 9 people like this
  • 2011年清明节后扬州行 1 people like this
  • 2008年十一云龙公园 5 people like this
  • 2008年之秋忆 7 people like this
  • 老照片 13 people like this
  • 火一样的六月 16 people like this
  • 发黄的相片 3 people like this
  • Cesium学习笔记 90 people like this
  • IntelliJ IDEA知识集锦 59 people like this
  • Bazel学习笔记 38 people like this
  • 基于Kurento搭建WebRTC服务器 38 people like this
  • PhoneGap学习笔记 32 people like this
  • NaCl学习笔记 32 people like this
  • 使用Oracle Java Mission Control监控JVM运行状态 29 people like this
  • Ceph学习笔记 27 people like this
  • 基于Calico的CNI 27 people like this
Tag Cloud
ActiveMQ AspectJ CDT Ceph Chrome CNI Command Cordova Coroutine CXF Cygwin DNS Docker eBPF Eclipse ExtJS F7 FAQ Groovy Hibernate HTTP IntelliJ IO编程 IPVS JacksonJSON JMS JSON JVM K8S kernel LB libvirt Linux知识 Linux编程 LOG Maven MinGW Mock Monitoring Multimedia MVC MySQL netfs Netty Nginx NIO Node.js NoSQL Oracle PDT PHP Redis RPC Scheduler ServiceMesh SNMP Spring SSL svn Tomcat TSDB Ubuntu WebGL WebRTC WebService WebSocket wxWidgets XDebug XML XPath XRM ZooKeeper 亚龙湾 单元测试 学习笔记 实时处理 并发编程 彩姐 性能剖析 性能调优 文本处理 新特性 架构模式 系统编程 网络编程 视频监控 设计模式 远程调试 配置文件 齐塔莉
Recent Comments
  • qg on Istio中的透明代理问题
  • heao on 基于本地gRPC的Go插件系统
  • 黄豆豆 on Ginkgo学习笔记
  • cloud on OpenStack学习笔记
  • 5dragoncon on Cilium学习笔记
  • Archeb on 重温iptables
  • C/C++编程:WebSocketpp(Linux + Clion + boostAsio) – 源码巴士 on 基于C/C++的WebSocket库
  • jerbin on eBPF学习笔记
  • point on Istio中的透明代理问题
  • G on Istio中的透明代理问题
  • 绿色记忆:Go语言单元测试和仿冒 on Ginkgo学习笔记
  • point on Istio中的透明代理问题
  • 【Maven】maven插件开发实战 – IT汇 on Maven插件开发
  • chenlx on eBPF学习笔记
  • Alex on eBPF学习笔记
  • CFC4N on eBPF学习笔记
  • 李运田 on 念爷爷
  • yongman on 记录一次KeyDB缓慢的定位过程
  • Alex on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • haolipeng on 基于本地gRPC的Go插件系统
  • 吴杰 on 基于C/C++的WebSocket库
©2005-2025 Gmem.cc | Powered by WordPress | 京ICP备18007345号-2