contributed by < ofAlpaca
>
CSIE5006
HW
C99 [3.14] Region of data storage in the execution environment, the contents of which can represent values.
&
在非 bitwise operator 時,應念 "address of" ,表示運算元的位址。
C99 [6.5.3.2] The unary & operator yields the address of its operand.
*
應念成 "value of" 或是 "dereference of" ,*
的運算元應該要是 pointer type,表示運算元所指向的值。
C99 [6.5.3.2] The operand of the unary * operator shall have pointer type.
char *
與 void *
是可以互換的。
C99 [6.2.5.27] A pointer to void shall have the same representation and alignment requirements as a pointer to a character type.
C99[6.2.5] Types are partitioned into object types (types that fully describe objects), function types (types that describe functions), and incomplete types (types that describe objects but lack information needed to determine their sizes).
void *
之謎void *
(pointer to void) 無法確定其大小。void *
與 char *
可以互換表示法。*(int32_t * const) (0x67a9) = 0xaa6;
ptrA
不會被改變到的原因是參數 p
只是個副本,而改變副本的值並不會影響到 ptrA
。memcpy()
也是個方法。extern char x[]
無法轉為 pointer。char x[10]
不能轉為 pointer ,但 char x[]
可以,因為它不是 object 而是 incomplete type 。func(char x[])
會被轉為func(char *x)
,但僅限於函式的參數, array argument 是不存在於C語言的。x[i]
會被編譯器改寫為 *(x + i)
, x[i] == (*((x)+(i)))
。x[i]
、 *(x + i)
、 *(i + x)
、 i[x]
這四者是等價的。int x[i][j]
意思是 x 是有 i 個 object 的陣列,每個 object 都是有 j 個 int 的陣列。此作法稱為 array subscripting。char * newargv[];
的結構如下,提醒自己用:char* s[]
與 char **s
是相同的。char *s[]
與 char (*s)[]
是不同的,前者是 an array of pointer to char ,後者是 a pointer to an array 。 (根據 C99 [A.2.1] , []
是優先於 *
)int *ptr, value;
ptr 為 pointer to int , value 為 int ,此手法容易搞混,不宜使用。ptr + 1
時,每次 +1
的單位取決於 ptr 的 type 。char *r = malloc(strlen(s) + strlen(t) + 1);
最後的+1為NULL的空間。memcpy()
、 malloc()
並不會自動配上最後的 terminated null byte ,在處理 char *
時要記得自己保留空間。strdup()
會使用 malloc()
配置足夠的記憶體空間來存放字串,也包含了最後的 terminated null byte ('\0')。 *(uint32 * const) (0x601020) = 0x601080
左手邊是 Lvalue,指的是 locator value ,並非 left 。
Lvalue 是指除了 void
型別以外,有著 object type 或 incomplete type 的表達式。ex. obj , *ptr , ptr[i] , and ++x。
C99 [6.3.2.1] An lvalue is an expression with an object type or an incomplete type other than void;
有著回傳型別的 function designator 會被轉為 pointer to returning type 。 ex. int test();
會被轉為 pointer to function return int , 型別為 int (*)()
。就像陣列這兩者互換的關係,char a[]
與 char * a
。
C99 [6.3.2.1] A function designator with type ‘‘function returning type’’ is converted to an expression that has type ‘‘pointer to function returning type’’.
若在 pointer to function 使用 deference operator(*) , 則會被轉為 function designator 。
此程式碼可以運作的原因是 put()
是個 function designator ,而 function designator 會被轉成 pointer to function ,在經過數次的 dereference 後仍然是 function designator,所以不影響結果。
char *s="Hello world"
此為string literal,和 char s[]="Hello world"
不同,前者存於read-only data section ,後者則是 stack 的 local variable。
strlen()
不能接受 null pointer ,會造成未定義的行為。#define NULL ((void *)0) // 一般C編譯器常見的寫法
((st *)0)->m
意思是將 0 轉型為 pointer to st ,並指向 m 這個 member 的位址。在此獲得相對位置。sizeof
一樣都是 operator ,用於回傳資料的型別,但 typeof
是 gcc 的 extension。PUSH
目前 rbp
的位址。rsp
複製至 rbp
。(兩者指向目前 stack frame 的起點)rsp
開始往下移動(由高位址往低位址),保留空間給 local variables 。rbp
複製至 rsp
,之前的 local variables 也被釋放了。(兩者指向目前 stack frame 的起點)rbp
的位址,讓目前的 rbp
回復成上個 stack frame 的 rbp
。free()
為釋放 pointer 所指向之記憶體空間,非 pointer 本身。free()
後 pointer 不會在指向任何記憶體,如果再次 free()
就會產生 error。_int_free
來看 glibc 是如何實作 free()
。struct malloc_chunk
: 一個 doubly linked list 的資料結構的節點,每個節點除了雙向指標外,還存有目前節點大小與前節點大小。fastbins
: 是個存有最近釋放掉的 malloc_chunk
的 single linked list。mchunkptr
: 是一個 pointer to malloc_chunk
。free()
時,會先透過 mem2chunk
來找到其對應的 mchunkptr
, 接著會去 fastbins
裡比對 mchunkptr
所指向的位址是否已經存在,有則表示之前此位址已經被 free()
過了,故產生 "double free or corruption" 的 error message 。這裡有個有趣的點,你可以嘗試看看 free 兩個 fastbin 大小的 chunk,順序:A, B, A,看看會發生什麼事
HexRabbit
memory allocator 內部實作可參照春季班課程報告: rpmalloc 探討
Tail recursion 實驗 : 查看有尾端遞迴與沒尾端遞迴的差別。 (reference)
要做 Tail Call Optimization 需要在編譯時期加上 -O2
參數來最佳化。
接下來使用指令 objdump -d
來觀察兩者之間組語的差別。
call
而後者使用 jne
。jne
來跳至前面的位址 (0x400500),並沒有額外呼叫函式,所以不會產生 stack frame ,進而達到 TCO 。&&
是左值優先,若左值不成立,右值就不會執行。||
是左值優先,若左值成立,右值就不執行。FILE * fopen(...)
的 pointer to FILE 為對檔案處理的抽象層,會隨著架構有所不同,為系統提供的包裝。 DIR * opendir(...)
也是同樣的道理。preprocessor 會處理的部分 :
#define
#include <stdio.h>
#if
、 #ifdef
#undef
、 #pragma
Stringfication : #
可以將 macro 的參數轉為字串常數。
Concatenation : ##
用於連接字串。
#define CMD(NAME) {#NAME, NAME##_command}
Feature test macro : 就算同個 source code ,還是有可能因為 posix macro 定義不同而有不同的行為。
編譯時使用 gcc -E -P
可以得到 preprocessor 的輸出結果。
-E
-P
參數,可以看到被 macro 取代掉的部分。typeof
為 gcc extension 的 macro。goto
與 continue
。goto
、 continute
、 multiple returns
能不使用就不使用,但是在 MISRA-C:2011 的草稿中卻說到,只要是往下 jump 不往上,就沒問題。(source)&&
為 GCC extension 的特殊 operator,回傳的位址以 void *
。goto
後面加上 value of pointer 即得到 label 。C99 [6.8.4.2] If a switch statement has an associated case or default label within the scope of an identifier with a variably modified type, the entire switch statement shall be within the scope of that identifier.
i
在同個 scope 下,故在編譯時期會有 redeclaration of ‘i’ with no linkage 的 error 。goto
之變形。if-else
而被繞過,label 也是一樣。#if 0 ... #endif
才會完全隔絕,因為已經被前置處理掉了。nn = 7
,當 7 mod 4
為 3 ,進入 case 3
。case 3
一路執行 case 2
、 case 1
至 for
迴圈的最後, nn -= 4
後判斷 nn
(目前為 3) 大於 0 ,故繼續執行迴圈。nn -= 4
後判斷 nn
(值為 -1) 小於 0 ,故結束迴圈。for
迴圈來跑,需要跑 7 次,但使用 Duff's device 技巧來寫,只需要 2 次,效率有差。indirect
指向的是 head
的位址。indirect
會指向目前節點的 next
的位址。indirect
(也就是 A->next
) 等於 target
故跳出迴圈。indirect
指向 target->next
,完成刪除 node B 。list_for_each
的缺點是如果在跑 pos
時將該節點刪除,將會導致後面的 pos = pos->next
出現錯誤; list_for_each_safe
則多出 n
pointer 來保存下個節點的指標,就此避免了上述的問題。C99 [6.7.8] All the expressions in an initializer for an object that has static storage duration shall be constant expressions or string literals.
strdup
會呼叫 malloc
來動態配置足夠的記憶體,存於 [HEAP] ,需要手動 free
來釋放資源;但是 strdupa
則是使用 alloca
來配置記憶體,並存於 [STACK] , STACK 會自動釋放資源。
函式名稱 | 呼叫 | 存放位置 | 釋放資源 |
---|---|---|---|
strdup | malloc | Heap | 手動 free |
strdupa | alloca | Stack | 自動 free |
alloca()
意思是配置會自動釋放的記憶體。
__attribute__
為 GCC extension 的定義函數屬性,可以讓編譯器在編譯時做些特殊處理或是檢查動作。autofree
就是個 variable attribute 。__attribute__ ((always_inline))
則是 function attribute 。inline
(行內函式) 意思是將此 function 的內容整合至呼叫此函式的位置。