10

我刚刚了解了函数指针(指向存储函数机器代码的地址的指针)。这让我想到了机器代码以及它是如何存储在内存中的。

机器代码是否连续存储在内存中,以便可以“手动”增加指针,直到它指向下一个/上一个函数?

这是调试器的作用吗?他让我“看到”程序计数器在机器代码中指向的位置?

结论:可以用函数指针编程原始调试器吗?

我理解对了吗,还是我走错了路?

4

5 回答 5

8

使用我设法追踪的 C 标准草案 (N1124),我们有类似的规则。关于加法表达式的部分(§6.5.6/2)说

另外,两个操作数都应具有算术类型,或者一个操作数应是指向对象类型的指针

并且对象类型在 §6.2.5/1 中定义为

存储在对象中或由函数返回的值的含义取决于用于访问它的表达式的类型。(声明为对象的标识符是最简单的此类表达式;类型在标识符的声明中指定。)类型分为对象类型(完全描述对象的类型)、函数类型(描述函数的类型)和不完整的类型(描述对象但缺乏确定其大小所需信息的类型)。

由于函数类型与对象类型不同,这表明禁止对函数指针进行指针运算。

在 C++ 中,此操作是非法的。§5.7/1 中给出的指针加法的定义如下:

此外,两个操作数都应具有算术或枚举类型,或者一个操作数应是指向完全定义的对象类型的指针,而另一个应具有整数或枚举类型。

但是,§3.9/9 指出

对象类型是(可能是 cv 限定的)类型,它不是函数类型,不是引用类型,也不是 void 类型。

总之,这意味着您不能在 C++ 中增加函数指针。

希望这可以帮助!

于 2011-02-23T00:02:18.110 回答
5

你可以(或至少可以)做这样的事情,但这绝对不是微不足道的。首先,您实际上不能递增或递减函数指针——它指向一个地址,但指针数学通常是以递增的方式完成的sizeof(pointed to type)——但是对于函数,这没有意义,所以你不能做数学。

大多数调试器(主要)通过使用将地址与行号、函数名、变量名等相关联的调试信息来工作。

于 2011-02-22T23:45:08.817 回答
4

有点儿。您假设函数在内存中的布局方式与它们在源代码中的方式相同。最有可能的是,它们不会——编译器通常会不择手段地移动它们。

但是,您可以做的是使用指向当前指令的指针单步执行代码,并将该计数器增加一定量以到达下一条指令。但是,在这种情况下,我们将不再将其称为函数指针,因为它不仅仅是指向函数的开头;它还指向函数的开头。相反,我们将其称为指令指针

事实上,这正是计算机的工作方式——它有一个称为程序计数器的特殊寄存器,它始终指向当前指令,并在每条指令后将其递增一定量(一个命令相当于将一个值写入程序计数器)GOTO

然而,在现实世界中,调试器不是这样工作的——事实上,我什至不确定是否有可能在 C 中使用指向内存中代码段的指针,而不是函数指针。更有可能的是,只有在需要模拟程序计数器时才需要使用这种技术,例如为另一种处理器类型编写模拟器。

于 2011-02-22T23:48:54.560 回答
2
  1. 机器码可以不连续存储。编译器可以随意拆分或合并某些功能(优化中)
  2. 如果您手动增加指向函数的指针,您可能会进入函数中间,这是错误的。
  3. 调试例程已经可用:您可以获得当前执行点的堆栈跟踪并解析堆栈中执行指针所在的函数名称(man backtraceman backtrace_symbols)。您可以将addr2line它们转换为行号。
于 2011-02-22T23:44:46.413 回答
1

无法保证各个函数将在内存中的位置。

函数本身将是一个连续的内存块(因为 CPU 顺序执行指令),但如果启用代码优化,它可能不会像函数本身(指令可能会被大量重新排序)。它甚至可以从不同的函数中借用清理代码。

您可以编写一个原始调试器,但找出函数的结束位置并非易事。

于 2011-02-22T23:44:48.973 回答