《程序员的自我修养》的原文摘录

  • 可以使用volatile关键字来试图阻止过度优化,volatile基本可以做到两件事情: 1、阻止编译器为了提高速度将一个变量缓存到寄存器内而不写回。 2、阻止编译器调整操作volatile变量的指令顺序。 (查看原文)
    ziyoudefeng 2回复 3赞 2012-12-22 14:21:19
    —— 引自第29页
  • 实际上在动态链接器的自举代码中,除了不可以使用全局变量和静态变量之外,甚至不能调用函数,即动态链接器本身的函数也不能调用。这是为什么呢?其实我们在前面分析地址无关代码时已经提到过,实际上使用PIC模式编译的共享对象,对于模块内部的函数调用也是采用外部函数调用一样的方式,即使用GOT/PLT的方式,所以在GOT/PLT没有被重定位之前,自举代码不可以使用任何全局变量,也不可以调用函数。 (查看原文)
    prife 6回复 3赞 2013-02-05 00:26:53
    —— 引自第214页
  • 这样看起来第一个模块内部调用或跳转很容易解决,但实际上这种方式还有一定的问题,这里存在一个名作共享对象“全局符号介入”问题。 (查看原文)
    prife 6回复 3赞 2013-02-05 00:26:53
    —— 引自第214页
  • 所以对于bar()函数的调用,编译器只能选择第三种,即当作模块的外部符号处理。 ... 即使用static关键字定义bar函数,这种情况下,编译器要确定bar()函数不会其他模块覆盖,就可以使用第一类的方法,即模块内部调用指令。 (查看原文)
    prife 6回复 3赞 2013-02-05 00:26:53
    —— 引自第214页
  • 由于可能存在的全局符号介入的问题,foo函数对于bar的调用不能够采用第一类模块调用的方法,因为一旦bar函数由于全局符号介入被其他模块中的同名函数覆盖,那么foo如果采用相对地址调用的话,那么这个相对地址就需要重定位,这由于共享对象的地址无关性矛盾。所以对于bar()函数的调用,编译器只能选择第三种,即当作模块外部符号处理。 (查看原文)
    prife 6回复 3赞 2013-02-05 00:26:53
    —— 引自第214页
  • 程序源代码被编译以后主要分成两种段:程序指令和程序数据。代码段属于程序指令,而数据段和.bss段属于程序数据。 (查看原文)
    ziyoudefeng 2赞 2012-12-05 21:31:54
    —— 引自第59页
  • 1:当程序被装载后,数据和指令分别被映射到两个虚存区域。由于数据区域对于进程来说是可读写的,而指令区域对于进程来说是只读的,所以这两个虚存区域的权限可以被分别设置成可读写和只读。这样可以防止程序的指令被有意或无意地改写。 2:对于现代的CPU来说,它们有着极为强大的缓存体系。由于缓存在现代的计算机中地位非常重要,所以程序必须尽量提高缓存的命中率。指令区和数据区的分离有利于提高程序的局部性。现代CPU的缓存一般都被设计成数据缓存和指令缓存分离,所以程序的指令和数据被分开存放对CPU的缓存命中率提高有好处。 3:最重要的原因。 当系统中运行着多个该程序的副本时,它们的指令都是一样的,内存中只需保存一份该程序的指令部分。 (查看原文)
    ziyoudefeng 2赞 2012-12-05 21:31:54
    —— 引自第59页
  • 其中: 第一列是VMA的地址范围 第二列是VMA的权限,r代表可读,w代表可写,x代表可执行,p代表私有,s代表共享 第三列是偏移,表示VMA对应的Segment在映像文件中的偏移 第四列是映像文件所在设备的主设备号和次设备号 第五列是映像文件的节点号 最后一列是映像文件的路径 (查看原文)
    ziyoudefeng 1赞 2012-12-09 21:24:56
    —— 引自第166页
  • 既然每个编译器都能将源代码编译成目标文件,那么有没有不同编译器编译出来的目标文件是不能够相互链接的呢?有没有可能将MSCV编译出来的目标文件和GCC编译出来的目标文件连接到一起,形成一个可执行文件呢? (查看原文)
    knightley 1赞 2018-03-25 08:40:21
    —— 引自第115页
  • 对于上面这些问题,首先我们想到的是,如果要将两个不同编译器的编译结果链接到一起,那么,首先连接器必须支持这两个编译器产生的目标文件的格式。比如MSVC编译器的目标文件是COFF/PE格式的,而GCC编译的结果是ELF格式的,那么连接器必须同时认识这两种格式才行,否则肯定没戏。那是不是连接器只要同时认识目标文件的格式就可以了呢? 事实并不是我们想象的这么简单,如果要将两个编译器编译出来的目标文件能够相互链接,那么这两个目标文件必须满足下面这些条件:采用同样的目标文件格式、拥有同样的符号修饰标准、变量的内存分布方式相同、函数的调用方式相同,等等。其中,我们把符号修饰标准,变量内存布局、函数调用方式等这些跟可执行代码二进制兼容相关的内容称为ABI(Application Binary Interface)。 (查看原文)
    knightley 1赞 2018-03-25 08:40:21
    —— 引自第115页
  • ABI&API 很多时候我们会碰到API(Application Programming Interface)这个概念,它与ABI只有一字之差,而且非常类似,很多人经常将它们的概念混淆。那么它们之间有什么区别呢?实际上它们都是所谓的应用程序接口,只是它们所描述的接口所在层面不一样。API往往指源代码级别的接口,比如我们可以说POSIX是一个API标准、Windows所规定的的应用程序接口是一个API;而ABI是二进制层面的接口,ABI的兼容程度要比API的更为严格,比如我们可以说C++的对象内存分布(Object Memory Layout)是C++ ABI的一部分。API更关注源代码层面的,比如POSIX规定printf()这个函数的原型,它能保证这个函数定义在所有遵循POSIX标准的系统之间都是一样的,但是它不保证printf在实际的每个系统中执行时,是否按照从右到左将参数压入堆栈,参数在堆栈中如何分布等这些实际运行时的二进制级别的问题。比如有两台机器,一台是Intel X86,另一台是MIPS的,他们都安装了Linux系统,由于Linux支持POSIX标准,所以他们的C运行库都应该有printf函数。但实际上printf在被调用过程中,这些关于参数和堆栈分布的细节在不同机器上肯定是不一样的,甚至调用printf的指令也是不一样的(x86是call指令,MIPS是jal指令),这就是说,API相同并不表示ABI相同。 ABI的概念其实从开始至今一直存在,因为人们总是希望程序能够在不经任何修改的情况下得到重用,最好的情况是二进制的指令和数据能够不加修改地得到重用。人们始终在朝这个方向努力,但是由于现实的因素,二进制级别的重用还是很难实现。最大的问题之一就是各种硬件平台、编程语言、编译器、链接器和操作系统之间的ABI互相不兼容,由于ABI的不兼容,各个目标文件之间无法相互链... (查看原文)
    knightley 1赞 2018-03-25 08:40:21
    —— 引自第115页
  • C++为了与C兼容,在符号的管理上,C++有一个用来声明或定义一个C的符号的"extern "C""关键字的用法: extern "C" { int func(int); int var; } C++编译器会将在extern "C"的大括号内部的代码当作C语言代码处理。所以很明显,上面的代码中,C++的名称修饰机制将不会起作用。它声明了一个C的函数func,定义了一个整型全局变量var。从上文中我们得知,在Visual C++平台下会将C语言的符号进行修饰,所以上述代码中的func和var的修饰后符号分别是_func和_var;但是在linux版本的GCC编译器下却没有这种修饰,extern "C"里面的符号都为修饰后符号,即前面不用加下划线。如果单独声明某个函数或变量为C语言的符号,那么也可以使用如何格式: extern "C" int func(int); extern "C" int var; 上面的代码声明了一个C语言的函数func和变量var。我们可以使用上述的机制来做一个小实验。。。。。。 很多时候我们都会碰到有些头文件声明了一些C语言的函数和全局变量,但是这个头文件可能会被C语言代码或C++代码包含。比如很常见的,我们的C语言库函数中的string.h中声明了memset这个函数,它的原型如下: void *memset(void*, int, size_t); 如果不加任何处理,当我们的C语言程序包含string.h的时候,并且用到了memset这个函数,编译器会将memset符号引用正确处理;但是在C++语言中,编译器会认为这个memset函数是一个C++函数,将memset的符号修饰成_Z6memsetPvii,这样连接器就无法与C语言库中的memset符号进行链接。所以对于C++来说,必须使用extern "C... (查看原文)
    knightley 1赞 2018-03-25 10:42:07
    —— 引自第90页
  • 第3章目标文件里有什么 另外一方面是对于现代的CPU来说,它们有着极为强大的缓存( Cache)体系。由于缓存在现代的计算机中地位非常重要,所以程序必须尽量提高缓存的命中率。指令区和数据区的分离有利于提高程序的局部性。现代CPU的缓存一般都被设计成数据缓存和指令缓存分离,所以程序的指令和数据被分开存放对CPU的缓存命中率提高有好处。 第三个原因,其实也是最重要的原因,就是当系统虫运行着多个该程序的副本时,它们的指令都是一样的,所以内存中只须要保存一份该程序的指令部分。对于指令这种只读的区域来说是这样,对于其他的只读数据也一样,比如很多程序里面带有的图标、图片、文本等资源也是属于可以共享的。当然每个副本进程的数据区域是不一样的,它们是进程私有的。不要小看这个共享指令的概念,它在现代的操作系统里面占据了极为重要的地位,特别是在有动态链接的系统中,可以节省大量的内存。比如我们常用的 Windows Internet Explorer7。0运行起来以后,它的总虚存空间为112844KB,它的私有部分数据为15944KB,即有96900KB的空间是共享部分(数据来源见图3-2)如果系统中运行了数百个进程,可以想象共享的方法将节省大量空间。关于内存共享的更为深入的内容我们将在装载这一章探讨 (查看原文)
    1赞 2020-04-06 00:36:29
    —— 引自章节:3.2 目标文件是什么样的
  • 其次,经典的helloworld由于使用了库,所以必须有main函数。 (查看原文)
    夏夜寂寞属壁虎 1赞 2021-02-22 23:53:22
    —— 引自章节:4.6 链接过程控制
  • 计算机科学领域内的所有问题都可以通过增加一个间接的中间层解决。 (查看原文)
    [已注销] 2011-03-11 12:58:23
    —— 引自第8页
  • 1,地址空间不隔离 2,内存使用效率低。 3,程序运行的地址不确定。(重定位问题) (查看原文)
    [已注销] 2011-03-11 13:09:42
    —— 引自第14页
  • 不使用任何(局部)静态或全局的非const变量 (查看原文)
    容貌焦虑主理人 2011-09-24 15:46:46
    —— 引自第28页
  • 原因在于早在几十年前, CPU就发展除了动态调度 (查看原文)
    容貌焦虑主理人 2011-09-24 15:46:46
    —— 引自第28页
  • t1=6. t1替换成数字6 (查看原文)
    容貌焦虑主理人 2011-09-25 14:10:15
    —— 引自第46页
  • 按照需要 (查看原文)
    容貌焦虑主理人 2011-09-25 15:14:13
    —— 引自第50页
<前页 1 2 3 4 5 6 后页>