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

相关文章

深度解析Spring Security 中的 SecurityFilterChain核心功能

《深度解析SpringSecurity中的SecurityFilterChain核心功能》SecurityFilterChain通过组件化配置、类型安全路径匹配、多链协同三大特性,重构了Spri... 目录Spring Security 中的SecurityFilterChain深度解析一、Security

全面解析Golang 中的 Gorilla CORS 中间件正确用法

《全面解析Golang中的GorillaCORS中间件正确用法》Golang中使用gorilla/mux路由器配合rs/cors中间件库可以优雅地解决这个问题,然而,很多人刚开始使用时会遇到配... 目录如何让 golang 中的 Gorilla CORS 中间件正确工作一、基础依赖二、错误用法(很多人一开

Mysql中设计数据表的过程解析

《Mysql中设计数据表的过程解析》数据库约束通过NOTNULL、UNIQUE、DEFAULT、主键和外键等规则保障数据完整性,自动校验数据,减少人工错误,提升数据一致性和业务逻辑严谨性,本文介绍My... 目录1.引言2.NOT NULL——制定某列不可以存储NULL值2.UNIQUE——保证某一列的每一

深度解析Nginx日志分析与499状态码问题解决

《深度解析Nginx日志分析与499状态码问题解决》在Web服务器运维和性能优化过程中,Nginx日志是排查问题的重要依据,本文将围绕Nginx日志分析、499状态码的成因、排查方法及解决方案展开讨论... 目录前言1. Nginx日志基础1.1 Nginx日志存放位置1.2 Nginx日志格式2. 499

MySQL CTE (Common Table Expressions)示例全解析

《MySQLCTE(CommonTableExpressions)示例全解析》MySQL8.0引入CTE,支持递归查询,可创建临时命名结果集,提升复杂查询的可读性与维护性,适用于层次结构数据处... 目录基本语法CTE 主要特点非递归 CTE简单 CTE 示例多 CTE 示例递归 CTE基本递归 CTE 结

Spring Boot 3.x 中 WebClient 示例详解析

《SpringBoot3.x中WebClient示例详解析》SpringBoot3.x中WebClient是响应式HTTP客户端,替代RestTemplate,支持异步非阻塞请求,涵盖GET... 目录Spring Boot 3.x 中 WebClient 全面详解及示例1. WebClient 简介2.

在MySQL中实现冷热数据分离的方法及使用场景底层原理解析

《在MySQL中实现冷热数据分离的方法及使用场景底层原理解析》MySQL冷热数据分离通过分表/分区策略、数据归档和索引优化,将频繁访问的热数据与冷数据分开存储,提升查询效率并降低存储成本,适用于高并发... 目录实现冷热数据分离1. 分表策略2. 使用分区表3. 数据归档与迁移在mysql中实现冷热数据分

C#解析JSON数据全攻略指南

《C#解析JSON数据全攻略指南》这篇文章主要为大家详细介绍了使用C#解析JSON数据全攻略指南,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、为什么jsON是C#开发必修课?二、四步搞定网络JSON数据1. 获取数据 - HttpClient最佳实践2. 动态解析 - 快速

Spring Boot3.0新特性全面解析与应用实战

《SpringBoot3.0新特性全面解析与应用实战》SpringBoot3.0作为Spring生态系统的一个重要里程碑,带来了众多令人兴奋的新特性和改进,本文将深入解析SpringBoot3.0的... 目录核心变化概览Java版本要求提升迁移至Jakarta EE重要新特性详解1. Native Ima

spring中的@MapperScan注解属性解析

《spring中的@MapperScan注解属性解析》@MapperScan是Spring集成MyBatis时自动扫描Mapper接口的注解,简化配置并支持多数据源,通过属性控制扫描路径和过滤条件,利... 目录一、核心功能与作用二、注解属性解析三、底层实现原理四、使用场景与最佳实践五、注意事项与常见问题六