Copyright (慣C) 2017 宅色夫 ==直播錄影==
C-section 是 Cesarean section 的縮寫,意思是「剖腹產術」,在本次講座中,我們引申「拿刀侵入程式開發者,萃取出新的生命」。這裡也有 "C"! [ Source ]
void naive_transpose(int *src, int *dst, int w, int h)
{
for (int x = 0; x < w; x++)
for (int y = 0; y < h; y++)
*(dst + x * h + y) = *(src + y * w + x);
}
使用方式如下:
int *src = (int *) malloc(sizeof(int) * TEST_W * TEST_H);
int *out2 = (int *) malloc(sizeof(int) * TEST_W * TEST_H);
naive_transpose(src, out2, TEST_W, TEST_H);
這有什麼問題呢?
typedef struct matrix_impl Matrix;
struct matrix_impl {
float values[4][4];
/* operations */
bool (*equal)(const Matrix, const Matrix);
Matrix (*mul)(const Matrix, const Matrix);
};
static Matrix mul(const Matrix a, const Matrix b)
{
Matrix matrix = { .values = {
{ 0, 0, 0, 0, }, { 0, 0, 0, 0, },
{ 0, 0, 0, 0, }, { 0, 0, 0, 0, },
},
};
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
for (int k = 0; k < 4; k++)
matrix.values[i][j] += a.values[i][k] * b.values[k][j];
return matrix;
}
int main()
{
Matrix m = {
.equal = equal,
.mul = mul,
.values = { ... },
};
Matrix o = { .mul = mul, };
o = o.mul(m, n);
這樣的好處是:
Matrix m
,可以很容易指定特定的方法 (method),藉由 designated initializers,在物件初始化的時候,就指定對應的實作,日後要變更 (polymorphism) 也很方便
equal
和 mul
函式實作時都標註 static
,所以只要特定實作存在獨立的 C 原始程式檔案中,就不會跟其他 compilation unit 定義的符號 (symbol) 有所影響,最終僅有 API gateway 需要公開揭露:::info ==Prefer to return a value rather than modifying pointers==
This encourages immutability, cultivates pure functions, and makes things simpler and easier to understand. It also improves safety by eliminating the possibility of a NULL argument.
unnecessary mutation (probably), and unsafe
void drink_mix(Drink * const drink, Ingredient const ingr) {
assert(drink);
color_blend(&(drink->color), ingr.color);
drink->alcohol += ingr.alcohol;
}
immutability rocks, pure and safe functions everywhere
Drink drink_mix(Drink const drink, Ingredient const ingr) {
return (Drink) {
.color = color_blend(drink.color, ingr.color),
.alcohol = drink.alcohol + ingr.alcohol
};
}
:::
不過仍是不夠好,因為:
values
欄位仍需要公開,我們就無法隱藏實作時期究竟用 float
, double
, 甚至是其他自訂的資料表達方式 (如 Fixed-point arithmetic)m.mul()
就註定會失敗matrixA * matrixB
,對應程式碼為 matO = matA.mul(matB)
,但在上述程式碼中,我們必須寫為 Matrix o = m.mul(m, n)
,後者較不直覺2.
,如果初始化時配置記憶體,那要如何確保釋放物件時,相關的記憶體也會跟著釋放呢?若沒有充分處理,就會遇到 memory leaks延伸閱讀:
:::success 作業要求:
_Generic
來提供矩陣 / 向量的乘法操作 :::const char *lookup[] = {
[0] = "Zero",
[1] = "One",
[4] = "Four"
};
assert(!strcasecmp(lookup[0], "ZERO"));
也可變化如下:
enum cities { Taipei, Tainan, Taichung, };
int zipcode[] = {
[Taipei] = 100,
[Tainan] = 700,
[Taichung] = 400,
};
前述矩陣操作的程式,我們期望能導入下方這樣自動的處理方式:
struct matrix { size_t rows, cols; int **data; };
struct matrix *matrix_new(size_t rows, size_t cols)
{
struct matrix *m = ncalloc(sizeof(*m), NULL);
m->rows = rows; m->cols = cols;
m->data = ncalloc(rows * sizeof(*m->data), m);
for (size_t i = 0; i < rows; i++)
m->data[i] = nalloc(cols * sizeof(**m->data), m->data);
return m;
}
void matrix_delete(struct matrix *m) { nfree(m); }
其中 nalloc
和 nfree
是我們預期的自動管理機制,對應的實作可見 nalloc
複製字串可用 strdup 函式:
char * strdup(const char *s);
strdup 函式會呼叫 malloc 來配置足夠長度的記憶體,當然,你需要適時呼叫 free 以釋放資源。 [==heap==]
strdupa 函式透過 alloca 函式來配置記憶體,後者存在 [==stack==],而非 heap,當函式返回時,整個 stack 空間就會自動釋放,不需要呼叫 free。
char * strdupa(const char *s);
:::info
alloca
function is not in POSIX.1.
alloca() function is machine- and compiler-dependent. * For certain applications, its use can improve efficiency compared to the use of malloc(3) plus free(3).
In certain cases, it can also simplify memory deallocation in applications that use longjmp(3) or siglongjmp(3). Otherwise, its use is discouraged.
strdupa() and strndupa() are GNU extensions. :::
alloca()
在不同軟硬體平臺的落差可能很大,在 Linux man-page 特別強調以下: :::warning RETURN VALUE The alloca() function returns a pointer to the beginning of the allocated space. If the allocation causes stack overflow, program behaviour is ==undefined==. :::
延伸閱讀:
這些 smart pointer 都是 template class 的形式,所以適用範圍很廣泛;他們都是被定義在 <memory>
標頭檔、在 std 這個 namespace 下。
Implementing smart pointers for C
#define autofree \
__attribute__((cleanup(free_stack)))
__attribute__ ((always_inline))
inline void free_stack(void *ptr) { free(*(void **) ptr); }
int main(void) {
autofree int *i = malloc(sizeof (int));
*i = 1;
return *i;
}
Smart pointers for the (GNU) C: Allocating a smart array and printing its contents before destruction:
#include <stdio.h>
#include <csptr/smart_ptr.h>
#include <csptr/array.h>
// @param ptr points to the current element
void print_int(void *ptr, void *meta) { printf("%d\n", *(int *) ptr); }
int main(void)
{
// Destructors for array types are run on every
// element of the array before destruction.
smart int *ints = unique_ptr(int[5],
{5, 4, 3, 2, 1},
print_int);
/* Smart arrays are length-aware */
for (size_t i = 0; i < array_length(ints); ++i)
ints[i] = i + 1;
return 0;
}
__attribute__((cleanup))
。但是function回傳的unbound物件,以及function argument attribute皆無支援__attribute__((cleanup))
。void foo(int (*a)[5])
{
int nb = ARRAY_SIZE(*a);
}
void f(int m, int C[m][m])
{
double v1[m];
...
#pragma omp parallel firstprivate(C, v1)
...
}
Randy Meyers (chair of J11, the ANSI C committee) 的文章 The New C:Why Variable Length Arrays?,副標題是 "C meets Fortran, at long last."
一個特例是 Arrays of Length Zero,GNU C 支援,在 Linux 核心出現多次
do { ... } while(0)
巨集-fplan9-extensions
可支援 Plan 9 C Compilers 特有功能typedef struct S {
int i;
} S;
typedef struct T {
S; // <- "inheritance"
} T;
void bar(S *s) { }
void foo(T *t) {
bar(t); // <- call with implict conversion to "base class"
bar(&t->S); // <- explicit access to "base class"
}
-fms-extensions
編譯選項。見 GCC Unnamed Fields&t->S
或 type cast (S*)t
。但若用 transparent union,即可透過更漂亮的語法來實作:typedef union TPtr TPtr;
union TPtr {
S *S;
T *T;
} __attribute__((__transparent_union__));
void foo(TPtr t)
{
t.S->s_element;
t.T->t_element;
}
T* t;
foo(t); // T * can be passed in as TPtr without explicit casting
typedef enum GenericType GenericType;
typedef struct A A;
typedef struct B B;
enum GenericType {
TYPE_A = 0,
TYPE_B,
};
struct A {
GenericType type;
...
};
struct B {
GenericType type;
...
};
union GenericPtr {
GenericType *type;
A *A;
B *B;
} __attribute__((__transparent_union__));
void foo (GenericPtr ptr)
{
switch (*ptr.type) {
case TYPE_A:
ptr.A->a_elements;
break;
case TYPE_B:
ptr.B->b_elements;
break;
default:
assert(false);
}
}
A *a;
B *b;
foo(a);
foo(b);
可寫出以下風格的 C 程式:
/* Stack objects are created using "$" */
var i0 = $(Int, 5);
var i1 = $(Int, 3);
var i2 = $(Int, 4);
/* Heap objects are created using "new" */
var items = new(Array, Int, i0, i1, i2);
/* Collections can be looped over */
foreach (item in items) {
print("Object %$ is of type %$\n",
item, type_of(item));
}
#define DOUBLE(a) ((a) + (a))
int foo()
{
printf(__func__);
return 3;
}
int main ()
{
DOUBLE(foo()); /* 呼叫 2 次 foo() */
}
為此,我們可以使用區域變數,搭配 GNU extension __typeof__
,改寫上述巨集:
#define DOUBLE(a) ({ \
__typeof__(a) _x_in_DOUBLE = (a); \
_x_in_DOUBLE + _x_in_DOUBLE; \
})
為什麼有 _x_in_DOUBLE
這麼不直覺的命名呢?因為如果 a
的表示式中恰好存在與上述的區域變數同名的變數,那麼就會發生悲劇。
如果你的編譯器支援 Block,比方說 clang,就可改寫為:
#define DOUBLE(a) \
(^(__typeof__(a) x){ return x + x; }(a))
:::info
-fblocks
編譯選項 :::延伸閱讀: