# 2018q3 Homework3 (review)
contributed by < `happyincent` >
## Environment
```
$ $ cat <(echo "CPU: " `lscpu | grep "Model name" | cut -d':' -f2 | sed "s/ //"`) <(echo "OS: " `lsb_release -d | cut -f2`) <(echo "Kernel: " `uname -a | cut -d' ' -f1,3,14`) <(echo "gcc: " `gcc --version | head -n1`) <(echo "bash: " `bash --version | head -n1`)
CPU: Intel(R) Xeon(R) CPU E5520 @ 2.27GHz
OS: Ubuntu 16.04.5 LTS
Kernel: Linux 4.15.0-36-generic x86_64
gcc: gcc (Ubuntu 5.4.0-6ubuntu1~16.04.10) 5.4.0 20160609
bash: GNU bash, version 4.3.48(1)-release (x86_64-pc-linux-gnu)
```
## 第二周測驗 [`2`](https://hackmd.io/s/BJA6LjbK7#%E6%B8%AC%E9%A9%97-2)
解析
```c
void (*signal(int sig, void (*handler)(int))) (int);
```
#### 拆開來看
* `signal` 是一個 function (有兩個參數 `sig`, `handler`)
* handler 是 a pointer to a function (參數為 int)
* `signal` returns a pointer to a function (有一個 `int` 的參數) returns void
#### 使用 typedef
GNU extension 使用 typedef 定義 sighandler_t 這個 function pointer type
[manpage: signal(2)](http://man7.org/linux/man-pages/man2/signal.2.html)
```c
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
```
#### signal's return value
* 7.14 Signal handling ([c99](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf))
* SIG_DFL
* `default handling for that signal`
* SIG_ERR
* `If signal cannot be honored, a value of SIG_ERR is returned and a positive value is stored in errno.`
* SIG_IGN
* `the signal will be ignored`
* glibc - [Basic Signal Handling](https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling) 的 example
```c
// Note that if a given signal was previously set to be ignored,
// this code avoids altering that setting.
if (signal (SIGINT, termination_handler) == SIG_IGN)
signal (SIGINT, SIG_IGN);
```
* 小實驗
```c
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
typedef void (*sighandler_t)(int);
static void exit_with_handler_param(int param);
int main()
{
sighandler_t old = NULL;
sighandler_t stop = (sighandler_t)(exit_with_handler_param);
old = signal(SIGINT, SIG_IGN);
printf("Old Handler: SIG_DFL, addr = %p = %p = %p\n", old, SIG_DFL, (sighandler_t)0);
old = signal(SIGINT, SIG_DFL);
printf("Old Handler: SIG_IGN, addr = %p = %p = %p\n", old, SIG_IGN, (sighandler_t)1);
signal(SIGINT, stop);
old = signal(SIGINT, SIG_IGN);
printf("Old Handler: stop, addr = %p = %p\n", old, stop);
signal(SIGTSTP, old);
sleep(100);
}
static void exit_with_handler_param(int param)
{
exit(param);
}
```
```
$ $ gcc -g -Wall a.c && ./a.out
Old Handler: SIG_DFL, addr = (nil) = (nil) = (nil)
Old Handler: SIG_IGN, addr = 0x1 = 0x1 = 0x1
Old Handler: stop, addr = 0x4006d4 = 0x4006d4
^C^C^C^C^C^Z
[4]+ Stopped ./a.out
$ echo $?
20
```
* 從結果可以看到 SIGINT 預設的 handling 為 SIG_DFL
* SIGINT 做最後一次 signal 後 handling 為 SIG_IGN (old handler 為 stop)
* Ctrl-Z (SIGTSTP) 後以 echo $? 看到 ./a.out 的 exit status 為 20
```
$ cat /usr/include/x86_64-linux-gnu/bits/signum.h | grep SIGTSTP
#define SIGTSTP 20 /* Keyboard stop (POSIX). */
```
* GNU bash [Exit Status](https://www.gnu.org/software/bash/manual/html_node/Exit-Status.html)
* a fatal signal whose number is N, uses the value 128+N as the exit status.
```
$ python3 -c 'input()' || echo $?
^Z
[8]+ Stopped python3 -c 'input()'
148
```
---
## 第二周測驗 [`4`](https://hackmd.io/s/BJA6LjbK7#%E6%B8%AC%E9%A9%97-4)
考慮到以下程式碼:
```c
int B = 2;
void func(int *p) { p = &B; }
int main() {
int A = 1, C = 3;
int *ptrA = &A;
func(ptrA);
printf("%d\n", *ptrA);
return 0;
}
```
該如何修改,才能讓 func 得以變更到 main 裡頭 ptrA 的內含值?
#### 題目問題
* main 傳入 ptrA (A 的位址) 給 func
* 進到 func 後 push **p** (value 為 ptrA) 到 func 的 stack
* 在 func 內更改 p 為 global int B 的位址
* 離開 func 後,func 的 stack 連帶其中的 p 被 pop 掉
* 因此 main 中的 ptrA、&A 一直都沒有被修改
* [Scope and Lifetime of Variables in C](https://blog.feabhas.com/2010/09/scope-and-lifetime-of-variables-in-c/)
* Function parameter 為 automatic storage duration, [6.9.1](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf)
#### 修改方法
1. 變更 main 裡頭 ptrA 的內含值
```c
void func(int **p) { *p = &B; }
func(&ptrA);
```
* `p` = function stack 中存放 main 中 `ptrA` 的位址
* `*p` = main 中 ptrA (一個可以被 dereference 成 integer 的 pointer) 的值
* 更改 *p 即是更改 main 中的 ptrA
* 執行 func 後
* `ptrA` 被改為 B 的位址 (!= &A)
* `*ptrA` 等於 B,而 A (*&A) 則沒被修改
2. 若**不需變更 main 裡頭 ptrA 的內含值**且不需保留 main 中 A 的值
```c
void func(int *p) { *p = B; }
func(ptrA);
```
* `p` = function stack 中存放 main 中 integer `A` 的位址
* `*p` = main 中的 `*ptrA` = main 中的 integer `A`
* 更改 *p 即是更改 main 中的 *ptrA、A
* 執行 func 後
* `*ptrA` 等於 A 等於 B (ptrA、&A 不變)
#### GitHub 使用 the pointer to the pointer 的 C 語言程式碼
* [scrcpy](https://github.com/Genymobile/scrcpy)
* https://github.com/topics/c 中找到 scrcpy 專案 (Display and control your Android device)
* [file_handler.h](https://github.com/Genymobile/scrcpy/blob/master/app/src/file_handler.h) (部分)
```c
#define REQUEST_QUEUE_SIZE 16
struct request_queue {
struct request *reqs[REQUEST_QUEUE_SIZE];
int tail;
int head;
};
```
* [file_handler.c](https://github.com/Genymobile/scrcpy/blob/master/app/src/file_handler.c) (部分)
```c
struct request {
file_handler_action_t action;
const char *file;
};
static SDL_bool request_queue_take(struct request_queue *queue, struct request **req) {
if (request_queue_is_empty(queue)) {
return SDL_FALSE;
}
// transfer ownership
*req = queue->reqs[queue->tail];
queue->tail = (queue->tail + 1) % REQUEST_QUEUE_SIZE;
return SDL_TRUE;
}
static int run_file_handler(void *data) {
...
struct request *req;
SDL_bool non_empty = request_queue_take(&file_handler->queue, &req);
SDL_assert(non_empty);
...
request_free(req);
...
}
```
* 在 file_handler.c 中的 function `request_queue_take` 發現 the pointer to the pointer
* 因為 `struct request_queue ` 中的 `struct request *reqs[REQUEST_QUEUE_SIZE];` 是在 function `request_new` 中使用 malloc 配置的,所以程式中是使用 **指標** 在操作 request
* `run_file_handler` 中將 **indeterminate** 的指標 `req` 的位址丟入 `request_queue_take`
* `request_queue_take` 修改 `req` 的位址為 request_queue 中 tail request 的位址
* 之後操作的 `req` 便是先前 malloc 過存放在 heap 中的記憶體位址
* 因此可以用來傳入 `request_free` 進行 `free`
* 因為有更動 `req` 位址的需求,所以 call function 時需以 the pointer to the pointer 的方式傳遞參數
* 不同於修改方法 2 的情境: ptrA 為 initialized,且沒有更動 ptrA 位址的需求
---
## 第二周測驗 [`8`](https://hackmd.io/s/BJA6LjbK7#%E6%B8%AC%E9%A9%97-8)
以下程式碼改寫自 Linux 核心原始程式碼,請對照註解 (預期功能),指出實作上的問題,假設輸入 value 不會超過 32-bit。
```c
/*
* Convert a signed n-bit integer to signed 32-bit integer.
* Common cases are done through the compiler,
* the screwed things has to be done by hand.
*/
static int32_t snto32(uint32_t value, unsigned n) {
switch (n) {
case 8: return ((int8_t) value);
case 16: return ((int16_t) value);
case 32: return ((int32_t) value);
}
return value & (1 << (n - 1)) ? value | (-1 << n) : value;
}
```
#### 程式碼解讀
解讀
* Common cases
* Cast operators (6.5.4)
* Exact-width integer types (7.18.1.1)
* The typedef name `intN_t` designates a signed integer type with width N, no padding bits, and a two’s complement representation.
* These types are optional. However, if an implementation provides integer types with widths of 8, 16, 32, or 64 bits, it shall define the corresponding typedef names.
* Other cases
* `value & (1 << (n - 1))` : 找出第 n 個 bit 為 1 or 0 (負 or 正)
* 第 n 個 bit 為 1 時 : `value | (-1 << n)`
* 將第 n+1 ~ 32 bit 設為 1
* e.g., n = 4, 0xe = -2 (2's complement), `0x0000000e -> 0xfffffffe`
* 第 n 個 bit 為 0 時 : `value`
* 直接 return 傳入的 value
#### 問題
:::info
傳入 `uint32_t value` 並非 `n-bit` 且 第 n 個 bit 為 0 時,`snto32` 的 return value 不會是 n-bit
:::
* e.g., n = 4, `0x000000f7 -> 0x000000f7` (並非預期的 `0x00000007`)
* 將 return 的部分改為 `return check ? value | (~0U << n) :` **`value ^ (value & (~0U << n));`** 即可處理這個問題
* 然而在 `git log -p ./drivers/hid/hid-core.c` 找到有關 snto32 的最新改動為
* commit message: don't use negative operands when shift [[08585e4](https://github.com/torvalds/linux/commit/08585e43d22802666a466af1ca5795085e74d60d)]
```c
- return value & (1 << (n - 1)) ? value | (-1 << n) : value;
+ return value & (1 << (n - 1)) ? value | (~0U << n) : value;
```
* 在同個專案中[搜尋 snto32](https://github.com/torvalds/linux/search?q=snto32&unscoped_q=snto32),分別在三個檔案出現: `hid-core.c`, `hid-logitech-hidpp.c`, `hid.h`、四種使用前先處理的方式: `hid_field_extract` 或 `s32ton` 或 `!(value & 0xfffffff0)` 或 `直接給值`
* 只有在 `hid-logitech-hidpp.c` 出現的 `hid_snto32(data[6], 8);` 時是直接給值
* `/* Logitech M560 mouse report - data[6] = whee */`
* 我猜測 function snto32 相信使用者會先確認: n-bit value 在第 n bit 為 0 的情況下第 n+1 ~ 32 bit 都會是 0,因此不需做額外的處理
#### undefined behavior
* 在 C99 的 6.5.7 中提到: `If the value of the right operand is` **`negative`** `or is greater than or equal to the width of the promoted left operand, the behavior is undefined.`
* 在 snto32 function 中若 `n=0`,`value & (1 << (n - 1))` 就屬於這個情境
* 在 [ISO/IEC 9899:201x](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1548.pdf) 的 6.5.7 中增加了:
```
The result of E1 << E2 is E1 left-shifted E2 bit positions; ... If E1 has a signed
type and nonnegative value, ...; otherwise, the behavior is undefined.
```
* 在題目中的 snto32 function 中 `-1 << n` 就屬於這個情境
---
## 第三周測驗 [`2`](https://hackmd.io/s/BkyQskjK7#%E6%B8%AC%E9%A9%97-2)
考慮以下程式碼,在 little-endian 硬體上,會返回 `1`,反之,在 big-endian 硬體上會返回 `0`:
```c
int main() {
union { int a; char b;
} c = { .a = 1 };
return c.b == 1;
}
```
#### union
* member 間共用記憶體,大小為最大 member type 的 size
* 6.7.2.1
* A union is a type consisting of a sequence of members whose storage overlap
* The size of a union is sufficient to contain the largest of its members
* There may be unnamed padding at the end of a structure or union
```c
union {
unsigned int a : 2;
int b : 2;
} d = { .a = 3 };
printf("%x\n", d.a); // 3
printf("%x\n", d.b); // ffffffff (-1)
printf("%lu\n", sizeof(d)); // 4 bytes
```
```
(gdb) x/4xb &d
0x7ffffffee300: 0x03 0x00 0x00 0x00
```
#### 運作原理
* 執行環境為 Intel CPU (little-endian)
* 透過 [`<netinet/in.h>`](https://github.com/bminor/glibc/blob/master/inet/netinet/in.h) 中的 `htonl` 可以將 host byte order 轉為 network byte order (big-endian)
* 底層使用 [`<bits/byteswap.h>`](https://github.com/bminor/glibc/blob/master/bits/byteswap.h) 的 `__builtin_bswap32` 或 `__bswap_constant_32`
```c
#define __bswap_constant_32(x) \
((((x) & 0xff000000u) >> 24) | (((x) & 0x00ff0000u) >> 8) \
| (((x) & 0x0000ff00u) << 8) | (((x) & 0x000000ffu) << 24))
```
* [glibc doc](https://www.gnu.org/software/libc/manual/html_node/Byte-Order.html), [Beej's Guide to Network Programming](https://beej-zhtw-gitbook.netdpi.net/htons,_htonl,_ntohs,_ntohl.html)
* 程式碼
```c
#include <stdio.h>
#include <netinet/in.h>
int main()
{
union { int a; char b;
} c = { .a = 1 }, c1;
c1.a = htonl( (uint32_t)c.b );
printf("little-endian: return %d\n", c.b); // 1
printf("big-endian: return %d\n", c1.b); // 0
}
```
```
gdb-peda$ x/4xb &c
0x7ffffffee330: 0x01 0x00 0x00 0x00
# Address: 低 <---> 高
gdb-peda$ x/4xb &c1
0x7ffffffee340: 0x00 0x00 0x00 0x01
```
#### 類似的程式碼
* 使用 [Predefined Macros](https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html) : `#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__`
```c
int main()
{
return __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__;
}
```
* Test with godbolt.org
* x86-64 (ARM) gcc >= 4.6.4, x86-64 clang >= 3.2
* 使用 htons (htonl)
```c
#include <netinet/in.h>
// #include <winsock2.h>
#define IS_BIG_ENDIAN (1 == htons(1))
int main()
{
return !IS_BIG_ENDIAN;
}
```
* Tested
* godbolt.org - x86-64 gcc >= 4.1.2
* Windows 10 - gcc 5.1.0 mingw32
* one-line endianness detection [[source](http://esr.ibiblio.org/?p=5095)]
```c
#include <stdint.h>
#define IS_BIG_ENDIAN (*(uint16_t *)"\0\xff" < 0x100)
```
* need <stdint.h> (C99)
* 不相容 `#if` preprocessor macro
* 原理
```c
#include <stdio.h>
#include <stdint.h>
#include <netinet/in.h>
int main()
{
char *arr = "\0\xff";
uint8_t *p8 = (uint8_t *)arr;
uint16_t *p16 = (uint16_t *)arr;
printf("%02x %02x\n", *p8, *(p8+1) ); // 00 ff
printf("%04x\n", *p16 ); // ff00
printf("%s\n", *p16 < 0x100 ? "true" : "false" ); // false
printf("%04x\n", htons(*p16) ); // 00ff
printf("%s\n", htons(*p16) < 0x100 ? "true" : "false"); // true
}
```
---
## 第三周測驗 [`4`](https://hackmd.io/s/BkyQskjK7#%E6%B8%AC%E9%A9%97-4)
* 函式 insert_sorted 的作用是在 r 所指向已依據數值排序的 linked list 中,安插給定的字串,考慮到 string literal 的生命週期,應該在新建立的節點中,複製給定的字串。
* 新增刪除和搜尋節點的程式,附上完整的測試計畫
#### Makefile
```Makefile
.PHONY: all test clean
CC = gcc
CFLAGS = -O0 -g -Wall -Werror -std=gnu11
all: mytest
myrec.o: myrec.c myrec.h
mytest: mytest.c myrec.o
test: mytest
valgrind --leak-check=full ./mytest
clean:
rm -f *.o mytest
```
#### myrec.h
```c
#ifndef _MYREC_H
#define _MYREC_H
#include <stdbool.h>
typedef struct rec {
char *value;
struct rec *next;
} record;
void insert_sorted(record **r, const char *value);
void remove_rec(record **r);
void print_rec(record *r);
bool search_rec(record **r, const char *str);
#endif
```
#### myrec.c
```c
#include "myrec.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
void insert_sorted(record **r, const char *value)
{
record *newrec = NULL;
while (*r && strcmp(value, (*r)->value) > 0)
r = &((*r)->next);
newrec = malloc(sizeof(record));
newrec->value = strdup(value);
newrec->next = *r;
*r = newrec;
}
void remove_rec(record **r)
{
record *delrec = *r;
if (delrec != NULL) {
*r = delrec->next;
free(delrec->value);
free(delrec);
}
}
void print_rec(record *r)
{
printf("[ ");
for (; r != NULL; r = r->next)
printf("%s ", r->value);
printf("]\n");
}
bool search_rec(record **r, const char *str)
{
while (*r && strcmp(str, (*r)->value) != 0)
r = &((*r)->next);
return (*r == NULL) ? false : true;
}
```
#### mytest.c
```c
#include "myrec.h"
#include <stdio.h>
int main()
{
setbuf(stdout, NULL);
record *rec = NULL;
char *str[3] = { "bb", "ccc", "a" };
for (int i=0; i<3; ++i) {
insert_sorted(&rec, str[i]);
printf("--------- insert %3s: ", str[i]);
print_rec(rec);
}
char *str2[4] = { str[0], "www", str[2], "ddl" };
for (int i=0; i<4; ++i) {
printf("--------- search %3s: ", str2[i]);
printf("%s\n", search_rec(&rec, str2[i]) ? "exist" : "not exist");
}
remove_rec(&rec);
printf("--------- pop one: ");
print_rec(rec);
while (rec != NULL)
remove_rec(&rec);
printf("--------- pop all: ");
print_rec(rec);
}
```
#### 執行結果
```
$ make clean test
rm -f *.o mytest
gcc -O0 -g -Wall -Werror -std=gnu11 -c -o myrec.o myrec.c
gcc -O0 -g -Wall -Werror -std=gnu11 mytest.c myrec.o -o mytest
valgrind --leak-check=full ./mytest
==28162== Memcheck, a memory error detector
==28162== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==28162== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==28162== Command: ./mytest
==28162==
--------- insert bb: [ bb ]
--------- insert ccc: [ bb ccc ]
--------- insert a: [ a bb ccc ]
--------- search bb: exist
--------- search www: not exist
--------- search a: exist
--------- search ddl: not exist
--------- pop one: [ bb ccc ]
--------- pop all: [ ]
==28162==
==28162== HEAP SUMMARY:
==28162== in use at exit: 0 bytes in 0 blocks
==28162== total heap usage: 6 allocs, 6 frees, 57 bytes allocated
==28162==
==28162== All heap blocks were freed -- no leaks are possible
==28162==
==28162== For counts of detected and suppressed errors, rerun with: -v
==28162== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
```