西工大CSAPP第二章课后题2.55答案及解析

2023-10-29 18:36

本文主要是介绍西工大CSAPP第二章课后题2.55答案及解析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

因为我获取并阅读CSAPP电子书的方式是通过第三方网站免费下载,没有付给原书作者相应的报酬,遵循价值交换原则,我会尽我所能通过博客的方式,推广这本书以及原书作者就职的大学,以此回馈原书作者的劳动成果。另外,由于西工大让我能够不认真听课、不好好写作业、糊弄考试还能过,有了很多很多时间做自己认为对社会有价值的事情,所以感谢西工大对我的宽容与支持。

2.55 在你能接触到的不同机器上,使用show-bytes.c文件中的show-bytes,编译并且运行样例代码,决定被这些机器所使用的字节顺序。

首先我们要获取到show-bytes.c文件,在Edge浏览器中输入“CSAPP”,发现第一个网站就是Carnegie Mellon University的官网,网址是CS:APP3e, Bryant and O'Hallaron,这本书就是被这所大学的几个教授联合编写的。单击进入网站后,单击选择“Student Site”,Student Site汇总了作为学生的读者为了帮助理解原书中的概念、原理、解决方案、题目、实验材料等,所需要的所有线上材料。进入Student Site之后,单击选择”Material from the CS:APP Textbook“标题下面的“Code examples”,"Material from the CS:APP Textbook"囊括了CS:APP这本书中,所有可能需要的电子版材料,"Code examples"囊括可CS:APP这本书中,所有可能需要用到的代码文件,2.55题目中所要求的"show-bytes.c"文件就处在"Code examples"中。进入"Code examples"之后,浏览内容,就能找到"show-bytes.c"文件,单击定位到的“show-bytes”后,我们就能看到"show-bytes.c"的内容。如果你发现,这时候界面网址是:csapp.cs.cmu.edu/3e/ics3/code/data/show-bytes.c,就说明你成功找到了"show-bytes.c"文件。复制页面中的所有内容,并将它粘贴到任意的记事本文件中。为了帮助理解"show-bytes.c"文件中各部分代码的功能,"show-bytes.c"文件的内容将被显示在下面:

/* $begin show-bytes */
#include <stdio.h>
/* $end show-bytes */
#include <stdlib.h>
#include <string.h>
/* $begin show-bytes */typedef unsigned char *byte_pointer;void show_bytes(byte_pointer start, size_t len) {size_t i;for (i = 0; i < len; i++)printf(" %.2x", start[i]);    //line:data:show_bytes_printfprintf("\n");
}void show_int(int x) {show_bytes((byte_pointer) &x, sizeof(int)); //line:data:show_bytes_amp1
}void show_float(float x) {show_bytes((byte_pointer) &x, sizeof(float)); //line:data:show_bytes_amp2
}void show_pointer(void *x) {show_bytes((byte_pointer) &x, sizeof(void *)); //line:data:show_bytes_amp3
}
/* $end show-bytes *//* $begin test-show-bytes */
void test_show_bytes(int val) {int ival = val;float fval = (float) ival;int *pval = &ival;show_int(ival);show_float(fval);show_pointer(pval);
}
/* $end test-show-bytes */void simple_show_a() {
/* $begin simple-show-a */
int val = 0x87654321;
byte_pointer valp = (byte_pointer) &val;
show_bytes(valp, 1); /* A. */
show_bytes(valp, 2); /* B. */
show_bytes(valp, 3); /* C. */
/* $end simple-show-a */
}void simple_show_b() {
/* $begin simple-show-b */
int val = 0x12345678;
byte_pointer valp = (byte_pointer) &val;
show_bytes(valp, 1); /* A. */
show_bytes(valp, 2); /* B. */
show_bytes(valp, 3); /* C. */
/* $end simple-show-b */
}void float_eg() {int x = 3490593;float f = (float) x;printf("For x = %d\n", x);show_int(x);show_float(f);x = 3510593;f = (float) x;printf("For x = %d\n", x);show_int(x);show_float(f);}void string_ueg() {
/* $begin show-ustring */
const char *s = "ABCDEF";
show_bytes((byte_pointer) s, strlen(s)); 
/* $end show-ustring */
}void string_leg() {
/* $begin show-lstring */
const char *s = "abcdef";
show_bytes((byte_pointer) s, strlen(s)); 
/* $end show-lstring */
}void show_twocomp() 
{
/* $begin show-twocomp */short x = 12345; short mx = -x; show_bytes((byte_pointer) &x, sizeof(short)); show_bytes((byte_pointer) &mx, sizeof(short)); 
/* $end show-twocomp */
}int main(int argc, char *argv[])
{int val = 12345;if (argc > 1) {if (argc > 1) {val = strtol(argv[1], NULL, 0);}printf("calling test_show_bytes\n");test_show_bytes(val);} else {printf("calling show_twocomp\n");show_twocomp();printf("Calling simple_show_a\n");simple_show_a();printf("Calling simple_show_b\n");simple_show_b();printf("Calling float_eg\n");float_eg();printf("Calling string_ueg\n");string_ueg();printf("Calling string_leg\n");string_leg();}return 0;
}

