在C语言中查找函数大小

12

我正在学习函数指针,我了解到我们可以使用函数指针指向函数。那么我假设它们会留在内存中,它们留在堆栈(stack)还是堆(heap)中呢?我们能计算它们的大小吗?


是什么促使您选择这样的用户名?为什么不用您自己的/更简单的名字? - mawia
2
@mawia 你在开玩笑吗??如果你没有选择用户名,那么它们就是默认的用户名。 - user681007
1
@mawia 我觉得那个数字很酷,所以继续使用它。 - Vaccum
使用 $ nm filename.o$ nm filename.obj 命令,然后查看“text”部分,这是十六进制的... - Yousha Aleayoub
9个回答

8
代码空间在构建代码时由链接器静态分配。如果您的代码由操作系统加载,则操作系统加载程序将从操作系统请求该内存,并将代码加载到其中。同样,静态数据也是在此时分配的,初始堆栈也是如此(尽管如果创建了其他线程,则可能会创建更多的堆栈)。
关于确定函数大小,这个信息已知于链接器,在大多数工具链中,链接器可以创建一个包括所有静态内存对象(即那些不在堆栈或堆上运行时实例化的对象)的大小和位置的映射文件。
没有保证在运行时确定函数大小的方法(也没有太多理由这样做),但是如果您假设链接器将按顺序将源代码中相邻的函数顺序地放置在内存中,则以下内容可能会给出函数大小的指示:
int first_function()
{
   ...
}

void second_function( int arg )
{
    ...
}

int main( void )
{
    int first_function_length = (int)second_function - (int)first_function ;
    int second_function_length = (int)main - (int)second_function ;

}

然而,结果因人而异;我在VC++中尝试过这个方法,只有在“Release”构建中才能得到有效的结果;而在“Debug”构建中,结果没有实际意义。我认为这个练习只是为了兴趣而已,没有实际用途。
当然,另一种观察代码大小的方法是查看调试器中代码的反汇编。

3

函数属于文本段(可能是堆内存或其等效物)或您使用的架构的等效物。编译后无法获得有关函数大小的数据,最多只能从符号表获取它们的入口点(该符号表不一定可用)。因此,在大多数C环境中,您无法实际计算它们的大小。


有时,可执行代码也可以在堆上(但这些是在程序执行期间生成的代码)。 - nhahtdh
@nhahtdh 是的,但这超出了 C 的范围,更多地涉及编译器/运行时系统环境。我相信 Zeta-C 以及各种基于 JVM 的 C 实现都可以计算函数大小 :) - p_l

3

它们(通常)与堆栈或堆是分离的。

有方法可以找到它们的大小,但它们中没有一个是全平台可移植的。如果您认为需要/想知道大小,则很可能正在做一些应该避免的事情。


但是你如何找到一个函数的大小呢?或者更重要的是,你如何定义“函数的大小”?如果有类似函数大小的东西,那么它应该是它的堆栈帧大小。你同意这个观点吗? - mawia
@mawia:我不确定他的意思。我猜测可能是代码的大小,你可以通常通过链接器告诉你(例如使用地图文件)。 - Jerry Coffin
我尝试了一下,发现一个函数的大小总是1。我改变了返回类型、局部变量的数量等,但每次都得到相同的结果(1)。但为什么当sizeof运算符应用于函数时,sizeof运算符总是返回1,答案仍然未知。 - Abhishek Jaiswal
我猜是因为你正在使用不符合规范的编译器。例如,gcc会将许多东西的大小视为1,即使符合规范的编译器应拒绝试图获取这些项大小的代码。例如:void *x; std::cout << sizeof(*x);不应该被编译--但是在gcc中,它会编译并打印出1 - Jerry Coffin
请问您能否详细解释一下“符合”和“不符合”编译器这两个术语吗?我在谷歌搜索中没有找到相关资料。 - Abhishek Jaiswal

3

有一个有趣的方法来发现函数的大小。

#define RETN_empty 0xc3
#define RETN_var   0xc2  
typedef unsigned char BYTE;
size_t FunctionSize(void* Func_addr) {
    BYTE* Addr = (BYTE*)Func_addr;
    size_t function_sz = 0;
    size_t instructions_qt = 0;
        while(*Addr != (BYTE)RETN_empty && *Addr != (BYTE)RETN_var) {
            size_t inst_sz = InstructionLength((BYTE*)Addr);
            function_sz += inst_sz;
            Addr += inst_sz;
            ++instructions_qt;
        }
    return function_sz + 1;
}

但你需要一个返回指令长度的函数。您可以在此处找到查找指令长度的函数:获取汇编指令的大小。 该函数基本上会不断地检查函数的指令,直到找到返回指令(RETN)[0xc3, 0xc2],然后返回函数的大小。

1
不错,但我认为没有规定说返回指令永远不能在函数中间。 - Petr Skocik

1
简单来说,函数通常不会进入堆栈或堆中,因为它们是只读数据,而堆栈和堆是可读写的内存。
你真的需要在运行时知道它的大小吗?如果不需要,可以通过一个简单的objdump -t -i .text a.out来获取它,其中a.out是您二进制文件的名称。.text是链接器放置代码的位置,加载器可以选择使此内存只读(甚至仅执行)。如果需要,在之前的帖子中已经有人回答了这个问题,有方法可以做到这一点,但这很棘手且不可移植... Clifford提供了最直接的解决方案,但链接器很少将函数以这种连续的方式放入最终二进制文件中。另一种解决方案是在链接器脚本中使用编译指示符定义部分,并保留用于全局变量的存储空间,链接器将使用包含您的函数的SIZEOF(...)部分填充该变量。这取决于链接器,而不是所有链接器都提供此功能。

1

如上所述,函数大小是由编译器在编译时生成的,并且所有大小在链接时都为链接器所知。如果您绝对必须这样做,可以让链接器输出一个包含起始地址、大小和名称的映射文件。然后您可以在运行时解析此代码。但我认为没有一种可移植、可靠的方法来在运行时计算它们,而不超出C的范围。

Linux内核也类似地用于运行时分析。


0

C语言没有垃圾回收器。拥有指向某个东西的指针并不意味着它会一直存在于内存中。

函数总是在内存中,无论您是否使用它们,无论您是否保留对它们的指针。

动态分配的内存可以被释放,但这与保留指向它的指针无关。您不应该保留已释放的内存的指针,并且在失去指向它的指针之前应该将其释放,但语言不会自动执行此操作。


0

如果有什么东西像函数的大小,它应该是它的堆栈帧大小。或者更好的是,请尝试思考一下,按照您的理解,函数的大小应该是多少?你是指它的静态大小,也就是当所有操作码加载到内存中时的大小吗?如果是这样,那么我没有看到任何语言提供的查找该大小的特性。也许你要寻找一些黑客方法。可能有很多,但我没有尝试过。


-1
#include<stdio.h>

int main(){
    void demo();
    int demo2();
    void (*fun)();
    fun = demo;
    fun();
    printf("\n%lu", sizeof(demo));
    printf("\n%lu", sizeof(*fun));
    printf("\n%lu", sizeof(fun));
    printf("\n%lu", sizeof(demo2));
    return 0;
}

void demo(){
    printf("tired");    
}

int demo2(){
    printf("int type funciton\n");
    return 1;
}

希望你能得到答案,所有的函数都存储在某个地方。
这是代码的输出。

above code's output


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接