C预处理器能进行算术运算吗?如果可以的话,如何操作?

8
我目前正在为一个微控制器编写代码;由于ATmega128没有硬件乘法器或除法器,这些操作必须在软件中完成,并且它们占用了相当多的周期。
然而,为了代码的可移植性和使用方便,我更希望不将预先计算的值硬编码到我的代码中。例如,我有许多任务依赖于系统时钟频率。目前我运行在16 MHz,但如果我选择降低频率,比如为了电池应用减少功耗,我想只改变一行代码而不是很多行。
所以说,C预处理器能否计算算术表达式,然后将结果“粘贴”到我的代码中,而不是将原始表达式“粘贴”到代码中?如果可以,我该如何做呢?是否需要考虑编译器选项之类的东西?
注意:我要计算的值是常量值,所以我认为这应该是一个功能。

3
如果这些表达式是常量,它们不是已经被编译器优化掉了吗? - 1''
1
请返回翻译后的文本。 - phuclv
目前我正在使用C89,但我可以切换到C99。不过我不确定如何使用它们,因为我正在使用IAR嵌入式工作台。 - audiFanatic
之前从未听说过这个编译器/集成开发环境,我注意到它不是通常的 gcc 版本,而是一个完全不同的东西。我认为,一般情况下也很少见到支持这个编译器的库。祝你好运。也许由于这个编译器并不那么流行,你应该尝试从一些针对 IAR 的 IRC、邮件列表或论坛获得帮助。 - user2485710
1
可能是Can the C preprocessor perform integer arithmetic?的重复问题。 - M.M
显示剩余2条评论
4个回答

8
这是一个问题:
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

如果我们将这个文件传递给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);

预处理器保留定义变量 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 工具来协助完成这项工作。如果我使用 gcc 编译 foo.c

gcc -c -o foo.o foo.c

然后调用:

objdump -s foo.o

要查看 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.

希望在 .data 部分中硬编码的数字 9 已经出现了。

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


请查看使用-S选项的gcc,它可以产生汇编代码。你可能会发现这样更容易理解。 - rici
2
仅供参考,预处理器*完全能够使用宏发出那个算术9(http://rosettacode.org/wiki/Order),但实现这一点所需的支持代码非常笨重。 - Alex Celeste
@Leushenko 哇塞!:) - Mike Kinghan
此外,“在intmax_t/uintmax_t中完成的预处理器算术” C11 §6.10.1 4。 - chux - Reinstate Monica

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

所以,通过重复使用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

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

宏定义了预处理器的加/减/乘/除的宏。但是对值范围有一些限制(饱和到BOOST_PP_LIMIT_MAG,可能定义为240)。https://www.boost.org/doc/libs/1_72_0/libs/preprocessor/doc/ref/add.html 和 c preprocessor: arithmetic in concatenation展示了一个示例。 - Peter Cordes

3
可以这样做,但是不需要:除非你确实想生成一些包含数字的新标识符(例如像 func1, func2 这样的东西),否则不需要涉及预处理器。
1 + 2 * 3 这样的表达式,其中所有元素都是编译时常量整数值,将在编译时替换为单个结果(这在 C 标准中或多或少是必需的,因此它并不是“真正”的优化)。所以只需使用#define定义常量来命名可以从一个地方更改的值,确保表达式不涉及任何运行时变量,除非你的编译器有意阻挠你,否则就没有运行时操作要担心。

好的,那么无论如何,只要我的#define中没有变量,我的表达式都将在编译时被替换为单个常量? - audiFanatic
我会再次检查汇编代码,以防万一。并非所有嵌入式C编译器都遵循C规范;有些偏离得相当大。 - nneonneo
2
@Leushenko 在第二种情况下,甚至可以使用枚举而不是定义,这样程序更容易调试,而且根本不需要预处理器。 - Étienne
@Étienne:而且使用enum时,常量表达式的值被强制在编译时计算,如果我没记错的话,这是标准规定的。 - Jack

0
我使用gcc -E编译了一个包含以下行的文件。
#define MUL(A, B) ((A)*(B))

#define CONST_A 10
#define CONST_B 20

int foo()
{
   return MUL(CONST_A, CONST_B);
}

输出结果为:
# 1 "test-96.c"
# 1 "<command-line>"
# 1 "test-96.c"


int foo()
{
   return ((10)*(20));
}

那只是一个数据点而已。

2
应该这样使用预处理器 - 它只是文本替换。编译器本身通常会在编译时将表达式替换为常量。 - Kevin Lam

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接