C预处理器可以执行算术吗?如果是,如何执行算术?

aud*_*tic 3 c compiler-construction compiler-optimization c-preprocessor

我正在为微控制器编写代码; 由于ATMega128没有硬件乘法器或分频器,因此这些操作必须在软件中完成,并且它们占用相当多的周期.但是,为了代码的可移植性和易用性,我宁愿不将预先计算的值硬编码到我的代码中.例如,我有许多依赖于系统时钟频率的任务.目前我的运行频率为16MHz,但我应该选择降低它,比如降低电池应用的功耗,我想改变一行代码而不是多行代码.

那么说,C预处理器可以计算算术表达式,然后将结果"粘贴"到我的代码中,而不是将原始表达式"粘贴"到代码中吗?如果是这样,我该怎么做呢?他们的编译器选项和我需要考虑的是什么?

注意:我想要计算的值是常量值,所以我没有理由认为这不是一个特征.

Mik*_*han 6

这是一个问题:

  • Q1.C预处理器可以执行算术吗?

这是另一个:

  • Q2.C预处理器可以计算算术表达式,然后将结果"粘贴"到我的代码中,而不是将原始表达式"粘贴"到代码中吗?

Q1的答案是肯定的.Q2的答案是否定的.两个事实都可以用以下文件说明:

foo.c的

#define EXPR ((1 + 2) * 3)
#if EXPR == 9
int nine = EXPR;
#else
int not_nine = EXPR;
#endif
Run Code Online (Sandbox Code Playgroud)

如果我们将它传递给C预处理器,无论是通过cpp foo.c等效还是等效gcc -E foo.c,我们都会看到如下输出:

# 1 "foo.c"
# 1 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 30 "/usr/include/stdc-predef.h" 3 4
# 1 "/usr/include/x86_64-linux-gnu/bits/predefs.h" 1 3 4
# 31 "/usr/include/stdc-predef.h" 2 3 4
# 1 "<command-line>" 2
# 1 "foo.c"


int nine = ((1 + 2) * 3);
Run Code Online (Sandbox Code Playgroud)

事实上,预处理器保留了行定义int nine并删除了行定义not_nine,这表明它已正确执行了评估所需的算法#if EXPR == 9.

事实上,定义的预处理文本int nine = ((1 + 2) * 3); 向我们显示该#define指令导致预处理器替换 EXPR为其定义((1 + 2) * 3),而不是其定义的算术值9.

C预处理器是否有任何指令除了#define具有第二个效果?没有.

但这当然不意味着定义int nine必须包含 运行时计算,因为编译器几乎肯定会((1 + 2) * 3)在编译时评估算术表达式并将其替换为常量9.

我们可以通过检查编译的目标文件来查看编译器如何翻译源文件.大多数工具链都会提供像GNU binutils这样的东西 objdump来帮助解决这个问题.如果我foo.c用gcc 编译:

gcc -c -o foo.o foo.c
Run Code Online (Sandbox Code Playgroud)

然后调用:

objdump -s foo.o
Run Code Online (Sandbox Code Playgroud)

看到全部内容foo.o,我得到:

foo.o:     file format elf64-x86-64

Contents of section .data:
 0000 09000000                             ....            
Contents of section .comment:
 0000 00474343 3a202855 62756e74 752f4c69  .GCC: (Ubuntu/Li
 0010 6e61726f 20342e38 2e312d31 30756275  naro 4.8.1-10ubu
 0020 6e747539 2920342e 382e3100           ntu9) 4.8.1.
Run Code Online (Sandbox Code Playgroud)

并且9在该.data部分中存在希望的硬编码.

请注意,预处理器的算术功能仅限于整数运算

  • 根据记录,预处理器*是*[完全能够使用宏发出算术“9”](http://rosettacode.org/wiki/Order),但是执行此操作所需的支持代码非常重量级。 (2认同)

Pau*_* II 5

是的,您可以使用预处理器进行算术运算,但这需要大量工作。在这里阅读本页,展示如何创建增量计数器和while循环。这样,您就可以创建加法:

#define ADD_PRED(x, y) y
#define ADD_OP(x, y) INC(x), DEC(y)
#define ADD(x, y) WHILE(ADD_PRED, ADD_OP, x, y)

EVAL(ADD(1, 2)) // Expands to 3
Run Code Online (Sandbox Code Playgroud)

所以重用ADD宏,然后就可以创建MUL宏了。像这样的东西:

#define MUL_PRED(r, x, y) y
#define MUL_OP(r, x, y) ADD(r, x), x, DEC(y)
#define MUL_FINAL(r, x, y) r
#define MUL(x, y) MUL_FINAL(WHILE(MUL_PRED, MUL_OP, 0, x, y))

EVAL(MUL(2, 3)) // Expands to 6
Run Code Online (Sandbox Code Playgroud)

除法和减法可以以类似的方式构建。


Leu*_*nko 3

它可以,但没有必要:您实际上不需要涉及预处理器,除非您实际上想要生成以某种方式涉及数字的新标识符(例如func1, ,之类的东西func2)。

像这样的表达式1 + 2 * 3,其中所有元素都是编译时常量整数值,将在编译时替换为单个结果(这或多或少是 C 标准的要求,因此它不是“真正的”优化)。因此,只需#define命名一个可以从一个地方更改的值的常量,确保表达式不涉及任何运行时变量,并且除非编译器故意妨碍您,否则您不应该担心运行时操作。

  • @Leushenko 在第二种情况下甚至可以使用枚举而不是定义,这样程序更容易调试,并且根本不需要预处理器。 (2认同)