0

我将其设为社区 wiki 是为了更好地理解这些错误与其运行时或编译结果之间的语义差异。另外,我在 Java 上的编码时间太长了,我想在 C++ 中更好地学习指针——所以我需要其他人来做。

Edit2:我正在重构这个问题。我试图得出的区别是,在托管代码上,这些错误都通过异常统一处理。但是,C++ 并不是那么简单——我想了解在每种情况下您是否可能会遇到错误、段错误、可恢复的行为,或者更糟糕的是传播的无声错误。请查看我的新具体示例(是的,我知道答案总是“完全按照编码”;毕竟我是一名程序员。我想知道您经常遇到的有趣细节。)

Edit3:在下文中,“类”是指类的一个实例。谢谢

错误 1:指针值为 NULL,即指针 == 0

  • 托管代码:在运行时引发 NullPointerException
  • C++:?
  • 示例:嗯,呃,你有一个指向的指针,但它被初始化为 0。当你将它发送到函数时会发生什么。IE。C++ 没有留下任何类的指示;它只是公共“占位符”的串联。

错误 2:指针指向内存中的前一个类,其值为 NULL 或 == 0

  • 托管代码:不允许通过内存模型。所有引用的对象都保留在内存中。没有特殊情况?
  • C++:?
  • 示例:您有一个指向的指针,并且该类被删除了。然后将指针作为参数传递给函数。显然,出现的问题取决于函数如何处理指向的类。我的问题是:在 STL 上是否有故障安全处理?一个好的专有库?平均开源代码?

错误 3:指针指向的类不属于正确的类或子类

  • 托管代码:引发 ClassCastException。
  • C++:[如果错误则更正]编译器试图通过不允许错误的强制转换来解决这个问题。但是,如果这发生在运行时,我认为是未定义的行为。是否存在类似的类对象不会总是爆炸的情况?
  • 示例:您的指针被错误地重新分配,使其值完全等于另一个类。我假设您将这个引用的类传递给的函数只会盲目地获取它引用的任何实例变量的偏移量。因此,它错误地解释了原始二进制文件。在 C++ 中没有办法防止这种情况发生吗?和/或……有没有这种能力被永远利用的情况?

错误 4:指针指向类中间(未对齐)或未初始化的垃圾

  • 托管代码:内存模型不允许。
  • C++:相当于案例 3?
  • 示例:您实际上经常合法地使用它。例如,您可以直接访问 STL 向量的数组 - 这是指向类的中间。然而,似乎也一样容易“错过”?是否存在一个常见的陷阱,您可能会违背自己的意愿发生这种情况,例如如果加载的库与您链接的库不同(并且是否有防止这种情况发生的机制?)

在此先感谢所有贡献者。

4

8 回答 8

1

其中大多数会导致不可预测的行为。引用 Steve McConnell 的 Code Complete 第 2 版“使用指针本质上是复杂的,正确使用它们需要您对编译器的内存管理方案有很好的理解”。

于 2009-02-06T01:12:08.247 回答
1

出色地。在 C++ 中,在除 case 2 之外的任何情况下取消引用指针都会产生未定义的行为,因此您不知道会发生什么。然而,对于大多数操作系统,取消引用空指针将导致分段错误。

仅在比较中使用指针对于空指针来说很好,但对于除此之外的任何其他情况和情况 2,都没有精确定义(未指定)。

案例 2 是完美定义的。你可以让你的指针指向一个值为 0 的 int。我不明白为什么这样的事情在 C# 中是非法的。可能我误解了你的情况2

对于案例 3,您必须区分指针是否已经指向错误的对象,或者您是否仍在尝试使其指向该对象。C++dynamic_cast将检查您指向的对象的类型,如果它不是派生的或与您转换为类型的类型相同,那么它会给您一个空指针。但是还有其他类型的转换不会进行该检查,并且会给您留下无效的指针。

