当我编写代码时,我尝试通过创建“模块”来保持程序的逻辑可分部分。使用NodeJS或Python等Javascript语言来实现这一目标非常容易。有了C,我已经找到了用一种模式来实现这个目标的方法,我提供了一个下面的例子。我使用带有const
声明结构的静态方法声明来创建“模块”来组织我的代码。
我注意到使用这种技术调用方法的成本通常只是每次调用一个汇编指令。
而不是
movl -8(%rbp), %edx
movl -12(%rbp), %eax
movl %edx, %esi
movl %eax, %edi
call my_add_method
“模块”技术将生成
movl $my_add_method, %ecx
movl -8(%rbp), %edx
movl -12(%rbp), %eax
movl %edx, %esi
movl %eax, %edi
call *%rcx
我想要找到的是一种声明这些模块的方法,但编译后的输出与仅通过它的直接名称调用方法相同。
我想知道的是:
有没有办法让编译器(gcc)使用标志或通过不同地声明结构,优化代码以使得结果asm相同?
我想,对于编译器来说,这将是简单的事情,如果不存在任何方法,为什么这种优化通常不可能? (考虑到结构都是恒定的和静态的)
/**
* File: main.c
* Target: x86_64-linux-gnu
* Compile: gcc main.c -S -o main
*/
#include <stdio.h>
typedef struct {
int (* const add_func)(int, int);
} MY_MOD_T;
static int my_add_method(int a, int b) {
return a+b;
}
const MY_MOD_T Module = {
.add_func = my_add_method
};
int main(void) {
int a = 5;
int b = 6;
// substitute these two lines to see the different output like above
int result = Module.add_func(a, b);
//int result = my_add_method(a, b);
printf("%d + %d = %d\n", a, b, result);
return 0;
}
答案 0 :(得分:1)
在一般情况下,不可能通过函数指针进行函数调用,与调用命名函数的行为相同。
在您的示例中,请考虑头文件module_interface.h
:
typedef struct {
int (* const add_func)(int, int);
} MY_MOD_T;
名为module_derived.h
的不同头文件:
#include "module_interface.h"
extern const MY_MOD_T Module;
module_derived.c
中派生模块的实现:
#include "module_derived.h"
static int my_add_method(int a, int b) {
return a+b;
}
const MY_MOD_T Module = {
.add_func = my_add_method
};
int module_add_method(int a, int b) {
return my_add_method(a, b);
}
然后,您的主程序将如下所示:
#include <stdio.h>
#include "module_derived.h"
extern int module_add_method(int a, int b);
int main(void) {
int a = 5;
int b = 6;
// substitute these two lines to see the different output like above
int result = Module.add_func(a, b);
//int result = module_add_method(a, b);
printf("%d + %d = %d\n", a, b, result);
return 0;
}
如果module_derived
实际上是一个共享库,那么实际上没有一个优化级别可以帮助克服必须取消引用函数指针值的事实。在-O3
:
# Calling named function
movl $6, %esi
movl $5, %edi
call module_add_method
# Calling through module
movl $6, %esi
movl $5, %edi
call *Module(%rip)
如您所见,在进行模块机制时会有额外的偏移计算和取消引用。
但是,对于共享库,此模块开销与位置无关代码(PLT和GOT开销)所产生的开销相当。所以,在实践中,开销不值得担心,除非分析告诉你不这样做。在这种情况下,您必须考虑找到一种内联热函数调用的方法。