前言
大多数初学者编写的第一个程序都是输出一段文字,最常见的是“Hello World!”。大家在编写完成之后,除了检查语法和逻辑之外,可能忽略了另一个方面的问题,那就是输出文件的大小。以C语言为例,我们编写如下一个程序:
#include <stdio.h>
int main(){printf("Hello World!");return 0;
}
这个程序输出:Hello World!,当然整个程序的结构非常简单,仅用了不到四行代码就完成了。借助TDM-GCC 4.9.2编译器,在C99标准下编译后,我们查看输出的文件大小并且与源文件对比:
可以看到,我们的可执行文件(.exe)的大小为151724字节,而源文件(.c)的大小为74字节,两者整整相差了2050倍之多!那么,是什么原因导致了这一结果呢?
我们先来回顾一下C语言的编译过程:(以gcc编译器为例)
由于我们只有Hello.c这一个源文件,因此汇编只产生了一个后缀为.o的文件。在这个过程之中,为了我们程序的顺利运行,编译器会根据我们的#inlcude命令引入相关的库文件,比如<stdio.h>,即标准输入输出文件,编译器在引入这个头文件时会直接将头文件直接复制粘贴到我们文件的最前面,因此产生的Hello.i文件会变得比较大(17772B,共859行)这是最后产生的文件变大的一个很重要的原因。
此外,在最后的链接部分编译器会把需要的其它库文件,比如在Windows系统下编译器自带的dll文件链接成最终的可执行文件(executable file)。
综上所述,我们最终得到的可执行文件臃肿了很多,主要原因就是往这道菜里面添加了很多“非必要”的“食材”,尽管这些是我们在现行的Windows系统下运行exe文件的必要步骤。
那么,有什么方法能够去掉这些编译器和系统“强加”给我们的束缚,得到一个最精简的程序呢?
化繁为简
为了得到最“纯真”的程序,我们需要一个古老的操作系统,也就是Windows系统的前身——DOS系统。可能很多读者曾使用过CMD,也就是命令提示符,但这个并不是DOS系统,而是一个输入DOS命令的程序,它可以使得Windows系统运行自带的DOS命令,而DOS系统是一个顶层的操作系统,两者只是看起来一样而已。具体可以参看这篇文章:(102条消息) cmd和dos的区别(汇总)_ChasingdreamLY的博客-CSDN博客https://blog.csdn.net/qq_26591517/article/details/80453533?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2~default~CTRLIST~Rate-1-80453533-blog-120355089.235%5Ev28%5Epc_relevant_t0_download&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2~default~CTRLIST~Rate-1-80453533-blog-120355089.235%5Ev28%5Epc_relevant_t0_download&utm_relevant_index=2因此我们需要一个真正意义上的DOS系统,但是随着WindowsGUI的推出,DOS已经不在受市场青睐,因此使用DOS系统的机器在市面上很少见,主要是一些专业的设备仍在使用DOS系统。好在我们还有DOSBOX,它可以在64位的操作系统下为我们模拟一个16位的DOS系统,让我们体验40多年之前的编程。关于DOSBOX的安装与使用可以参看下面这篇文章:
(102条消息) DOSBOX怎么使用 从编译到连接到执行操作全过程 + debug_dosbox使用方法_我叫两万块的博客-CSDN博客https://blog.csdn.net/lwk___123/article/details/124398284?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522168084404316782427412877%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=168084404316782427412877&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_click~default-2-124398284-null-null.142%5Ev81%5Einsert_down38,201%5Ev4%5Eadd_ask,239%5Ev2%5Einsert_chatgpt&utm_term=DOSBOX&spm=1018.2226.3001.4187
开始编程
在开始编程之前,我们要搞清楚以下几个问题:
一是我们的目的,即输出“Hello World!”这一串字符,并且程序要小,因此我们需要把代码和数据挤到一个足够小的空间内,并且互不干扰,这里我们的程序为先是代码后为数据
二是我们如何实现我们的目的,即我们如何输出,幸好DOS系统有一个简单的解决方式,也就是调用BIOS系统的中断来实现,因此接下来我们采用BIOS中断的方式来输出
打开DOSBOX,进入debug程序,debug是一个及其强大的程序,它为我们提供了一系列调试的工具,包括内存的修改、汇编和反汇编。
首先使用d命令查看内存:
由于这个系统是基于8086处理器的,因此我们可以看到地址由两部分组成:左侧的段地址和右侧的偏移地址,这是因为8086的处理器是16位的,但是其地址线为20位,因此采用32位的地址,具体怎么计算由于这里不涉及,我们暂不深究。
我们可以看到这里默认的地址为0100,我们也可以手动设置地址,比如-d 0120,查看偏移地址为0120的内存(段地址默认为073F),或者直接指明段地址和偏移地址 如-d 0770:0100.
然后我们使用r命令查看寄存器:
这里我们先关注CS和IP寄存器,CS和IP寄存器对指明了程序是从哪里运行的,组合为CS:IP,这里系统默认为073F:0100,也就是我们查看内存的第一行,为了减小我们的代码量,我们的程序将从这里开始。
此外我们还需要关注DS和DX寄存器,这两个寄存器对和上面作用相似,但是它们指明了数据存放在哪个位置,它默认是073F:0000,为了达到足够小的要求(不留空白),这里就需要我们手动来改变了。
初步的构想如下:
MOV AX,DATA_ADDRESS
MOV DX,AX
MOV AH,09
INT 21
MOV AH,4C
INT 21
这个是我们的汇编程序,我们来逐条分析:
MOV AX,DATA_ADDRESS
MOV DX,AX
这两条的作用借助中间寄存器AX,将字符串的地址DATA_ADDRESS移入DX寄存器,结合我们上面讲到的DS就可以指明我们的字符串“Hello World!”是放在哪里的,这里我们还暂时不知道,因此先空出来
MOV AH,09
INT 21
这里将AX的高位8位,即AH设置为09,然后调用BIOS系统的21号中断,这时候系统就会将DS:DX指向的地址开始的字符串显示出来,直到遇到终止符‘$’为止。
MOV AH,4C
INT 21
这两行是程序退出的通用指令,相当于高级语言里面的return,将程序控制权交还给系统
最后我们就剩下一个问题:DATA_ADDRESS究竟应该是多少?
这时候就需要我们来计算了,要了解每一条指令具体会占用多少空间:
MOV AX,DATA_ADDRESS (3字节) ; MOV DX,AX(2字节) ; MOV AH,09(2字节)
INT 21(2字节,两条) ; MOV AH,4C(2字节)
因此我们需要3+2+2+4+2=13个字节来存放我们的指令!将D加上开始的地址,即0100+D,得到010D,这个就是我们字符串应该存放的地址!完整的代码如下:
MOV AX,010D
MOV DX,AX
MOV AH,09
INT 21
MOV AH,4C
INT 21
"Hello World!$"整个字符串一共要占用13个字节,因此加起来整个程序一共只需要要26个字节就可以了!
下面我们开始编程:
输入e 010D “Hello World!$",进行数据的写入;然后输入d,查看内存,发现成功写入:
输入a 0100,开始汇编,把汇编代码写入到指定的内存中
之后输入u,反汇编,查看我们输入的指令 ,可以看到指令成功写入了
之后我们要做的就是把我们写的程序输出到磁盘里面
先使用r cx命令,修改寄存器CX的值为1A,即十六进制的26,这样我们在之后的写盘命令的时候就可以输出一个大小为26个字节的程序了
修改寄存器之后,使用n命令定义操作文件名,为之后的写盘做准备,这里我们的文件名为HELLO.COM,这是一个非常古老的可执行文件格式,可以看作是exe文件的前辈,是一种“丐中丐”的可执行文件,简陋得不能再简陋了,如果说exe文件时法拉利,这个com文件就是自行车,但我们要的就是这个效果。
之后用W指令写盘,可以看到成功写入:
退出debug,回到C盘,使用dir指令查看我们的文件:
可以看到,我们的HELLO.COM文件仅占了26个字节,这甚至比我们上面编写的C语言的代码都还要小!这么小的程序能运行吗,我们试一试:
我们的Hello World成功运行了,打印出了预期结果!
当然,这个程序并不能在Windows系统下运行,因为它是16位的,和现在的系统不兼容
以上就是如何写出一个最小的Hello World程序,本篇文章纯属整活,没有任何实用价值。由于作者本身水平有限,还请大家多多指正!