1、64K限制的困扰
我们考虑下面一段16位C语言代码片断:
char g_c;
void func (char *p)
{
char c;
p = &c;
p = &g_c;
p = (char *)malloc(10 * sizeof(char));
free(p);
return;
}
显然,g_c是全局变量,按照教科书的说法,它应该放在全局变量区,c是局部变量,因此c放在栈区,而malloc所申请的空间应该是在堆区。上面的代码从语义上讲没有半点错误,一个指针p可以指向全局变量区的内存,也可以指向堆区或栈区的内存。
大家知道,16Bit C语言的指针是16位的,因此只能访问64K大小的空间,然而我们使用指针对全局变量,堆,栈进行寻址的时候,都没有指定一个数据段。那么也就是说,全局变量,堆以及栈都是在同一个段中的,这三部分的大小加起来不可能大于64K,这也是malloc的参数int类型的最大表示范围。
按照我的理解这64K应该是这样的:
64K最顶端是栈区(因为栈总是从上往下的),一般我们在程序的开始会设置SS的内容和DS一样,SP的值为0,如果有进栈操作的话,SP会减少2,而变成从FFFE,然后往下减。
64K最底端是数据区(这部分是固定的,堆和栈是可变的),我们声明了Data Segment之后,一般会将Data Segment的段起始地址给DS,然而,Data Segment中的变量是按顺序从低往高排的,一旦编译连接成可执行文件之后,这部分内容的大小(或称为长度)是不会变的。我们可以更改的是堆和栈的大小。
堆区位于数据区以上部分,也就是如果堆区的不断增长,或者栈区的不断进栈,最终可能造成堆区和栈区交叉,也就是我们俗称的堆栈溢出。
或许会有些疑问,认为我将局部变量放在栈区是不对的,这里就这部分内容再展开讨论一下,对于函数的参数是放在栈区的,大家不会有所怀疑,因为函数调用除了使用寄存器传递参数之外只能使用堆栈传递了(当然使用全局变量也可以,不过这里只讨论形参与实参的传递),因此调用Func的过程伪代码如下所示:
lea bx, p
push bx
call func