第11章 运行库

11.1入口函数和程序初始化

11.1.1程序从main开始执行吗

答案是否定的,具体请看P343~P344。

程序首先运行的代码不是main而是入口函数或者入口点,它是程序的初始化和结束部分,它往往是运行库的一部分。

一个程序的典型执行步骤见P344。

11.1.2入口函数是如何实现的

glibc默认的入口点是_start,它是由连接器脚本指定的,当然这个入口点你也可以自定义,不过一般没人自定义。

在_start中的实现代码中的前三行,主要实现了如下功能:

1、将EBP寄存器清0以表示_start是最外层的函数。

2、把用户的参数和环境变量压栈。一般栈顶是argc,接着是argv,然后是环境变量。

3、把ESI指向argc,把ECX指向argv和环境变量。

接着执行的是_libc_start_main,它的参数里面就包括了main、init、fini、rtld_fini。

init:main前的初始化工作。

fini:main后的收尾工作。

rtld_fini:动态加载的收尾工作。

MSVC的函数入口名为mainCRTStartup,P349~P350的部分显示了该函数代码的内部实现。

在mainCRTStartup中给程序分配内存用的不是malloc而是alloc,这是因为程序在开始时堆还没有初始化,而alloc是唯一不需要堆的动态分配机制。

mainCRTStartup首先分配空间,然后初始化堆,接着各种初始化包括对main的argv、argc和其他C库。

try-except是Windows结构化异常处理机制的一部分。

P351有mainCRTStartup的内部执行步骤总结。

11.1.3运行库与I/O

指代一个文件的标识,在Linux下是文件描述符,在Windows下是文件句柄,一般情况下简称为句柄。

句柄存在的意义在于防止用户随便操纵系统内核的文件对象,因为用户是通过句柄才能间接接触到的。

在Linux下每个进程都有一个打开文件表,其实质是一个指针数组,文件描述符就是这个表的下标。

C语言中的FILE结构与文件句柄是一一对应的关系。

11.1.4

MSVC CRT的入口函数初始化

主要包括堆初始化和I/O初始化两部分。

在MSVC中堆的初始化工作是由_heap_init完成的,在32位编译环境下,MSVC仅仅是调用HeapAlloc创建了一个堆,可以推出malloc必然是调用了HeapAlloc函数。

句柄是一个32位的数据类型,在MSVC

CRT中已经打开的文件句柄是用ioinfo结构来表示的。

用户的代开文件表是一个二维数组,第二维存储的就是ioinfo结构。

从FILE到文件句柄是通过_osfhnd的宏实现的,

MSVC的I/O初始化函数叫_ioinit,它的工作是初始化一些预定义的打开文件。包括两部分:1、从父进程进程而来的打开文件句柄。2、操作系统提供的标准输入输出。

MSVC中I/O初始化的步骤见P360。

11.2

C/C++运行库

11.2.1

C语言运行库

就是C程序运行所需要的代码集合。

C语言运行库英文缩写为CRT(C Run-Time Library)。

C语言运行库的功能可见P360~P361。

11.2.2

C语言标准库

这里介绍了变长参数的用法,它的实现依赖于cdecl调用惯例的自右向左压栈的参数传递方式,其实就是利用栈的性质实现的。

va_list是一个指针用来指向某参数。

va_start指向第一个参数的位置。

va_arg获取当前参数的值,并自动移动到下一个参数的位置。

va_end清零指针。

宏也可以是边长的,这种宏是变长参数宏,在gcc中用##表示这种宏,在MSVC中用__VA_ARGS__。

非局部跳转是C语言里面很少用到的语法,最起码我不怎么用。首先在你想跳转到的地方留个记号,就是setjmp(标记)。然后,在你想跳转的地方写上一条跳转语句longjmp(标记,返回值),那么它就会跳转到setjmp所在的地方并且在那个地方留下longjmp中的返回值。

11.2.3

glibc和MSVC CRT

C语言运行库就是个中间层。

Linux下的C语言运行库是glibc,而Windows下的则是MSVCCRT。

C++语言的实现与编译器紧密相关。

MSVC的CRT的版本多了去了,可按照静态和动态、单线程和多线程、调试和发行、托管代码和本地代码分成多个不同的版本。

这些版本之间的组合会出现很多子版本,为此微软推出了一套命名规则。比如:p代表C Plus

Plus——C++标准库,mt代表Multi-Thread,d代表debug。

在MSVC下静态链接就涉及到一个lib,而在动态链接环境下还涉及到lib和dll。

11.3运行库与多线程

11.3.1

CRT的多线程困扰

线程可以访问自己进程里面所有的公共资源,也可以访问进程里面其他线程的资源,不过线程也有自己的私有空间,这包括栈、线程局部存储和寄存器。

多线程并不是C/C++标准库的组成部分。

所谓的多线程库都是系统相关的,这包括提供了线程操作接口和能在多线程环境下运行两方面。

原来C/C++存在的问题就是原来单线程环境下,公共资源没人抢,自给自足,运行的非常顺畅。到了多线程阶段,同一个公共资源被多个线程共同使用,某线程自己的数据在无意中被别的县城给改了,这种情况在各线程身上都能发生,结果哪个线程都运行不好,这就是困扰所在。