接下来读题目。题目要求,这个代码应该被运行在多种机器上。考虑到目前市面上个人用计算机的CPU生产厂家几乎都是Intel,而适配Intel生产的CPU的机器几乎都是用一样的字节顺序,所以我们没有办法在使用不同字节顺序的机器上运行"show-bytes.c"代码。那么,在我们的运行Windows操作系统的个人用计算机上,打开Powershell并且进入到粘贴有"show-bytes.c"代码的txt文件的目录中,使用gcc编译"show-bytes.c"文件以得到"show-bytes.exe"可执行文件,接着执行该可执行文件,得到了如下图所示的运行结果:

PS D:\C> dir -Filter a10292023.c

    Directory: D:\C

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---          10/29/2023 10:31 AM           2763 a10292023.c

PS D:\C> gcc -o a10292023 a10292023.c
PS D:\C> a10292023.exe
calling show_twocomp
 39 30
 c7 cf
Calling simple_show_a
 21
 21 43
 21 43 65
Calling simple_show_b
 78
 78 56
 78 56 34
Calling float_eg
For x = 3490593
 21 43 35 00
 84 0c 55 4a
For x = 3510593
 41 91 35 00
 04 45 56 4a
Calling string_ueg
 41 42 43 44 45 46
Calling string_leg
 61 62 63 64 65 66

在Powershell中,"dir"命令使得当前目录下的所有文件被输出在Powershell中,配合使用"-Filter"选项以及"a10292023.c"参数,过滤掉"dir"命令的输出结果,使得当前目录下只有名为"a10292023.c"的文件才会被输出在Powershell中(被执行粘贴操作,并且保存有"show-bytes.c "文件内容的记事本文件的名称是"a10292023.c")。通过这条命令,我们确保"a10292023.c"文件就处在当前的工作目录下,这样子在使用gcc命令编译文件时,就不会出现文件未找到的错误。接着使用"gcc -o a10292023 a10292023.c"命令编译"a10292023.c"文件,其中"-o"选项以及"a10292023"参数表明将要被生成的可执行文件的名称是"a10292023",但考虑到Windows系统为了帮助使用者区分不同文件类型,会自动在文件后添加相应的文件后缀,比如".msi"、".iso"、".exe",所以将要被生成的可执行文件的名称是"a10292023.exe"。接下来我们运行该可执行文件。之后便得到了上图的结果。

