16位C编译器是如何工作的?

dsi*_*cha 14 c legacy x86 assembly 16-bit

C的内存模型,使用指针算法和所有,似乎模拟平面地址空间.16位计算机使用分段存储器访问.16位C编译器如何处理这个问题并从C程序员的角度模拟一个扁平的地址空间?例如,下面的代码在8086上编译成大致的汇编语言指令?

long arr[65536];  // Assume 32 bit longs.
long i;
for(i = 0; i < 65536; i++) {
    arr[i] = i;
}
Run Code Online (Sandbox Code Playgroud)

dan*_*n04 13

16位C编译器如何处理这个问题并从C程序员的角度模拟一个扁平的地址空间?

他们没有.相反,他们提出分割可见的C程序员,通过具有多类型的指针扩展语言:near,far,和huge.甲near指针是独偏移,而farhuge指针是一个组合的分段和偏移量.有一个编译器选项来设置内存模型,它确定默认指针类型是近还是远.

在Windows代码中,即使在今天,您也经常会看到类似LPCSTR(for const char*)的typedef ."LP"是16位日的延续; 它代表"Long(far)Pointer".


wal*_*lyk 9

真正的16位环境使用16位指针到达任何地址.示例包括PDP-11,6800系列(6802,6809,68HC11)和8085.这是一个干净,高效的环境,就像一个简单的32位架构.

80x86系列迫使我们在所谓的"实模式" - 原生8086寻址空间中使用混合16位/ 20位地址空间.解决这个问题的常用机制是将指针类型增强为两种基本类型near(16位指针)和far(32位指针).:代码和数据指针的默认可在散装通过"内存模型"进行设置 tiny,small,compact,medium,far,和huge(一些编译器不支持所有型号).

tiny存储器模型是小程序在其整个空间(代码+数据+栈)小于64K是有用的.所有指针(默认情况下)为16位或near; 指针与整个程序的段值隐式关联.

small模型假设数据+堆栈小于64K并且在同一段中; 代码段仅包含代码,因此最多可以包含64K,最大内存占用量为128K.代码指针near与CS(代码段)隐式关联.数据指针也near与DS(数据段)相关联.

medium模型具有高达64K的数据+堆栈(如小),但可以有任意数量的代码.数据指针是16位,并且隐含地绑定到数据段.代码指针是32位far指针,并且具有段值,具体取决于链接器如何设置代码组(令人讨厌的簿记麻烦).

compact模型是媒介的补充:少于64K的代码,但任何数量的数据.数据指针是far和代码指针near.

largehuge模型中,指针的默认子类型是32位或far.主要区别在于巨大的指针总是自动标准化,因此递增它们可以避免64K环绕问题.看到这个.

  • 仅仅因为一个人依赖于另一个并不意味着他们是同一个东西.例如,没有"微小"指针这样的东西 - 指针是"近","远"或"巨大".相反,`tiny`是一种记忆模型.内存模型是由默认情况下代码和数据的指针类型定义的,以及用于"near"指针的段.在"tiny"的情况下,代码和数据都使用`near`指针,两种类型的`near`指针都引用了相同的段. (5认同)

AnT*_*AnT 9

C内存模型不以任何方式暗示平坦的地址空间.它从来没有.实际上,C语言规范是专门为允许非平坦地址空间而设计的.

在具有分段地址空间的最简单的实现中,最大连续对象的大小将受到段的大小(16位平台上的65536字节)的限制.这意味着size_t在这样的实现中将是16位,并且您的代码根本不会编译,因为您试图声明一个大于允许的最大值的对象.

更复杂的实现将支持所谓的巨大内存模型.你看,在分段存储器模型上寻找任何大小的连续内存块确实没有问题,它只需要在指针算术中做一些额外的努力.因此,在巨大的内存模型中,实现会做出额外的努力,这会使代码变慢,但同时允许寻址几乎任何大小的对象.所以,你的代码编译得很好.


old*_*mer 6

在DOS 16位,我不记得能够做到这一点.你可能有多个东西,每个64K(字节)(因为段可以调整,偏移量归零),但不记得你是否可以跨越单个阵列的边界.平坦的内存空间,你可以毫不犹豫地分配任何你想要的东西,并尽可能深入到一个阵列,直到我们可以编译32位DOS程序(在386或486处理器上).也许除了microsoft和borland之外的其他操作系统和编译器可能会生成大于64k字节的扁平阵列.Win16我不记得自由直到win32命中,也许我的记忆生锈了......无论如何,你很幸运或有钱拥有一兆兆字节的内存,一台256kbyte或512kbyte的机器并非闻所未闻.你的软盘驱动器最终只有一小部分到1.44微克,

我记得当你下载整个地球上所有已注册域名的DNS数据库时,我学到了关于DNS的特殊挑战,事实上你必须建立自己的dns服务器,当时几乎需要有一个网络现场.那个文件是35兆字节,我的硬盘是100兆字节,加上dos和windows嚼了一些.可能有1或2兆的内存,当时可能已经能够做32位dos程序.部分如果是我想要解析我在多次传递中执行的ascii文件,但是每次传递输出都必须转到另一个文件,并且我必须删除先前的文件以在磁盘上为下一个文件留出空间.标准主板上有两个磁盘控制器,一个用于硬盘,一个用于光盘驱动器,这里的东西也不便宜,

甚至存在用C读取64k字节的问题,你通过fread在16位int中读取你希望读取的字节数,这意味着0到65535而不是65536字节,如果你没有读取偶数大小的扇区,那么性能会大幅下降一次读取32kbytes以最大限度地提高性能,64k直到最后才进入dos32天,当你终于确信传递给fread的值现在是32位数并且编译器不会切断高16位并且只有使用低16位(如果您使用了足够的编译器/版本,则会经常发生).我们目前在32位到64位转换中遇到类似的问题,就像我们在16位到32位转换时那样.最有趣的是来自像我这样的人的代码,他们知道从16位到32位的int改变了大小,但是unsigned char和unsigned long没有,所以你适应并很少使用int,这样你的程序就可以编译并适用于16位和32位.(来自那一代人的代码突出了其他人也经历过它并使用相同的技巧).但是对于32到64的转换,它是另一种方式,并且使用uint32类型声明的代码没有被重构.

阅读wallyk的答案刚刚进来,缠绕的巨大指针确实敲响了铃声,也不总是能够编译得很大.很小的是我们今天很舒服的平板记忆模型,和今天一样容易,因为你不必担心细分.因此,当你可以的时候,编译小版本是一种可取的.你仍然没有很多内存或磁盘或软盘空间,所以你通常没有处理大数据.

并同意另一个答案,分段偏移的事情是8088/8086英特尔.整个世界尚未被英特尔所主宰,因此其他平台只有一个平坦的内存空间,或者在硬件(处理器之外)中使用其他技巧来解决问题.由于分段/偏移,英特尔能够驾驶16位的东西比它应该拥有的更长.段/偏移有一些很酷和有趣的事情你可以用它做,但它和其他任何东西一样痛苦.您要么简化了生活,要么生活在平坦的记忆空间中,要么您经常担心细分界限.