在 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 循环,并且仍是临时翻书。

相关文章
相关标签/搜索