MIT 6.828 Lab1 exercise8

exercise 1.8

要求

补充printfmt.c line 208处的代码,让程序能够打印出八进制形式数。

  1. 解释printf.c和console.c之间的接口,特别是,console提供了什么函数?这个函数是怎样被printf.c使用的?
  2. 解释下面这段来自console.c的代码片段。
if (crt_pos >= CRT_SIZE) {
	int i;
	memmove(crt_buf, crt_buf + CRT_COLS, (CRT_SIZE - CRT_COLS) * sizeof(uint16_t));
	for (i = CRT_SIZE - CRT_COLS; i < CRT_SIZE; i++)
		crt_buf[i] = 0x0700 | ' ';
	crt_pos -= CRT_COLS;
}
  1. 单步追踪下面的代码运行。
int x = 1, y = 3, z = 4;
cprintf("x %d, y %x, z %d\n", x, y, z);
  • 在对cprintf()调用中,fmt指向什么?ap指向什么?
  • 按照执行顺序列出每一个对cons_putc,va_arg,和vcprintf的调用。对于cons_putc,列出它的变量。对于va_arg,列出调用前和调用后ap指向什么。对于vcprintf,列出它的两个参数的变量。
  1. 运行下面的代码。
unsigned int i = 0x00646c72;
cprintf("H%x Wo%s", 57616, &i);

输出是什么?用上一个练习的方法,解释输出是怎样实现的。需要ASCII码表。

  1. 在下面的代码中,在’y='的后面将会打印什么?(注意:答案不是一个明确的数。)为什么这会发生?
cprintf("x=%d y=%d", 3);
  1. GCC改变了它调用习惯,它将变量按声明顺序压栈,所以最后一个变量被最后压栈。你应该怎样改变cprintf或者它的接口,才可以仍能传递可变个数个参数?

解答:

填代码:那个位置处的上下文如下:

		// unsigned decimal

case 'u':
			num = getuint(&ap, lflag);
			base = 10;
			goto number;

		// (unsigned) octal
case 'o':
			// Replace this with your code.
			putch('X', putdat);
			putch('X', putdat);
			putch('X', putdat);
			break;

这打印的三个叉就让我很迷。如果照着上文的格式填,这仨叉就不管了。或许这就对了吧。

case 'o':
			num = getuint(&ap, lflag);
			base = 8;
			goto number;
			putch('X', putdat);
			putch('X', putdat);
			putch('X', putdat);
			break;

下面是其他习题答案。

  1. 用vscode很容易找到调用关系。接口是void cputchar(int c)。console提供了这个接口,以及接口调用的cons_putc()。printf通过将要输出的字符ASCII码作为整型参数传递给cputchar,在console.c内调用cons_putc()等完成输出工作。

  2. 这段函数的意思要结合上下文,以及存在与其他位置的定义来理解。部分显示输出的知识,来自之前读的《x86汇编语言:从实模式到保护模式》。

    if (crt_pos >= CRT_SIZE) {
    	int i;
    	memmove(crt_buf, crt_buf + CRT_COLS, (CRT_SIZE - CRT_COLS) * sizeof(uint16_t));
    	for (i = CRT_SIZE - CRT_COLS; i < CRT_SIZE; i++)
    		crt_buf[i] = 0x0700 | ' ';
    	crt_pos -= CRT_COLS;
    }
    

    下面给出一些关键量的定义,都来自引用的其他代码。讲真,下面这些量都明白了,这段代码的意思就明白了。

参数:

crt_pos:当前输出位置指针,指向内存区中对应输出映射地址。

CRT_SIZE:是CRT_COLS和CRT_ROWS的乘积,即2000=80*25,是不翻页时一页屏幕最大能容纳的字数

crt_buf:输出缓冲区内存映射地址

CRT_COLS:默认输出格式下整个屏幕的列数,为80

CRT_ROWS:默认输出格式下整个屏幕的行数,为25

unit16_t:typedef unsigned short 正好两字节,可以分别用来表示当前要打印的字符ASCII码和打印格式属性。

函数:

memmove(): memmove(void *dst, const void *src, size_t n).意为将从src指向位置起的n字节数据送到dst指向位置,可以在两个区域重叠时复制。

好了,有了上述说明,函数意图就很明确了:若当前输出位置大于屏幕最大输出范围,则将缓冲区整体前移一行(整体前移80字,覆盖之前的内容),之后将最后一行置为全0x0,最后将当前输出位置上移一行(也是80字),从这一页之后移到这一页内。

还是很抽象是吧,其实就是完成了向下滚动一行的操作。。。不信细读读,看看是不是。

  1. 分问题回答
  • fmt指向字符串"x %d, y %x, z %d\n",它本省就是一个指向字符串的指针。ap指向后面的变参。变参就是参数的个数可变,牵扯到标准库中的变量类型va_list,限于时间原因我没有继续研究,留待以后。
  • cons_putc:调用顺序为cprintf->vcprintf->vprintfmt->putch->cputchar->cons_putc。cons_putc的作用是输出一个字符到控制台。参数是一个整型表示的ASCII码。
  • va_arg:在vprintfmt中不是每个switch分支都调用了它,用vscode查看引用操作就能看到,太多了。在switch分支中有一个借口getint,也调用了va_arg。它的作用是将指针指向变参的下一个参数。则调用前,ap指向x,调用后,ap指向y。va_start宏识别并指向第一个变参,va_arg一个一个依次指向接下来的变参。详细概念可自行上网搜索这个结构体.这篇博客很有帮助 va_list/va_start/va_arg/va_end深入分析
  • vcprintf:只在cprintf被调用了一次。两个调用参数是常数字符型指针fmt,指向printf的字符串;va_list型变量ap,用于控制变参指针。
  1. 下面是输出结果
He110 World

要求运行代码,但是我花了接近两个小时都没有在原来的代码上运行出来,最后把cprintf换成了printf,打出来了。

输出很有意思。其实如果学了计组,这题不用做的。57616用十六进制打印符%x打印出来就是110。x86采用小端方式,这是计组的内容,不说了,科班的没理由不会。0x00646c72按字符串打印,64,6c,72分别代表字符d,l,r。小端方式低位字节放在低地址。输出时从低到高读,就是rld。

如果是大端方式的处理器,就改成0x72626400。57616不用改,处理器应该是自动识别整型变量并自动按自己的大小端方式存储。

  1. 显示的是一个变化的值,应该是个随机的地址之类的。因为之前已知,va_arg控制指向参数的指针,每次向后移动一个参数。这里后面没有正确的指向参数的指针了,则应该移动到了一个野指针,所以显示乱七八糟的值。
  2. 其实办法很简单,只需要我们手动把所有参数指针都倒置一遍就行了。。。

至于最后那个挑战,其实在《x86汇编语言:从实模式到保护模式》有很详细的讲解,讲了字节控制文字颜色显示等,照着书就能做出来。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值