assume cs:codesg datasg segment db '1975','1976','1977','1978','1979','1980','1981','1982','1983' db '1984','1985','1986','1987','1988','1989','1990','1991','1992' db '1993','1994','1995' dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514 dd 345980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000 dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226 dw 11542,14430,15257,17800 datasg ends table segment db 21 dup ('year summ ne ?? ') table ends data segment db 10 dup(0) data ends stacksg segment db 16 dup (0) stacksg ends codesg segment start: mov ax,datasg mov es,ax mov ax,table mov ds,ax mov ax,stacksg mov ss,ax mov sp,10h mov bx,0 mov si,0 mov di,0 mov cx,21 ;s0循环是为了把datasg段中的数据经过处理后放入data段,为了后面的输出做准备 s0: ;copy the year data push es:[bx] push es:[bx+2] pop ds:[si+2] pop ds:[si] ;copy the income data push es:[bx+54h ] push es:[bx+54h +2] pop ds:[si+7] pop ds:[si+5] ;copy the count of employees push es:[di+0a8h] pop ds:[si+0ah] mov ax,ds:[si+5] mov dx,ds:[si+7] div word ptr ds:[si+10] mov ds:[si+0dh],ax add di,2 add si,10h add bx,4 loop s0 ;下面开始从table段中拿出数据,然后到显存段中输出,这里的思路是挨个输出table段中的数据 mov sp,10h;设置ss:sp指向stacsg栈段的栈顶 mov si,0;ds:[si]指向我们将要处理的数据的table段 mov ax,0b800h mov es,ax mov bx,0;设置es:[bx]为显示的缓存器的地址 mov cx,21;因为要处理的数据为21,所以循环次数为21 s: ;在每次循环一开始就计算该次循环所对应的行的显存段的偏移地址,结果存储到dx中 call calculate_line_value push bx push es push cx push di;因为下面的程序中将要用到es、bx、cx、di,所以这里先把他们入栈保存起来 ;这里是输出年份,因为年份已经是一字符的形式存储的,不需要在经过转换,所以直接输出 mov al,ds:[bx];去取data段的数据,这里bx是偏移量 call show_str_year;顾名思义,就是专门为输出年份所写的输出子程序 mov al,ds:[bx+1] call show_str_year mov al,ds:[bx+2] call show_str_year mov al,ds:[bx+3] call show_str_year ;因为在调用子程序时,已经改变了显存段的偏移地址,而为了对齐输出,我们必须确保每行的开始地址即di的值不能改变,所以入栈保护起来 pop di; push di add di,40;这是为了对齐输出,在输出收入数据应该从 ;下面是输出收入的数据,在输出后面的收入数据、雇员人数和平均工资时都要先把数字转换成对应的字符。再调用show_str_other子程序输出 ;这里的关键是运用定义的段data段来存放相应的数据如收入数据转换后的字符,然后马上输出。 mov ax,ds:[bx+5] mov dx,ds:[bx+7]; mov cx,data mov ds,cx;这里将要用到程序刚开始时定义的用来存放相应的数据如收入数据转换后的字符 mov si,10;设置ds:[si]用来存放从data段中取出的数据转换为对应字符串的值,特别注意这里ds中存放的是data段的段地址 mov byte ptr ds:[si],0;因为在调用change_to_char子程序时得到的数据室逆序的,故我这里采用的是先填充末尾,倒着填过来。 call change_to_char;调用把数字转换成对应得字符的子程序,结果存储在data段中 call show_str_other;调用输出字符串子程序show_str_others输出change_to_char中存放在data段中的字符串 pop di push di add di,80;输出完后,要把di的值恢复,再放到栈中保护起来,再设置该行的偏移地址 ;恢复ds指向table段,使其能继续取table段中的值 mov cx,table mov ds,cx mov ax,ds:[bx+10] mov dx,0; mov cx,data mov ds,cx mov si,10;设置ds:[si]用来存放从data段中取出的数据转换为对应字符串的值 mov byte ptr ds:[si],0;因为在调用change_to_charc子程序时得到的数据是逆序的,故我这里采用的是先填充末尾,倒着填过来。 call change_to_char call show_str_other pop di add di,120;输出完后,要把di的值恢复,再放到栈中保护起来,再设置该行的偏移地址 ;恢复ds的值 mov cx,table mov ds,cx mov ax,ds:[bx+13] mov dx,0; mov cx,data mov ds,cx mov si,10;设置ds:[si]用来存放从data段中取出的数据转换为对应字符串的值 mov byte ptr ds:[si],0;因为在调用change_to_char子程序时得到的数据是逆序的,故我这里采用的是先填充末尾,倒着填过来。 call change_to_char call show_str_other ;恢复ds的值 mov cx,table mov ds,cx ;在调用完子程序后要把cx、es、bx的值恢复给他们 pop cx pop es pop bx add bx,10h ;把bx的值加2使得es:[bx]能取得下一个数据 loop s mov ax,4c00h int 21h ;计算该次循环要显示的行号的开始地址 calculate_line_value: mov dh,22 sub dh,cl;这里是用22减去cl中的值就是该次循环要显示的行号 mov dl,0;列号 ;我们在第九章的实验材料中知道显示的一行的偏移为0a0h mov al,0a0h dec dh;因为0a0h是第二行的开始,所以减去1后,比如要在第八行显示,就要7*0a0h,不减的话就是在第九行显示了 mul dh; mov di,ax;把上面乘积的值给我们使用的偏移地址的寄存器di ret ;把table段中的数据转换成对应的字符串 change_to_char: mov cx,ax;把ax的值给cx,借以判断ax中的值是否为0,因为ax中的值是除法中的商 jcxz dtocOK;如果ax中的值为0,说明除法结束了 mov cx,10;把要现实的数据转换成十进制数对应的字符,所以基数是10 ;由于下面还要用到si和ax,所以先把他们的值给保存起来。 push si push ax ;下面就是除法不会溢出的公式的应用 mov ax,dx mov dx,0 div cx mov si,ax pop ax div cx mov cx,dx mov dx,si pop si add cx,30h sub si,1;每处理传进来的数据的每一位数据时,记得要把si减一以便ds:[si]指向下一个内存单元 mov ds:[si],cl jmp short change_to_char dtocok:ret show_str_year: mov cl,2 ;设置显示的字体颜色 mov ah,cl mov es:[di],ax add di,2 ret show_str_other: mov al,ds:[si] mov cl,al jcxz ok mov cl,2 mov ah,cl mov es:[di],ax add di,2 inc si; jmp short show_str_other ok: ret codesg ends end start 但是在dos下面编译不能成功,说loop s 处jmp out of range by 8 byte.在emu8086下面能够编译并允许成功,究竟是哪个地方超出了范围了呢?正在慢慢的寻找问题的所在。