为了确定该机器使用的字节顺序,我们需要参照代码执行的结果。但是为了更好理解代码执行的结果,我们仍需要参考源文件中的主函数部分。在"a10292023.c"文件中,"int main(int argc, char* argv[])"表示定义一个main函数,main函数的返回值是int类型。它有两个参数,一个是int型的变量argc,另一个是数组argv,数组argv中的元素都是指针,指向字符串。当Powershell中的命令是"PS D:\C> a10292023.exe"时,argc的值为1,argv[0]的值为“a10292023.exe。当Powershell中的命令是“PS D:\C> a10292023.exe 0x80000000”时,argc的值为2,argv[0]的值为"a10292023.exe",argv[1]的值为"0x80000000"。"int val=12345;"一个有符号整形变量val被定义并且初始化,val的值被初始化为12345。接着判断"argc>1",如果argc大于1,那么就意味着,在Powershell中输入"a10292023.exe"时,后面还添加了一个参数,而且这个参数已经以字符串的形式被保存在argv[1]字符数组中。在这里"if(argc>1)"出现了两次,我认为是错误的。考虑到我们初次运行"a10292023.exe"文件,遵循从简到繁的原则,先不带参数运行"a10292023.exe",所以我们跳过"argc>1"时的情况,转而去看else下的情况。在else中,"printf("calling show_twocomp\n");"在Powershell中输出"calling show_twocomp"这句话,"show_twocomp();"表示,调用"show_twocomp()"函数。"printf("Calling simple_show_a\n");"在Powershell中输出"Calling simple_show_a"这句话,"simple_show_a();"表示,调用"simple_show_a()"函数,"printf("Calling simple_show_b\n");"表示在Powershell中输出"Calling simple_show_b"这句话,"simple_show_b();"表示调用"simple_show_b()"函数,"printf("Calling float_eg\n");"表示在Powershell中输出"Calling float_eg"这句话,"float_eg();"表示调用"simple_show_b"函数。剩下的4句执行类似的操作。我们接下来看这6个函数:

首先是"show_twocomp()"函数,"void show_twocomp()"表示"show_twocomp()"函数是一个既没有返回值,又没有参数的函数。忽略掉"/* $begin show-twocomp */"注释,"short x = 12345;"表示一个有符号短整型变量x被定义,并且变量x的值被初始化为12345,"short mx = -x;"表示一个有符号短整型变量mx被定义,并且变量mx的值被初始化为-12345。"show_bytes((byte_pointer) &x, sizeof(short));"表示调用show_bytes函数,并将变量x的地址、有符号短整型变量的长度(以字节为单位)这两个参数传给show_bytes函数。考虑到show_bytes函数将会在整个程序中被频繁用到,将show_bytes函数的内容显示在下面并辅以讲解:

void show_bytes(byte_pointer start, size_t len) {size_t i;for (i = 0; i < len; i++)printf(" %.2x", start[i]);    //line:data:show_bytes_printfprintf("\n");
}

"void show_bytes(byte_pointer start, size_t len)"表示"show_bytes"函数是一个没有返回值的函数,并且需要两个参数作为输入,一个是byte_pointer类型的变量"start",另一个是"size_t"类型的变量"len"。在程序的开头有一个宏"typedef unsigned char *byte_pointer;",意思是byte_pointer类型的变量的值,将会是无符号字符型变量的地址。"size_t"是一个无符号整型的数据类型,至于"size_t"类型的变量的长度,则将取决于具体的机器。"size_t i"表示定义一个"size_t"类型的变量i,但却不对变量i的值进行初始化。"for (i = 0; i < len; i++)"表示,循环len-0=len次。"printf(" %.2x", start[i]);"表示,在每一次循环中,都输出"start[i]"。"%.2x"中的".2"表示,至少输出2个字符,"x"表示以16进制形式输出。最后"printf("\n");"表示另起一行。

