sternacht
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    --- tags: linux2022 --- # 2022q1 Homework2 (quiz2) contributed by < `sternacht` > > [作業要求](https://hackmd.io/@sysprog/BybKYf5xc) ### 測驗 1 #### 答案 EXP1 = `a & b & 1` EXP2 = `a & b` EXP3 = `a ^ b` #### 延伸 - 解釋程式碼運作的原理 本題描述一個取 a,b 兩值平均值的方式,若是直接相加再除以 2 ,可能面臨 overflow 的風險,而寫成 a + (b - a) / 2 則是比較安全的方式之一,要求將此方式以 bitwise 操作改寫 ```c uint32_t average(uint32_t a, uint32_t b) { return (a >> 1) + (b >> 1) + (EXP1); } ``` 在 bitwise 操作中, `a >> 1` 相當於把 a 除二並捨去小數,於是就可以想到 EXP1 所指的就是要對捨去的小數做進位的動作,因為當 a,b 都是奇數時 (LSB == 1) , LSB 相加的進位會變成各自的小數被捨棄。因此, EXP1 的表示式要在 a,b 的 LSB 都為 1 的時候為 1 ,其餘為 0 ,答案就是 `a & b & 1` 接著再進一步改寫成 ```c uint32_t average(uint32_t a, uint32_t b) { return (EXP2) + ((EXP3) >> 1); } ``` 乍看之下看不出個所以然,所以先來看看 & , ^ 等 bistwise 操作在兩個數上的另一個意義。 & 會令兩數同為 1 的 bit 為 1 ,而以加法來看,兩個同為 1 的 bit 會有進位存在,因此可以把 & 想作為 '取得相加後會進位的 bit 並除以 2 '。而 ^ 則是在兩 bit 不相同時會為 1 ,因此可以看成是 '相加後會變成 1 的 bit'。 綜合以上兩個操作,可以把 a + b 寫成 `((a & b) << 1) + (a ^ b)` ,分別表示了有進位與沒有進位的 bit ,接著在把 a + b 除以二取平均,也就是 right shift 一個 bit , 就變成 `(a & b) + ((a ^ b) >> 1)` ,於是就得到了答案。 #### 延伸 - 比較上述實作在編譯器最佳化開啟的狀況,對應的組合語言輸出,並嘗試解讀 利用 `gcc -S -O2 average.c` 命令取得最佳化的組合語言的程式碼如下 ```asm= average: .LFB0: .cfi_startproc endbr64 movl %edi, %eax movl %esi, %edx andl %esi, %edi shrl %eax shrl %edx andl $1, %edi addl %edx, %eax addl %edi, %eax ret .cfi_endpro ``` 從第 5 行開始,前兩行是將傳入值 a, b 搬到暫存器上 第 7 行將 a, b 做 & 操作,此時取得 `a & b` 8,9 行對暫存器中的 a , b 做 shift ,得到 `(a << 1)` 和 `(b << 1)` 第 10 行再回頭做第二次 & 操作,取得 `a & b & 1` 11, 12 行則是將得到的三個值相加 用第二種程式取得的最佳化組合語言如下 ```asm= average: .LFB0: .cfi_startproc endbr64 movl %edi, %eax andl %esi, %edi orl %esi, %eax shrl %eax addl %edi, %eax ret .cfi_endproc ``` 第 5 行將傳入值 b 放到暫存器上 第 6 行做 `a & b` 的操作 第 7, 8 做 `(a | b) << 1` 第 9 行把兩個相加 ### 測驗 2 #### 答案 EXP4 = `a ^ b` EXP5 = `a < b` #### 延伸 - 解釋程式碼運作的原理 本題要求利用 bitwise 操作求得 a, b 兩者中較大的值 ```c #include <stdint.h> uint32_t max(uint32_t a, uint32_t b) { return a ^ ((EXP4) & -(EXP5)); } ``` 這題的解法要先從 ^ 的幾個特性說起, ^ 也就是 XOR,在 `0 ^ 1` 時為 1 , `0 ^ 0` 或 `1 ^ 1` 時為 0 ,而且這個操作具有交換率,也就是 (a ^ b) ^ c == a ^ (b ^ c),延伸可得一個特性是 x ^ (x ^ y) == y,這就可以用交換率解釋 : (x ^ x) = 0 然後得到 (0 ^ y) = y。 接下來先看題目中 EXP5 的部分,因為我們希望當 a 是較大的值時,整個函式會得到 a ^ 0,因此 -EXP5 就要在 a > b 的時候為 0 ,可得到答案是 `a < b`。至於為何要用負號,是因為當 a < b 時,EXP5 會得到 1 ,而我們要把內容轉換成 -1 ,也就是 0xFFFFFFFF 讓 EXP4 與其做 & 操作,得到 EXP4 內的值,最後結合前面提過的 a ^ (a ^ b) == b , 可得知 EXP4 答案為 `a ^ b` #### 延伸 - 針對 32 位元無號/有號整數,撰寫同樣 branchless 的實作 這題運用到的方法不管在有號或是無號數上,只要 (a < b) 的結果正確,那就是通用的。因此有號數的版本只要使讀進來的兩個值型態為有號數即可 ```c #include <stdint.h> uint32_t max(int32_t a, int32_t b) { return a ^ ((a ^ b) & -(a < b)); } ``` #### 延伸 - 請在 Linux 核心原始程式碼中,找出更多類似的實作手法 [實作 1](https://github.com/torvalds/linux/commit/6c786bcb29dd684533ec165057f437d5bb34a4b2) [實作 2](https://github.com/torvalds/linux/commit/747f49ba67b8895a5831ab539de551b916f3738c) ### 測驗 3 #### 答案 COND = `v` RET = `u << shift` #### 延伸 - 解釋程式運作原理 題目改寫自 GCD 演算法,對兩數求最大公因數 ([來源](https://hackmd.io/@sysprog/linux2022-quiz2#測驗-3)) 1. If both x and y are 0, gcd is zero $gcd(0, 0) = 0$. 2. $gcd(x, 0) = x$ and $gcd(0, y) = y$ because everything divides $0$. 3. If x and y are both even, $gcd(x, y) = 2*gcd(\frac{x}{2}, \frac{y}{2})$ because $2$ is a common divisor. Multiplication with $2$ can be done with bitwise shift operator. 3. If x is even and y is odd, $gcd(x, y) = gcd(\frac{x}{2}, y)$. 4. On similar lines, if x is odd and y is even, then $gcd(x, y) = gcd(x, \frac{y}{2})$. It is because $2$ is not a common divisor. 原本的函式 ```c #include <stdint.h> uint64_t gcd64(uint64_t u, uint64_t v) { if (!u || !v) return u | v; while (v) { uint64_t t = v; v = u % v; u = t; } return u; } ``` 改寫成以下 ```c #include <stdint.h> uint64_t gcd64(uint64_t u, uint64_t v) { if (!u || !v) return u | v; int shift; for (shift = 0; !((u | v) & 1); shift++) { u /= 2, v /= 2; } while (!(u & 1)) u /= 2; do { while (!(v & 1)) v /= 2; if (u < v) { v -= u; } else { uint64_t t = u - v; u = v; v = t; } } while (COND); return RET; } ``` 雖然改寫後複雜了許多,但本質上還是 GCD 的演算法。多的是 shift 這個變數,這個變數儲存的是 u, v 兩數對 2 的冪的公因數,也就是在除以 $2^{shift}$ 後,必至少有一個數會是奇數,並且可以確定不會再有更多 2 出現在最大公因數裡,於是接下來的操作就是把 u 中多餘的 2 先除掉,這一步我的理解是為了要盡可能減少計算的量,連同後續在 do while 中對 v 做除 2 也是相同的原因。 ```c while (!(u & 1)) u /= 2; ``` 接著 do while 的部分,在原函式中是用 % (mod) 的方式來取的值,若一開始 u < v,則會變成 u, v 交換,接著在後續的過程中保證 u >= v,直到 v 為 0 ,換句話說,v 是用來判斷函式結束的關鍵。 而改寫後更像是輾轉相除法,用相減來逼近答案,因此要判斷 u 是否大於 v ,以保證相減後獲得的值是大於 0 ,最後結束條件同樣是 v 等於 0, COND 部分可簡寫成 while (v) 來檢查 v 是否為 0。 RET 要回傳的就是結果,因此除了從 GCD 中得到的 u 之外,還要把最前面除掉的 $2^{shift}$ 乘回去,因此 RET 為 `u << shift` #### 延伸 - 在 x86_64 上透過 __builtin_ctz 改寫 GCD,分析對效能的提升 透過 __builtin_ctz 改寫後的程式碼如下,主要將除 2 的部份透過 __builting_ctz 來取代迴圈執行 ```c uint64_t gcd64(uint64_t u, uint64_t v) { if (!u || !v) return u | v; int shift = __builtin_ctz(u) < __builtin_ctz(v)? \ __builtin_ctz(u): __builtin_ctz(v); u >>= __builtin_ctz(u); do { v >>= __builtin_ctz(v); if (u < v) { v -= u; } else { uint64_t t = u - v; u = v; v = t; } } while (v); return u << shift; } ``` 撰寫一個簡單的測試,對兩個數求最大公因數並輸出兩個函式分別執行的時間,結果如下圖,使用了 __builtin_ctz 的函式明顯比未使用得來的快。 ![](https://i.imgur.com/YZ72PPJ.png) ### 測驗 4 #### 答案 EXP6 = `bitset & -bitset` #### 延伸 - 解釋程式運作原理,並舉出這樣的程式碼用在哪些真實案例中 本題要求在 EXP6 表示式中取得 bitset 中最低位元的 1 ,而這裡運用的就是 - (負號)的特性。對一個值加負號,就相當於翻轉全部的位元並 +1,也就是 -a = ~a + 1 ,最低的 1 位元之前的所有 0 都會被反轉成 1 ,並在 + 1 之後一路進位,直到最低位元的 1 的位置(被反轉成 0 ,進位後又是 1 ),最後再透過 & 操作來得到答案。 ```c #include <stddef.h> size_t improved(uint64_t *bitmap, size_t bitmapsize, uint32_t *out) { size_t pos = 0; uint64_t bitset; for (size_t k = 0; k < bitmapsize; ++k) { bitset = bitmap[k]; while (bitset != 0) { uint64_t t = EXP6; int r = __builtin_ctzll(bitset); out[pos++] = k * 64 + r; bitset ^= t; } } return pos; } ``` 測驗 3 的延伸題中,[lib/math/gcd.c](https://github.com/torvalds/linux/blob/master/lib/math/gcd.c) 即用到這個方法來計算 lsbit ### 測驗 5 #### 答案 PPP = `pos--` MMM = `list_add` EEE = `&heads[remainder % size]` #### 延伸 - 解釋上述程式碼運作原理,指出其中不足,並予以改進 這題題目是分數轉成十進位小數的字串的實作,而計算方式是先記錄出現過的餘數,當該餘數再次出現時就表示進入循環小數的部分了,而記錄的方式就是熟悉的 linked-list 結合 hash table,接著看看題目問題出現的地方 ```c char *fractionToDecimal(int numerator, int denominator) { int size = 1024; char *result = malloc(size); char *p = result; if (denominator == 0) { result[0] = '\0'; return result; } if (numerator == 0) { result[0] = '0'; result[1] = '\0'; return result; } /* using long long type make sure there has no integer overflow */ long long n = numerator; long long d = denominator; /* deal with negtive cases */ if (n < 0) n = -n; if (d < 0) d = -d; bool sign = (float) numerator / denominator >= 0; if (!sign) *p++ = '-'; long long remainder = n % d; long long division = n / d; sprintf(p, "%ld", division > 0 ? (long) division : (long) -division); if (remainder == 0) return result; p = result + strlen(result); *p++ = '.'; /* Using a map to record all of reminders and their position. * if the reminder appeared before, which means the repeated loop begin, */ char *decimal = malloc(size); memset(decimal, 0, size); char *q = decimal; size = 1333; struct list_head *heads = malloc(size * sizeof(*heads)); for (int i = 0; i < size; i++) INIT_LIST_HEAD(&heads[i]); for (int i = 0; remainder; i++) { int pos = find(heads, size, remainder); if (pos >= 0) { while (PPP > 0) *p++ = *decimal++; *p++ = '('; while (*decimal != '\0') *p++ = *decimal++; *p++ = ')'; *p = '\0'; return result; } struct rem_node *node = malloc(sizeof(*node)); node->key = remainder; node->index = i; MMM(&node->link, EEE); *q++ = (remainder * 10) / d + '0'; remainder = (remainder * 10) % d; } strcpy(p, decimal); return result; } ``` 最後一個 for 迴圈就是用於判斷循環小數, pos 表示循環小數出現的位置,也可以理解從第幾位之後是循環小數,還沒有循環時, pos 得到的回傳值是 -1, 當 pos 大於 0 之後,程式要將剩餘的十進位小數補滿,補滿的形式是 `整數.非循環小數(循環小數)` ,因此首先 PPP 要補齊的的是非小數部分,以 pos 遞減的方式計算,答案就是 `pos--`。 當還沒循環時,程式要記錄每一個餘數以及對應的小數十進位值,並放進 hash table 中,MMM 根據提示及題意可以判斷出是 `list_add` 或 `list_add_tail` 的其中一種,符合最精簡的原則,前者是較好的答案,再來 EEE 就是具體要放在哪個 heads 的位置,根據另一個函式 `find` 的內容,我們可以得知 hash 值的計算方式就是`remainder % size`,因此得到 EEE 的答案就是 `&heads[remainder % size]` 除了提示中提到計算正負號可以寫作 `bool isNegative = numerator < 0 ^ denominator < 0;` 之外,我在閱讀的過程中,對於 size 值的設定感到有些困惑,考慮到 hash table 要達到盡可能分散進來的值,將 size 值設定的大一些是可以理解的。但對於這樣找循環小數的程式來說,其實只要讓 size = denominator 就夠了,因為我們可以確定的是 remainder 不會大於 denominator,,也就是說 remainder 的範圍就是從 0 到 denominator ,只要遇到重複就表示循環小數發生,因此在最糟的情況下,也只會是每一個 heads 都被放入一次 remainder ,接著就一定會循環了,第二個 size 可以改寫成 `size = (denominator > n)? n: denominator;` , n 是我們設定的 hash 範圍,範例中是 1333。 同時,如果輸入的 denominator 很大的時候,原先設定的字串長度 1024 也會不夠用導致程式錯誤,將第一個 size 改成以下的條件式 `int size = (denominator > 0)? denominator: -denominator;` 即可。 ### 測驗 6 #### 答案 M = `_h` X = `0` #### 延伸 - 解釋上述程式碼運作原理 ```c #define ALIGNOF(t) \ ((char *)(&((struct { char c; t _h; } *)0)->M) - (char *)X) ``` 先將這提拆分成減號前後兩部份,先細看前面的部份。 在 `(char *)(&((struct { char c; t _h; } *)0)->M)` 中有一個 `struct { char c; t _h; }` 的結構,建立一個此結構的指標,並給與此指標值為 0 ,也就是將這個結構的指標放到記憶體 0 的位址。接著因為 alignment 的緣故, `_h` 會根據型態 t 的不同,與 char `c` 的距離有所改變,而 `_h` 的記憶體起始位址減去 `c`的記憶體起始位址,就是 alignment 的距離,至此我們可以理解前面的部份就是 `_h` 的位址, M = `_h` ,而後面的部份就是 char `c` 的起始位址,本題題目將整個結構放在 0 的位址上, `c` 的起始位置在 0 , X = 0 。 #### 延伸 - 在 Linux 核心原始程式碼中找出 __alignof__ 的使用案例 2 則,並針對其場景進行解說 - [include/uapi/linux/netfilter_bridge/ebtables.h](https://github.com/torvalds/linux/blob/5bfc75d92efd494db37f5c4c173d3639d4772966/include/uapi/linux/netfilter_bridge/ebtables.h) line 90 ```c char data[0] __attribute__ ((aligned (__alignof__(struct ebt_replace)))); ``` 利用 `__alignof__` 取得某個 struct alignment,接著用 `__attribute__ (aligned)` 使變數對齊。 - [include/linux/kstrtox.h](https://github.com/torvalds/linux/blob/35e43538af8fd2cb39d58caca1134a87db173f75/include/linux/kstrtox.h) line 36-37 ```c if (sizeof(unsigned long) == sizeof(unsigned long long) && __alignof__(unsigned long) == __alignof__(unsigned long long)) ``` 這一段看起來很有趣,以我的電腦來說 long long 相當於 64 bits , long 也是 64 bits,但是在某些不同的架構底下 long 可能是 32 bits,因此這一段程式碼主要就是用在分辨不同的長度,以確保回傳值的長度固定。 ### 測驗 7 #### 答案 KK1 = `div3` KK2 = `div5` KK3 = `div3 << 2` #### 延伸 - 解釋上述程式運作原理並評估 naive.c 和 bitwise.c 效能落差 FizzBuzz 的題目要求題目: - 從 1 數到 n,如果是 3的倍數,印出 “Fizz” - 如果是 5 的倍數,印出 “Buzz” - 如果是 15 的倍數,印出 “FizzBuzz” - 如果都不是,就印出數字本身 而觀察輸出則可以發現輸出的文字就是 "Fizz", "Buzz" 兩個的排列組合,因此給定一個字串 `FizzBuzz`,並透過調整輸出字串的 "起始位置" 及 "長度",就可以滿足三種不同的輸出形式。 題目的程式碼如下 ```c static inline bool is_divisible(uint32_t n, uint64_t M) { return n * M <= M - 1; } static uint64_t M3 = UINT64_C(0xFFFFFFFFFFFFFFFF) / 3 + 1; static uint64_t M5 = UINT64_C(0xFFFFFFFFFFFFFFFF) / 5 + 1; int main(int argc, char **argv) { for (size_t i = 1; i <= 100; i++) { uint8_t div3 = is_divisible(i, M3); uint8_t div5 = is_divisible(i, M5); unsigned int length = (2 << KK1) << KK2; char fmt[9]; strncpy(fmt, &"FizzBuzz%u"[(9 >> div5) >> (KK3)], length); fmt[length] = '\0'; printf(fmt, i); printf("\n"); } return 0; } ``` 首先在決定長度的部分,當 n 是 3 或 5 的倍數時為 2,同時是 3, 5 的倍數時為 8 ,因此 KK1 及 KK2 就分別代表 n 是否為 3, 5 的倍數,以 0, 1 表示,而在其他狀況下,長度為 2 ,也就是字串中用來輸出整數的 `"%d"` 。回頭看前兩行的 div3 跟 div5,從變數名稱大概可以猜得出來其所代表的意涵,實際上的運作方式是這樣的。 當有一個 3 的倍數 n 與 M3 一起傳入, n * M3 會相當於 `(n / 3) * 0xFFFFFFFFFFFFFFFF + n` ,而在溢位之後剩下的就只有 `2n / 3` ,因此在一定範圍內會比 M3 這樣的數字來的小,當 n 不是 3 的倍數時,溢位後仍會剩下 `0xFFFFFFFFFFFFFFFF` 加上一個大於等於 1 的整數,使條件不成立。 於是 KK1 及 KK2 的答案就分別是 div3 及 div5 根據上述獲得的字串長度,接著要知道起始位置,在`(8 >> div5) >> (KK3)`這個部分,可以考慮以下情況時起始位置的不同 > 原題目是寫`(9 >> div5) >> (KK3)`,但應該將 9 改成 8,亦即開頭應該設定在 `%` 的位置,否則其他狀況下的輸出會剩下一個 `u`,不符合題目要求 - 3 的倍數,應該為 0 ,8 要右移至少 4 個 bits - 5 的倍數,應該為 4 ,8 要右移 1 個 bit - 15 的倍數,應該為 0 ,要右移 4 個 bits(1 + 至少 3) - 以上皆非,不須位移 因此我們可以得知 KK3 至少要是 4 ,在符合最簡潔程式碼的條件下 KK3 = `div3 << 2` 是最好的答案

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully