【问题标题】:Can the C preprocessor perform arithmetic and if so, how?C 预处理器可以执行算术吗?如果可以,如何执行?
【发布时间】:2014-04-26 04:12:31
【问题描述】:

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

话虽如此,C 预处理器能否计算算术表达式,然后将结果“粘贴”到我的代码中,而不是将原始表达式“粘贴”到代码中?如果是这样,我将如何去做?我需要考虑他们的编译器选项吗?

注意:我要计算的值是常数值,所以我看不出这不是一个特性的原因。

【问题讨论】:

  • 如果表达式是常量,编译器不会优化掉它们吗?
  • 好吧,有预处理器库,也许有一个适合你,这主要取决于你的目标 C 语言版本,例如 C99 及以上有 P99 p99.gforge.inria.fr跨度>
  • 目前我使用的是C89,但我可以切换到C99。不确定如何使用这些,因为我正在使用 IAR Embedded Workbench
  • 以前从未听说过这个编译器/IDE,我注意到它不是通常的gcc 端口,它是一个完全不同的野兽,我认为我从未见过支持这个编译器的库一般的。祝你好运。也许因为这个编译器不是那么流行,你应该尝试从一些 IRC、恶意列表或 IAR 特定的论坛获得帮助。

标签: c compiler-construction compiler-optimization c-preprocessor


【解决方案1】:

这是一个问题:

  • 第一季度。 C 预处理器可以执行算术运算吗?

这是另一个:

  • 第二季度。 C预处理器可以计算算术表达式然后“粘贴”吗 将结果放入我的代码中,而不是将原始表达式“粘贴”到代码中?

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

foo.c

#define EXPR ((1 + 2) * 3)
#if EXPR == 9
int nine = EXPR;
#else
int not_nine = EXPR;
#endif

如果我们通过 cpp foo.c 或将其传递给 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),而 not 与算术值 它的定义,9

除了#define 之外,C 预处理器还有其他指令吗? 影响?没有。

但这当然并不意味着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 选项,它会生成汇编代码。您可能会发现它更容易解释。
  • 为了记录,预处理器 perfectly capable of emitting that arithmetic 9 with macros,但是执行它所需的支持代码是可笑的重量级。
  • @Leushenko strewth!:)
  • 还有“在intmax_t/uintmax_t中完成的预处理算术”C11 §6.10.1 4
【解决方案2】:

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

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

【讨论】:

  • 好的,所以无论哪种方式,我的表达式都将在编译时被单个常量替换(假设我的 #define 中没有变量)?
  • 我会仔细检查程序集以防万一。并非所有嵌入式 C 编译器都遵循 C 规范;有些偏差相当大。
  • @Leushenko 在第二种情况下,甚至可以使用枚举而不是定义,这样程序更容易调试,而且根本不需要预处理器。
  • @Étienne: 使用enum,常量表达式值被强制在编译时按照标准 IIRC 计算
【解决方案3】:

是的,您可以使用预处理器进行算术运算,但这需要大量工作。阅读this page here,展示了如何创建增量计数器和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

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

【讨论】:

    【解决方案4】:

    我使用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));
    }
    

    这只是你的一个数据点。

    【讨论】:

    • 应该如此 - 预处理器的使用只是文本替换。不过,编译器本身通常会在编译时将表达式替换为常量。
    猜你喜欢
    • 2010-12-06
    • 2018-03-19
    • 1970-01-01
    • 2018-12-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-10
    相关资源
    最近更新 更多