回到6个函数中的第1个函数"show_twocomp()","show_twocomp()"函数中的语句"show_bytes((byte_pointer) &x, sizeof(short));"表示调用show_bytes函数,并将变量x的地址、有符号短整型变量的长度(以字节为单位)这两个参数传给show_bytes函数。而变量x的值是12345,或者是0x3039,现在市面上普遍使用的笔记本电脑都使用64位Intel的CPU,对应的short类型变量的长度都是2字节,所以"sizeof(short)"的值是2。但是变量x在内存中的地址未知,不妨假设变量x在内存中的占据的空间的地址从0x004005f4开始,考虑到变量x的数据类型是short,占据2个字节,所以变量x在内存中占据的空间的地址在0x004005f5结束。进入show_bytes()函数,变量start的值是0x004005f4,变量len的值是2。在第一次循环中,printf将以无符号16进制整型的形式输出start[0]的值,start[0]=*(start+0)=*start,输出start[0]的值,意味着将输出内存中地址为0x004005f4的值。第二次循环中,printf同样以无符号16进制整型的形式输出start[1]的值,start[1]=*(start+1),输出start[1]的值,意味着将输出内存中地址为0x004005f5的值。至此循环结束。运行的结果是"39 30",这就表明内存中地址为0x004005f4的空间存储的是0x39,内存中地址为0x004005f5的空间存储的是0x30,即低地址存储低有效位,高地址存储高有效位,这就是小段的字节顺序。为什么printf以无符号16进制整型的形式输出一个变量的值,就能够输出这个变量在内存中分配到的空间中的值呢?因为无符号16进制整型数和机器数的对应关系是最简单最直观的,相比于有符号16进制整型数和机器数、浮点数和机器数而言。接着函数"show_twocomp()"中的语句"show_bytes((byte_pointer) &mx, sizeof(short));"接着调用show_bytes函数,同时将有符号短整型变量mx在内存中的地址、short类型的变量的大小这两个参数传给show_bytes函数。在Windows系统使用计算器,切换到程序员模式,在十进制模式输入-12345,然后切换到十六进制模式,得知-12345的用两个字节的十六进制表示是:0xcf c7。但是在Powershell中运行a10292023.exe,发现0xcfc7中,低位字节0xc7处在内存中的低地址,高位字节0xcf处在内存中的高地址。所以我们得知,被市面上大部分适配Intel64位CPU的笔记本采用的字节顺序是小端。

接着看6个函数中的第2个函数,"simple_show_a()","void simple_show_a()"表示"simple_show_a"函数没有返回值,并且也没有参数作为输入。"int val = 0x87654321;"表示定义一个有符号整型变量val,并将变量val的值初始化为0x87654321。"byte_pointer valp = (byte_pointer) &val;"表示定义一个byte_pointer类型的变量valp,指向变量val。"show_bytes(valp, 1); /* A. */"表示将valp和1作为参数,传给show_bytes函数。"show_bytes(valp, 2); /* B. */"与"show_bytes(valp, 3); /* C. */"同理。分析输出结果,我们不难得知,被市面上大部分适配Intel64位CPU的笔记本采用的字节顺序是小端。

6个函数中的第3个函数,"simple_show_b()",除了有符号整型变量val的值被初始化为0x12345678之外,与第2个函数相同。

6个函数中的第4个函数,"float_eg()","int x = 3490593;"定义一个有符号整型变量x,并初始化变量x的值为3490593,用16进制形式可以表示为0x00354321,遵循IEEE的相关规定,变量x在内存中的值也为0x00354321。"float f = (float) x;"定义一个单精度浮点型变量f,并且初始化f的值为x,遵循IEEE对单精度浮点数格式的要求,得知变量f在内存中的值为:0x4a550c84。"printf("For x = %d\n", x); show_int(x); show_float(f);"在Powershell中输出"For x = 3490593"之后,调用"show_int()"和"show_float()"函数。最终在Powershell中,通过输出结果,我们能够得知,被市面上大部分适配Intel64位CPU的笔记本采用的字节顺序是小端。

6个函数中的第5、6个函数,需要注意的是,不要将数组中的元素在内存中的存放顺序与数组的索引之间的关系,和应用于整型、短整型、长整型、长长整型变量的字节顺序搞混。在数组中,元素的索引值小的将被存放在低地址,元素的索引值大的将被存放在高地址。

综上所述,我们得知,被市面上大部分适配Intel64位CPU的笔记本采用的字节顺序是小端。

