在assembly / C中使用offsetof

时间:2013-10-08 10:05:54

标签: c inline-assembly

我正在尝试在汇编代码中使用offsetof

#define     offsetof(TYPE, MEMBER)   ((size_t) &((TYPE *)0)->MEMBER)

#define     DEFINE(sym, val)   asm volatile("\n->" #sym " %0 " #val : : "i" (val))

并说结构是

struct mystruct {
int a;
int b;
int c;

}

在我的汇编代码中,我必须这样做 SUB sp,sp,# -

如何声明宏

2 个答案:

答案 0 :(得分:2)

你还没有说你为哪个处理器生成代码...我假设它是一台 RISC 机器,因为你的减法指令有 3 个操作数,但你没有说哪个。我将向您展示这对 x86 的影响,因为我知道答案是正确的。

我还假设您使用 asm volatile 表示您使用的是遵循 gcc's standard 的编译器。

无论如何,假设你有一个这样的结构:

struct mystruct {
  unsigned char a;
  const char *b;
  int c;
};

int a_or_c(mystruct *str) {
  int a_val = str->a;
  return a_val & 1 ? str->c : (a_val >> 1);
}

编译器并没有为此生成特别好的代码 - 它有 7 条指令,我们可以做得更好,因为我们知道一条指令可以同时测试“& 1”和右移。

要指定寄存器,请使用 r 表示“寄存器”,我确定您知道。要指定输入/输出寄存器,请使用 +r。要指定像结构偏移这样的常量,请使用 i 表示“立即”。那么汇编语法将是,例如:

int a_or_c_asm(mystruct *str) {
  int a_val = str->a;
  asm("shr $1,%0\n\t"
      "cmovcl %c2(%1), %0"
      : "+r"(a_val)
      : "r"(str), "i"(offsetof(mystruct, c))
      : "memory"      // tell the compiler that our code reads from memory
     );

  return a_val;
}

这里的技巧是你必须使用 %c2 而不仅仅是 %2 来让内联汇编器输出一个 2 而不是 $2,因为 x86 汇编器使用寻址模式中偏移量的语法与立即操作数不同。 x86 中的减法指令如下所示,例如:

  asm("subq %0, %%rsp"
      "... other instructions ..."
      : // no output operands
      : "i"(offsetof(mystruct, c)));
  // expands to   subq $16, %rsp   for x86-64

根据您的评论,我假设您需要 ARM32 语法。为此,您的减法指令如下所示:

  asm("sub sp, %0"
      "... other instructions ..."
      : // no output operands
      : "i"(offsetof(mystruct, c)));
  // expands to  sub sp, #8    for ARM

(Obligatory Godbolt: 为 ARM 正确编译和汇编。)
Obligatory Godbolt:为 x86 正确编译和汇编。)

请注意,gcc 假定堆栈指针在任何汇编块的末尾与在开始时相同;汇编块的其余部分必须包含一条将 sp 恢复为其原始值的指令。

对于 ARM32,我的示例如下所示 - 请注意在 ARM32 中使用寄存器加偏移寻址模式的不同语法:

int a_or_c_asm(mystruct *str) {
  int a_val = str->a;
  asm("lsrs %0, #1\t\n"       // shift and set flags
      "ldrcs %0, [%1, %2]"    // load (predicated on Carry Set)
  : "+r"(a_val)
  : "r"(str), "i"(offsetof(mystruct, c))
  : "memory"  // we access memory that isn't a declared input.
  );
  return a_val;
}

当然,这一切都是为了展示如何将已知常量值传递给 gcc 的内联 asm 语法。更常见的替代方法是使用 "m"(str->c) 输入操作数用于 ldrcs %0, %1.,这样您就不必使用 offsetof 宏。

此外,您可以传递一个 dummy 输入操作数来告诉编译器 "memory" 字段是一个输入,而不是使用 c,但实际上您仍然形成了自己寻址模式;有关这方面的更多信息,请参阅 How can I indicate that the memory *pointed* to by an inline ASM argument may be used?

答案 1 :(得分:0)

我认为这不起作用。

offsetof运算符是编译时的事情,它不由预处理器评估。这几乎是神奇的,因为预处理器不解析C,它如何计算结构偏移?这样做需要大量特定于机器的信息,因此很大程度上是编译器的责任范围。预处理器只是按摩文本。

虽然typical documentation调用offsetof宏,但这并不意味着它由预处理器进行评估。它可能只是意味着它是一个宏,可以评估一些特定于编译器的魔法。

例如for gcc,可以这样定义:

#define offsetof(type, member)  __builtin_offsetof (type, member)

这里,__builtin_offsetof()是神奇的编译器特定函数,真正进行计算。在汇编器源需要字面偏移量的情况下调用它当然不是解决方案。