为什么 C 风格的“类”会产生额外的指令?

Pis*_*ic 8 c assembly struct arm class

将具有“成员”函数的 C 风格结构与 C++ 类进行比较,试图对 C++ 开销进行建模,我怀疑以下实现通过包含相同数量的指令大致相同。我发现 C 实现在调用“成员”函数时会产生额外的指令。

\n
int main()\n{\n\xc2\xa0 \xc2\xa0 uint32_t a;\n\xc2\xa0 \xc2\xa0 rectangle_t r;\n    \n    /* struct */\n\xc2\xa0 \xc2\xa0 r.set(2,3, &r);\n    /* asm \n       ldr \xc2\xa0 \xc2\xa0 r3, [r7, #12]\n       adds \xc2\xa0 \xc2\xa0r2, r7, #4\n       movs \xc2\xa0 \xc2\xa0r1, #3\n       movs \xc2\xa0 \xc2\xa0r0, #2\n       blx \xc2\xa0 \xc2\xa0 r3\n    */\n\n\xc2\xa0 \xc2\xa0 a = r.getArea(&r);\n    /* asm \n       ldr \xc2\xa0 \xc2\xa0 r3, [r7, #16]\n       adds \xc2\xa0 \xc2\xa0r2, r7, #4\n       mov \xc2\xa0 \xc2\xa0 r0, r2\n       blx \xc2\xa0 \xc2\xa0 r3\n       str \xc2\xa0 \xc2\xa0 r0, [r7, #20]\n       movs \xc2\xa0 \xc2\xa0r3, #0\n    */\n\n    /* Class */\n\xc2\xa0 \xc2\xa0 r.set(2, 3);\n    /* asm \n       adds \xc2\xa0 \xc2\xa0r3, r7, #4\n       movs \xc2\xa0 \xc2\xa0r2, #3\n       movs \xc2\xa0 \xc2\xa0r1, #2\n       mov \xc2\xa0 \xc2\xa0 r0, r3\n       bl \xc2\xa0 \xc2\xa0 \xc2\xa0rectangle_t::set(unsigned int, unsigned int)\n    */\n\xc2\xa0 \xc2\xa0 a = r.getArea();\n    /* asm \n       adds \xc2\xa0 \xc2\xa0r3, r7, #4\n       mov \xc2\xa0 \xc2\xa0 r0, r3\n       bl \xc2\xa0 \xc2\xa0 \xc2\xa0rectangle_t::getArea()\n       str \xc2\xa0 \xc2\xa0 r0, [r7, #12]\n       movs \xc2\xa0 \xc2\xa0r3, #0\n    */\n}\n
Run Code Online (Sandbox Code Playgroud)\n

为什么在调用 Stuct 'member' 与类成员时会有额外的 ldr 指令?

\n

编译器:ARM GCC 12.2.0(Linux)

\n

声明:

\n
typedef struct rectangle rectangle_t;\nstruct rectangle\n{\n\xc2\xa0 \xc2\xa0 uint32_t w;\n\xc2\xa0 \xc2\xa0 uint32_t l;\n\xc2\xa0 \xc2\xa0 void (*set)(uint32_t L, uint32_t W, rectangle_t *self);\n\xc2\xa0 \xc2\xa0 uint32_t (*getArea)(rectangle_t *self); \xc2\xa0 \xc2\xa0\n};\nvoid set(uint32_t L, uint32_t W, rectangle_t *self)\n{\n\xc2\xa0 \xc2\xa0 self->w = W;\n\xc2\xa0 \xc2\xa0 self->l = L;\n}\n\nuint32_t getArea(rectangle_t *self)\n{\n\xc2\xa0 \xc2\xa0 return self->l * self->w;\n}\n\nclass rectangle_t\n{\npublic:\xc2\xa0 \xc2\xa0\n\n\xc2\xa0\xc2\xa0\xc2\xa0 uint32_t w;\n\xc2\xa0 \xc2\xa0 uint32_t l; \xc2\xa0\n\xc2\xa0 \xc2\xa0 void set(uint32_t L, uint32_t W);\n\xc2\xa0 \xc2\xa0 uint32_t getArea();\xc2\xa0\n};\n\nvoid rectangle_t::set(uint32_t L, uint32_t W)\n{\n\xc2\xa0 \xc2\xa0 w = W;\n\xc2\xa0 \xc2\xa0 l = L;\n}\n\nuint32_t rectangle_t::getArea()\n{\n\xc2\xa0 \xc2\xa0 return l * w;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

Eri*_*idt 7

在 C++ 中,我们可以声明函数类型的结构和/或类成员,并且在给定类的实例的情况下,这些函数变得可访问。

\n

但是,在 C 中,我们不能拥有函数类型的字段,因此,您所做的是函数指针。 \xc2\xa0 因此,这些字段是真正的变量\xe2\x80\x94处的实例变量 \xe2\x80 \x94 任何人都可以动态分配给它,这就是开销的来源(必须查阅变量的值,除非编译器可以看到优化;像任何字段变量一样,对于同一结构的不同实例也可以是不同的值).\xc2\xa0 请注意,您还没有在必须执行的某处显示函数指针 \xe2\x80\x94 的初始化r.set = set; r.getArea = getArea;为每个单独的结构实例执行的地方显示函数指针 \xe2\x80\x94 的初始化,否则根据所使用的存储,您将得到垃圾或零对于结构。

\n

为了使 C 代码“某种程度上”等效,请创建函数,而不是函数指针,并且必须在 之外声明它们struct,因此,与 C++ 相比,它们实际上是解绑的。

\n

  • 另请注意,函数指针是非“静态”的,因此它们占用了结构的每个实例中的空间!对于应该很小的对象来说完全是可怕的,并且破坏了内联+进一步优化的机会。 (2认同)