在 C 的世界以外

本文爲「C 的容器、環境與算法」的續篇算法

本文的大的背景見「基於 m4 的 C 代碼模板化編程

A:「密集型」的運算過程,還能夠用 void * 進行抽象嗎?segmentfault

B:不能夠,除非不在乎本身的 C 程序在性能上輸給 C++ 模板程序。若是這種密集型的運算過程僅僅是對某些數據類型有所依賴,此時能夠用宏進行抽象。bash

A:「宏」,彷佛我看到了一幅可怕的景象。編程語言

B:也不是很難,能夠從簡單的一點一點寫起。有了感受以後再去寫複雜一些的。像「C 的容器、環境與算法」中的代碼,都能忍受,還有什麼不能忍受的?看到宏代碼,應該馬上會以爲小清新無限……性能

A:小星星都不見了?優化

B:創建一個 array.h 文件,其內容爲:code

#ifndef ARRAY_H
#define ARRAY_H

typedef struct {
        size_t n;
        T *data;
} Array;

Array * array_alloc(size_t n)
{
        Array *v = malloc(sizeof(Array));
        v->n = n;
        v->data = malloc(n * sizeof(T));
        return v;
}

void array_free(Array *x)
{
        free(x->data);
        free(x);
}

Array * point_sub(Array *a, Array *b)
{
        if (a->n != b->n) fprintf(stderr, "兩個不一樣維度的點沒法構成向量!");
        Array *v = array_alloc(a->n);
        for (size_t i = 0; i < a->n; i++) v->data[i] = a->data[i] - b->data[i];
        return v;
}

#endif

而後,再創建一份 main.c 文件,其內容爲:教程

#include <stdio.h>
#include <stdlib.h>

#define T float
#include "array.h"

int main(void)
{
        size_t n = 3;
        Array *a = array_alloc(n);
        a->data[0] = 1.0f; a->data[1] = 2.0f; a->data[2] = 3.0f;
        Array *b = array_alloc(n);
        b->data[0] = -1.0f; b->data[1] = -2.0f; b->data[2] = -3.0f;
        
        Array *v = point_sub(a, b);
        for (size_t i = 0; i < v->n; i++) {
                if (i < v->n - 1) printf("%f ", v->data[i]);
                else printf("%f\n", v->data[i]);
        }
        
        array_free(v);
        array_free(b);
        array_free(a);
        
        return 0;
}

A:這些代碼似曾相識,星星果真少多了!開發

B:T 表示 arrao_allocpoint_sub 等運算所須要的數據類型。它是一個宏,要想讓 array.h 中的代碼可以經過編譯,必須在 #include "array.h" 以前定義 T

A:既然用宏可讓代碼乾淨許多,那爲啥還要用 void * 呢?

B:array.h 若是被多個 .c 文件 include,那麼 array.h 中的代碼會被 C 編譯器重複編譯爲目標代碼,這樣便會致使程序體積膨脹。

A:這個世界不完美。

B:C++ 的模板,其思路與上述的宏代碼很類似,結果也很類似——程序的體積會膨脹,不過 C++ 的編譯器提供了優化功能,開了 -O2 以後,那些重複的目標代碼會被清除。

A:C++ 更完美……

B:那是由於 C++ 編譯器的開發者們把這些髒活累活替你們作了,致使 C++ 編譯器的複雜程度跟 C 編譯器不是一個數量級。當我使用 CGAL 中的 kd 樹對 400,000 個數據點進行 k 近鄰檢索,程序的執行時間只需 2.8 秒,可是 g++ 編譯這個程序所用的時間須要 18 秒。我用 C 實現的 kd 樹,用盡個人洪荒之力,也只能將其 k 近鄰檢索的運算時間降至 4.7 秒,可是 gcc 編譯個人 C 程序只需 0.5 秒。

A:這個世界不完美!

B:你要享受現代科技,那就只能多吸點霧霾了。

A:……跑步去

B:我以爲,許多人說 C++ 的模板比 C 的宏更好,這是正確的觀點。可是要說 C++ 模板比宏更好,這是錯誤的觀點。

A:是 C 的宏太弱了,陪襯出了 C++ 的模板更好?

B:是這樣的。若是咱們跳出 C 的世界,來看容器與算法所依賴的那些數據類型,它們不過是很是普通的文本而已。既然如此,咱們爲何非要在編程語言自身的體系內左右互搏來解決這個問題呢?

A:我以爲……你在說一些我並不擅長的話題……

B:我以爲二維世界裏的人會以爲一維世界裏的人無比可憐,三維世界裏的人看到二維世界裏的人由於吃飯而致使本身的身體被分紅兩半也會以爲很可憐。四維世界裏的人怎麼看待咱們,我不可思議。不過,對於 C 程序而言,它僅僅是個一維世界裏的事物而已。void * 也好,宏也好,這些努力都是企圖在一維世界裏去解決二維問題。

A:你到底想表達什麼?

