contributed by < TingL7
>
1
FuncA
, FuncB
, FuncC
的作用,並且推測程式執行結果。假設條件:
malloc
總是成功而且返回的記憶體空間可讀寫
#include <stdlib.h>
#include <stdio.h>
struct node { int data; struct node *next, *prev; };
void FuncA(struct node **start, int value) {
if (!*start) {
struct node *new_node = malloc(sizeof(struct node));
new_node->data = value;
new_node->next = new_node->prev = new_node;
*start = new_node;
return;
}
struct node *last = (*start)->prev;
struct node *new_node = malloc(sizeof(struct node));
new_node->data = value;
new_node->next = *start;
(*start)->prev = new_node;
new_node->prev = last;
last->next = new_node;
}
void FuncB(struct node **start, int value) {
struct node *last = (*start)->prev;
struct node *new_node = malloc(sizeof(struct node));
new_node->data = value;
new_node->next = *start;
new_node->prev = last;
last->next = (*start)->prev = new_node;
*start = new_node;
}
void FuncC(struct node **start, int value1, int value2) {
struct node *new_node = malloc(sizeof(struct node));
new_node->data = value1;
struct node *temp = *start;
while (temp->data != value2)
temp = temp->next;
struct node *next = temp->next;
temp->next = new_node;
new_node->prev = temp;
new_node->next = next;
next->prev = new_node;
}
void display(struct node *start) {
struct node *temp = start;
printf("Traversal in forward direction \n");
for (; temp->next != start; temp = temp->next)
printf("%d ", temp->data);
printf("%d ", temp->data);
printf("\nTraversal in reverse direction \n");
struct node *last = start->prev;
for (temp = last; temp->prev != last; temp = temp->prev)
printf("%d ", temp->data);
printf("%d ", temp->data);
printf("\n");
}
int main() {
struct node *start = NULL;
FuncA(&start, 51); FuncB(&start, 48);
FuncA(&start, 72); FuncA(&start, 86);
FuncC(&start, 63, 51);
display(start);
return 0;
}
FuncA
的作用是
next
是 *start
, prev
是 last
,且 *start
沒有變動,因此新節點是安插在結尾。FuncA
:建立新節點,內容是 value
,並安插在結尾FuncB
的作用是
FuncA
差不多*start = new_node;
改變了開頭為新節點,也就是說,新節點是安插在開頭FuncB
:建立新節點,內容是 value
,並安插在開頭FuncC
的作用是
value1
的新節點value2
的節點FuncC
:找到節點內容為 value2
的節點,並在之後插入新節點,內容為 value1
在程式輸出中,訊息 Traversal in forward direction
後依序印出哪幾個數字呢?
main
中,依序建立:
start
不斷印出 next
的結果:48 51 63 72 86
在程式輸出中,訊息 Traversal in reverse direction
後依序印出哪幾個數字呢?
last
不斷印出 prev
的結果:86 72 63 51 48
2
考慮以下程式碼,推敲程式作用並分析輸出。
假設條件:
malloc
總是成功而且返回的記憶體空間可讀寫malloc()
得到的地址成嚴格單調遞增函數#include <stdio.h>
#include <stdlib.h>
/* Link list node */
struct node { int data; struct node *next; };
int FuncX(struct node *head, int *data) {
struct node *node;
for (node = head->next; node && node != head; node = node->next)
data++;
return node - head;
}
struct node *node_new(int data) {
struct node *temp = malloc(sizeof(struct node));
temp->data = data; temp->next = NULL;
return temp;
}
int main() {
int count = 0;
struct node *head = node_new(0);
head->next = node_new(1);
head->next->next = node_new(2);
head->next->next->next = node_new(3);
head->next->next->next->next = node_new(4);
printf("K1 >> %s\n", FuncX(head, &count) ? "Yes" : "No");
head->next->next->next->next = head;
printf("K2 >> %s\n", FuncX(head, &count) ? "Yes" : "No");
head->next->next->next->next->next = head->next;
printf("K3 >> %s\n", FuncX(head, &count) ? "Yes" : "No");
head->next = head->next->next->next->next->next->next->next->next;
printf("K4 >> %s\n", FuncX(head, &count) ? "Yes" : "No");
printf("K5 >> %d\n", head->next->data);
printf("count >> %d\n", count);
return 0;
}
FuncX
的作用是 (涵蓋程式執行行為的正確描述最多者)
int FuncX(struct node *head, int *data) {
struct node *node;
for (node = head->next; node && node != head; node = node->next)
(* data)++;
return node - head;
}
0
,其他非零值K1 >>
後面接的輸出為何
FuncX
回傳 0K1 >> Yes
K2 >>
後面接的輸出為何
head->next->next->next->next = head;
FuncX
回傳 1K2 >> No
K3 >>
後面接的輸出為何
head->next->next->next->next->next = head->next;
FuncX
回傳 1K3 >> No
K4 >>
後面接的輸出為何
head->next = head->next->next->next->next->next->next->next->next;
FuncX
回傳 1K4 >> No
K5 >>
後面接的輸出為何
K5 >> 0
count >>
後面接的輸出為何
count
初始值為 0FuncX
時,會傳入 count
的位址作為參數 data
,並在 FuncX
中改變 data
,但改變後的 data
不會回傳,因此實際上 count
沒有任何改變count >> 0
1
考慮以下程式碼,推敲其作用 (選出最符合行為的描述)
uint8_t Func32(uint32_t x) {
return x ? Func32(x >> 1) - 1 : 32;
}
將 Func32
改寫為以下功能等價的程式碼:
uint8_t FuncI(uint32_t x)
{
if (!x) return 32;
int n = 1;
if (!(x >> 16)) { n += 16; x <<= 16; }
if (!(x >> M1)) { n += 8; x <<= 8; }
if (!(x >> M2)) { n += 4; x <<= 4; }
if (!(x >> M3)) { n += 2; x <<= 2; }
n = n - (x >> 31);
return n;
}
x
不為 0 的話,每次就會向右移一位,且會傳值會減一,也就是 32 減去遞迴層數。x
為 0 就回傳 32if (!(x >> 16)) { n += 16; x <<= 16; }
n
加 16 ,表示 n
計算 0 的數量n = n - (x >> 31);
n
的初始值為 1 ,假設至少有一個 0 ,所以這邊由最高位的值來決定 0 的數量x
的二進位表示中,回傳開頭 (從高位到低位元) 有幾個 0,倘若 x 為 0x0,則輸出 3232 - 8
2
延伸測驗 1
,將 Func32
應用於以下程式碼,可輸入給定 64-bit 數值的 10 進位表達方式 (類似 printf
函式的 %d
格式),補完程式碼並推測其作用。
#include <stdint.h>
#include <stdio.h>
union u_qword {
struct {
uint32_t low;
uint32_t high;
} dwords;
uint64_t qword;
};
struct udiv_result {
union u_qword q;
union u_qword r;
};
static inline int Func64(uint64_t val)
{
uint32_t lo = (uint32_t) val;
uint32_t hi = (uint32_t) (val >> 32);
return hi ? Func32(hi) : 32 + Func32(lo);
}
static int do_udiv32(uint32_t dividend,
uint32_t divisor,
struct udiv_result *res)
{
/* Ensure dividend is always greater than or equal to the divisor. */
uint32_t mask = Func32(divisor) - Func32(dividend);
divisor <<= mask; /* align divisor */
mask = 1U << mask; /* align dividend */
do {
if (dividend >= divisor) {
dividend -= divisor;
res->q.dwords.low |= mask;
}
divisor >>= 1;
} while ((P1) && dividend);
res->r.dwords.low = dividend;
return 0;
}
int udiv64(uint64_t dividend, uint64_t divisor, struct udiv_result *res)
{
uint64_t mask;
uint64_t bits;
res->q.qword = res->r.qword = 0;
if (divisor == 0) { /* division by 0 ? */
res->q.qword = 0xffffffffffffffffull;
return -1;
}
if (divisor == dividend) {
res->q.qword = 1;
return 0;
}
if (divisor > dividend) {
res->r.qword = dividend;
return 0;
}
/* only 32 bit operands that the preconditions are fulfilled. */
if (!(divisor >> 32) && !(dividend >> 32))
return do_udiv32((uint32_t) dividend, (uint32_t) divisor, res);
bits = Func64(divisor) - Func64(dividend);
divisor <<= bits; /* align divisor and dividend */
mask = 1ULL << bits;
/* division loop */
do {
if (dividend >= divisor) {
dividend -= divisor;
res->q.qword |= mask;
}
divisor >>= 1;
mask >>= 1;
} while ((P2) && dividend);
res->r.qword = dividend;
return 0;
}
int main()
{
char digitbuff[20];
char *pos = digitbuff + sizeof(digitbuff);
union u_qword v; /* current value */
union u_qword nv; /* next value */
struct udiv_result d;
int64_t value = 0xCAFEBABE; /* Java classfile magic number */
v.qword = (unsigned long long) value;
while (v.dwords.high != 0) { /* process 64 bit value as long as needed */
/* determine digits from right to left */
udiv64(v.qword, 10, &d);
*--pos = d.r.dwords.low + P3;
v.qword = d.q.qword;
}
do { /* process 32 bit (or reduced 64 bit) value */
nv.dwords.low = v.dwords.low / P4;
*--pos = (v.dwords.low - (10 * nv.dwords.low)) + P5;
} while ((v.dwords.low = nv.dwords.low) != 0);
int len = digitbuff + sizeof(digitbuff) - pos;
char *p = pos;
while (len--)
putchar(*p++);
printf("\n");
return 0;
}
Func64
的作用為?
Func32
的功能為 計算輸入數值 x
的二進位表示中,回傳開頭 (從高位到低位元) 有幾個 0,倘若 x
為 0x0,則輸出 32Func64
中先將輸入的數字切成兩個 32bit 的 unsigned int,再測試 val
的前 32 是否全為 0 ,之後呼叫 Func32
得到開頭 0 的數量x
為 0x0 ,輸出64x
的二進位表示中,回傳開頭 (從高位到低位元) 有幾個 0,倘若 x
為 0x0,則輸出 64P1
= ?
static int do_udiv32(uint32_t dividend,
uint32_t divisor,
struct udiv_result *res)
{
/* Ensure dividend is always greater than or equal to the divisor. */
uint32_t mask = Func32(divisor) - Func32(dividend);
divisor <<= mask; /* align divisor */
mask = 1U << mask; /* align dividend */
do {
if (dividend >= divisor) {
dividend -= divisor;
res->q.dwords.low |= mask;
}
divisor >>= 1;
} while ((P1) && dividend);
res->r.dwords.low = dividend;
return 0;
}
do_udiv32()
的功能: 做 32 位元的除法res
與 mask 做orP1
= mask >>= 1
P2
= ?
udiv64()
與 udiv32()
在除法的方面很像,最大的不同在於多了一個變數 bits
,此變數的功能在於計算商數的位數P2
= bits--
P3
= ?
while (v.dwords.high != 0) { /* process 64 bit value as long as needed */
/* determine digits from right to left */
udiv64(v.qword, 10, &d);
*--pos = d.r.dwords.low + P3;
v.qword = d.q.qword;
}
pos
, pos
是最後要以 char 印出的結果,因此為了使結果符合 ASCII 要加上 '0'
也就是 0x30P3
= 0x30
P4
= ? P5
= ?
do { /* process 32 bit (or reduced 64 bit) value */
nv.dwords.low = v.dwords.low / P4;
*--pos = (v.dwords.low - (10 * nv.dwords.low)) + P5;
} while ((v.dwords.low = nv.dwords.low) != 0);
P4
= 10
pos
儲存結果,如同 P3
要加上 '0'
P5
= 0x30
while (v.dwords.high != 0) { /* process 64 bit value as long as needed */
/* determine digits from right to left */
udiv64(v.qword, 16, &d);
*--pos = d.r.dwords.low + ((d.r.dwords.low > 9) ? 0x37 : 0x30);
v.qword = d.q.qword;
}
do { /* process 32 bit (or reduced 64 bit) value */
nv.dwords.low = v.dwords.low / 16;
*--pos = (v.dwords.low - (16 * nv.dwords.low)) + (((v.dwords.low - (16 * nv.dwords.low)) > 9) ? 0x37 : 0x30);
} while ((v.dwords.low = nv.dwords.low) != 0);
0xCAFEBABE
為 magic number,舉出在真實世界中程式的實際作用 (提示: 如 malloc)CAFEBABE
主要做用在於辨認 file 是否是能被接受的 class filemalloc()
中也會設置 magic number,主要是因為避免 free 到沒有 malloc 的位址,藉由 magic number 設置與否就可以檢查1
已知在 x86_64 架構,以下程式碼的執行輸出是 jserv++C
:
#include <stdio.h>
int main() {
puts((char *) &(double []){ 3823806048287157.0, 96 });
}
考慮以下程式碼,假設 puts 函式總是可正確運作,那麼程式輸出為何?
#include <stdio.h>
double m[] = { 3823806048287157.0, 96 };
void gen() {
if (m[1]--) {
m[0] *= 2;
gen();
} else
puts((char *) m);
}
int main() { gen(); return 0; }
double
與 char
的轉換
3823806048287157.0
轉換成 double 的表示式為 0x432b2b767265736a
0x6a736572762b2b43
6a | 73 | 65 | 72 | 76 | 2b | 2b | 43 |
---|---|---|---|---|---|---|---|
j | s | e | r | v | + | + | C |
解讀程式
gen()
的功能為改變 m[0] ,使輸出的字串改變。實際上做的是 m[0] * 2960x432 + 0x60 = 0x492
0x43
變成 0x49
,也就是 C
變成 I
jserv++I
TingL7
#include <stdio.h>
int main() {
puts((char *) &(double []){ 3.003983e-310, 96 });
}
յugL7
#include <stdio.h>
int main() {
puts((char *) &(double []){ 3.0039829763669880E-310, 96 });
}
TingL7
0x0000374c676e6954
轉換成 0x000000000069614c
TingL7 00000000 00000000 00110111 01001100 01100111 01101110 01101001 01010100
Lai 00000000 00000000 00000000 00000000 00000000 01101001 01100001 01001110
diff 00000000 00000000 00110111 01001100 01100111 00000111 00001000 00011010
#include <stdio.h>
double m[] = { 3.0039829763669880E-310, 3.0039826351559646E-310 };
void gen() {
m[0] = m[0] - m[1];
puts((char *) m);
}
int main() { gen(); return 0; }
修改輸入,不需要轉換
程式碼
#include <stdio.h>
int main() {
puts((char *) &(double []){ 4.3456679652866146e+98, 96 });
}
#include <stdio.h>
double m[] = { 4.3456679652866146e+98, 128, 4.0279445985412390e+59 };
void gen() {
if (m[1]--) {
m[0] *= 2;
gen();
} else {
m[0] = m[0] - m[2];
puts((char *) m);
}
int main() { gen(); return 0; }
考慮某款 MIPS 實作,其 data path 如下:
上圖紅色的 1
, 2
, 3
對 MIPS 做了對應的修改,請考慮以下狀況,分別解釋對應的程式碼是否能運作。
1
1: Stuck at 0 left of cut
的修改,sw $s1, 0($s2)
能否運作?
1: Stuck at 0 left of cut
也不影響運作data memory 要 load 或 store 都需要兩個值, address 及 data ,為什麼圖片上的 data memory 只有一個輸入?
lw $s1, 0($s2)
add $s1,$2,$3
2
2: Cut here
的修改後,以下程式能否運作?
add $s1, $s1, $s1
add $s1, $t0, $t1
add $t1, $s1, $s1
sub $s1, $t0, $t1
$t1
在 sub
exe 階段時, add
的 $t1
還沒寫回 reg ,因此 $t1
的值是不正確的,修正方法便是將在 mem 階段的結果丟給 sub ,也就是 2: Cut here
修改前可以做到的事3
Q31: 做了 3: Cut here
的修改後,以下程式能否運作? (exit
為某個副程式)
cmp:
xor $t1, $s2, $s1
slt $t2, $zero, $t1
sll $t2, 2
add $ra, $ra, $t2
jr $ra
entry:
addi $s2, $zero, 2
addi $s1, $zero, 2
jal cmp
j exit
3
的線都無法做 jump ,因為在本題圖片的架構中,能夠改變 PC 的只有下一道指令 (PC+4) 以及 branch 的結果參考其他同學的答案都是可以的,如果可以運作,在題目的 MIPS 實做是如何運作呢?
Q32: 承上,以下程式能否運作?
addi $s2, $zero, 2
addi $s1, $zero, 2
beq $s2, $s1, exit
3
會無法運作5
考慮以下整數乘法的實作程式碼:
int mul(int n, int m)
{
int ret = 0;
for (int c = 0; m; c++, m /= 2)
if (!(m % 2 - 1)) OP
return ret;
}
上述 OP
對應到下方哪個程式敘述呢?
1011
* 101
---------
1011
0000
+ 1011
---------
110111
ret += n << c;
2
考慮到下方函式 shift_right_arith
和 shift_right_logical
分別表示算術右移和邏輯右移,請嘗試補完程式碼。可由 sizeof(int) * 8
得知整數型態 int
的位元數 w
,而位移量 k
的有效範圍在 0 到 w - 1
。
#include <stdio.h>
int shift_right_arith(int x, int k) {
int xsrl = (unsigned) x >> k;
int w = sizeof(int) << P1;
int mask = (int) -1 << (P2);
if (x < 0)
return xsrl P3 mask;
return xsrl;
}
unsigned shift_right_logical(unsigned x, int k) {
unsigned xsra = (int) x >> k;
int w = sizeof(int) << P4;
int mask = (int) -1 << (P5);
return xsra P6 P7;
}
P1 = ? P4 = ?
sizeof()
回傳的單位是 byte 但是 w
是 int
的位元數,因此要乘上 8* 8
等價於 `<< 3P2 = ? P5 = ?
P3 = ? (為某種 operation)
xsrl
這部份全為 0xsrl
, mask 這部份全為 0|
便能夠合成兩個變數取出需要的部份|
P6 = ? (為某種 operation) P7 = ?
return xsra & ~mask