西工大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

相关文章

网页解析 lxml 库--实战

lxml库使用流程 lxml 是 Python 的第三方解析库,完全使用 Python 语言编写,它对 XPath表达式提供了良好的支 持,因此能够了高效地解析 HTML/XML 文档。本节讲解如何通过 lxml 库解析 HTML 文档。 pip install lxml lxm| 库提供了一个 etree 模块,该模块专门用来解析 HTML/XML 文档,下面来介绍一下 lxml 库

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

poj 3104 二分答案

题意: n件湿度为num的衣服,每秒钟自己可以蒸发掉1个湿度。 然而如果使用了暖炉,每秒可以烧掉k个湿度,但不计算蒸发了。 现在问这么多的衣服,怎么烧事件最短。 解析: 二分答案咯。 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <c

OWASP十大安全漏洞解析

OWASP(开放式Web应用程序安全项目)发布的“十大安全漏洞”列表是Web应用程序安全领域的权威指南,它总结了Web应用程序中最常见、最危险的安全隐患。以下是对OWASP十大安全漏洞的详细解析: 1. 注入漏洞(Injection) 描述:攻击者通过在应用程序的输入数据中插入恶意代码,从而控制应用程序的行为。常见的注入类型包括SQL注入、OS命令注入、LDAP注入等。 影响:可能导致数据泄

从状态管理到性能优化:全面解析 Android Compose

文章目录 引言一、Android Compose基本概念1.1 什么是Android Compose?1.2 Compose的优势1.3 如何在项目中使用Compose 二、Compose中的状态管理2.1 状态管理的重要性2.2 Compose中的状态和数据流2.3 使用State和MutableState处理状态2.4 通过ViewModel进行状态管理 三、Compose中的列表和滚动

Spring 源码解读:自定义实现Bean定义的注册与解析

引言 在Spring框架中,Bean的注册与解析是整个依赖注入流程的核心步骤。通过Bean定义,Spring容器知道如何创建、配置和管理每个Bean实例。本篇文章将通过实现一个简化版的Bean定义注册与解析机制,帮助你理解Spring框架背后的设计逻辑。我们还将对比Spring中的BeanDefinition和BeanDefinitionRegistry,以全面掌握Bean注册和解析的核心原理。

CSP 2023 提高级第一轮 CSP-S 2023初试题 完善程序第二题解析 未完

一、题目阅读 (最大值之和)给定整数序列 a0,⋯,an−1,求该序列所有非空连续子序列的最大值之和。上述参数满足 1≤n≤105 和 1≤ai≤108。 一个序列的非空连续子序列可以用两个下标 ll 和 rr(其中0≤l≤r<n0≤l≤r<n)表示,对应的序列为 al,al+1,⋯,ar​。两个非空连续子序列不同,当且仅当下标不同。 例如,当原序列为 [1,2,1,2] 时,要计算子序列 [

多线程解析报表

假如有这样一个需求,当我们需要解析一个Excel里多个sheet的数据时,可以考虑使用多线程,每个线程解析一个sheet里的数据,等到所有的sheet都解析完之后,程序需要提示解析完成。 Way1 join import java.time.LocalTime;public class Main {public static void main(String[] args) thro

ZooKeeper 中的 Curator 框架解析

Apache ZooKeeper 是一个为分布式应用提供一致性服务的软件。它提供了诸如配置管理、分布式同步、组服务等功能。在使用 ZooKeeper 时,Curator 是一个非常流行的客户端库,它简化了 ZooKeeper 的使用,提供了高级的抽象和丰富的工具。本文将详细介绍 Curator 框架,包括它的设计哲学、核心组件以及如何使用 Curator 来简化 ZooKeeper 的操作。 1

Unity3D自带Mouse Look鼠标视角代码解析。

Unity3D自带Mouse Look鼠标视角代码解析。 代码块 代码块语法遵循标准markdown代码,例如: using UnityEngine;using System.Collections;/// MouseLook rotates the transform based on the mouse delta./// Minimum and Maximum values can