于 2009-02-06T01:35:20.087 回答
1
  1. 指针值为 NULL,也就是指针 == 0未定义的行为。编译器可以做它想做的任何事情,包括每次都不同的事情。在大多数基于 unix 的系统下,这将导致分段错误。
  2. 访问已删除的指针这是未定义的行为。在某些情况下,根据内存分配和使用的确切模式,如果内存没有被其他用途重用,您可能能够使用已删除的指针,就好像它没有被删除一样。这可能导致很难追踪错误。如果你第二次删除指针,你可能会破坏内存分配系统,导致完全不相关的新闻/删除崩溃
  3. 指向不属于正确类或子类的类的指针C++ 不进行运行时类型检查。它将尝试将内存位置解释为指针的类型。如果尚未在该位置创建正确类型的对象,则它是未定义的行为,并且可能发生任何事情(包括看起来正常工作)。
  4. 指针指向类中间(未对齐)或未初始化的垃圾同上,未定义的行为。

总而言之,您不能依赖其中任何一个来做任何有价值的事情。设计您的代码以使其不会发生是很重要的。编译器尽其所能提供帮助,因此在尝试欺骗它时要非常小心(例如,强制转换)。编译器最终会报仇雪恨。

于 2009-02-06T01:37:03.033 回答
0

这将是我对您的错误的修订版:

错误 1:空指针/引用

  • 托管代码:如果是引用则抛出 NullReferenceException,如果是指针则抛出 AccessViolationException(是的!托管代码中存在指针!)
  • 本机代码:在 Windows 上,这会导致“访问冲突”(通常称为 AV)。在 Unix 上,这被称为“段错误”。在 Windows 上,这在理论上可以使用异常处理来捕获

错误 2:指向已释放对象的指针

  • 托管代码:通常未定义,但很可能是 AccessViolationException。(请注意,这指的是实际的指针使用情况,而不是托管引用,它总是有效的)
  • 本机代码:通常未定义,但可能是访问冲突。

错误 3:

  • 托管代码:引发异常
  • 本机代码:根据转换的类型,如果是静态转换,则将是编译器错误,或者如果重新解释转换,将是未定义的结果。

错误 4:

  • 托管代码:未定义
  • 本机代码:未定义
于 2009-02-06T01:52:17.387 回答
0

我将仅添加一些信息。指针会做任何你告诉他们做的事情。如果程序可以访问所述内核,则包括覆盖内核。

以第 3 点为例,这是许多针对内核的攻击中使用的技术。找出内核所在的位置并使用指针来更改信息。我绝不是建议有人尝试这样做,我不容忍使用 Rootkit 或任何其他恶意软件。

于 2009-02-06T01:57:44.753 回答
0

如果您真的想学习指针,因为您想更好地了解您的计算机,请专注于 C 或汇编。事实上,有一些很棒的微型 C 编译器是用 C 编写的,将它们拆开再组合起来。

C++ 降级为 C(我的意思是它可以编译 C 文件),但是在 C++ 中还有很多事情要处理,而对于 C,您可以只考虑指针的基础知识。

我还强烈建议您编译一个 C 程序并用汇编语言通过它进行跟踪(单步调试)。如果你真的想了解底层系统,了解堆栈帧和调用期间发生的事情是非常关键的。

学习这些东西的其他方法:

  • 去审计编译器构造中的一个类。
  • 使用 PIC 控制器(机器人或计算器)构建一些有趣的东西。
于 2009-02-06T02:11:14.587 回答
0

#1 应该抛出一个段错误。

#2、#3 和 #4 可能会起作用,具体取决于方法尝试执行的操作。请记住,在 C++ 中,类代码只存储一次(并且与实例数据分开,这是对象指针引用的内容),因此可以在随机的内存块上调用类方法。例如,以下打印“-1”(使用 g++ 4 测试):

#include <iostream>

class Foo
{
public:
    int x;
    void foo()
    {
        std::cout << x << std::endl;
    }
};

int main(void)
{
    void* mem = malloc(1024);
    memset(mem, 0xff, 1024);
    Foo* myFoo = (Foo*)mem;
    myFoo->foo();
    return 0;
}
于 2009-02-06T01:21:46.167 回答
0

在 Windows 下,错误 1 ​​将导致在您尝试访问您没有读取权限的虚拟内存页面时引发访问冲突的结构化 (win32) 异常。Unix 派生的操作系统具有类似的机制,尽管术语不同。

这是明确定义的(如果通常是不受欢迎的!)行为,并且可以被结构化异常处理程序捕获。通常,托管运行时将依赖于引发此异常的底层操作系统,然后对其进行处理并将其转换为托管异常。这比在跟随它之前检查每个指针访问是否为空要有效得多。

于 2009-02-06T01:23:57.940 回答