检查以下代码:
#include <avr/io.h>
const uint16_t baudrate = 9600;
void setupUART( void ) {
uint16_t ubrr = ( ( F_CPU / ( 16 * (float) baudrate ) ) - 1 + .5 );
UBRRH = ubrr >> 8;
UBRRL = ubrr & 0xff;
}
int main( void ) {
setupUART();
}
这是用于编译代码的命令:
avr-gcc -g -DF_CPU=4000000 -Wall -Os -Werror -Wextra -mmcu=attiny2313 -Wa,-ahlmns=project.lst -c -o project.o project.cpp
ubrr
由编译器计算为25,到目前为止一直很好。但是,为了检查编译器计算的内容,我查看了反汇编列表。
000000ae <setupUART()>:
ae: 12 b8 out UBRRH, r1 ; 0x02
b0: 89 e1 ldi r24, 0x19 ; 25
b2: 89 b9 out UBRRL, r24 ; 0x09
b4: 08 95 ret
是否可以让avr-gcc
在编译时打印出中间结果 (或从.o文件中提取信息),所以当我编译代码时会打印一行像(uint16_t) ubbr = 25
或类似?这样我就可以快速检查计算和设置。
答案 0 :(得分:3)
GCC具有命令行选项,可以在任何编译阶段之后请求它转储其中间表示。 “树”转储采用伪C语法并包含所需的信息。对于您要执行的操作,-fdump-tree-original
和-fdump-tree-optimized
转储发生在优化管道中的有用位置。我没有手持AVR编译器,因此我将测试用例修改为自包含并且可以与我拥有的编译器一起编译:
typedef unsigned short uint16_t;
const int F_CPU = 4000000;
const uint16_t baudrate = 9600;
extern uint16_t UBRRH, UBRRL;
void
setupUART(void)
{
uint16_t ubrr = ((F_CPU / (16 * (float) baudrate)) - 1 + .5);
UBRRH = ubrr >> 8;
UBRRL = ubrr & 0xff;
}
然后
$ gcc -O2 -S -fdump-tree-original -fdump-tree-optimized test.c
$ cat test.c.003t.original
;; Function setupUART (null)
;; enabled by -tree-original
{
uint16_t ubrr = 25;
uint16_t ubrr = 25;
UBRRH = (uint16_t) ((short unsigned int) ubrr >> 8);
UBRRL = ubrr & 255;
}
$ cat test.c.149t.optimized
;; Function setupUART (setupUART, funcdef_no=0, decl_uid=1728, cgraph_uid=0)
setupUART ()
{
<bb 2>:
UBRRH = 0;
UBRRL = 25;
return;
}
你可以看到,常量表达式折叠已经很早就完成了,它已经发生在“原始”转储中(这是你可以拥有的最早可理解的转储),并且优化进一步将转移和掩码操作折叠到了写给UBRRH和UBRRL的声明。
文件名(003t和149t)中的数字可能与您有所不同。如果要查看所有“树”转储,请使用-fdump-tree-all
。还有“RTL”转储,看起来不像C,可能对你没用。但是,如果你很好奇,-fdump-rtl-all
会让他们开心。总共有大约100个树和60个RTL转储,所以在临时目录中执行此操作是个好主意。
(Psssst:每当你在括号内放置空格时,上帝会杀死一只小猫。)
答案 1 :(得分:2)
可能有打印中间结果的解决方案,但需要一些时间才能实现。因此,仅适用于相当大的源代码库。
您可以自定义GCC编译器;通过插件(在C或C ++中痛苦地编码)或通过MELT扩展。 MELT是一种高级的,类似Lisp的领域特定语言,用于扩展GCC。 (它作为GCC的[meta-]插件实现,并被翻译为适合GCC的C ++代码。)
然而,这种方法要求您了解GCC内部,然后添加您自己的“优化”传递来执行aspect oriented programming(例如使用MELT)来打印相关的中间结果。
您不仅可以查看生成的程序集(并使用-fverbose-asm -S
作为GCC的选项),还可以查看生成的Gimple表示(可能使用-fdump-tree-gimple
)。对于某些交互式工具,请考虑图形MELT probe。
也许添加你自己的内置(带有MELT扩展名),如__builtin_display_compile_time_constant
可能是相关的。
答案 2 :(得分:1)
我怀疑有一种简单的方法可以确定编译器的功能。 gcc中可能有一些工具专门用于转储语言的中间形式,但它绝对不容易阅读,除非你真的怀疑编译器做错了什么(并且有一个很小的例子来展示它) ,你不可能将它用于任何有意义的事情 - 仅仅因为要做的事情太多了。
更好的方法是在代码中添加临时变量(可能还有打印件),如果您担心它是正确的:
uint16_t ubrr = ( ( F_CPU / ( 16 * (float) baudrate ) ) - 1 + .5 );
uint8_t ubrr_high = ubrr >> 8
uint8_t ubrr_low = ubrr & 0xff;
UBRRH = ubrr_high;
UBRRL = ubrr_low;
现在,如果你有一个非优化的构建并在GDB中逐步完成它,你应该能够看到它的作用。否则,在代码中添加某种打印输出以显示值是什么......
如果您无法在目标系统上打印它,因为您正在设置将要用于打印的uart,请在本地主机系统上复制代码并在那里进行调试。除非编译器非常错误,否则应从同一编译中获取相同的值。
答案 3 :(得分:1)
这是一个黑客攻击:现在只需手动自动化你正在做的事情。
-ahlms=
output.lst )。或者,使用您自己的反汇编方法作为makefile中的后编译步骤。out UBRRH
和out UBRRL
行。这些将从寄存器加载,因此您的脚本可以将紧接的指定分配给将加载到UBRRH
和UBRRL
的寄存器。然后,该脚本可以从加载到通用寄存器中的值重新组合UBRR
值,该寄存器用于设置UBRRH
和UBRRL
。这听起来比Basile Starynkevich对MELT扩展非常有用的建议更容易。现在,乍一看,这个解决方案似乎很脆弱,所以让我们考虑一下这个问题:
out UBRR_, r__
将出现在反汇编列表中:没有其他方法可以将寄存器/写入数据设置为端口。可能会改变的一件事是这些行中/周围的间距,但这可以通过脚本轻松处理out
指令只能来自通用寄存器,所以我们知道通用寄存器会作为out
指令行的第二个参数,所以应该不是问题。out
指令之前设置。这里我们必须允许一些可变性:而不是LDI
(立即加载),avr-gcc可能会产生一些其他指令来设置寄存器值。我认为作为第一遍,脚本应该能够解析立即加载,否则转储它找到的涉及将写入UBRR_
端口的寄存器的最后一条指令。如果更改平台,脚本可能必须更改(某些处理器的UBRRH1
/ 2
寄存器UBRRH
,但是在这种情况下,您需要更改波特码。该脚本抱怨它无法解析反汇编,那么您至少会知道您的支票尚未执行。