11.3.2

CRT改进

1、原先的全局变量改为各线程私有,每线程一个。

2、加锁,这个不解释。

3、改进函数调用方式,把所有多线程不安全的函数改成多线程安全的函数。

11.3.3线程局部存储实现

TLS(Thread Local Storage),线程局部存储,即线程私有的局部变量,每个线程都有一个该变量的副本,这样线程之间的互操作就不会彼此影响了。

在Windows下TLS变量会被存储在PE文件中的tls段中,每个线程的副本都是从进程的堆中把tls段中的内容复制到本线程中的。如果tls中的内容是对象,该过程还需要进行构造和析构操作。

TEB(Thread Environment Block,线程环境块)记录了每个线程的堆栈地址,又因为TLS相对于每个线程的偏移量是固定的,所以可按此法找到相应的TLS。

上面提到的是隐式TLS,即自动的TLS,还有一种,是显式TLS,即程序员手动TLS方式。

显式TLS,即TLS变量的申请、初始化和释放都需要手动操作。它是借助TEB结构中的TLS数组实现的。

WindowsAPI中的线程创建和释放函数是最基本的,其他诸如CRT和MFC线程创建和释放函数都是WindowsAPI的封装。如果你是用WindowsAPI来创建和销毁线程那就不要用运行库中的,反之亦然,不然会出现莫名其妙的错误。

11.4

C++全局构造和析构

这一工作是由入口函数实现的。

11.4.1

glibc全局构造和析构

init和finit会生成init()和finit()函数,它们分别在main前后执行。

_start中的init函数指向了__libc_csu_init函数,__libc_csu_init在glibc中的csu\Elf-init.c。

__libc_csu_init调用的是init,即用户所有存放在init中的段都会被执行,init段是由所有输入的目标文件中的init段拼接而成的。

init调用了一个__do_global_ctors_aux的函数,它是由编译器提供的,负责C++中全局对象的构造和语言特性相关的内容。

__do_global_ctors_aux函数里面有个__CTOR_LIST__数组,该数组第一个元素是元素的个数,后面每个元素存储的是函数指针,这些函数指针就是所有全局对象的构造函数的指针。

gcc会遍历每个cpp,然后针对每个编译单元生成一个特殊的构造函数来初始化所有的全局对象,该函数叫__GLOBAL_I_Hw,负责对本编译单元的所有全局和静态对象进行构造和析构。

在编译单元产生的目标文件中有一个ctors段,该段中存放的是指向__GLOBAL_I_Hw的指针,那么所有目标文件组织在一起就会形成一个ctors段,该段中存储着一个__GLOBAL_I_Hw的指针数组,

每个目标文件的前后还要连接上crtbegin.o文件和crtend.o文件。在crtbegin.o中的ctors段中存储的是-1,链接器负责将这个数值改造成为全局变量的个数,然后再将ctors段定义成__CTOR_LIST__,所以实际上__CTOR_LIST__就是所有ctors段合并后的起始地址了。

crtend.o中的ctors段中存储的是0,它定义了一个符号__CTOR_END__指向ctors段的末尾。

gcc可以指定某函数为全局构造函数,方法就是通过在函数声明前添加__attribute__((constructor))。

全局对象当然也有析构的过程,这与构造过程是互逆的。

11.4.2

MSVC的全局构造和析构

与gcc类似,首先mainCRTStartup调用_initterm,_initterm的功能类似于__do_global_ctors_aux。__xc_a和__xc_z是_initterm的参数,前者是函数指针数组的其实笛子,后者是结束地址。

全局变量__xc_a和__xc_z在mainCRTStartup之前就被设置好了,这是因为它俩本来就在两个特殊的段里面,最后合并段的的时候被划分到了只读段之中。

各个操作系统在全局构造和析构这一点上的实现机制是相同的。

至于析构,MSVC也是通过把某函数注册为程序退出时的全局析构函数的方式实现的。

11.5

fread的实现

它归根结底是调用WindowsAPI中的ReadFile来实现文件读取的。

11.5.1缓冲

因为一次写入读出的数据量很小,为此调用系统的API显得较为低效。设置一块缓冲区域,等这块地方被填满再一次性的写入或者读出,即所谓的缓冲。

11.5.2

fread_s

fread的功能是直接交给fread_s来完成的。

fread_s的主要的功能是检查参数的正确性,保证多线程安全的加锁,然后它直接调用了_fread_nolock_s。

11.5.3

_fread_nolock_s

它完成了fread的真正功能,本节讲述的主要是它对缓冲区的各种操作和设置。

_fread_nolock_s调用了_read函数,该函数完成从文件中读取数据和转换文本文件中的回车符两项功能。

11.5.4

_read

_read调用了WindowsAPI ReadFile。

11.5.5文本换行

在Windows下回车的存储方式是0x0D(CR)和0x0A(LF)两个字节。但是在Linux/Unix下回车和换行都是\n,在MAC OS下是\r。

在不同的操作系统中都把回车和换行转换为\n。

11.5.6

fread回顾

本节对fread做了一个总结,还是满精辟的,前边讲的都太杂了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容