B:咱們試試 m4。

A:HA,HA,HA……你暴露了年齡,之前打 CS 時,我最喜歡用的武器!

B:貌似是你暴露了年齡。我都不知道你說的是什麼。我說的 m4 是一種語言。

A:老夫……

B:將 array.h 改造爲:

#ifndef ARRAY_H
#define ARRAY_H

typedef struct {
        size_t n;
        T *data;
} Array;

Array * array_alloc(size_t n)
{
        Array *v = malloc(sizeof(Array));
        v->n = n;
        v->data = malloc(n * sizeof(T));
        return v;
}

void array_free(Array *x)
{
        free(x->data);
        free(x);
}

Array * point_sub(Array *a, Array *b)
{
        if (a->n != b->n) fprintf(stderr, "兩個不一樣維度的點沒法構成向量!");
        Array *v = array_alloc(a->n);
        for (size_t i = 0; i < a->n; i++) v->data[i] = a->data[i] - b->data[i];
        return v;
}

#endif

A:恕我老眼昏花,愣是沒看出來改了何處。

B:你沒看錯,原樣照抄,絲毫未變。試試下面這條命令:

$ m4 -D T=float array.h > array_float.h

A:請容許我作個吃驚的表情,array_float.h 的內容以下:

#ifndef ARRAY_H
#define ARRAY_H

typedef struct {
        size_t n;
        float *data;
} Array;

Array * array_alloc(size_t n)
{
        Array *v = malloc(sizeof(Array));
        v->n = n;
        v->data = malloc(n * sizeof(float));
        return v;
}

void array_free(Array *x)
{
        free(x->data);
        free(x);
}

Array * point_sub(Array *a, Array *b)
{
        if (a->n != b->n) fprintf(stderr, "兩個不一樣維度的點沒法構成向量!");
        Array *v = array_alloc(a->n);
        for (size_t i = 0; i < a->n; i++) v->data[i] = a->data[i] - b->data[i];
        return v;
}

#endif

B:如今你明白了個人意思了吧?

A:似懂非懂。

B: 如今將以前的代碼文件都刪除,新建 array.h_T,其內容爲:

#ifndef ARRAY_H
#define ARRAY_H

typedef struct {
        size_t n;
        T *data;
} Array;

Array * array_alloc(size_t n);
void array_free(Array *x);
Array * point_sub(Array *a, Array *b);

#endif

再創建 array.c_T 文件,其內容爲:

#include <stdio.h>
#include <stdlib.h>
`#'include `"'array_`'T`'.h`"'

Array * array_alloc(size_t n)
{
        Array *v = malloc(sizeof(Array));
        v->n = n;
        v->data = malloc(n * sizeof(T));
        return v;
}

void array_free(Array *x)
{
        free(x->data);
        free(x);
}

Array * point_sub(Array *a, Array *b)
{
        if (a->n != b->n) fprintf(stderr, "兩個不一樣維度的點沒法構成向量!");
        Array *v = array_alloc(a->n);
        for (size_t i = 0; i < a->n; i++) v->data[i] = a->data[i] - b->data[i];
        return v;
}

注意,上面這兩份文件的末尾都要留出一個空行。

main.c 文件內容爲:

#include <stdio.h>
#include <stdlib.h>
#include "array_float.h"

int main(void)
{
        size_t n = 3;
        Array *a = array_alloc(n);
        a->data[0] = 1.0f; a->data[1] = 2.0f; a->data[2] = 3.0f;
        Array *b = array_alloc(n);
        b->data[0] = -1.0f; b->data[1] = -2.0f; b->data[2] = -3.0f;
        
        Array *v = point_sub(a, b);
        for (size_t i = 0; i < v->n; i++) {
                if (i < v->n - 1) printf("%f ", v->data[i]);
                else printf("%f\n", v->data[i]);
        }
        
        array_free(v);
        array_free(b);
        array_free(a);
        
        return 0;
}

而後在 Bash 中執行如下命令:

$ ls
array.c_T  array.h_T  main.c

$ for i in array.h array.c ; do m4 -D T=float ${i}_T > ${i%%.*}_float.${i##*.}; done

$ ls
array.c_T  array_float.c  array_float.h  array.h_T  main.c

$ gcc -std=c11 -pedantic -Werror array_float.c main.c -o array-test

$ ./array-test
2.000000 4.000000 6.000000

A:我是否是須要對 m4 有所瞭解方能看懂下面這樣的咒語?

`#'include `"'array_`'T`'.h`"'

是否是須要對 Bash 有所瞭解,方能看懂:

$ for i in array.h array.c ; do m4 -D T=float ${i}_T > ${i%%.*}_float.${i##*.}; done

B:然。我已經爲你寫了一份 m4 教程,詳見「讓這世界再多一份 GNU m4 教程」。至於 Bash,我也只是會用個 for 循環,並且仍是臨時翻書。

相關文章
相關標籤/搜索