预先计算的变量定义与通过计算初始化

precalculated definition of variable vs initializing by calculation

例如,假设我想使用一个函数来初始化我的变量:

int x[10];
void init_x(){
    for(int i=0; i<10; ++i){
        x[i]=i*i;
    }
}

它不一定是这个确切的函数,它可以更复杂并在更大的数组或不同的 int 类型上完成,但无论如何,关键是 结果是确定性的。我的问题是:预先计算结果并直接定义它会更好吗(例如,我的程序每次初始化速度会更快)?

int x[10]={0, 1, 4, 9, etc...}

这样,我只 运行 初始化函数一次(例如 运行 函数,然后将结果复制+粘贴到数组定义并将代码注释掉)而不是一次又一次每次我打开程序。 (至少我是这么认为的)

这样做有什么缺点吗?

初始化的数组必须存储在可执行文件中并从磁盘加载。如果计算很简单,那么处理器执行计算的速度可能比从磁盘读取数据的速度更快。 这意味着将初始化数据放在可执行文件中可能会导致可执行文件臃肿,启动速度变慢,双输局面。

My question is: would it be better (e.g. will my program initialize faster every time) to just calculate the result of this beforehand and just define it outright?

肯定会更快。它是否足以引起您的注意取决于计算的复杂程度以及您需要多久初始化一次变量。

如果您决定使用硬编码数字来初始化数组,我强烈建议您在注释中添加注释或代码,以解释您最初是如何获得这些数字的。否则维护起来会很头疼。

Are there any disadvantages to doing this?

除非有支持证据证明这样做可以节省大量资金,否则我不会对数字进行硬编码。

在其他条件相同的情况下,人力比 cpu 时间或磁盘 space 更昂贵。做任何需要最少的前期和持续的人力努力的事情。进行复杂的多阶段构建过程可能会节省一点 cpu 或磁盘,但会耗费精力。

如果我对你的问题的理解正确,你是在问是否可以在编译时而不是 运行 时进行计算,是否有注意事项?

答案取决于计算的复杂程度。如果它们很简单(如您所说的确定性),您通常可以成功地做到这一点。需要注意的是,进行计算的代码可能不容易阅读,而且会大大增加编译时间。

这种技术的推广称为 meta-programming,在普通代码 -> 二进制转换之前添加一层额外的代码转换(编译)。

您可以使用预处理器执行有限的形式。 GCC 还支持一些静态计算的表达式。其他技术包括使用 X-Macros 基本上实现类似于 C++ 中的参数化模板。

有些库能够在编译时使用预处理器 (P99 for instance) 执行图灵完备计算。通常语法很复杂,有很多约定和很多习语需要学习才能发挥作用。

与复杂的元编程相比,当使用例如一个 Perl 或 Python 脚本,而不是当我与预处理器一起破解某些东西时。

编辑:

为了用一个例子回答你的问题,我会告诉你我专业地为具有 4-16kb RAM 和 16-128kb 闪存代码的微控制器编写了很多 C 代码 space。大多数应用程序至少存在十年,并且需要 运行ning 更新和功能添加。这意味着我必须小心不要浪费资源,所以我总是更喜欢在编译时而不是在 运行 时计算某些东西。这以增加构建系统的复杂性为代价节省了代码 space。 如果数据是恒定的,也意味着我可以把它放在只读闪存中,节省宝贵的RAM。

另一个示例在 aes-min project 中,它是 AES128 的一个小实现。我认为有一个构建选择,以便算法中的一个组件(S-box?)得到预先计算并放入 ROM 而不是 RAM。其他对称加密算法需要从密钥计算一些数据,如果密钥是静态的,这种预计算技术可以有效地使用。

正如其他人所提到的,这取决于生成相关变量需要多长时间。如果需要大量 运行 时间,那么预先计算它就有意义了。

这是一个如何做到这一点的例子:

genx.c(预先计算数组的程序):

#include <stdio.h>

int main()
{
    int i;
    printf("int x[] = {");
    for(i=0; i<10; ++i){
        if (i) printf(",");
        printf(" %d", i*i);
    }
    printf(" };\n");
    return 0;
}

当运行时,输出:

int x[] = { 0, 1, 4, 9, 16, 25, 36, 49, 64, 81 };

生成文件:

CFLAGS=-Wall -Wextra

app: app.c x.c
        gcc $(CFLAGS) -o app app.c

x.c: genx
        ./genx > x.c

genx: genx.c
        gcc $(CFLAGS) -o genx genx.c

clean:
        rm -f app genx x.c

app.c(申请文件):

#include <stdio.h>

#include "x.c"

int main()
{
    int i;
    for (i=0;i<10;i++) {
        printf("x[%d]=%d\n",i,x[i]);
    }
    return 0;
}

当您 运行 make app 时,它会发现 x.c 是一个依赖项,并且首先 运行 是它的目标。 x.c 目标由 运行ning genx 构建,它本身由 genx 目标编译。

假设 genx.c 没有改变,x.c 只构建一次,其内容会在任何需要的地方包含。

app 的输出:

x[0]=0
x[1]=1
x[2]=4
x[3]=9
x[4]=16
x[5]=25
x[6]=36
x[7]=49
x[8]=64
x[9]=81