【问题标题】:In Which memory segment we shall find the memory address of a function in C我们将在哪个内存段中找到 C 中函数的内存地址
【发布时间】:2020-08-05 21:41:59
【问题描述】:

函数指针指向 C 中函数的地址。这意味着函数有一个内存地址,它将是任何内存段的一部分。我刚刚打印了函数地址并指向代码段。是否所有用 C 编写的函数在代码段中都有地址?

#include <stdio.h>

void func()
{
        printf("hi!!!\n");
}

int main()
{
        void (*fptr)();
        fptr = func;
        printf("fptr pointing func addr : %p and &fptr : %p\n", fptr, &fptr);
        fptr();
        while (1) {
                sleep(1); /* Sleep for accessing procfs of the process */
        }
        return 0;
}

程序输出:

[revarath@bgl-vms-vm0251 basic]$ ./a.out &
[1] 20168
[revarath@bgl-vms-vm0251 basic]$ fptr pointing func addr : 0x4005ad and &fptr : 0x7fff301e6b80
hi!!!
c = 10

查找 PID 以访问 procfs:

[revarath@bgl-vms-vm0251 basic]$ ps -ef | grep a.out
revarath 20168 24339  0 04:55 pts/2    00:00:00 ./a.out
revarath 20180 24339  0 04:56 pts/2    00:00:00 grep --color=auto a.out

内存映射输出:

[revarath@bgl-vms-vm0251 basic]$ cat /proc/20168/maps
00400000-00401000 r-xp 00000000 00:2c 124964755                          /ws/revarath-bgl/backup/tests/basic/a.out
00600000-00601000 r--p 00000000 00:2c 124964755                          /ws/revarath-bgl/backup/tests/basic/a.out
00601000-00602000 rw-p 00001000 00:2c 124964755                          /ws/revarath-bgl/backup/tests/basic/a.out
.......
7fff301c8000-7fff301e9000 rw-p 00000000 00:00 0                          [stack]

我的 func addr : 0x4005ad 位于内存区域 00400000-00401000

我的 Cpu 信息:多核 Intel

[revarath@bgl-vms-vm0251 basic]$ lscpu
Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                8

编译器:

gcc
gcc version 4.8.5 20150623 (Red Hat 4.8.5-36)

说函数的内存地址在OR指向代码段是否正确?

【问题讨论】:

  • 有些 CPU 甚至没有“段”的概念。您的问题不是关于 C 语言,而是关于您的特定编译器、CPU 和操作系统组合,您没有明确提及(尽管我们可以猜测)。
  • 另外,"%p" 用于打印void * 的值。没有标准的格式说明符来打印指向函数的指针——正确的方法可能是打印这样的指针,就好像它是一个字节数组一样。
  • @mlp :我更新了 CPU 和编译器信息。请您对此发表意见。

标签: c function linker function-pointers memory-segmentation


【解决方案1】:

这里有更多语言律师的答案(例如,不要依赖代码中的系统实现,请参阅下面的免责声明),但基本上,是的,它就是这样工作的。

从您的程序中编译的所有代码,包括您所说明的函数,都是生成的二进制图像中代码段(也容易混淆地称为“文本”段)的一部分。操作系统的加载器会将这些编译后的代码字节放入进程内的内存段中,如果您以这种方式获取静态函数的地址,您将看到其对应的代码位于该段中的某个位置。 main 等也是如此。


编译后的程序进入内存并被执行的方式远没有看起来那么神奇。抛开现代操作系统的安全机制等,它有点像这样:

  1. 工具链将所有 C 代码转换为可执行字节流。每个编译的函数都必须从该字节流的某个特定偏移量开始。
  2. “图像”(可执行文件)在其中包含不同的段:代码段、a set of 全局变量及其初始化值、段 indicating some initially-zero 值需要在程序启动时出现但不会出现需要明确地包含在图像中,等等。
  3. 操作系统中的加载程序将图像文件分开,并使用适当的可执行/读/写标志以它想要的方式将不同的部分放入内存,修补静态引用,然后将控制权跳转到第一个字节您的 main 函数(通过隐藏的 C 运行时内容)。

当您获得函数的地址时,这并没有什么神奇之处,它是指向该函数已编译指令的第一个字节的指针。

专业软件工程免责声明 正如 cmets 在这个答案和问题上所表明的那样,在现实世界中,链接器和加载器的工作方式有无数种变体。有些系统并没有真正做到这一点。当然,您永远不会编写对内存段做出假设的广泛可移植的 C 代码。但出于教学目的,如果您使用的是知名的传统桌面操作系统之一,它 *squints* 基本上可以或多或少地像您想象的那样工作。 (如果你能找到 Tanenbaum 的《现代操作系统》的旧副本,甚至是旧的第一版,它将阐明许多看似不透明的主题。)

【讨论】:

  • 鼓励正确使用高度技术性的术语“splat”和“thwack”。
  • 好答案。你可能想在这适用的地方做序言。我知道这对于 linux 是正确的,我不知道对于 windows。那么没有操作系统的独立环境呢?
  • @bolov,同样的一般原则也适用。对于独立环境,“OS 中的加载器”通常作为工具链的一部分(静默且透明地)被调用。
猜你喜欢
  • 1970-01-01
  • 2019-12-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-06
  • 1970-01-01
  • 2014-09-06
  • 1970-01-01
相关资源
最近更新 更多