姓名:王希豪
学号:1600012794
讨论班班号:21
教师:曹东刚
助教:金典
void sigchld_handler(int sig);
void sigchld_handler(int sig) {
int olderrno = errno;
sigset_t mask_all, prev_all;
pid_t pid;
Sigfillset(&mask_all);
while ((pid = waitpid(-1, NULL, 0)) > 0) {
Sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
deletejob(pid);
Sigprocmask(SIG_SETMASK, &prev_all, NULL);
}
if (errno != ECHILD)
Sio_error("waitpid error");
errno = olderrno;
}
int main(int argc, char **argv)
{
int listenfd, connfd;
char hostname[MAXLINE], port[MAXLINE];
socklen_t clientlen;
struct sockaddr_storage clientaddr;
/* Check command line args */
if (argc != 2) {
fprintf(stderr, "usage: %s <port>\n", argv[0]);
exit(1);
}
Signal(SIGCHLD, sigchld_handler);
listenfd = Open_listenfd(argv[1]);
while (1) {
clientlen = sizeof(clientaddr);
connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen); //line:netp:tiny:accept
Getnameinfo((SA *) &clientaddr, clientlen, hostname, MAXLINE,
port, MAXLINE, 0);
printf("Accepted connection from (%s, %s)\n", hostname, port);
doit(connfd); //line:netp:tiny:doit
Close(connfd); //line:netp:tiny:close
}
}
同时还需要删去serve_dynamic
函数体中最后一行的Wait(NULL);
/*
* serve_static - copy a file back to the client
*/
/* $begin serve_static */
void serve_static(int fd, char *filename, int filesize)
{
int srcfd;
char *srcp, filetype[MAXLINE], buf[MAXBUF];
/* Send response headers to client */
get_filetype(filename, filetype); //line:netp:servestatic:getfiletype
sprintf(buf, "HTTP/1.0 200 OK\r\n"); //line:netp:servestatic:beginserve
sprintf(buf, "%sServer: Tiny Web Server\r\n", buf);
sprintf(buf, "%sConnection: close\r\n", buf);
sprintf(buf, "%sContent-length: %d\r\n", buf, filesize);
sprintf(buf, "%sContent-type: %s\r\n\r\n", buf, filetype);
Rio_writen(fd, buf, strlen(buf)); //line:netp:servestatic:endserve
printf("Response headers:\n");
printf("%s", buf);
/* Changed code */
/* Send response body to client */
srcfd = Open(filename, O_RDONLY, 0); //line:netp:servestatic:open
srcp = (char*)malloc(filesize); //line:netp:servestatic:malloc
Rio_readn(srcfd, srcp, filesize); //line:netp:servestatic:read
Close(srcfd); //line:netp:servestatic:close
Rio_writen(fd, srcp, filesize); //line:netp:servestatic:write
Free(srcp); //line:netp:servestatic:free
}
第一题:有误
第二题:正确
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <setjmp.h>
#define N 1000
FILE* fin;
int pid;
char file_name[N];
char s[N];
char t[N];
int* start; // start of a block
int* end; // end of a block
int page_size; // page size
int p;
jmp_buf buf;
long a, b;
char c;
void sigsegv_handler(int sig) {
siglongjmp(buf, 1);
}
void init() {
int i;
pid = getpid();
sprintf(file_name, "/proc/%d/maps", pid);
fin = fopen(file_name, "r");
page_size = sysconf(_SC_PAGESIZE);
for (i = page_size; i != 1; i >>= 1, p++);
signal(SIGSEGV, sigsegv_handler);
}
int main() {
init();
while (fscanf(fin, "%lx-%lx", &a, &b) == 2) {
while ((c = fgetc(fin)) != '\n');
start = (int*)a;
end = (int*)b;
for (; start < end; start += page_size) {
if (sigsetjmp(buf, 1) == 0) {
int tmp = *start;
printf("%lx\n", (long)start >> p);
}
}
}
return 0;
}
提交时间:2017-11-26
解:
进程对 | 并发 |
---|---|
AB | 否 |
AC | 是 |
AD | 是 |
BC | 是 |
BD | 是 |
CD | 是 |
解:
ACE
说明:
设该程序开始时的进程号为1,第10行后,fork出进程2且将void end(void)函数添加到exit函数列表中。
之后,进程1和进程2各自fork出一个进程(设分别为进程3和进程4),其中,进程4和进程2一样,在exit函数列表中有void end(void)函数。
进程1只会输出一个1,进程2会先后输出1和2,进程3只会输出一个0,进程4会先后输出0和2。
A选项按照进程1、进程2、进程3、进程4的顺序一次性输出,即可得。
B选项2不可能是最先输出的,所以不可能出现。
C选项按照进程1、进程4、进程2、进程3的顺序一次性输出可得。
D选项,因为2是在第2、4进程的第2个位输出的,所以,第二个2至少位于输出的第四位,而该选项中,第二个2在输出的第三位,不可能出现。
E选项按照进程1、进程3、进程4、进程2的顺序一次性输出可得。
注:为了方便期间,我此处假设恰好是一个进程输出完之后才轮到另外一个进程,但实际过程中,由于并发的原因,可能会出现进程2输出一个数字后,进程1输出一个数字,再进程2输出另外一个数字类似的情况,并不一定会恰好等一个进程输出完了才轮到另一个进程。
#include "csapp.h"
#define N 2
int main()
{
int status, i;
pid_t pid;
/* Parent creates N children */
for (i = 0; i < N; i++)
if ((pid = Fork()) == 0) { /* Child */
int* data;
data[0] = 0;
}
/* Parent reaps N children in no particular order */
while ((pid = waitpid(-1, &status, 0)) > 0) {
if (WIFEXITED(status))
printf("child %d terminated normally with exit status=%d\n",
pid, WEXITSTATUS(status));
else if (WIFSIGNALED(status)) {
char buf[100];
memset(buf, 0, sizeof(buf));
sprintf(buf, "child %d terminated by signal %d", pid, WTERMSIG(status));
psignal(WTERMSIG(status), buf);
}
else
printf("child %d terminated abnormally\n", pid);
}
/* The only normal termination is if there are no more children */
if (errno != ECHILD)
unix_error("waitpid error");
exit(0);
}
解:
输出是"fd2 = 4\n"
(不包括引号)
说明:
标准输入输出STDIN、STDOUT、STDERR分别占据了0、1、2号文件描述符,程序第7行打开foo.txt文件,占用了3号文件描述符,第4行打开bar.txt文件,占用了4号文件描述符。
第9行关闭了bar.txt文件,释放了4号描述符,第10行打开baz.txt文件时,系统会自动分配最小的没有被用的文件描述符的编号(此处为4),所以fd2的值为4,所以输出为"fd2 = 4\n"
if (Fork() == 0) { /* child */
int fd = Open(argv[3], O_RDONLY, 0);
Dup2(fd, STDIN_FILENO);
Execve("fstatcheck", argv, envp);
}
第一题:正确
第二题:正确
第三题:正确
第四题:正确
第五题:close(fd)
订正:
if (Fork() == 0) { /* child */
int fd = Open(argv[3], O_RDONLY, 0);
Dup2(fd, STDIN_FILENO);
close(fd);
Execve("fstatcheck", argv, envp);
}
提交时间:2017-11-19
解:
删去x = -0.0;
,将代码改成如下所示:
符号 | swap.o.symtab条目 | 符号类型 | 定义符号的模块 | 节 |
---|---|---|---|---|
buf | 是 | 全局 | m.o | .data |
bufp0 | 是 | 全局 | swap.o | .data |
bufp1 | 是 | 局部 | swap.o | .symtab |
swap | 是 | 全局 | swap.o | .text |
temp | 否 | -- | -- | -- |
incr | 是 | 局部 | swap.o | .text |
count | 是 | 局部 | swap.o | .symtab |
解:
删去x = -0.0;
,将代码改成如下所示:
double x;
void f()
{
}
解:
A. callq指令对swap的重定位引用的值是0xa
B. callq指令对swap的重定位引用的值是0x22
说明:
A.
refaddr = ADDR(.text) + r.offset = 0x4004e0 + 0xa = 0x4004ea
*refptr = (unsigned)(ADDR(r.symbol) + r.addend - refaddr)
= (unsigned)(0x4004f8 + (-4) - 0x4004ea)
= (unsigned)(0xa)
B.
refaddr = ADDR(.text) + r.offset = 0x4004d0 + 0xa = 0x4004da
*refptr = (unsigned)(ADDR(r.symbol) + r.addend - refaddr)
= (unsigned)(0x400500 + (-4) - 0x4004da)
= (unsigned)(0x22)
解:
A. libc.a包含1579个目标文件,libm.a包含471个目标文件
B. 一样。可重定向文件是不同的,加了-g指令的,有debug数据,但是可执行文件是一样的
C.
linux-vdso.so.1 => (0x00007ffcd79f8000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f060142a000)
/lib64/ld-linux-x86-64.so.2 (0x000055760b449000)
第一题:有三处错误
第二题:正确
第三题:正确
第四题:正确
订正:
符号 | swap.o.symtab条目 | 符号类型 | 定义符号的模块 | 节 |
---|---|---|---|---|
buf | 是 | 全局 | m.o | .data |
bufp0 | 是 | 全局 | swap.o | .data |
bufp1 | 是 | 局部 | swap.o | .bss |
swap | 是 | 全局 | swap.o | .text |
temp | 否 | -- | -- | -- |
incr | 是 | 局部 | swap.o | .text |
count | 是 | -- | swap.o | .bss |
提交时间:2017-10-29
解:
A.
/* Bubble sort: Pointer version */
void bubble_b(long *data, long count) {
long last;
long length = count - 1;
long* pointer;
long* next;
for (last = count - 1; last > 0; last--) {
pointer = data;
while ((pointer - data) != length) {
next = pointer + 1;
if ((*next) < (*pointer)) {
long t = *next;
*next = *pointer;
*pointer = t;
}
pointer = next;
}
}
}
B.
/* $begin code-yso */
/* $begin code-ysa */
#Execution begins at address 0
.pos 0
init: irmovl Stack, %esp
irmovl Stack, %ebp
call Main
halt
.align 4
# Array for test
data:
.long 0x5
.long 0x4
.long 0x3
.long 0x2
.long 0x1
.long 0x0
Main: pushl %ebp
rrmovl %esp, %ebp
irmovl data, %edx
irmovl $6, %esi
call bubble_b
rrmovl %ebp, %esp
popl %ebp
ret
# void bubble_b(long* data, long count)
# edx = data
# esi = count
bubble_b:
pushl %ebx
pushl %ecx
pushl %edi
pushl %ebp
rrmovl %esi, %eax # eax = count
irmovl $1, %ebx # ebx = 1
subl %ebx, %eax # eax = count - 1
addl %eax, %eax # eax = 2 * (count - 1)
addl %eax, %eax # eax = 4 * (count - 1)
rrmovl %esi, %ecx # ecx = count
Loop1: irmovl $1, %ebx
subl %ebx, %ecx # ecx--
jle End # If ecx <= 0, go to End
rrmovl %edx, %edi # edi = data
Loop2: rrmovl %edi, %ebp # ebp = pointer
subl %edx, %ebp # ebp = pointer - data
subl %eax, %ebp # ebp = pointer - data - eax
je Loop1 # If pointer - data == count - 1, go to Loop1
mrmovl (%edi), %ebx # ebx = *pointer
mrmovl 4(%edi), %ebp # ebp = *next
subl %ebp, %ebx # ebx = *pointer - *next
jle Next # If *next >= *pointer
mrmovl (%edi), %ebx # swap(*pointer, *next)
mrmovl 4(%edi), %ebp
rmmovl %ebx, 4(%edi)
rmmovl %ebp, (%edi)
Next: irmovl $4, %ebx
addl %ebx, %edi # pointer++
jmp Loop2
End: popl %ebp
popl %edi
popl %ecx
popl %ebx
# The stack starts here and grows to lower addresses
.pos 0x400
Stack:
/* $end code-ysa */
/* $end code-yso */
解:
1. 对于预测错误的情况,改正下条指令的f_pc值
####################################################################
# 当前的f_pc会被上条JXX指令决定的
# 但是现在的valP和valC不是上条JXX指令的valP和valC
# 为了选择分支,需要将JXX指令的valP和valC一直传递下去
# 其中,一个存在M_valA中(不妨让valP存在M_valA中)
# 另一个,通过ALU,存在M_valE中(不妨让valC存在M_valE中)
#
# 下面这处修改主要是在预测错误的情况下,改正f_p
#
# M_icode == IJXX && !M_Cnd : M_valA ->
# M_icode == IJXX && M_ifun != UNCOND &&
# && M_valE < M_valA && !M_Cnd : M_valA; (1)
# M_icode == IJXX && M_ifun != UNCOND &&
# && M_valE >= M_valA && M_Cnd : M_valE; (2)
#
# M_ifun != UNCOND 说明当前的JXX指令不是无条件跳转
# 无条件跳转预测为选择分支,不可能错误,所以不需要修改pc值
#
# (1)式中,M_valE < M_valA 说明我们预测选择分支
# !M_Cnd 说明条件不成立,即不应该选择分支
# 所以把f_pc的值改为 M_valA 即 valP
#
# (2)式中,M_valE >= M_valA 说明我们预测不选择分支
# M_Cnd 说明条件成立,即需要选择分支
# 所以把f_pc的值改为 M_valE 即 valC
#
####################################################################
## What address should instruction be fetched at
int f_pc = [
# Mispredicted branch. Fetch at incremented PC
M_icode == IJXX && M_ifun != UNCOND && M_valE < M_valA && !M_Cnd : M_valA;
M_icode == IJXX && M_ifun != UNCOND && M_valE >= M_valA && M_Cnd : M_valE;
# Completion of RET instruction.
W_icode == IRET : W_valM;
# Default: Use predicted value of PC
1 : F_predPC;
];
2. 如果当前指令是条件跳转指令,则预测下一条指令的pc值
####################################################################
#
# 预测PC值
#
# f_icode in { IJXX, ICALL } : f_valC; ->
# f_icode == ICALL : f_valC;
# f_icode == IJXX &&
# (f_ifun == UNCOND || f_valC < f_valP) : f_valC;
#
# 对于指令是IJXX的情况
# 如果f_ifun是UNCOND,即指令是无条件转移,那么预测为选择分支
# 如果f_ifun是条件转移,如果valC < valP,那么预测为选择分支
# 其他情况,都预测为不选择分支
#
####################################################################
# Predict next value of PC
int f_predPC = [
# BBTFNT: This is where you'll change the branch prediction rule
f_icode == ICALL : f_valC;
f_icode == IJXX && (f_ifun == UNCOND || f_valC < f_valP) : f_valC;
1 : f_valP;
];
3. 通过ALU
传递valC
值
####################################################################
#
# 为了在Memory阶段获取valC,需要将valC通过ALU得到M_valE
# 即,M_valE = E_valC + 0
#
# E_icode in { IIRMOVL, IRMMOVL, IMRMOVL } : E_valC; ->
# E_icode in { IIRMOVL, IRMMOVL, IMRMOVL, IJXX } : E_valC;
#
####################################################################
## Select input A to ALU
int aluA = [
E_icode in { IRRMOVL, IOPL } : E_valA;
E_icode in { IIRMOVL, IRMMOVL, IMRMOVL, IJXX } : E_valC;
E_icode in { ICALL, IPUSHL } : -4;
E_icode in { IRET, IPOPL } : 4;
# Other instructions don't need ALU
];
####################################################################
#
# E_icode in { IRRMOVL, IIRMOVL } : 0; ->
# E_icode in { IRRMOVL, IIRMOVL, IJXX } : 0;
#
####################################################################
## Select input B to ALU
int aluB = [
E_icode in { IRMMOVL, IMRMOVL, IOPL, ICALL,
IPUSHL, IRET, IPOPL } : E_valB;
E_icode in { IRRMOVL, IIRMOVL } : 0;
# Other instructions don't need ALU
];
4. 异常情况处理
####################################################################
#
# 如果分支预测错误,需要在D阶段和E阶段加气泡
#
# (E_icode == IJXX && !e_Cnd) || ->
# (E_icode == IJXX && E_ifun != UNCOND &&
# ((E_valC < E_valA && !e_Cnd) ||
# (E_valC >= E_valA && e_Cnd))) ||
#
# 如果当前指令是条件转移且预测错误了,那么需要加气泡
#
####################################################################
bool D_bubble =
# Mispredicted branch
(E_icode == IJXX && E_ifun != UNCOND &&
((E_valC < E_valA && !e_Cnd) || (E_valC >= E_valA && e_Cnd))) ||
# BBTFNT: This condition will change
# Stalling at fetch while ret passes through pipeline
# but not condition for a load/use hazard
!(E_icode in { IMRMOVL, IPOPL } && E_dstM in { d_srcA, d_srcB }) &&
IRET in { D_icode, E_icode, M_icode };
# Should I stall or inject a bubble into Pipeline Register E?
# At most one of these can be true.
bool E_stall = 0;
bool E_bubble =
# Mispredicted branch
(E_icode == IJXX && E_ifun != UNCOND &&
((E_valC < E_valA && !e_Cnd) || (E_valC >= E_valA && e_Cnd))) ||
# BBTFNT: This condition will change
# Conditions for a load/use hazard
E_icode in { IMRMOVL, IPOPL } &&
E_dstM in { d_srcA, d_srcB};
解:
B. 3.0
C. 1.0
D. 每次的浮点乘法操作都与寄存器%rcx
有关,%rcx
每次的add
操作的延迟是1,而浮点乘法的发射时间也是1,所以浮点乘法可以通过流水线的方式,连续不断地运算下去,平均每2.5个周期可以算好一次(因为只有两个单元支持浮点乘法)。而关键路径上的浮点加法的延迟是3,浮点加法前后有关联,必须等上次计算结果出来之后才能进行下一次计算,且计算机也只有一个单元支持浮点加法,所以,浮点加法的延迟影响了CPE,使得CPE为3.0
解:
Code:
/* 6*1循环展开 */
void inner5(vec_ptr u, vec_ptr v, data_t *dest) {
long i;
long length = vec_length(u);
data_t *udata = get_vec_start(u);
data_t *vdata = get_vec_start(v);
data_t sum = (data_t) 0;
for (i = 0; i < length; i += 6) {
sum = sum + udata[i] * vdata[i];
sum = sum + udata[i + 1] * vdata[i + 1];
sum = sum + udata[i + 2] * vdata[i + 2];
sum = sum + udata[i + 3] * vdata[i + 3];
sum = sum + udata[i + 4] * vdata[i + 4];
sum = sum + udata[i + 5] * vdata[i + 5];
}
*dest = sum;
}
A. 因为该机器的功能单元中只有两个单元支持加载,且延迟为1。而该程序中,每一次都要加载udata和vdata的一个值,所以,不可能达到比1.0更小的CPE了。
B. 该机器的功能单元中,只有一个单元支持浮点加法运算。无论该程序怎么循环展开,都会受到浮点加法延迟的限制,所以浮点数据的CPE下界为3.0,不可能通过循环展开而得到提高了。
第一题:正确
第二题:正确
第三题:正确
第四题:代码有误
订正:
/* 6*1循环展开 */
void inner5(vec_ptr u, vec_ptr v, data_t *dest) {
long i;
long length = vec_length(u);
data_t *udata = get_vec_start(u);
data_t *vdata = get_vec_start(v);
data_t sum = (data_t) 0;
for (i = 0; i + 6 < length; i += 6) {
sum = sum + udata[i] * vdata[i];
sum = sum + udata[i + 1] * vdata[i + 1];
sum = sum + udata[i + 2] * vdata[i + 2];
sum = sum + udata[i + 3] * vdata[i + 3];
sum = sum + udata[i + 4] * vdata[i + 4];
sum = sum + udata[i + 5] * vdata[i + 5];
}
for (; i < length; i++)
sum = sum + udata[i] * vdata[i];
*dest = sum;
}
提交时间:2017-10-12
解:
首先,由汇编代码第四行的movq %rac, 184(%rdi)
和对齐原则可知,str1
中int x[A][B]
所占内存大小大于176,小于等于184。所以,A * B > 44
且A * B <= 46
。
之后,根据汇编代码的第二行movslq 8(%rsi), %rax
可知,str2的第一个元素char array[B]
的长度小于等于8,再根据对齐原则可知该数组的长度大于4。即B > 4
且B <= 8
。
再根据汇编代码的第三行addq 32(%rsi), %rax
可知,array[B]
、t
和s[A]
总共占了32字节的空间,所以,s[A]
占了20字节的空间。根据对齐原则,A > 6
且A <= 10
。
综上解得A = 9
,B = 5
解:
A. 根据汇编代码第一句mov 0x120(%rsi), %ecx
和函数test()
第一行int n = bp->first + bp->last;
可知bp->last
的首地址为%rsi + 0x120
,所以数组a[CNT]
和变量first
共占用288字节的内存。
由汇编代码的第三行和第四行可知,a_struct占用内存大小为40字节,所以CNT = (288 - 4) / 40 = 7
B. 由函数第三行ap->x[ap->idx] = n
可知结构体a_struct包含数组x和变量idx。由汇编代码的第八行可知,x数组和变量idx的数据类型大小都是8字节,所以x和idx的数据类型是long,且idx在x之前定义。所以x的长度为(40 - 8) / 8 = 4
所以,结构体为
typedef struct {
long idx;
long x[4];
} a_struct;
解:
A.
变量 | 偏移量 |
---|---|
e1.p | 0字节 |
e1.y | 8字节 |
e2.x | 0字节 |
e2.next | 8字节 |
B. 16字节
C.
void proc(union ele* up) {
up->e2.x = *(up->e2.next->e1.p) - up->e2.next->e1.y;
}
第一题:正确
第二题:正确
第三题:正确
提交时间:2017-10-2
解:
A.
寄存器 | 变量 |
---|---|
%rdi | x |
%rsi %rcx | n |
%rax | result |
%rdx | mask |
B.
变量 | 初始值 |
---|---|
result | 0 |
mask | 1 |
C. mask != 0
D. mask = mask << (n & 0xff)
E. result |= (x & mask)
F.
long loop(long x, int n)
{
long result = 0;
long mask;
for (mask = 1; mask != 0; mask = mask << (n & 0xff)) {
result |= (x & mask);
}
return result;
}
说明:
A. 汇编代码第二行movl %esi, %ecx
把%esi
中的值赋给了%ecx
,所以,%rsi
和%rcx
中都存储了变量n
的值
D. 汇编代码第十行,%rdx
左移了%cl
位,%cl
是%rcx
的最低8位,所以mask
变量修改的时候,应该是左移n & 0xff
位,而不是直接左移n
位
解:
/* Enumerated type creates set of constants numbered 0 and upward */
typedef enum {MODE_A, MODE_B, MODE_C, MODE_D, MODE_E} mode_t;
/* p1 in %rdi, p2 in %rsi, action in %edx */
long switch3(long *p1, long *p2, mode_t action)
{
/* movq $0, %rax */
long result = 0;
switch(action) {
case MODE_A:
/* movq (%rsi), %rax */
result = *p2;
/* movq (%rdi), %rdx
* movq %rdx, (%rsi) */
*p2 = *p1;
/* 或者写成
* action = *p1;
* *p2 = action; */
break;
case MODE_B:
/* movq (%rdi), %rax
* addq (%rsi), %rax */
result = *p1 + *p2;
/* 或者写成
* result = *p1;
* result += *p2; */
/* movq %rax, (%rdi) */
*p1 = result;
break;
case MODE_C:
/* movq $59, (%rdi) */
*p1 = 59;
/* movq (%rsi), %rax */
result = *p2;
break;
case MODE_D:
/* movq (%rsi), %rax
* movq %rax, (%rdi) */
*p1 = *p2;
/* 或者写成
* result = *p2;
* *p1 = result; */
/* movl $27, %eax */
result = 27;
break;
case MODE_E:
/* movl $27, %eax */
result = 27;
break;
default:
/* movl $12, %eax */
result = 12;
}
return result;
}
说明:
MODE_B和MODE_D中的第一二步操作都修改了存储返回值的寄存器%rax
中的值,但因为之后%rax
被重新赋值了,所以将两步操作合成一步操作之后,并没有影响返回值。
MODE_A中的第二三步操作修改了寄存器%rdx
中的值,因为%rdx
作为传入的参数,在之后不会再被使用,且函数返回之后,将会被运行时栈中的数据覆盖,所以也可以将两步合成为一步。
解:
A. &A[i][j][k] = x_A + L * (S * T * i + T * j + k)
B. R = 7; S = 5; T = 13;
说明:
A. 公式中,x_A
为数组A[R][S][T]
在内存中的起始位置(基地址);L
为数据类型long
以字节为单位的大小,在32位系统中L = 4
,在64位系统中L = 8
B.
store_ele:
leaq (%rsi, %rsi, 2), %rax // 计算3j
leaq (%rsi, %rax, 4), %rax // 计算4 * 3j + j = 13j
movq %rdi, %rsi
salq $6, %rsi // 计算64i
addq %rsi, %rdi // 计算64i + i = 65i
addq %rax, %rdi // 计算65i + 13j
addq %rdi, %rdx // 计算65i + 13j + k
movq A(, %rdx, 8), %rax // 取得A[i][j][k]的值
movq %rax, (%rcx)
movl $3640, %eax // 返回sizeof(A)
ret
从汇编代码中可以发现,数组元素A[i][j][k]
的求地址公式为x_A + 8 * (65 * i + 13 * j + k)
,所以,S * T = 65
且T = 13
,解得S = 5
又因为sizeof(A) = 3640
,即数组A在内存中占用的空间为3640字节,也就是说,8 * R * S * T = 8640
,解得R = 7
第一题:正确
第二题:正确
第三题:正确
提交时间:2017-9-24
解:
格式A(位) | 格式A(值) | 格式B(位) | 格式B(值) |
---|---|---|---|
1 01110 001 | -9/16 | 1 0110 0010 | -9/16 |
0 10110 101 | 208 | 0 1110 1010 | 208 |
1 00111 110 | -7/1024 | 1 0000 0111 | -7/1024 |
0 00000 101 | 5/131072 | 0 0000 0001 | 1/1024 |
1 11011 000 | -4096 | 1 1110 1111 | -248 |
0 11000 100 | 768 | 0 1111 0000 | +∞ |
说明:
第四行中,B格式的浮点数,与5/131072最接近的理应是0(0 0000 0000),但是由于要求向+∞取整,所以答案是1/1024(0 0000 0001)
同理,第五行中,B格式的浮点数理应是-∞(1 1111 0000),但是由于要求向+∞取整,所以答案是-248(1 1110 1111)
解:
float_bits float_negate(float_bits f) {
unsigned exp = (f >> 23) & 0xff;
unsigned frac = f & 0x7fffff;
if (exp == 0xff && frac != 0)
return f;
else return f ^ (1 << 31);
}
说明:
float类型的二进制形式(一共32位)由1位符号位+8位阶码+23位尾数组成。首先单独提取出阶码和尾数,如果阶码全为1且尾数不全为0,则表示是NaN,那么返回f,否则将f的符号位取反并返回。
// 测试代码
float_bits negate(float_bits f) {
float t = 1.0;
unsigned char* start = (unsigned char*) &t;
size_t i;
for (i = 0; i < sizeof(float); i++) { // 直接对内存进行修改,经测试,本机是小端法存储数值
start[i] = f & 0xff;
f = f >> 8;
}
if (!isnan(t)) t = -t; // 判断是不是nan
for (i = sizeof(float) - 1; (int)i >= 0; i--) // size_t不类型转换为int再和0比较会出错
f = (f << 8) | start[i]; // 读取内存从而获得二进制码
return f;
}
解答
int float_f2i(float_bits f) {
unsigned sign = (f >> 31) & 1;
unsigned exp = (f >> 23) & 0xff;
unsigned frac = f & 0x7fffff;
int ans = 0;
int x = 1; // 用于表示2 ^ i
int i = 0;
int bit = exp - 127;
if (exp >= 159) // 如果超出了范围 >= 2^32(包括NaN)
return 0x80000000;
if (exp == 158 && frac != 0) // 如果阶码是31且尾数不为0
return 0x80000000;
if (exp < 127) // 如果小于1,直接返回0
return 0;
for (i = 0; i < bit; ++i, x = x << 1) {
int t = 23 - bit + i;
if (t < 0)
continue;
if (frac & (1 << t))
ans = ans + x;
}
ans = ans + x;
return sign ? -ans : ans;
}
说明
首先通过阶码特判overflow和NaN的情况,再特判指数小于0(阶码小于127)的情况。
所得的结果是M * 2^e,在二进制下,相当于将M的小数点向右移动e位。因为是向0取整,所以舍去小数部分,计算整数部分(要加上没有存储的原先整数部分的1)的十进制值,最后再考虑符号位。
// 测试代码
int f2i(unsigned x) {
float t = 1.0;
unsigned char* start = (unsigned char*) &t;
size_t i;
for (i = 0; i < sizeof(float); i++) { // 直接对内存进行修改,经测试,本机是小端法存储数值
start[i] = x & 0xff;
x = x >> 8;
}
return (int)t;
}
第一题:正确
第二题:正确
第三题:正确
提交时间:2017-9-18
解:
A. !~x
B. !x
C. !~(~0xff|x)
//如果假设int为32位的话,也可以写成!~(0xffffff00|x)
D. !(~(INT_MAX>>7)&x) 或者 !(-1<<((sizeof(int)-1)<<3)&x)
//如果假设int为32位的话,也可以写成!(0xff000000&x)
说明:
根据优先级表,!
运算符和~
运算符属于同个优先级,但是要从右往左依次计算,所以A题的表达式计算时,将首先进行~
运算再进行!
运算。如果x所有位都是1,那么~
运算之后所有位都是0,再通过!
运算可得到1,否则将得到0。
C题和D题使用~0xff
和~(INT_MAX>>7)
是考虑到不同机器上int类型的大小可能会不同。用~(INT_MAX>>7)
来获得二进制首8位为1、其他位为0的int类型数而不用-1u>>8
是考虑到不同机器的右移运算可能是逻辑右移也可能是算数右移(虽然大多数机器中的c语言都默认无符号整型的右移运算是逻辑右移)。
解:
int int_shifts_are_arithmetic() {
return !~(-1 >> 1);
}
说明:
int类型的数,当其值为-1时,其二进制表示所有位都是1,对其进行右移1位操作,如果计算机使用算数右移,那么得到的数依旧是-1(即二进制表示下所有位都是1),如果计算机使用逻辑右移,那么得到的数会是INT_MAX
(即二进制表示下,首位为0,其他位为1),所以只要判断-1右移一位后是否还是-1就可以判断计算机使用的是算数右移还是逻辑右移
解:
int odd_ones(unsigned x) {
x = x >> 1 ^ x;
x = x >> 2 ^ x;
x = x >> 4 ^ x;
x = x >> 8 ^ x;
x = x >> 16 ^ x;
return x & 1;
}
说明:
本题是求一个数二进制表示中1的个数是否为奇数,根据异或的原理,奇数个1异或的结果为1,偶数个1异或的结果为0,所以,只要将x的所有位异或起来就可以得到答案。
首先,将x和其右移一位后的结果异或得到x',则x'的第i位相当于x的第i位和第i+1位的异或结果。
同理,将x'和其右移二位后的结果异或得到x'',则x''的第i位相当于x的第i、i+1、i+2、i+3位的异或结果。
依次计算后,最后得到的数的第0位(最低位)就是x所有二进制位的异或结果,通过&1
运算将这一位提取出来就是所求的答案。
第一题:正确
第二题:正确
第三题:正确