c语言程序经过编译后，每条指令都有一个内存地址，那两个程序如果有相同内存地址的指令怎么办?
很意外好像没有人答对（在写本回答的时候好像只有一个人回答沾边），大部分回答都在谈内存地址的虚拟化，那么在CPU有保护模式（内存地址的虚拟化）之前，难道就只能同时加载一个程序？ （编辑：说虚拟内存的错在逻辑上本末倒置。就好像吊葡萄糖可以补充能量，吊的时候不吃饭也行，然而说是吊葡萄糖解决了吃饭问题这就是逻辑上的本末倒置。） 欢迎来到凯叔讲故事。 其实这个问题的关键和解决的方法并不是内存虚拟地址，虽然内存虚拟地址的确貌似可以解决这个问题（其实并不能，最后有提到），但是历史上这个问题的出现和解决远早于保护模式（内存虚拟地址）的出现。 这个问题的秘密在于EXE/ELF等可执行文件格式上。这些可执行文件格式的发明和制定，就是为了解决这个问题。 在很久很久以前（其实也就是30多年前），计算机程序的确是只能加载到内存当中的固定位置的。比如，BOOTLOADER、以及DOS启动时所需的那3个著名文件（这三个模块是输入输出模块(IO.SYS）、文件管理模块（MSDOS.SYS）及命令解释模块）就是这样。 然后，就有了题主所担心的问题，很不方便。 于是，就有了. COM的格式（可能还不是最早的，只是比较著名的）。. COM格式允许将程序放在任意一个内存分段当中，但是程序在段当中的偏移地址是固定的，也不能有另外独立的数据段。整个程序的大小（含数据）不能超过64K，因为这是一个内存段的大小。 内存需要分段，以及一个段是64K的原因是，那个时候的CPU本身是16bit的（e，忽略更老的8bit CPU不谈），就是所有寄存器都是16bit的，但是地址总线是20bit的。这样的话，就需要将一个被称为段寄存器的内容左移4bit然后加上另外一个寄存器（称为偏移寄存器）的内容，才能得到20bit的内存地址。16bit正好64K，也就是在不修改段寄存器的情况下，能够直接寻址的范围就是64K。超过这个范围就需要修改段寄存器。这在以前也叫做长跳转。 但是后来随着程序越来越大，64K已经放不下了。首先想到的是将程序当中的数据部分分离出去放在单独的段当中，这就是数据段。然后程序本身也可能需要跨多个段。 但是这样还是不是很方便。由于数据段分离出去了，有的程序段可能很小。但是如果所有程序都是从一个固定的偏移地址开始，那么这些程序至少不能放在同一个段内。这就比较浪费内存，特别是在没有保护模式（虚拟地址）的年代，每个段可就是实打实的64K物理内存。那个时候，总共物理内存大部分也就256K，1M已经算高端机了（1M是20bit地址总线的寻址上限），所以并没有那么多段可用。 所以就有了浮动二进制可执行方式，这就是EXE或者ELF等。这种可执行文件当中的地址只是对于程序当中某个位置（比如main函数开始位置）的一个偏移量，操作系统可以将其加载到内存任何位置，然后根据实际main函数入口所处地址和这些偏移量，去更新机器码当中所有的地址参照，将其改为实际地址。也就是，在程序实际被执行之前，操作系统会对其进行一次修改/更新。 而题主用objdump所看到的“地址”，其实就是这个相对于程序自身的偏移地址，并不是内存地址，即便是保护模式下，也不是按照这个地址执行的。当然，更加准确地来说，还要看被objdump的是什么文件。如果是. obj/.a文件，也就是链接之前的文件，那么这个偏移只是对于. obj/.a自身入口的偏移，在C语言链接的这一步，linker会将这些. obj/.a组织到可执行文件当中，并且更新这些偏移。（然后在可执行文件被加载到内存之后，操作系统还会再次更新。是的，这个过程和linker很像） 后来，随着32bit/64bit CPU的出现，CPU寻址范围大大增加，才出现所谓的flat模式内存空间，也就是不分段（整个内存空间只有一段）。但是即便这样，在使用动态库等的时候，依然存在动态库加载到哪里的问题，因为不同的宿主程序长度不同，动态库加载数量和顺序不同，即便有虚拟地址空间，同一个动态库在不同程序的虚拟地址空间当中也不可能永远都加载到同一个位置。 此外还有安全方面的考虑。如果程序每次都加载到同样的地方，那么程序当中的每个变量也很可能每次都出现在同样的位置，尤其是全局变量。这使得程序非常容易被监视及hack。所以每次让操作系统将其加载到不同位置，可以提高一点儿安全性。 总之，这个问题其实与虚拟地址空间并没有什么关系。虽然虚拟地址空间的确可以避免程序之间的内存访问冲突，但是历史上并不是用虚拟地址空间解决的这个问题，而且它解决不了动态库的加载问题。