这篇关于西工大CSAPP第二章课后题2.55答案及解析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/302570

相关文章

Java的IO模型、Netty原理解析

《Java的IO模型、Netty原理解析》Java的I/O是以流的方式进行数据输入输出的,Java的类库涉及很多领域的IO内容:标准的输入输出,文件的操作、网络上的数据传输流、字符串流、对象流等,这篇... 目录1.什么是IO2.同步与异步、阻塞与非阻塞3.三种IO模型BIO(blocking I/O)NI

Python 中的异步与同步深度解析(实践记录)

《Python中的异步与同步深度解析(实践记录)》在Python编程世界里,异步和同步的概念是理解程序执行流程和性能优化的关键,这篇文章将带你深入了解它们的差异,以及阻塞和非阻塞的特性,同时通过实际... 目录python中的异步与同步:深度解析与实践异步与同步的定义异步同步阻塞与非阻塞的概念阻塞非阻塞同步

Redis中高并发读写性能的深度解析与优化

《Redis中高并发读写性能的深度解析与优化》Redis作为一款高性能的内存数据库,广泛应用于缓存、消息队列、实时统计等场景,本文将深入探讨Redis的读写并发能力,感兴趣的小伙伴可以了解下... 目录引言一、Redis 并发能力概述1.1 Redis 的读写性能1.2 影响 Redis 并发能力的因素二、

Spring MVC使用视图解析的问题解读

《SpringMVC使用视图解析的问题解读》:本文主要介绍SpringMVC使用视图解析的问题解读,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Spring MVC使用视图解析1. 会使用视图解析的情况2. 不会使用视图解析的情况总结Spring MVC使用视图

利用Python和C++解析gltf文件的示例详解

《利用Python和C++解析gltf文件的示例详解》gltf,全称是GLTransmissionFormat,是一种开放的3D文件格式,Python和C++是两个非常强大的工具,下面我们就来看看如何... 目录什么是gltf文件选择语言的原因安装必要的库解析gltf文件的步骤1. 读取gltf文件2. 提

Java中的runnable 和 callable 区别解析

《Java中的runnable和callable区别解析》Runnable接口用于定义不需要返回结果的任务,而Callable接口可以返回结果并抛出异常,通常与Future结合使用,Runnab... 目录1. Runnable接口1.1 Runnable的定义1.2 Runnable的特点1.3 使用Ru

使用EasyExcel实现简单的Excel表格解析操作

《使用EasyExcel实现简单的Excel表格解析操作》:本文主要介绍如何使用EasyExcel完成简单的表格解析操作,同时实现了大量数据情况下数据的分次批量入库,并记录每条数据入库的状态,感兴... 目录前言固定模板及表数据格式的解析实现Excel模板内容对应的实体类实现AnalysisEventLis

Java的volatile和sychronized底层实现原理解析

《Java的volatile和sychronized底层实现原理解析》文章详细介绍了Java中的synchronized和volatile关键字的底层实现原理,包括字节码层面、JVM层面的实现细节,以... 目录1. 概览2. Synchronized2.1 字节码层面2.2 JVM层面2.2.1 ente

Redis 内存淘汰策略深度解析(最新推荐)

《Redis内存淘汰策略深度解析(最新推荐)》本文详细探讨了Redis的内存淘汰策略、实现原理、适用场景及最佳实践,介绍了八种内存淘汰策略,包括noeviction、LRU、LFU、TTL、Rand... 目录一、 内存淘汰策略概述二、内存淘汰策略详解2.1 ​noeviction(不淘汰)​2.2 ​LR

IDEA与JDK、Maven安装配置完整步骤解析

《IDEA与JDK、Maven安装配置完整步骤解析》:本文主要介绍如何安装和配置IDE(IntelliJIDEA),包括IDE的安装步骤、JDK的下载与配置、Maven的安装与配置,以及如何在I... 目录1. IDE安装步骤2.配置操作步骤3. JDK配置下载JDK配置JDK环境变量4. Maven配置下