5

我需要加载 18h 并将其输出到端口 60h,以下工作(在 asm("") 内)。

ldi r1, 0x18 ; 0x18 -> r1
sts 0x60, r1 ; output r1 -> 0x60

我不在乎寄存器 r1 或任何其他是否用于此目的。有没有一种简单的方法让编译器决定使用哪个寄存器?

我可以使用外部 r/w 变量,但它会产生一些不必要的开销:

register uint8_t tmp;
asm volatile (
    "ldi %[tmp], 0x18 \n\t"
    "sts 0x60, %[tmp]"
    : [tmp] "=r"(tmp) :);

这适用于 AVR atmega(8 位)处理器。使用 GCC 4.3.2

4

2 回答 2

3

我认为您应该对输出操作数使用“=&”约束。

机制是这样的:

  • 对于任何输入操作数,编译器将提供一些寄存器,并在您的内联汇编开始之前加载它们的操作数的值。输入操作数是只读的,这意味着编译器将进一步希望您在程序集中保持寄存器不变。也就是说,编译器希望这些寄存器在汇编之后具有相同的值,因为它可能会决定随后使用这些值。

  • 对于输出操作数,编译器也会为您分配一系列寄存器。在您的内联汇编结束时,您应该已经将这些寄存器与您的结果一起加载,因为编译器希望在那里找到它们。警告:为输出操作数提供的寄存器可能与为输入操作数提供的寄存器一致!然而,这完全是有道理的:例如,编译器在汇编之前在某个寄存器中为您提供输入,并在汇编之后将结果收集在同一个寄存器中。

  • 您可以使用仅输出修饰符 ('&') 来防止这种情况发生。编译器保证仅输出寄存器永远不会兼作输入寄存器。

  • 您可以使用输入输出修饰符 ('+') 来明确要求编译器将相同的寄存器用于操作数的输入和输出。这实际上使您可以透明地访问操作数。概括一下相反的情况不成立:使用此修饰符不会阻止编译器使用相同的寄存器加倍——为此使用仅输出修饰符。

所以基本上,你的选择很简单:

  • 由于其只读性质和编译器的假设,请勿为临时变量请求输入操作数。例如,如果您创建一个临时变量并使用某个值(可能为零)对其进行初始化,编译器可能会在以后再次需要其值(零)时重用它提供给您的寄存器。
  • 不要请求输出操作数,因为它可能与某些输入操作数重合。当您使用所谓的临时性时,您可能会意外地与输入操作数发生冲突。
  • 使用带有仅输出修饰符的输出。这样,编译器将为您分配一些您可以安全擦除的寄存器(因为它期望您在那里提供一些输出)。

使用输入输出还为您提供了一个安全的寄存器。但是,操作数需要初始化(因为编译器假定您将其作为输入读取),并且如果您在内联汇编的多个片段中使用相同的操作数,则需要恢复(出于相同的原因)。相反,当使用output-only修饰符时,编译器可以很容易地发现该变量永远不会被读取。

于 2016-10-31T18:32:15.000 回答
1

我不知道为什么我之前看到了开销,但是使用外部register临时变量即使使用 -O0 也没有开销(没有优化)。所以我正在使用:

register uint8_t tmp;
asm volatile (
    "ldi %[tmp], 0x18 \n\t"
    "sts 0x60, %[tmp]"
    : [tmp] "=r"(tmp) :);

它仍然需要在 asm 语句的输出块中声明和描述一个变量,但它确实创建了我想要的汇编代码(没有开销)。

于 2011-08-16T20:26:15.930 回答