Skip to content
This repository has been archived by the owner on Oct 8, 2022. It is now read-only.

Latest commit

 

History

History
1169 lines (890 loc) · 33.8 KB

1600012794.md

File metadata and controls

1169 lines (890 loc) · 33.8 KB

计算机系统导论作业

姓名:王希豪

学号:1600012794

讨论班班号:21

教师:曹东刚

助教:金典


第九次作业

11.8

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);

11.9

/*
 * 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

8.9

解:

进程对 并发
AB
AC
AD
BC
BD
CD

8.18

解:

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输出另外一个数字类似的情况,并不一定会恰好等一个进程输出完了才轮到另一个进程。

8.24

#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);
}

10.6

解:

输出是"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"

10.9

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

7.6

解:

删去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

7.7

解:

删去x = -0.0;,将代码改成如下所示:

double x;

void f() 
{
}

7.12

解:

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)

7.13

解:

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

4.47

解:

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 */

4.56

解:

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};

5.13

解:

A. image1

image2

B. 3.0

C. 1.0

D. 每次的浮点乘法操作都与寄存器%rcx有关,%rcx每次的add操作的延迟是1,而浮点乘法的发射时间也是1,所以浮点乘法可以通过流水线的方式,连续不断地运算下去,平均每2.5个周期可以算好一次(因为只有两个单元支持浮点乘法)。而关键路径上的浮点加法的延迟是3,浮点加法前后有关联,必须等上次计算结果出来之后才能进行下一次计算,且计算机也只有一个单元支持浮点加法,所以,浮点加法的延迟影响了CPE,使得CPE为3.0

5.14

解:

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

3.68

解:

首先,由汇编代码第四行的movq %rac, 184(%rdi)和对齐原则可知,str1int x[A][B]所占内存大小大于176,小于等于184。所以,A * B > 44A * B <= 46

之后,根据汇编代码的第二行movslq 8(%rsi), %rax可知,str2的第一个元素char array[B]的长度小于等于8,再根据对齐原则可知该数组的长度大于4。即B > 4B <= 8

再根据汇编代码的第三行addq 32(%rsi), %rax可知,array[B]ts[A]总共占了32字节的空间,所以,s[A]占了20字节的空间。根据对齐原则,A > 6A <= 10

综上解得A = 9B = 5

3.69

解:

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;

3.70

解:

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

3.60

解:

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

3.62

解:

/* 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作为传入的参数,在之后不会再被使用,且函数返回之后,将会被运行时栈中的数据覆盖,所以也可以将两步合成为一步。

3.64

解:

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 = 65T = 13,解得S = 5

又因为sizeof(A) = 3640,即数组A在内存中占用的空间为3640字节,也就是说,8 * R * S * T = 8640,解得R = 7

第三次作业反馈

第一题:正确
第二题:正确
第三题:正确


第二次作业

提交时间:2017-9-24

2.88

解:

格式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)

2.92

解:

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;
}

2.96

解答

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

2.61

解:

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语言都默认无符号整型的右移运算是逻辑右移)。

2.62

解:

int int_shifts_are_arithmetic() {
    return !~(-1 >> 1);
}

说明:

int类型的数,当其值为-1时,其二进制表示所有位都是1,对其进行右移1位操作,如果计算机使用算数右移,那么得到的数依旧是-1(即二进制表示下所有位都是1),如果计算机使用逻辑右移,那么得到的数会是INT_MAX(即二进制表示下,首位为0,其他位为1),所以只要判断-1右移一位后是否还是-1就可以判断计算机使用的是算数右移还是逻辑右移

2.65

解:

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运算将这一位提取出来就是所求的答案。

第一次作业反馈

第一题:正确
第二题:正确
第三题:正确